"""TemplateClipConfig domain entity unit tests (wave219).""" from datetime import datetime, timezone import pytest from domain.template_clip_config import ( ClipType, TemplateClipConfig, TransitionEffect, ) # --------------------------------------------------------------------------- # ClipType enum # --------------------------------------------------------------------------- class TestClipType: def test_all_members_exist(self): assert ClipType.INTRO.value == "intro" assert ClipType.MAIN.value == "main" assert ClipType.TRANSITION.value == "transition" assert ClipType.OUTRO.value == "outro" assert ClipType.TITLE.value == "title" assert ClipType.SUBTITLE.value == "subtitle" def test_is_str_enum(self): # StrEnum members are instances of str assert isinstance(ClipType.INTRO, str) assert ClipType.INTRO == "intro" def test_from_string(self): assert ClipType("intro") is ClipType.INTRO assert ClipType("main") is ClipType.MAIN def test_invalid_value_raises(self): with pytest.raises(ValueError): ClipType("invalid") # --------------------------------------------------------------------------- # TransitionEffect enum # --------------------------------------------------------------------------- class TestTransitionEffect: def test_all_members_exist(self): assert TransitionEffect.CUT.value == "cut" assert TransitionEffect.FADE.value == "fade" assert TransitionEffect.SLIDE_LEFT.value == "slide_left" assert TransitionEffect.SLIDE_RIGHT.value == "slide_right" assert TransitionEffect.DISSOLVE.value == "dissolve" assert TransitionEffect.WIPE.value == "wipe" def test_is_str_enum(self): assert isinstance(TransitionEffect.CUT, str) assert TransitionEffect.CUT == "cut" def test_from_string(self): assert TransitionEffect("cut") is TransitionEffect.CUT assert TransitionEffect("fade") is TransitionEffect.FADE def test_invalid_value_raises(self): with pytest.raises(ValueError): TransitionEffect("invalid_effect") # --------------------------------------------------------------------------- # TemplateClipConfig.create() # --------------------------------------------------------------------------- class TestTemplateClipConfigCreate: def test_create_basic(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, ) assert cfg.template_id == "tpl_001" assert cfg.clip_type == ClipType.MAIN assert cfg.order == 1 assert cfg.id # auto-generated uuid hex assert len(cfg.id) == 32 # uuid4 hex length def test_create_with_string_clip_type(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type="intro", order=0, ) assert cfg.clip_type == ClipType.INTRO def test_create_with_string_transition_effect(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, transition_effect="fade", ) assert cfg.transition_effect == TransitionEffect.FADE def test_create_empty_template_id_raises(self): with pytest.raises(ValueError, match="template_id 不能为空"): TemplateClipConfig.create( template_id="", clip_type=ClipType.MAIN, order=1, ) def test_create_whitespace_template_id_raises(self): with pytest.raises(ValueError, match="template_id 不能为空"): TemplateClipConfig.create( template_id=" ", clip_type=ClipType.MAIN, order=1, ) def test_create_template_id_stripped(self): cfg = TemplateClipConfig.create( template_id=" tpl_001 ", clip_type=ClipType.MAIN, order=1, ) assert cfg.template_id == "tpl_001" def test_create_negative_min_duration_raises(self): with pytest.raises(ValueError, match="min_duration 不能为负数"): TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, min_duration=-1.0, ) def test_create_negative_max_duration_raises(self): with pytest.raises(ValueError, match="max_duration 不能为负数"): TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, max_duration=-5.0, ) def test_create_min_greater_than_max_raises(self): with pytest.raises(ValueError, match="min_duration 不能大于 max_duration"): TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, min_duration=10.0, max_duration=5.0, ) def test_create_min_equals_max_ok(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, min_duration=5.0, max_duration=5.0, ) assert cfg.min_duration == 5.0 assert cfg.max_duration == 5.0 def test_create_default_values(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, ) assert cfg.min_duration == 0.0 assert cfg.max_duration == 0.0 assert cfg.text_template == "" assert cfg.material_requirements == {} assert cfg.transition_effect == TransitionEffect.CUT assert cfg.config == {} def test_create_material_requirements_none_becomes_empty_dict(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, material_requirements=None, ) assert cfg.material_requirements == {} # should be a fresh dict, not shared cfg.material_requirements["key"] = "val" cfg2 = TemplateClipConfig.create( template_id="tpl_002", clip_type=ClipType.MAIN, order=1, ) assert cfg2.material_requirements == {} def test_create_config_none_becomes_empty_dict(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, config=None, ) assert cfg.config == {} def test_create_text_template_stripped(self): cfg = TemplateClipConfig.create( template_id="tpl_001", clip_type=ClipType.MAIN, order=1, text_template=" hello {{name}} ", ) assert cfg.text_template == "hello {{name}}" def test_create_generates_unique_ids(self): cfg1 = TemplateClipConfig.create("tpl", ClipType.MAIN, 1) cfg2 = TemplateClipConfig.create("tpl", ClipType.MAIN, 2) assert cfg1.id != cfg2.id def test_create_timestamps_are_utc(self): before = datetime.now(timezone.utc) cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1) after = datetime.now(timezone.utc) assert before <= cfg.created_at <= after assert before <= cfg.updated_at <= after assert cfg.created_at.tzinfo is timezone.utc assert cfg.updated_at.tzinfo is timezone.utc # --------------------------------------------------------------------------- # has_duration_range property # --------------------------------------------------------------------------- class TestHasDurationRange: def test_both_zero(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1) assert cfg.has_duration_range is False def test_only_min_positive(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0) assert cfg.has_duration_range is True def test_only_max_positive(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0) assert cfg.has_duration_range is True def test_both_positive(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=5.0) assert cfg.has_duration_range is True # --------------------------------------------------------------------------- # default_duration property # --------------------------------------------------------------------------- class TestDefaultDuration: def test_both_zero_returns_zero(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1) assert cfg.default_duration == 0.0 def test_only_min_returns_min(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0) assert cfg.default_duration == 3.0 def test_only_max_returns_max(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0) assert cfg.default_duration == 5.0 def test_both_set_returns_midpoint(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=6.0) assert cfg.default_duration == 4.0 def test_min_equals_max_returns_same(self): cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0, max_duration=3.0) assert cfg.default_duration == 3.0