"""template_clip_config 单测. domain 层模板片段配置纯逻辑模块,0 外部依赖。 覆盖:ClipType枚举、TransitionEffect枚举、create工厂/校验、计算属性。 """ from __future__ import annotations from packages.domain.template_clip_config import ( ClipType, TemplateClipConfig, TransitionEffect, ) class TestClipType: """ClipType 枚举测试.""" def test_six_types(self): """六种片段类型.""" assert len(ClipType) == 6 def test_intro(self): assert ClipType.INTRO == "intro" def test_main(self): assert ClipType.MAIN == "main" def test_transition(self): assert ClipType.TRANSITION == "transition" def test_outro(self): assert ClipType.OUTRO == "outro" def test_title(self): assert ClipType.TITLE == "title" def test_subtitle(self): assert ClipType.SUBTITLE == "subtitle" def test_from_string(self): """可从字符串构造.""" assert ClipType("main") == ClipType.MAIN assert ClipType("intro") == ClipType.INTRO class TestTransitionEffect: """TransitionEffect 枚举测试.""" def test_six_effects(self): """六种转场效果.""" assert len(TransitionEffect) == 6 def test_cut(self): assert TransitionEffect.CUT == "cut" def test_fade(self): assert TransitionEffect.FADE == "fade" def test_slide_left(self): assert TransitionEffect.SLIDE_LEFT == "slide_left" def test_slide_right(self): assert TransitionEffect.SLIDE_RIGHT == "slide_right" def test_dissolve(self): assert TransitionEffect.DISSOLVE == "dissolve" def test_wipe(self): assert TransitionEffect.WIPE == "wipe" def test_from_string(self): """可从字符串构造.""" assert TransitionEffect("fade") == TransitionEffect.FADE assert TransitionEffect("cut") == TransitionEffect.CUT class TestTemplateClipConfigCreate: """TemplateClipConfig.create 工厂测试.""" def test_create_minimal(self): """最简创建.""" clip = TemplateClipConfig.create( template_id="tpl1", clip_type=ClipType.MAIN, order=0, ) assert clip.template_id == "tpl1" assert clip.clip_type == ClipType.MAIN assert clip.order == 0 assert clip.min_duration == 0.0 assert clip.max_duration == 0.0 assert clip.text_template == "" assert clip.material_requirements == {} assert clip.transition_effect == TransitionEffect.CUT assert clip.config == {} assert isinstance(clip.id, str) assert len(clip.id) > 0 def test_create_with_string_clip_type(self): """用字符串传 clip_type.""" clip = TemplateClipConfig.create( template_id="t1", clip_type="intro", order=0, ) assert clip.clip_type == ClipType.INTRO def test_create_with_string_transition(self): """用字符串传 transition_effect.""" clip = TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, transition_effect="fade", ) assert clip.transition_effect == TransitionEffect.FADE def test_create_full(self): """带全部字段.""" clip = TemplateClipConfig.create( template_id=" tpl1 ", clip_type=ClipType.TITLE, order=2, min_duration=2.0, max_duration=5.0, text_template=" 欢迎关注 ", material_requirements={"category": "scenic"}, transition_effect=TransitionEffect.FADE, config={"font_size": 24}, ) assert clip.template_id == "tpl1" # strip assert clip.clip_type == ClipType.TITLE assert clip.order == 2 assert clip.min_duration == 2.0 assert clip.max_duration == 5.0 assert clip.text_template == "欢迎关注" # strip assert clip.material_requirements == {"category": "scenic"} assert clip.transition_effect == TransitionEffect.FADE assert clip.config == {"font_size": 24} def test_create_empty_template_id(self): """空 template_id 无效.""" try: TemplateClipConfig.create(template_id="", clip_type=ClipType.MAIN, order=0) assert False except ValueError as e: assert "template_id" in str(e) def test_create_whitespace_template_id(self): """空白 template_id 无效.""" try: TemplateClipConfig.create(template_id=" ", clip_type=ClipType.MAIN, order=0) assert False except ValueError as e: assert "template_id" in str(e) def test_create_negative_min_duration(self): """min_duration 为负无效.""" try: TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, min_duration=-1.0, ) assert False except ValueError as e: assert "min_duration" in str(e) def test_create_negative_max_duration(self): """max_duration 为负无效.""" try: TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, max_duration=-1.0, ) assert False except ValueError as e: assert "max_duration" in str(e) def test_create_min_greater_than_max(self): """min > max 且 max > 0 时无效.""" try: TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, min_duration=5.0, max_duration=3.0, ) assert False except ValueError as e: assert "min_duration" in str(e) and "max_duration" in str(e) def test_create_min_equals_max_valid(self): """min == max 合法.""" clip = TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, min_duration=3.0, max_duration=3.0, ) assert clip.min_duration == 3.0 assert clip.max_duration == 3.0 def test_create_zero_both_valid(self): """都为 0 合法(未设置时长).""" clip = TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, min_duration=0.0, max_duration=0.0, ) assert clip.min_duration == 0.0 assert clip.max_duration == 0.0 def test_create_invalid_clip_type_string(self): """无效的 clip_type 字符串抛错.""" try: TemplateClipConfig.create(template_id="t1", clip_type="invalid", order=0) assert False except ValueError: pass # 枚举构造失败会抛 ValueError def test_create_material_req_none_defaults_empty(self): """material_requirements=None 默认为空 dict.""" clip = TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, material_requirements=None, ) assert clip.material_requirements == {} def test_create_config_none_defaults_empty(self): """config=None 默认为空 dict.""" clip = TemplateClipConfig.create( template_id="t1", clip_type=ClipType.MAIN, order=0, config=None, ) assert clip.config == {} def test_create_unique_id(self): """不同配置 id 不同.""" c1 = TemplateClipConfig.create("t", ClipType.MAIN, 0) c2 = TemplateClipConfig.create("t", ClipType.MAIN, 0) assert c1.id != c2.id def test_create_has_timestamps(self): """有创建和更新时间.""" clip = TemplateClipConfig.create("t", ClipType.MAIN, 0) assert clip.created_at is not None assert clip.updated_at is not None class TestTemplateClipConfigProperties: """计算属性测试.""" def test_has_duration_range_false_zero(self): """都为 0 时没有时长范围.""" clip = TemplateClipConfig.create("t", ClipType.MAIN, 0) assert clip.has_duration_range is False def test_has_duration_range_true_min_only(self): """只有 min > 0 也算有范围.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, min_duration=2.0, ) assert clip.has_duration_range is True def test_has_duration_range_true_max_only(self): """只有 max > 0 也算有范围.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, max_duration=5.0, ) assert clip.has_duration_range is True def test_has_duration_range_true_both(self): """两者都 > 0 有范围.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, min_duration=2.0, max_duration=5.0, ) assert clip.has_duration_range is True def test_default_duration_zero(self): """都为 0 默认时长 0.""" clip = TemplateClipConfig.create("t", ClipType.MAIN, 0) assert clip.default_duration == 0.0 def test_default_duration_middle(self): """两者都有取中间值.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, min_duration=2.0, max_duration=6.0, ) assert clip.default_duration == 4.0 def test_default_duration_min_only(self): """只有 min 用 min.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, min_duration=3.0, ) assert clip.default_duration == 3.0 def test_default_duration_max_only(self): """只有 max 用 max.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, max_duration=5.0, ) assert clip.default_duration == 5.0 def test_default_duration_equal_min_max(self): """min == max 时值相等.""" clip = TemplateClipConfig.create( "t", ClipType.MAIN, 0, min_duration=3.0, max_duration=3.0, ) assert clip.default_duration == 3.0