Files
xiaoxia-saas/tests/unit/test_transition_config.py
T
xiaoxia ac667e60c9
CI/CD Pipeline / Check if frontend-only change (push) Blocked by required conditions
CI/CD Pipeline / Validate - Code Quality (push) Blocked by required conditions
CI/CD Pipeline / Validate - Type Check (mypy) (push) Blocked by required conditions
CI/CD Pipeline / Validate - Migration (alembic) (push) Blocked by required conditions
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
refactor(wave113): 抽离transition_config领域模型 + 49单测 (#999)
2026-07-27 07:18:00 +08:00

277 lines
10 KiB
Python
Executable File

"""transition_config 模块单测 — 纯逻辑,无 FFmpeg 依赖."""
from __future__ import annotations
import pytest
from packages.domain.transition_config import (
CUT_TRANSITION,
DEFAULT_TRANSITION_DURATION,
MAX_TRANSITION_DURATION,
MIN_TRANSITION_DURATION,
TransitionConfig,
TransitionType,
)
# ── 常量 ──────────────────────────────────────────────────────────────────────
class TestConstants:
def test_duration_bounds(self):
assert MIN_TRANSITION_DURATION == 0.3
assert MAX_TRANSITION_DURATION == 2.0
assert DEFAULT_TRANSITION_DURATION == 0.5
assert MIN_TRANSITION_DURATION < DEFAULT_TRANSITION_DURATION < MAX_TRANSITION_DURATION
def test_cut_transition(self):
assert CUT_TRANSITION == "cut"
# ── TransitionType 枚举 ──────────────────────────────────────────────────────
class TestTransitionType:
def test_all_supported_includes_all_except_cut(self):
supported = TransitionType.all_supported()
assert "cut" not in supported
assert "fade" in supported
assert "dissolve" in supported
assert len(supported) >= 10 # 至少有10种转场
def test_all_supported_unique(self):
supported = TransitionType.all_supported()
assert len(supported) == len(set(supported))
def test_is_supported_exact_match(self):
assert TransitionType.is_supported("fade") is True
assert TransitionType.is_supported("dissolve") is True
assert TransitionType.is_supported("slideleft") is True
def test_is_supported_case_insensitive(self):
assert TransitionType.is_supported("FADE") is True
assert TransitionType.is_supported("Fade") is True
assert TransitionType.is_supported("SlideLeft") is True
def test_is_supported_with_underscores(self):
assert TransitionType.is_supported("slide_left") is True
assert TransitionType.is_supported("wipe_right") is True
assert TransitionType.is_supported("circle_crop") is True
def test_is_supported_with_hyphens(self):
assert TransitionType.is_supported("slide-left") is True
assert TransitionType.is_supported("wipe-down") is True
def test_is_supported_aliases(self):
assert TransitionType.is_supported("crossfade") is True
assert TransitionType.is_supported("crossdissolve") is True
assert TransitionType.is_supported("fadein") is True
assert TransitionType.is_supported("fadeout") is True
assert TransitionType.is_supported("slide") is True
assert TransitionType.is_supported("wipe") is True
assert TransitionType.is_supported("zoomin") is True
assert TransitionType.is_supported("zoomout") is True
assert TransitionType.is_supported("circle") is True
assert TransitionType.is_supported("rect") is True
def test_is_supported_unknown(self):
assert TransitionType.is_supported("unknown_effect") is False
assert TransitionType.is_supported("") is False
assert TransitionType.is_supported("12345") is False
def test_enum_values_match_ffmpeg(self):
# 枚举值应该就是 ffmpeg xfade 的 transition 名
assert TransitionType.FADE.value == "fade"
assert TransitionType.DISSOLVE.value == "dissolve"
assert TransitionType.SLIDE_LEFT.value == "slideleft"
assert TransitionType.CUT.value == "cut"
# ── TransitionConfig 默认值 ──────────────────────────────────────────────────
class TestTransitionConfigDefaults:
def test_default_config(self):
cfg = TransitionConfig()
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
assert cfg.is_cut is True
def test_is_cut_true(self):
cfg = TransitionConfig(effect="cut")
assert cfg.is_cut is True
def test_is_cut_false(self):
cfg = TransitionConfig(effect="fade")
assert cfg.is_cut is False
# ── TransitionConfig.parse ───────────────────────────────────────────────────
class TestTransitionConfigParse:
def test_none_params_default(self):
cfg = TransitionConfig.parse()
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_empty_effect_default(self):
cfg = TransitionConfig.parse(effect="")
assert cfg.effect == CUT_TRANSITION
def test_whitespace_effect_default(self):
cfg = TransitionConfig.parse(effect=" ")
assert cfg.effect == CUT_TRANSITION
def test_valid_effect_fade(self):
cfg = TransitionConfig.parse(effect="fade")
assert cfg.effect == "fade"
assert cfg.is_cut is False
def test_valid_effect_case_insensitive(self):
cfg = TransitionConfig.parse(effect="FADE")
assert cfg.effect == "fade"
def test_valid_effect_with_underscores(self):
cfg = TransitionConfig.parse(effect="slide_left")
assert cfg.effect == "slideleft"
def test_alias_effect(self):
cfg = TransitionConfig.parse(effect="crossfade")
assert cfg.effect == "dissolve" # 别名映射到 dissolve
def test_unknown_effect_falls_back_to_cut(self):
cfg = TransitionConfig.parse(effect="magic_sparkles")
assert cfg.effect == CUT_TRANSITION
assert cfg.is_cut is True
def test_cut_effect_stays_cut(self):
cfg = TransitionConfig.parse(effect="cut")
assert cfg.effect == CUT_TRANSITION
def test_cut_effect_case_insensitive(self):
cfg = TransitionConfig.parse(effect="CUT")
assert cfg.effect == CUT_TRANSITION
def test_duration_default(self):
cfg = TransitionConfig.parse(duration=None)
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_duration_within_range(self):
cfg = TransitionConfig.parse(duration=1.0)
assert cfg.duration == 1.0
def test_duration_at_min(self):
cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_duration_at_max(self):
cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION)
assert cfg.duration == MAX_TRANSITION_DURATION
def test_duration_below_min_clamped(self):
cfg = TransitionConfig.parse(duration=0.1)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_duration_above_max_clamped(self):
cfg = TransitionConfig.parse(duration=3.0)
assert cfg.duration == MAX_TRANSITION_DURATION
def test_duration_zero_clamped(self):
cfg = TransitionConfig.parse(duration=0)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_duration_negative_clamped(self):
cfg = TransitionConfig.parse(duration=-1.0)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_duration_invalid_string_fallback(self):
cfg = TransitionConfig.parse(duration="bad") # type: ignore[arg-type]
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_duration_numeric_string(self):
cfg = TransitionConfig.parse(duration="1.5") # type: ignore[arg-type]
assert cfg.duration == 1.5
def test_full_parse(self):
cfg = TransitionConfig.parse(effect="wipe_up", duration=1.2)
assert cfg.effect == "wipeup"
assert cfg.duration == 1.2
assert cfg.is_cut is False
# ── TransitionConfig.ffmpeg_transition ───────────────────────────────────────
class TestFfmpegTransition:
def test_cut_returns_empty(self):
cfg = TransitionConfig(effect="cut")
assert cfg.ffmpeg_transition == ""
def test_fade_matches(self):
cfg = TransitionConfig(effect="fade")
assert cfg.ffmpeg_transition == "fade"
def test_dissolve_matches(self):
cfg = TransitionConfig(effect="dissolve")
assert cfg.ffmpeg_transition == "dissolve"
def test_slide_left_matches(self):
cfg = TransitionConfig(effect="slideleft")
assert cfg.ffmpeg_transition == "slideleft"
def test_wipe_down_matches(self):
cfg = TransitionConfig(effect="wipedown")
assert cfg.ffmpeg_transition == "wipedown"
def test_zoom_matches_zoomin(self):
cfg = TransitionConfig(effect="zoom")
assert cfg.ffmpeg_transition == "zoomin"
def test_circle_crop_matches(self):
cfg = TransitionConfig(effect="circlecrop")
assert cfg.ffmpeg_transition == "circlecrop"
# ── TransitionConfig.validate ────────────────────────────────────────────────
class TestTransitionConfigValidate:
def test_valid_cut(self):
cfg = TransitionConfig(effect="cut", duration=0.5)
ok, err = cfg.validate()
assert ok is True
assert err == ""
def test_valid_fade(self):
cfg = TransitionConfig(effect="fade", duration=1.0)
ok, err = cfg.validate()
assert ok is True
def test_duration_below_min_invalid(self):
cfg = TransitionConfig(effect="fade", duration=0.1)
ok, err = cfg.validate()
assert ok is False
assert "duration" in err
def test_duration_above_max_invalid(self):
cfg = TransitionConfig(effect="fade", duration=3.0)
ok, err = cfg.validate()
assert ok is False
assert "duration" in err
def test_unsupported_effect_invalid(self):
cfg = TransitionConfig(effect="unknown", duration=0.5)
ok, err = cfg.validate()
assert ok is False
assert "不支持的转场" in err
def test_min_duration_boundary_valid(self):
cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION)
ok, _ = cfg.validate()
assert ok is True
def test_max_duration_boundary_valid(self):
cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION)
ok, _ = cfg.validate()
assert ok is True