Compare commits

...

2 Commits

Author SHA1 Message Date
CI Bot 3fcabc8058 style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 21s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 1m13s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m7s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m54s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m26s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 47s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 43s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m42s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m13s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 5m13s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m26s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m22s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 4m26s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 6m56s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m49s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 17s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 17s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 20s
2026-07-30 01:41:56 +00:00
xiaoxia 7ef234fe55 test(wave219): template_clip_config +33测
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m17s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m21s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-07-30 09:37:39 +08:00
2 changed files with 275 additions and 1 deletions
@@ -159,4 +159,3 @@
flex-wrap: wrap;
}
}
+275
View File
@@ -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