""" 片头片尾引擎配置与纯逻辑测试. 覆盖 IntroOutroConfig.from_dict / validate / has_intro / has_outro 等纯逻辑. 引擎核心 render 方法依赖 FFmpeg,由集成测试覆盖. """ import pytest from video_processing.intro_outro_engine import IntroOutroConfig class TestIntroOutroConfigFromDict: """from_dict 构造逻辑.""" def test_none_returns_default_disabled(self): cfg = IntroOutroConfig.from_dict(None) assert cfg.enabled is False assert cfg.intro_type == "none" assert cfg.outro_type == "none" def test_empty_dict_returns_default_disabled(self): cfg = IntroOutroConfig.from_dict({}) assert cfg.enabled is False def test_enabled_false_returns_default_disabled(self): cfg = IntroOutroConfig.from_dict({"enabled": False}) assert cfg.enabled is False def test_enabled_with_video_intro(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": { "type": "video", "video_path": "/tmp/intro.mp4", "duration": 5.0, }, "outro": {"type": "none"}, }) assert cfg.enabled is True assert cfg.intro_type == "video" assert cfg.intro_video_path == "/tmp/intro.mp4" assert cfg.intro_duration == 5.0 def test_enabled_with_text_intro(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": { "type": "text", "title": "Hello", "subtitle": "World", "background": "#ffffff", "title_color": "black", "title_size": 64, "duration": 2.5, }, "outro": {"type": "none"}, }) assert cfg.enabled is True assert cfg.intro_type == "text" assert cfg.intro_title == "Hello" assert cfg.intro_subtitle == "World" assert cfg.intro_background == "#ffffff" assert cfg.intro_title_color == "black" assert cfg.intro_title_size == 64 assert cfg.intro_duration == 2.5 def test_enabled_with_video_outro(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": {"type": "none"}, "outro": { "type": "video", "video_path": "/tmp/outro.mp4", "duration": 4.0, }, }) assert cfg.enabled is True assert cfg.outro_type == "video" assert cfg.outro_video_path == "/tmp/outro.mp4" assert cfg.outro_duration == 4.0 def test_enabled_with_text_outro_default_values(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": {"type": "none"}, "outro": {"type": "text"}, }) assert cfg.outro_title == "感谢观看" assert cfg.outro_subtitle == "点赞关注不迷路" assert cfg.outro_title_size == 48 assert cfg.outro_duration == 3.0 def test_video_key_fallback(self): """video 字段作为 video_path 的 fallback.""" cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": { "type": "video", "video": "/tmp/fallback.mp4", }, "outro": {"type": "none"}, }) assert cfg.intro_video_path == "/tmp/fallback.mp4" def test_transition_config(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": {"type": "none"}, "outro": {"type": "none"}, "transition": "fade", "transition_duration": 1.0, }) assert cfg.transition_effect == "fade" assert cfg.transition_duration == 1.0 def test_default_transition(self): cfg = IntroOutroConfig.from_dict({ "enabled": True, "intro": {"type": "none"}, "outro": {"type": "none"}, }) assert cfg.transition_effect == "fade" assert cfg.transition_duration == 0.5 class TestIntroOutroConfigProperties: """has_intro / has_outro 属性.""" def test_has_intro_video_type(self): cfg = IntroOutroConfig( enabled=True, intro_type="video", intro_video_path="/tmp/a.mp4", ) assert cfg.has_intro is True def test_has_intro_text_type(self): cfg = IntroOutroConfig( enabled=True, intro_type="text", intro_title="Hi", ) assert cfg.has_intro is True def test_no_intro_when_disabled(self): cfg = IntroOutroConfig( enabled=False, intro_type="video", intro_video_path="/tmp/a.mp4", ) assert cfg.has_intro is False def test_no_intro_when_none_type(self): cfg = IntroOutroConfig( enabled=True, intro_type="none", ) assert cfg.has_intro is False def test_has_outro_video_type(self): cfg = IntroOutroConfig( enabled=True, outro_type="video", outro_video_path="/tmp/a.mp4", ) assert cfg.has_outro is True def test_has_outro_text_type(self): cfg = IntroOutroConfig( enabled=True, outro_type="text", outro_title="Bye", ) assert cfg.has_outro is True def test_has_outro_follow_type(self): cfg = IntroOutroConfig( enabled=True, outro_type="follow", outro_title="Follow me", ) assert cfg.has_outro is True def test_no_outro_when_disabled(self): cfg = IntroOutroConfig( enabled=False, outro_type="text", outro_title="Bye", ) assert cfg.has_outro is False def test_no_outro_when_none_type(self): cfg = IntroOutroConfig( enabled=True, outro_type="none", ) assert cfg.has_outro is False class TestIntroOutroConfigValidate: """validate 校验逻辑.""" def test_disabled_is_valid(self): cfg = IntroOutroConfig(enabled=False) ok, msg = cfg.validate() assert ok is True assert msg == "" def test_video_intro_missing_path(self): cfg = IntroOutroConfig( enabled=True, intro_type="video", intro_video_path="", outro_type="none", ) ok, msg = cfg.validate() assert ok is False assert "video_path" in msg def test_text_intro_missing_title(self): cfg = IntroOutroConfig( enabled=True, intro_type="text", intro_title="", outro_type="none", ) ok, msg = cfg.validate() assert ok is False assert "title" in msg def test_video_outro_missing_path(self): cfg = IntroOutroConfig( enabled=True, intro_type="none", outro_type="video", outro_video_path="", ) ok, msg = cfg.validate() assert ok is False assert "video_path" in msg def test_text_outro_missing_title(self): cfg = IntroOutroConfig( enabled=True, intro_type="none", outro_type="text", outro_title="", ) ok, msg = cfg.validate() assert ok is False assert "title" in msg def test_intro_duration_zero(self): cfg = IntroOutroConfig( enabled=True, intro_type="text", intro_title="Hi", intro_duration=0, outro_type="none", ) ok, msg = cfg.validate() assert ok is False assert "片头时长" in msg def test_intro_duration_negative(self): cfg = IntroOutroConfig( enabled=True, intro_type="text", intro_title="Hi", intro_duration=-1.0, outro_type="none", ) ok, msg = cfg.validate() assert ok is False assert "片头时长" in msg def test_outro_duration_zero(self): cfg = IntroOutroConfig( enabled=True, intro_type="none", outro_type="text", outro_title="Bye", outro_duration=0, ) ok, msg = cfg.validate() assert ok is False assert "片尾时长" in msg def test_valid_full_config(self): cfg = IntroOutroConfig( enabled=True, intro_type="video", intro_video_path="/tmp/intro.mp4", intro_duration=3.0, outro_type="text", outro_title="Thanks", outro_duration=2.0, ) ok, msg = cfg.validate() assert ok is True assert msg == ""