Files
xiaoxia-saas/tests/unit/test_intro_outro_engine.py
T
CI Bot 2df7bc9dc8
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m41s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m45s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m34s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 5m49s
CI/CD Pipeline / Frontend Lint (push) Successful in 6m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m27s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
CI/CD Pipeline / Unit Tests (push) Failing after 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m10s
style: auto-format with black + isort + prettier
2026-07-24 13:35:05 +00:00

302 lines
8.7 KiB
Python
Executable File

"""
片头片尾引擎配置与纯逻辑测试.
覆盖 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 == ""