Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fcabc8058 | |||
| 7ef234fe55 |
@@ -159,4 +159,3 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+275
@@ -0,0 +1,275 @@
|
||||
"""TemplateClipConfig domain entity unit tests (wave219)."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from domain.template_clip_config import (
|
||||
ClipType,
|
||||
TemplateClipConfig,
|
||||
TransitionEffect,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ClipType enum
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestClipType:
|
||||
def test_all_members_exist(self):
|
||||
assert ClipType.INTRO.value == "intro"
|
||||
assert ClipType.MAIN.value == "main"
|
||||
assert ClipType.TRANSITION.value == "transition"
|
||||
assert ClipType.OUTRO.value == "outro"
|
||||
assert ClipType.TITLE.value == "title"
|
||||
assert ClipType.SUBTITLE.value == "subtitle"
|
||||
|
||||
def test_is_str_enum(self):
|
||||
# StrEnum members are instances of str
|
||||
assert isinstance(ClipType.INTRO, str)
|
||||
assert ClipType.INTRO == "intro"
|
||||
|
||||
def test_from_string(self):
|
||||
assert ClipType("intro") is ClipType.INTRO
|
||||
assert ClipType("main") is ClipType.MAIN
|
||||
|
||||
def test_invalid_value_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
ClipType("invalid")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TransitionEffect enum
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestTransitionEffect:
|
||||
def test_all_members_exist(self):
|
||||
assert TransitionEffect.CUT.value == "cut"
|
||||
assert TransitionEffect.FADE.value == "fade"
|
||||
assert TransitionEffect.SLIDE_LEFT.value == "slide_left"
|
||||
assert TransitionEffect.SLIDE_RIGHT.value == "slide_right"
|
||||
assert TransitionEffect.DISSOLVE.value == "dissolve"
|
||||
assert TransitionEffect.WIPE.value == "wipe"
|
||||
|
||||
def test_is_str_enum(self):
|
||||
assert isinstance(TransitionEffect.CUT, str)
|
||||
assert TransitionEffect.CUT == "cut"
|
||||
|
||||
def test_from_string(self):
|
||||
assert TransitionEffect("cut") is TransitionEffect.CUT
|
||||
assert TransitionEffect("fade") is TransitionEffect.FADE
|
||||
|
||||
def test_invalid_value_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
TransitionEffect("invalid_effect")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TemplateClipConfig.create()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestTemplateClipConfigCreate:
|
||||
def test_create_basic(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
assert cfg.template_id == "tpl_001"
|
||||
assert cfg.clip_type == ClipType.MAIN
|
||||
assert cfg.order == 1
|
||||
assert cfg.id # auto-generated uuid hex
|
||||
assert len(cfg.id) == 32 # uuid4 hex length
|
||||
|
||||
def test_create_with_string_clip_type(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type="intro",
|
||||
order=0,
|
||||
)
|
||||
assert cfg.clip_type == ClipType.INTRO
|
||||
|
||||
def test_create_with_string_transition_effect(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
transition_effect="fade",
|
||||
)
|
||||
assert cfg.transition_effect == TransitionEffect.FADE
|
||||
|
||||
def test_create_empty_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
|
||||
def test_create_whitespace_template_id_raises(self):
|
||||
with pytest.raises(ValueError, match="template_id 不能为空"):
|
||||
TemplateClipConfig.create(
|
||||
template_id=" ",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
|
||||
def test_create_template_id_stripped(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id=" tpl_001 ",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
assert cfg.template_id == "tpl_001"
|
||||
|
||||
def test_create_negative_min_duration_raises(self):
|
||||
with pytest.raises(ValueError, match="min_duration 不能为负数"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
min_duration=-1.0,
|
||||
)
|
||||
|
||||
def test_create_negative_max_duration_raises(self):
|
||||
with pytest.raises(ValueError, match="max_duration 不能为负数"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
max_duration=-5.0,
|
||||
)
|
||||
|
||||
def test_create_min_greater_than_max_raises(self):
|
||||
with pytest.raises(ValueError, match="min_duration 不能大于 max_duration"):
|
||||
TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
min_duration=10.0,
|
||||
max_duration=5.0,
|
||||
)
|
||||
|
||||
def test_create_min_equals_max_ok(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
min_duration=5.0,
|
||||
max_duration=5.0,
|
||||
)
|
||||
assert cfg.min_duration == 5.0
|
||||
assert cfg.max_duration == 5.0
|
||||
|
||||
def test_create_default_values(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
assert cfg.min_duration == 0.0
|
||||
assert cfg.max_duration == 0.0
|
||||
assert cfg.text_template == ""
|
||||
assert cfg.material_requirements == {}
|
||||
assert cfg.transition_effect == TransitionEffect.CUT
|
||||
assert cfg.config == {}
|
||||
|
||||
def test_create_material_requirements_none_becomes_empty_dict(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
material_requirements=None,
|
||||
)
|
||||
assert cfg.material_requirements == {}
|
||||
# should be a fresh dict, not shared
|
||||
cfg.material_requirements["key"] = "val"
|
||||
cfg2 = TemplateClipConfig.create(
|
||||
template_id="tpl_002",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
)
|
||||
assert cfg2.material_requirements == {}
|
||||
|
||||
def test_create_config_none_becomes_empty_dict(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
config=None,
|
||||
)
|
||||
assert cfg.config == {}
|
||||
|
||||
def test_create_text_template_stripped(self):
|
||||
cfg = TemplateClipConfig.create(
|
||||
template_id="tpl_001",
|
||||
clip_type=ClipType.MAIN,
|
||||
order=1,
|
||||
text_template=" hello {{name}} ",
|
||||
)
|
||||
assert cfg.text_template == "hello {{name}}"
|
||||
|
||||
def test_create_generates_unique_ids(self):
|
||||
cfg1 = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
|
||||
cfg2 = TemplateClipConfig.create("tpl", ClipType.MAIN, 2)
|
||||
assert cfg1.id != cfg2.id
|
||||
|
||||
def test_create_timestamps_are_utc(self):
|
||||
before = datetime.now(timezone.utc)
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
|
||||
after = datetime.now(timezone.utc)
|
||||
assert before <= cfg.created_at <= after
|
||||
assert before <= cfg.updated_at <= after
|
||||
assert cfg.created_at.tzinfo is timezone.utc
|
||||
assert cfg.updated_at.tzinfo is timezone.utc
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# has_duration_range property
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestHasDurationRange:
|
||||
def test_both_zero(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
|
||||
assert cfg.has_duration_range is False
|
||||
|
||||
def test_only_min_positive(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0)
|
||||
assert cfg.has_duration_range is True
|
||||
|
||||
def test_only_max_positive(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0)
|
||||
assert cfg.has_duration_range is True
|
||||
|
||||
def test_both_positive(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=5.0)
|
||||
assert cfg.has_duration_range is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# default_duration property
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestDefaultDuration:
|
||||
def test_both_zero_returns_zero(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1)
|
||||
assert cfg.default_duration == 0.0
|
||||
|
||||
def test_only_min_returns_min(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0)
|
||||
assert cfg.default_duration == 3.0
|
||||
|
||||
def test_only_max_returns_max(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, max_duration=5.0)
|
||||
assert cfg.default_duration == 5.0
|
||||
|
||||
def test_both_set_returns_midpoint(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=2.0, max_duration=6.0)
|
||||
assert cfg.default_duration == 4.0
|
||||
|
||||
def test_min_equals_max_returns_same(self):
|
||||
cfg = TemplateClipConfig.create("tpl", ClipType.MAIN, 1, min_duration=3.0, max_duration=3.0)
|
||||
assert cfg.default_duration == 3.0
|
||||
Reference in New Issue
Block a user