Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc11719e7d |
@@ -0,0 +1,377 @@
|
||||
"""小领域模块组合单测 — template_clip_config / tag / exceptions / editing_mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.domain.editing_mode import EditingMode
|
||||
from packages.domain.exceptions import (
|
||||
DomainError,
|
||||
NotFoundError,
|
||||
QuotaExceededError,
|
||||
ValidationError,
|
||||
)
|
||||
from packages.domain.tag import Tag
|
||||
from packages.domain.template_clip_config import (
|
||||
ClipType,
|
||||
TemplateClipConfig,
|
||||
TransitionEffect,
|
||||
)
|
||||
|
||||
|
||||
# ── EditingMode ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestEditingMode:
|
||||
def test_one_take(self):
|
||||
assert EditingMode.ONE_TAKE == "one_take"
|
||||
|
||||
def test_pip(self):
|
||||
assert EditingMode.PIP == "pip"
|
||||
|
||||
def test_voice_over(self):
|
||||
assert EditingMode.VOICE_OVER == "voice_over"
|
||||
|
||||
def test_voice_pip(self):
|
||||
assert EditingMode.VOICE_PIP == "voice_pip"
|
||||
|
||||
def test_from_string(self):
|
||||
assert EditingMode("one_take") == EditingMode.ONE_TAKE
|
||||
assert EditingMode("pip") == EditingMode.PIP
|
||||
|
||||
def test_invalid_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
EditingMode("invalid_mode")
|
||||
|
||||
def test_is_str(self):
|
||||
# StrEnum 实例本身就是 str
|
||||
assert isinstance(EditingMode.ONE_TAKE, str)
|
||||
assert EditingMode.ONE_TAKE + "_suffix" == "one_take_suffix"
|
||||
|
||||
|
||||
# ── 异常类 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestExceptions:
|
||||
def test_domain_error_is_exception(self):
|
||||
err = DomainError("test")
|
||||
assert isinstance(err, Exception)
|
||||
assert str(err) == "test"
|
||||
|
||||
def test_not_found_inherits_domain(self):
|
||||
err = NotFoundError("not found")
|
||||
assert isinstance(err, DomainError)
|
||||
assert str(err) == "not found"
|
||||
|
||||
def test_validation_error_inherits_domain(self):
|
||||
err = ValidationError("bad input")
|
||||
assert isinstance(err, DomainError)
|
||||
assert str(err) == "bad input"
|
||||
|
||||
def test_quota_exceeded_error_attributes(self):
|
||||
err = QuotaExceededError(dimension="storage_gb", limit=100, used=150)
|
||||
assert err.dimension == "storage_gb"
|
||||
assert err.limit == 100
|
||||
assert err.used == 150
|
||||
assert "storage_gb" in str(err)
|
||||
assert "150" in str(err)
|
||||
assert "100" in str(err)
|
||||
|
||||
def test_quota_exceeded_is_domain_error(self):
|
||||
err = QuotaExceededError("x", 10, 20)
|
||||
assert isinstance(err, DomainError)
|
||||
|
||||
def test_catch_domain_error_catches_all(self):
|
||||
"""所有领域异常都能被 DomainError catch."""
|
||||
for cls in [NotFoundError, ValidationError, QuotaExceededError]:
|
||||
try:
|
||||
if cls == QuotaExceededError:
|
||||
raise cls("dim", 10, 20)
|
||||
raise cls("msg")
|
||||
except DomainError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail(f"{cls.__name__} 未被 DomainError 捕获")
|
||||
|
||||
|
||||
# ── Tag ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTag:
|
||||
def test_create_basic(self):
|
||||
tag = Tag.create(user_id="user1", name="旅行")
|
||||
assert tag.id
|
||||
assert tag.user_id == "user1"
|
||||
assert tag.name == "旅行"
|
||||
|
||||
def test_create_name_stripped(self):
|
||||
tag = Tag.create(user_id="u1", name=" 美食 ")
|
||||
assert tag.name == "美食"
|
||||
|
||||
def test_create_empty_name_raises(self):
|
||||
with pytest.raises(ValueError, match="标签名称不能为空"):
|
||||
Tag.create(user_id="u1", name="")
|
||||
|
||||
def test_create_whitespace_name_raises(self):
|
||||
with pytest.raises(ValueError, match="标签名称不能为空"):
|
||||
Tag.create(user_id="u1", name=" ")
|
||||
|
||||
def test_create_has_created_at(self):
|
||||
tag = Tag.create(user_id="u1", name="t1")
|
||||
assert tag.created_at is not None
|
||||
|
||||
def test_unique_ids(self):
|
||||
t1 = Tag.create(user_id="u1", name="t1")
|
||||
t2 = Tag.create(user_id="u1", name="t2")
|
||||
assert t1.id != t2.id
|
||||
|
||||
|
||||
# ── ClipType 枚举 ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestClipType:
|
||||
def test_all_types(self):
|
||||
assert ClipType.INTRO == "intro"
|
||||
assert ClipType.MAIN == "main"
|
||||
assert ClipType.TRANSITION == "transition"
|
||||
assert ClipType.OUTRO == "outro"
|
||||
assert ClipType.TITLE == "title"
|
||||
assert ClipType.SUBTITLE == "subtitle"
|
||||
|
||||
def test_from_string(self):
|
||||
assert ClipType("main") == ClipType.MAIN
|
||||
assert ClipType("intro") == ClipType.INTRO
|
||||
|
||||
def test_invalid_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
ClipType("invalid")
|
||||
|
||||
|
||||
# ── TransitionEffect 枚举 ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTransitionEffect:
|
||||
def test_all_effects(self):
|
||||
assert TransitionEffect.CUT == "cut"
|
||||
assert TransitionEffect.FADE == "fade"
|
||||
assert TransitionEffect.SLIDE_LEFT == "slide_left"
|
||||
assert TransitionEffect.SLIDE_RIGHT == "slide_right"
|
||||
assert TransitionEffect.DISSOLVE == "dissolve"
|
||||
assert TransitionEffect.WIPE == "wipe"
|
||||
|
||||
def test_from_string(self):
|
||||
assert TransitionEffect("fade") == TransitionEffect.FADE
|
||||
assert TransitionEffect("cut") == TransitionEffect.CUT
|
||||
|
||||
def test_invalid_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
TransitionEffect("invalid_effect")
|
||||
|
||||
|
||||
# ── TemplateClipConfig.create ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTemplateClipConfigCreate:
|
||||
def test_basic_creation(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="tmpl_1",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
assert clip.id
|
||||
assert clip.template_id == "tmpl_1"
|
||||
assert clip.clip_type == ClipType.MAIN
|
||||
assert clip.order == 1
|
||||
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 == {}
|
||||
|
||||
def test_clip_type_string(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type="intro", order=0,
|
||||
)
|
||||
assert clip.clip_type == ClipType.INTRO
|
||||
|
||||
def test_template_id_stripped(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id=" tmpl_1 ", clip_type=ClipType.MAIN, order=1,
|
||||
)
|
||||
assert clip.template_id == "tmpl_1"
|
||||
|
||||
def test_empty_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
TemplateClipConfig.create(template_id="", clip_type=ClipType.MAIN, order=1)
|
||||
|
||||
def test_whitespace_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
TemplateClipConfig.create(template_id=" ", clip_type=ClipType.MAIN, order=1)
|
||||
|
||||
def test_negative_min_duration_raises(self):
|
||||
with pytest.raises(ValueError, match="min_duration 不能为负数"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=-1.0,
|
||||
)
|
||||
|
||||
def test_negative_max_duration_raises(self):
|
||||
with pytest.raises(ValueError, match="max_duration 不能为负数"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
max_duration=-1.0,
|
||||
)
|
||||
|
||||
def test_min_greater_than_max_raises(self):
|
||||
with pytest.raises(ValueError, match="min_duration 不能大于 max_duration"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=10.0, max_duration=5.0,
|
||||
)
|
||||
|
||||
def test_zero_min_and_max_ok(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=0, max_duration=0,
|
||||
)
|
||||
assert clip.min_duration == 0
|
||||
assert clip.max_duration == 0
|
||||
|
||||
def test_min_zero_max_positive_ok(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=0, max_duration=10.0,
|
||||
)
|
||||
assert clip.max_duration == 10.0
|
||||
|
||||
def test_min_equals_max_ok(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=5.0, max_duration=5.0,
|
||||
)
|
||||
assert clip.min_duration == 5.0
|
||||
assert clip.max_duration == 5.0
|
||||
|
||||
def test_with_text_template(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
text_template=" 欢迎收看 {channel} ",
|
||||
)
|
||||
# text_template 会 strip
|
||||
assert clip.text_template == "欢迎收看 {channel}"
|
||||
|
||||
def test_with_material_requirements(self):
|
||||
reqs = {"material_type": "video", "min_duration": 3}
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
material_requirements=reqs,
|
||||
)
|
||||
assert clip.material_requirements == reqs
|
||||
|
||||
def test_material_requirements_none_defaults_empty(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
material_requirements=None,
|
||||
)
|
||||
assert clip.material_requirements == {}
|
||||
|
||||
def test_transition_effect_string(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
transition_effect="fade",
|
||||
)
|
||||
assert clip.transition_effect == TransitionEffect.FADE
|
||||
|
||||
def test_with_config(self):
|
||||
config = {"speed": 1.5, "filter": "vibrance"}
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
config=config,
|
||||
)
|
||||
assert clip.config == config
|
||||
|
||||
def test_config_none_defaults_empty(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
config=None,
|
||||
)
|
||||
assert clip.config == {}
|
||||
|
||||
def test_created_at_and_updated_at(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
)
|
||||
assert clip.created_at is not None
|
||||
assert clip.updated_at is not None
|
||||
|
||||
def test_unique_ids(self):
|
||||
c1 = TemplateClipConfig.create(template_id="t1", clip_type=ClipType.MAIN, order=1)
|
||||
c2 = TemplateClipConfig.create(template_id="t1", clip_type=ClipType.MAIN, order=2)
|
||||
assert c1.id != c2.id
|
||||
|
||||
|
||||
# ── TemplateClipConfig 属性 ────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTemplateClipConfigProperties:
|
||||
def test_has_duration_range_false_both_zero(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
)
|
||||
assert clip.has_duration_range is False
|
||||
|
||||
def test_has_duration_range_true_min_only(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=2.0,
|
||||
)
|
||||
assert clip.has_duration_range is True
|
||||
|
||||
def test_has_duration_range_true_max_only(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
max_duration=10.0,
|
||||
)
|
||||
assert clip.has_duration_range is True
|
||||
|
||||
def test_has_duration_range_true_both_set(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=2.0, max_duration=10.0,
|
||||
)
|
||||
assert clip.has_duration_range is True
|
||||
|
||||
def test_default_duration_zero(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
)
|
||||
assert clip.default_duration == 0.0
|
||||
|
||||
def test_default_duration_min_only(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=3.0,
|
||||
)
|
||||
assert clip.default_duration == 3.0
|
||||
|
||||
def test_default_duration_max_only(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
max_duration=10.0,
|
||||
)
|
||||
assert clip.default_duration == 10.0
|
||||
|
||||
def test_default_duration_both_midpoint(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=2.0, max_duration=8.0,
|
||||
)
|
||||
assert clip.default_duration == 5.0
|
||||
|
||||
def test_default_duration_min_equals_max(self):
|
||||
clip = TemplateClipConfig.create(
|
||||
template_id="t1", clip_type=ClipType.MAIN, order=1,
|
||||
min_duration=5.0, max_duration=5.0,
|
||||
)
|
||||
assert clip.default_duration == 5.0
|
||||
Reference in New Issue
Block a user