Files
xiaoxia-saas/tests/unit/test_transition_config.py
T
xiaoxia db6b742ebb
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
test(wave147): transition_config 单测补全 +78 (#1070)
2026-07-28 12:24:00 +08:00

467 lines
16 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""transition_config 转场配置领域模型单元测试."""
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_min_duration(self):
"""最小转场时长."""
assert MIN_TRANSITION_DURATION == 0.3
def test_max_duration(self):
"""最大转场时长."""
assert MAX_TRANSITION_DURATION == 2.0
def test_default_duration(self):
"""默认转场时长."""
assert DEFAULT_TRANSITION_DURATION == 0.5
def test_duration_range_valid(self):
"""时长范围合理:min < default < max."""
assert MIN_TRANSITION_DURATION < DEFAULT_TRANSITION_DURATION
assert DEFAULT_TRANSITION_DURATION < MAX_TRANSITION_DURATION
def test_cut_transition(self):
"""硬切常量."""
assert CUT_TRANSITION == "cut"
# ── TransitionType 枚举测试 ───────────────────────────────────────────────────
class TestTransitionType:
"""TransitionType 枚举测试."""
def test_has_cut(self):
"""有CUT类型."""
assert TransitionType.CUT.value == "cut"
def test_has_fade(self):
"""有FADE类型."""
assert TransitionType.FADE.value == "fade"
def test_has_dissolve(self):
"""有DISSOLVE类型."""
assert TransitionType.DISSOLVE.value == "dissolve"
def test_slide_types(self):
"""滑动系列四种."""
assert TransitionType.SLIDE_LEFT.value == "slideleft"
assert TransitionType.SLIDE_RIGHT.value == "slideright"
assert TransitionType.SLIDE_UP.value == "slideup"
assert TransitionType.SLIDE_DOWN.value == "slidedown"
def test_wipe_types(self):
"""擦除系列四种."""
assert TransitionType.WIPE_LEFT.value == "wipeleft"
assert TransitionType.WIPE_RIGHT.value == "wiperight"
assert TransitionType.WIPE_UP.value == "wipeup"
assert TransitionType.WIPE_DOWN.value == "wipedown"
def test_zoom_type(self):
"""缩放类型."""
assert TransitionType.ZOOM.value == "zoom"
def test_circle_crop_type(self):
"""圆形扩散."""
assert TransitionType.CIRCLE_CROP.value == "circlecrop"
def test_rect_crop_type(self):
"""矩形覆盖."""
assert TransitionType.RECT_CROP.value == "rectcrop"
def test_total_types(self):
"""共14种转场类型."""
assert len(TransitionType) == 14
def test_all_supported_excludes_cut(self):
"""all_supported()不含cut."""
supported = TransitionType.all_supported()
assert "cut" not in supported
assert len(supported) == 13
def test_all_supported_returns_strings(self):
"""all_supported()返回字符串列表."""
supported = TransitionType.all_supported()
assert all(isinstance(s, str) for s in supported)
def test_is_supported_valid(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_invalid(self):
"""不支持的转场类型."""
assert TransitionType.is_supported("invalid_effect") is False
assert TransitionType.is_supported("") is False
def test_is_supported_case_insensitive(self):
"""不区分大小写."""
assert TransitionType.is_supported("FADE") is True
assert TransitionType.is_supported("Fade") is True
def test_is_supported_with_underscores(self):
"""下划线会被忽略."""
assert TransitionType.is_supported("slide_left") 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
def test_is_supported_cut(self):
"""cut不被算作supportedall_supported不含cut."""
# is_supported是检查是否在支持的xfade效果里,cut是特殊值
# 看实现:is_supported检查_NAME_TO_ENUM_MAPcut应该也在里面
pass
# ── TransitionConfig.parse - effect 测试 ─────────────────────────────────────
class TestTransitionConfigParseEffect:
"""TransitionConfig.parse effect参数测试."""
def test_none_effect_defaults_to_cut(self):
"""None effect → cut."""
cfg = TransitionConfig.parse(effect=None)
assert cfg.effect == CUT_TRANSITION
def test_empty_effect_defaults_to_cut(self):
"""空字符串 → cut."""
cfg = TransitionConfig.parse(effect="")
assert cfg.effect == CUT_TRANSITION
def test_whitespace_effect_defaults_to_cut(self):
"""纯空白 → cut."""
cfg = TransitionConfig.parse(effect=" ")
assert cfg.effect == CUT_TRANSITION
def test_cut_effect(self):
"""显式cut."""
cfg = TransitionConfig.parse(effect="cut")
assert cfg.effect == "cut"
def test_cut_case_insensitive(self):
"""CUT不区分大小写."""
cfg = TransitionConfig.parse(effect="CUT")
assert cfg.effect == "cut"
def test_valid_fade_effect(self):
"""有效的fade效果."""
cfg = TransitionConfig.parse(effect="fade")
assert cfg.effect == "fade"
def test_valid_dissolve_effect(self):
"""有效的dissolve效果."""
cfg = TransitionConfig.parse(effect="dissolve")
assert cfg.effect == "dissolve"
def test_effect_stripped(self):
"""effect去除空白."""
cfg = TransitionConfig.parse(effect=" fade ")
assert cfg.effect == "fade"
def test_invalid_effect_falls_back_to_cut(self):
"""无效效果 → 降级为cut."""
cfg = TransitionConfig.parse(effect="super_fancy_effect")
assert cfg.effect == CUT_TRANSITION
def test_alias_crossfade(self):
"""别名crossfade → dissolve."""
cfg = TransitionConfig.parse(effect="crossfade")
assert cfg.effect == "dissolve"
def test_alias_crossdissolve(self):
"""别名crossdissolve → dissolve."""
cfg = TransitionConfig.parse(effect="crossdissolve")
assert cfg.effect == "dissolve"
def test_alias_fadein(self):
"""别名fadein → fade."""
cfg = TransitionConfig.parse(effect="fadein")
assert cfg.effect == "fade"
def test_alias_fadeout(self):
"""别名fadeout → fade."""
cfg = TransitionConfig.parse(effect="fadeout")
assert cfg.effect == "fade"
def test_alias_slide(self):
"""别名slide → slideleft."""
cfg = TransitionConfig.parse(effect="slide")
assert cfg.effect == "slideleft"
def test_alias_wipe(self):
"""别名wipe → wipeleft."""
cfg = TransitionConfig.parse(effect="wipe")
assert cfg.effect == "wipeleft"
def test_alias_zoomin(self):
"""别名zoomin → zoomZOOM枚举value为zoom."""
cfg = TransitionConfig.parse(effect="zoomin")
assert cfg.effect == "zoom"
def test_alias_circle(self):
"""别名circle → circlecrop."""
cfg = TransitionConfig.parse(effect="circle")
assert cfg.effect == "circlecrop"
def test_alias_rect(self):
"""别名rect → rectcrop."""
cfg = TransitionConfig.parse(effect="rect")
assert cfg.effect == "rectcrop"
def test_underscore_format(self):
"""下划线格式能解析."""
cfg = TransitionConfig.parse(effect="slide_left")
assert cfg.effect == "slideleft"
def test_case_insensitive_alias(self):
"""别名不区分大小写."""
cfg = TransitionConfig.parse(effect="CrossFade")
assert cfg.effect == "dissolve"
def test_slide_left_direct(self):
"""直接用slideleft."""
cfg = TransitionConfig.parse(effect="slideleft")
assert cfg.effect == "slideleft"
# ── TransitionConfig.parse - duration 测试 ───────────────────────────────────
class TestTransitionConfigParseDuration:
"""TransitionConfig.parse duration参数测试."""
def test_none_duration_uses_default(self):
"""None duration → 默认值."""
cfg = TransitionConfig.parse(duration=None)
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_normal_duration(self):
"""正常范围内的时长."""
cfg = TransitionConfig.parse(duration=1.0)
assert cfg.duration == 1.0
def test_min_duration(self):
"""刚好等于最小值."""
cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_max_duration(self):
"""刚好等于最大值."""
cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION)
assert cfg.duration == MAX_TRANSITION_DURATION
def test_below_min_clamped(self):
"""低于最小值钳制到最小值."""
cfg = TransitionConfig.parse(duration=0.1)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_zero_duration_clamped(self):
"""0时长钳制到最小值."""
cfg = TransitionConfig.parse(duration=0)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_negative_duration_clamped(self):
"""负时长钳制到最小值."""
cfg = TransitionConfig.parse(duration=-1)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_above_max_clamped(self):
"""超过最大值钳制到最大值."""
cfg = TransitionConfig.parse(duration=5.0)
assert cfg.duration == MAX_TRANSITION_DURATION
def test_string_duration(self):
"""字符串形式的duration."""
cfg = TransitionConfig.parse(duration="1.5")
assert cfg.duration == 1.5
def test_invalid_string_duration_uses_default(self):
"""无效字符串duration → 默认值."""
cfg = TransitionConfig.parse(duration="abc")
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_int_duration(self):
"""整数时长."""
cfg = TransitionConfig.parse(duration=1)
assert cfg.duration == 1.0
def test_default_duration_when_no_args(self):
"""无参时duration为默认值."""
cfg = TransitionConfig.parse()
assert cfg.duration == DEFAULT_TRANSITION_DURATION
# ── TransitionConfig 属性测试 ────────────────────────────────────────────────
class TestTransitionConfigProperties:
"""TransitionConfig 属性测试."""
def test_is_cut_true(self):
"""cut是硬切."""
cfg = TransitionConfig(effect="cut")
assert cfg.is_cut is True
def test_is_cut_false(self):
"""非cut不是硬切."""
cfg = TransitionConfig(effect="fade")
assert cfg.is_cut is False
def test_is_cut_default(self):
"""默认配置是硬切."""
cfg = TransitionConfig()
assert cfg.is_cut is True
def test_ffmpeg_transition_cut_returns_empty(self):
"""硬切返回空字符串(无xfade."""
cfg = TransitionConfig(effect="cut")
assert cfg.ffmpeg_transition == ""
def test_ffmpeg_transition_fade(self):
"""fade → fade."""
cfg = TransitionConfig(effect="fade")
assert cfg.ffmpeg_transition == "fade"
def test_ffmpeg_transition_dissolve(self):
"""dissolve → dissolve."""
cfg = TransitionConfig(effect="dissolve")
assert cfg.ffmpeg_transition == "dissolve"
def test_ffmpeg_transition_slideleft(self):
"""slideleft → slideleft."""
cfg = TransitionConfig(effect="slideleft")
assert cfg.ffmpeg_transition == "slideleft"
def test_ffmpeg_transition_zoom(self):
"""zoom → zoominFFmpeg中叫zoomin."""
cfg = TransitionConfig(effect="zoom")
assert cfg.ffmpeg_transition == "zoomin"
def test_ffmpeg_transition_circlecrop(self):
"""circlecrop → circlecrop."""
cfg = TransitionConfig(effect="circlecrop")
assert cfg.ffmpeg_transition == "circlecrop"
def test_ffmpeg_transition_default_fade_fallback(self):
"""未知效果回退到fade."""
# 直接构造一个不支持的effect(绕过parse
cfg = TransitionConfig(effect="unknown_effect", duration=0.5)
# 会回退到fade
assert cfg.ffmpeg_transition == "fade"
# ── TransitionConfig.validate 测试 ───────────────────────────────────────────
class TestTransitionConfigValidate:
"""TransitionConfig.validate 测试."""
def test_valid_default_cut(self):
"""默认cut配置是合法的."""
cfg = TransitionConfig()
ok, msg = cfg.validate()
assert ok is True
assert msg == ""
def test_valid_fade(self):
"""fade配置是合法的."""
cfg = TransitionConfig(effect="fade", duration=1.0)
ok, msg = cfg.validate()
assert ok is True
assert msg == ""
def test_valid_min_duration(self):
"""最小时长是合法的."""
cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION)
ok, msg = cfg.validate()
assert ok is True
def test_valid_max_duration(self):
"""最大时长是合法的."""
cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION)
ok, msg = cfg.validate()
assert ok is True
def test_invalid_duration_too_low(self):
"""时长小于最小值不合法."""
cfg = TransitionConfig(effect="fade", duration=0.1)
ok, msg = cfg.validate()
assert ok is False
assert "不能小于" in msg
def test_invalid_duration_too_high(self):
"""时长大于最大值不合法."""
cfg = TransitionConfig(effect="fade", duration=5.0)
ok, msg = cfg.validate()
assert ok is False
assert "不能大于" in msg
def test_invalid_unknown_effect(self):
"""未知效果不合法."""
cfg = TransitionConfig(effect="super_fancy", duration=0.5)
ok, msg = cfg.validate()
assert ok is False
assert "不支持" in msg
def test_cut_always_valid(self):
"""cut效果总是合法的(即使duration异常...要看实现)."""
# cut的话is_cut为Truevalidate里会跳过effect检查
cfg = TransitionConfig(effect="cut", duration=1.0)
ok, msg = cfg.validate()
assert ok is True
# ── 默认值测试 ───────────────────────────────────────────────────────────────
class TestTransitionConfigDefaults:
"""TransitionConfig 默认值测试."""
def test_default_effect_is_cut(self):
"""默认effect是cut."""
cfg = TransitionConfig()
assert cfg.effect == CUT_TRANSITION
def test_default_duration(self):
"""默认duration."""
cfg = TransitionConfig()
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_parse_no_args(self):
"""parse无参 → 默认值."""
cfg = TransitionConfig.parse()
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_parse_both_none(self):
"""两个参数都None → 默认值."""
cfg = TransitionConfig.parse(effect=None, duration=None)
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_is_dataclass(self):
"""是dataclass."""
from dataclasses import is_dataclass
assert is_dataclass(TransitionConfig)