Files
xiaoxia-saas/tests/unit/domain/test_transition_config.py

377 lines
13 KiB
Python
Executable File
Raw Permalink 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.
"""转场配置领域模型单测.
纯逻辑模块,覆盖: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