test(wave184): intro_outro_config 片头片尾配置 +59测 #1145
Executable
+593
@@ -0,0 +1,593 @@
|
||||
"""intro_outro_config 片头片尾配置单测."""
|
||||
|
||||
import pytest
|
||||
from domain.intro_outro_config import (
|
||||
INTRO_OUTRO_TYPE_FOLLOW,
|
||||
INTRO_OUTRO_TYPE_NONE,
|
||||
INTRO_OUTRO_TYPE_TEXT,
|
||||
INTRO_OUTRO_TYPE_VIDEO,
|
||||
TRANSITION_FADE,
|
||||
TRANSITION_SLIDE,
|
||||
TRANSITION_WIPE,
|
||||
IntroOutroConfig,
|
||||
)
|
||||
|
||||
# ── 常量测试 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""模块常量"""
|
||||
|
||||
def test_type_constants(self):
|
||||
assert INTRO_OUTRO_TYPE_NONE == "none"
|
||||
assert INTRO_OUTRO_TYPE_VIDEO == "video"
|
||||
assert INTRO_OUTRO_TYPE_TEXT == "text"
|
||||
assert INTRO_OUTRO_TYPE_FOLLOW == "follow"
|
||||
|
||||
def test_transition_constants(self):
|
||||
assert TRANSITION_FADE == "fade"
|
||||
assert TRANSITION_SLIDE == "slide"
|
||||
assert TRANSITION_WIPE == "wipe"
|
||||
|
||||
|
||||
# ── 默认值与基础属性 ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestDefaultConfig:
|
||||
"""IntroOutroConfig 默认值"""
|
||||
|
||||
def test_default_not_enabled(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.enabled is False
|
||||
|
||||
def test_default_intro(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.intro_type == INTRO_OUTRO_TYPE_NONE
|
||||
assert c.intro_video_path == ""
|
||||
assert c.intro_duration == 3.0
|
||||
assert c.intro_background == "#000000"
|
||||
assert c.intro_title == ""
|
||||
assert c.intro_subtitle == ""
|
||||
assert c.intro_title_color == "white"
|
||||
assert c.intro_title_size == 48
|
||||
assert c.intro_subtitle_color == "gray"
|
||||
assert c.intro_subtitle_size == 24
|
||||
|
||||
def test_default_outro(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_NONE
|
||||
assert c.outro_video_path == ""
|
||||
assert c.outro_duration == 3.0
|
||||
assert c.outro_background == "#000000"
|
||||
assert c.outro_title == "感谢观看"
|
||||
assert c.outro_subtitle == "点赞关注不迷路"
|
||||
assert c.outro_title_color == "white"
|
||||
assert c.outro_title_size == 48
|
||||
assert c.outro_subtitle_color == "gray"
|
||||
assert c.outro_subtitle_size == 24
|
||||
|
||||
def test_default_transition(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.transition_effect == TRANSITION_FADE
|
||||
assert c.transition_duration == 0.5
|
||||
|
||||
|
||||
# ── from_dict 构造 ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestFromDict:
|
||||
"""from_dict 工厂方法"""
|
||||
|
||||
def test_none_returns_default(self):
|
||||
c = IntroOutroConfig.from_dict(None)
|
||||
assert c.enabled is False
|
||||
|
||||
def test_empty_dict_returns_default(self):
|
||||
c = IntroOutroConfig.from_dict({})
|
||||
assert c.enabled is False
|
||||
|
||||
def test_enabled_false_returns_default(self):
|
||||
c = IntroOutroConfig.from_dict({"enabled": False})
|
||||
assert c.enabled is False
|
||||
|
||||
def test_minimal_enabled(self):
|
||||
c = IntroOutroConfig.from_dict({"enabled": True})
|
||||
assert c.enabled is True
|
||||
assert c.intro_type == INTRO_OUTRO_TYPE_NONE
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_NONE
|
||||
|
||||
def test_intro_video(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {
|
||||
"type": "video",
|
||||
"video_path": "/tmp/intro.mp4",
|
||||
"duration": 5.0,
|
||||
},
|
||||
}
|
||||
)
|
||||
assert c.intro_type == INTRO_OUTRO_TYPE_VIDEO
|
||||
assert c.intro_video_path == "/tmp/intro.mp4"
|
||||
assert c.intro_duration == 5.0
|
||||
|
||||
def test_intro_text(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {
|
||||
"type": "text",
|
||||
"title": "欢迎来到",
|
||||
"subtitle": "我的频道",
|
||||
"background": "#ffffff",
|
||||
"title_color": "black",
|
||||
"title_size": 64,
|
||||
"subtitle_color": "darkgray",
|
||||
"subtitle_size": 32,
|
||||
},
|
||||
}
|
||||
)
|
||||
assert c.intro_type == INTRO_OUTRO_TYPE_TEXT
|
||||
assert c.intro_title == "欢迎来到"
|
||||
assert c.intro_subtitle == "我的频道"
|
||||
assert c.intro_background == "#ffffff"
|
||||
assert c.intro_title_size == 64
|
||||
assert c.intro_subtitle_size == 32
|
||||
|
||||
def test_outro_text(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"outro": {
|
||||
"type": "text",
|
||||
"title": "再见",
|
||||
"subtitle": "下次见",
|
||||
"title_size": 56,
|
||||
},
|
||||
}
|
||||
)
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_TEXT
|
||||
assert c.outro_title == "再见"
|
||||
assert c.outro_subtitle == "下次见"
|
||||
assert c.outro_title_size == 56
|
||||
|
||||
def test_outro_follow_type(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"outro": {"type": "follow", "title": "关注我"},
|
||||
}
|
||||
)
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_FOLLOW
|
||||
assert c.outro_title == "关注我"
|
||||
|
||||
def test_outro_video(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"outro": {
|
||||
"type": "video",
|
||||
"video_path": "/tmp/outro.mp4",
|
||||
"duration": 4.0,
|
||||
},
|
||||
}
|
||||
)
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_VIDEO
|
||||
assert c.outro_video_path == "/tmp/outro.mp4"
|
||||
assert c.outro_duration == 4.0
|
||||
|
||||
def test_transition_config(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"transition": "slide",
|
||||
"transition_duration": 1.0,
|
||||
}
|
||||
)
|
||||
assert c.transition_effect == TRANSITION_SLIDE
|
||||
assert c.transition_duration == 1.0
|
||||
|
||||
def test_video_field_alias(self):
|
||||
# video 字段兼容(video_path 和 video 都能用)
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {"type": "video", "video": "old_path.mp4"},
|
||||
}
|
||||
)
|
||||
assert c.intro_video_path == "old_path.mp4"
|
||||
|
||||
def test_video_path_preferred_over_video(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {"type": "video", "video_path": "new.mp4", "video": "old.mp4"},
|
||||
}
|
||||
)
|
||||
assert c.intro_video_path == "new.mp4"
|
||||
|
||||
def test_invalid_duration_falls_back(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {"duration": "abc"},
|
||||
}
|
||||
)
|
||||
assert c.intro_duration == 3.0
|
||||
|
||||
def test_invalid_size_falls_back(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {"title_size": "not_a_number"},
|
||||
}
|
||||
)
|
||||
assert c.intro_title_size == 48
|
||||
|
||||
def test_none_intro_outro(self):
|
||||
c = IntroOutroConfig.from_dict({"enabled": True, "intro": None, "outro": None})
|
||||
assert c.intro_type == INTRO_OUTRO_TYPE_NONE
|
||||
assert c.outro_type == INTRO_OUTRO_TYPE_NONE
|
||||
|
||||
def test_empty_title_defaults_for_outro(self):
|
||||
# outro title 为空时回退到默认值
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"outro": {"type": "text", "title": ""},
|
||||
}
|
||||
)
|
||||
assert c.outro_title == "感谢观看"
|
||||
|
||||
def test_empty_subtitle_defaults_for_outro(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"outro": {"subtitle": ""},
|
||||
}
|
||||
)
|
||||
assert c.outro_subtitle == "点赞关注不迷路"
|
||||
|
||||
def test_combined_full_config(self):
|
||||
c = IntroOutroConfig.from_dict(
|
||||
{
|
||||
"enabled": True,
|
||||
"intro": {
|
||||
"type": "text",
|
||||
"title": "片头标题",
|
||||
"subtitle": "片头副标题",
|
||||
"background": "#123456",
|
||||
"duration": 2.5,
|
||||
"title_size": 72,
|
||||
},
|
||||
"outro": {
|
||||
"type": "video",
|
||||
"video_path": "/outro.mp4",
|
||||
"duration": 4.0,
|
||||
},
|
||||
"transition": "wipe",
|
||||
"transition_duration": 0.8,
|
||||
}
|
||||
)
|
||||
assert c.intro_title == "片头标题"
|
||||
assert c.intro_duration == 2.5
|
||||
assert c.outro_type == "video"
|
||||
assert c.outro_video_path == "/outro.mp4"
|
||||
assert c.transition_effect == "wipe"
|
||||
assert c.transition_duration == 0.8
|
||||
|
||||
def test_does_not_mutate_input(self):
|
||||
data = {"enabled": True, "intro": {"type": "text", "title": "test"}}
|
||||
data_copy = {
|
||||
"enabled": True,
|
||||
"intro": {"type": "text", "title": "test"},
|
||||
}
|
||||
IntroOutroConfig.from_dict(data)
|
||||
assert data == data_copy
|
||||
|
||||
|
||||
# ── has_intro / has_outro 属性 ───────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestHasIntroOutro:
|
||||
"""has_intro / has_outro 属性"""
|
||||
|
||||
def test_disabled_no_intro(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.has_intro is False
|
||||
|
||||
def test_disabled_no_outro(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.has_outro is False
|
||||
|
||||
def test_enabled_none_type_no_intro(self):
|
||||
c = IntroOutroConfig(enabled=True, intro_type=INTRO_OUTRO_TYPE_NONE)
|
||||
assert c.has_intro is False
|
||||
|
||||
def test_video_intro_has_intro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
intro_video_path="/x.mp4",
|
||||
)
|
||||
assert c.has_intro is True
|
||||
|
||||
def test_text_intro_has_intro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
)
|
||||
assert c.has_intro is True
|
||||
|
||||
def test_video_outro_has_outro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
outro_video_path="/x.mp4",
|
||||
)
|
||||
assert c.has_outro is True
|
||||
|
||||
def test_text_outro_has_outro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
)
|
||||
assert c.has_outro is True
|
||||
|
||||
def test_follow_outro_has_outro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_FOLLOW,
|
||||
outro_title="关注",
|
||||
)
|
||||
assert c.has_outro is True
|
||||
|
||||
def test_follow_type_no_intro(self):
|
||||
# follow 只是片尾类型,片头不支持
|
||||
c = IntroOutroConfig(enabled=True, intro_type=INTRO_OUTRO_TYPE_FOLLOW)
|
||||
assert c.has_intro is False
|
||||
|
||||
|
||||
# ── total_extra_duration ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTotalExtraDuration:
|
||||
"""total_extra_duration 属性"""
|
||||
|
||||
def test_disabled_zero(self):
|
||||
c = IntroOutroConfig()
|
||||
assert c.total_extra_duration == 0.0
|
||||
|
||||
def test_only_intro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_duration=3.0,
|
||||
)
|
||||
assert c.total_extra_duration == 3.0
|
||||
|
||||
def test_only_outro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
outro_duration=4.0,
|
||||
)
|
||||
assert c.total_extra_duration == 4.0
|
||||
|
||||
def test_both_intro_outro(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
intro_video_path="/i.mp4",
|
||||
intro_duration=2.5,
|
||||
outro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
outro_video_path="/o.mp4",
|
||||
outro_duration=3.5,
|
||||
)
|
||||
assert c.total_extra_duration == 6.0
|
||||
|
||||
def test_zero_duration_not_counted(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_duration=0.0,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
outro_duration=0.0,
|
||||
)
|
||||
assert c.total_extra_duration == 0.0
|
||||
|
||||
def test_negative_duration_not_counted(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_duration=-1.0,
|
||||
)
|
||||
assert c.total_extra_duration == 0.0
|
||||
|
||||
|
||||
# ── validate 校验 ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestValidate:
|
||||
"""validate 方法"""
|
||||
|
||||
def test_disabled_always_valid(self):
|
||||
c = IntroOutroConfig()
|
||||
valid, msg = c.validate()
|
||||
assert valid is True
|
||||
assert msg == ""
|
||||
|
||||
def test_none_types_valid(self):
|
||||
c = IntroOutroConfig(enabled=True)
|
||||
valid, msg = c.validate()
|
||||
assert valid is True
|
||||
|
||||
def test_invalid_intro_type(self):
|
||||
c = IntroOutroConfig(enabled=True, intro_type="invalid")
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "片头类型" in msg
|
||||
|
||||
def test_invalid_outro_type(self):
|
||||
c = IntroOutroConfig(enabled=True, outro_type="invalid")
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "片尾类型" in msg
|
||||
|
||||
def test_video_intro_no_path(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
intro_video_path="",
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "video_path" in msg
|
||||
|
||||
def test_video_intro_with_path_valid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
intro_video_path="/path.mp4",
|
||||
)
|
||||
valid, _ = c.validate()
|
||||
assert valid is True
|
||||
|
||||
def test_text_intro_no_title(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="",
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "title" in msg
|
||||
|
||||
def test_text_intro_with_title_valid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
)
|
||||
valid, _ = c.validate()
|
||||
assert valid is True
|
||||
|
||||
def test_video_outro_no_path(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_VIDEO,
|
||||
outro_video_path="",
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "video_path" in msg
|
||||
|
||||
def test_text_outro_no_title(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="",
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "title" in msg
|
||||
|
||||
def test_follow_outro_no_title(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_FOLLOW,
|
||||
outro_title="",
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "title" in msg
|
||||
|
||||
def test_follow_outro_with_title_valid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_FOLLOW,
|
||||
outro_title="关注",
|
||||
)
|
||||
valid, _ = c.validate()
|
||||
assert valid is True
|
||||
|
||||
def test_zero_intro_duration_invalid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_duration=0.0,
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "片头时长" in msg
|
||||
|
||||
def test_negative_outro_duration_invalid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
outro_duration=-1.0,
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "片尾时长" in msg
|
||||
|
||||
def test_negative_transition_duration_invalid(self):
|
||||
c = IntroOutroConfig(enabled=True, transition_duration=-0.5)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "转场" in msg
|
||||
|
||||
def test_zero_transition_valid(self):
|
||||
c = IntroOutroConfig(enabled=True, transition_duration=0.0)
|
||||
valid, _ = c.validate()
|
||||
assert valid is True
|
||||
|
||||
def test_zero_title_size_invalid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_title_size=0,
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "标题字号" in msg
|
||||
|
||||
def test_negative_subtitle_size_invalid(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
outro_subtitle_size=-1,
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is False
|
||||
assert "副标题字号" in msg
|
||||
|
||||
def test_full_valid_config(self):
|
||||
c = IntroOutroConfig(
|
||||
enabled=True,
|
||||
intro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
intro_title="Hi",
|
||||
intro_duration=3.0,
|
||||
intro_title_size=48,
|
||||
intro_subtitle_size=24,
|
||||
outro_type=INTRO_OUTRO_TYPE_TEXT,
|
||||
outro_title="Bye",
|
||||
outro_duration=3.0,
|
||||
outro_title_size=48,
|
||||
outro_subtitle_size=24,
|
||||
transition_duration=0.5,
|
||||
)
|
||||
valid, msg = c.validate()
|
||||
assert valid is True
|
||||
assert msg == ""
|
||||
Reference in New Issue
Block a user