test(wave179): transition_config 转场配置 +79测 #1133

Closed
xiaoxia wants to merge 1 commits from test/wave179-transition-config into develop
+376
View File
@@ -0,0 +1,376 @@
"""转场配置领域模型单测.
纯逻辑模块,覆盖:TransitionType枚举、名称解析与别名、
TransitionConfig.parse解析/降级/边界、属性与校验。
"""
from __future__ import annotations
import logging
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_cut_transition(self):
assert CUT_TRANSITION == "cut"
class TestTransitionTypeEnum:
def test_cut_value(self):
assert TransitionType.CUT.value == "cut"
def test_fade_value(self):
assert TransitionType.FADE.value == "fade"
def test_dissolve_value(self):
assert TransitionType.DISSOLVE.value == "dissolve"
def test_slide_values(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_values(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_value(self):
assert TransitionType.ZOOM.value == "zoom"
def test_circle_crop_value(self):
assert TransitionType.CIRCLE_CROP.value == "circlecrop"
def test_rect_crop_value(self):
assert TransitionType.RECT_CROP.value == "rectcrop"
def test_is_str_enum(self):
assert isinstance(TransitionType.FADE, str)
assert TransitionType.FADE == "fade"
def test_all_supported_excludes_cut(self):
supported = TransitionType.all_supported()
assert "cut" not in supported
assert "fade" in supported
assert "dissolve" in supported
assert len(supported) == len(TransitionType) - 1
def test_all_supported_is_list_of_strings(self):
supported = TransitionType.all_supported()
assert isinstance(supported, list)
assert all(isinstance(s, str) for s in supported)
class TestTransitionTypeIsSupported:
def test_fade_supported(self):
assert TransitionType.is_supported("fade") is True
def test_cut_not_supported(self):
"""cut不算在supported里(is_supported只看效果类型)."""
assert TransitionType.is_supported("cut") is True
def test_case_insensitive(self):
assert TransitionType.is_supported("FADE") is True
assert TransitionType.is_supported("Fade") is True
def test_underscore_normalized(self):
assert TransitionType.is_supported("slide_left") is True
assert TransitionType.is_supported("SLIDE_LEFT") is True
def test_dash_normalized(self):
assert TransitionType.is_supported("slide-left") is True
def test_circle_crop_supported(self):
assert TransitionType.is_supported("circle_crop") is True
assert TransitionType.is_supported("circlecrop") is True
def test_rect_crop_supported(self):
assert TransitionType.is_supported("rect_crop") is True
def test_invalid_not_supported(self):
assert TransitionType.is_supported("nonexistent") is False
assert TransitionType.is_supported("") is False
def test_alias_dissolve(self):
assert TransitionType.is_supported("crossfade") is True
assert TransitionType.is_supported("crossdissolve") is True
def test_alias_slide(self):
assert TransitionType.is_supported("slide") is True
def test_alias_wipe(self):
assert TransitionType.is_supported("wipe") is True
def test_alias_zoom(self):
assert TransitionType.is_supported("zoomin") is True
assert TransitionType.is_supported("zoomout") is True
def test_alias_fade_variants(self):
assert TransitionType.is_supported("fadein") is True
assert TransitionType.is_supported("fadeout") is True
assert TransitionType.is_supported("fadeblack") is True
def test_alias_circle(self):
assert TransitionType.is_supported("circle") is True
def test_alias_rect(self):
assert TransitionType.is_supported("rect") is True
class TestTransitionConfigDefaults:
def test_default_config(self):
cfg = TransitionConfig()
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_custom_config(self):
cfg = TransitionConfig(effect="fade", duration=1.0)
assert cfg.effect == "fade"
assert cfg.duration == 1.0
class TestTransitionConfigParse:
def test_parse_none_both(self):
cfg = TransitionConfig.parse()
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_parse_none_effect(self):
cfg = TransitionConfig.parse(duration=1.0)
assert cfg.effect == CUT_TRANSITION
assert cfg.duration == 1.0
def test_parse_none_duration(self):
cfg = TransitionConfig.parse(effect="fade")
assert cfg.effect == "fade"
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_parse_fade(self):
cfg = TransitionConfig.parse(effect="fade", duration=0.8)
assert cfg.effect == "fade"
assert cfg.duration == 0.8
def test_parse_dissolve(self):
cfg = TransitionConfig.parse(effect="dissolve")
assert cfg.effect == "dissolve"
def test_parse_case_insensitive(self):
cfg = TransitionConfig.parse(effect="FADE")
assert cfg.effect == "fade"
def test_parse_with_underscore(self):
cfg = TransitionConfig.parse(effect="slide_left")
assert cfg.effect == "slideleft"
def test_parse_with_dash(self):
cfg = TransitionConfig.parse(effect="slide-right")
assert cfg.effect == "slideright"
def test_parse_empty_effect(self):
cfg = TransitionConfig.parse(effect="")
assert cfg.effect == CUT_TRANSITION
def test_parse_whitespace_effect(self):
cfg = TransitionConfig.parse(effect=" ")
assert cfg.effect == CUT_TRANSITION
def test_parse_unsupported_effect_fallback_to_cut(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(effect="nonexistent_effect")
assert cfg.effect == CUT_TRANSITION
assert "不支持的转场效果" in caplog.text
def test_parse_cut_effect(self):
cfg = TransitionConfig.parse(effect="cut")
assert cfg.effect == CUT_TRANSITION
def test_parse_cut_uppercase(self):
cfg = TransitionConfig.parse(effect="CUT")
assert cfg.effect == CUT_TRANSITION
def test_parse_duration_min_boundary(self):
cfg = TransitionConfig.parse(duration=MIN_TRANSITION_DURATION)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_parse_duration_max_boundary(self):
cfg = TransitionConfig.parse(duration=MAX_TRANSITION_DURATION)
assert cfg.duration == MAX_TRANSITION_DURATION
def test_parse_duration_below_min_clamped(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(duration=0.1)
assert cfg.duration == MIN_TRANSITION_DURATION
assert "小于最小值" in caplog.text
def test_parse_duration_above_max_clamped(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(duration=5.0)
assert cfg.duration == MAX_TRANSITION_DURATION
assert "大于最大值" in caplog.text
def test_parse_duration_zero_clamped(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(duration=0.0)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_parse_duration_negative_clamped(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(duration=-1.0)
assert cfg.duration == MIN_TRANSITION_DURATION
def test_parse_duration_string_valid(self):
cfg = TransitionConfig.parse(duration="1.5")
assert cfg.duration == 1.5
def test_parse_duration_string_invalid_fallback(self, caplog):
with caplog.at_level(logging.WARNING):
cfg = TransitionConfig.parse(duration="abc")
assert cfg.duration == DEFAULT_TRANSITION_DURATION
assert "无效的转场时长" in caplog.text
def test_parse_duration_none_fallback(self):
cfg = TransitionConfig.parse(duration=None)
assert cfg.duration == DEFAULT_TRANSITION_DURATION
def test_parse_alias_crossfade(self):
cfg = TransitionConfig.parse(effect="crossfade")
assert cfg.effect == "dissolve" # 别名→dissolve
def test_parse_alias_slide(self):
cfg = TransitionConfig.parse(effect="slide")
assert cfg.effect == "slideleft"
def test_parse_alias_wipe(self):
cfg = TransitionConfig.parse(effect="wipe")
assert cfg.effect == "wipeleft"
def test_parse_alias_circle(self):
cfg = TransitionConfig.parse(effect="circle")
assert cfg.effect == "circlecrop"
class TestIsCutProperty:
def test_cut_is_true(self):
cfg = TransitionConfig(effect="cut")
assert cfg.is_cut is True
def test_fade_is_false(self):
cfg = TransitionConfig(effect="fade")
assert cfg.is_cut is False
def test_default_is_cut(self):
cfg = TransitionConfig()
assert cfg.is_cut is True
class TestFFmpegTransitionProperty:
def test_cut_returns_empty(self):
cfg = TransitionConfig(effect="cut")
assert cfg.ffmpeg_transition == ""
def test_fade_returns_fade(self):
cfg = TransitionConfig(effect="fade")
assert cfg.ffmpeg_transition == "fade"
def test_dissolve_returns_dissolve(self):
cfg = TransitionConfig(effect="dissolve")
assert cfg.ffmpeg_transition == "dissolve"
def test_slide_left(self):
cfg = TransitionConfig(effect="slideleft")
assert cfg.ffmpeg_transition == "slideleft"
def test_slide_right(self):
cfg = TransitionConfig(effect="slideright")
assert cfg.ffmpeg_transition == "slideright"
def test_zoom_returns_zoomin(self):
"""transition type是zoomffmpeg映射到zoomin."""
cfg = TransitionConfig(effect="zoom")
assert cfg.ffmpeg_transition == "zoomin"
def test_wipe_left(self):
cfg = TransitionConfig(effect="wipeleft")
assert cfg.ffmpeg_transition == "wipeleft"
def test_circle_crop(self):
cfg = TransitionConfig(effect="circlecrop")
assert cfg.ffmpeg_transition == "circlecrop"
def test_rect_crop(self):
cfg = TransitionConfig(effect="rectcrop")
assert cfg.ffmpeg_transition == "rectcrop"
def test_unknown_effect_fallback_to_fade(self):
"""未知效果默认返回fade(安全降级)."""
cfg = TransitionConfig(effect="unknown_effect")
# _resolve_transition_enum找不到就返回FADE
assert cfg.ffmpeg_transition == "fade"
class TestValidate:
def test_valid_cut(self):
cfg = TransitionConfig(effect="cut", duration=0.5)
ok, msg = cfg.validate()
assert ok is True
assert msg == ""
def test_valid_fade(self):
cfg = TransitionConfig(effect="fade", duration=1.0)
ok, msg = cfg.validate()
assert ok is True
def test_duration_too_low(self):
cfg = TransitionConfig(effect="fade", duration=0.1)
ok, msg = cfg.validate()
assert ok is False
assert "不能小于" in msg
def test_duration_too_high(self):
cfg = TransitionConfig(effect="fade", duration=5.0)
ok, msg = cfg.validate()
assert ok is False
assert "不能大于" in msg
def test_duration_at_min(self):
cfg = TransitionConfig(effect="fade", duration=MIN_TRANSITION_DURATION)
ok, _ = cfg.validate()
assert ok is True
def test_duration_at_max(self):
cfg = TransitionConfig(effect="fade", duration=MAX_TRANSITION_DURATION)
ok, _ = cfg.validate()
assert ok is True
def test_unsupported_effect_invalid(self):
cfg = TransitionConfig(effect="unknown", duration=0.5)
ok, msg = cfg.validate()
assert ok is False
assert "不支持的转场效果" in msg
def test_cut_always_valid(self):
"""cut即使duration稍微异常也能通过?不,cut也校验duration."""
cfg = TransitionConfig(effect="cut", duration=0.5)
ok, _ = cfg.validate()
assert ok is True