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
302 lines
8.7 KiB
Python
Executable File
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 == ""
|