Files
xiaoxia-saas/tests/unit/test_domain_small_modules.py
T
CI Bot ac01bee33b test(wave99): 35 unit tests for small domain modules
Clean up zero-test small domain modules in one batch:
- EditingMode enum: 4 tests
- TemplateSegment dataclass: 2 tests
- ClipType + TransitionEffect enums: 3 tests
- TemplateClipConfig create + properties: 11 tests (validation + duration range + transition)
- EditTemplateVersion create: 6 tests
- VoiceLibraryItem: 2 tests
- TitleLibraryItem: 4 tests
- RecipeItem: 3 tests
Total: 35 tests across 8 modules
2026-07-26 20:10:46 +08:00

392 lines
12 KiB
Python
Executable File

"""Domain 小模块合集单元测试。
覆盖零测试的小 domain 模块:
- EditingMode 枚举
- Template / TemplateSegment
- TemplateClipConfig + ClipType + TransitionEffect
- EditTemplateVersion
- VoiceLibraryItem
- TitleLibraryItem
- Recipe / RecipeItem
"""
from datetime import datetime, timezone
import pytest
from packages.domain.editing_mode import EditingMode
from packages.domain.recipe import RecipeItem
from packages.domain.template import TemplateSegment
from packages.domain.template_clip_config import (
ClipType,
TemplateClipConfig,
TransitionEffect,
)
from packages.domain.template_version import EditTemplateVersion
from packages.domain.title_library import TitleLibraryItem
from packages.domain.voice_library import VoiceLibraryItem
class TestEditingMode:
def test_all_modes_exist(self):
assert EditingMode.ONE_TAKE.value == "one_take"
assert EditingMode.PIP.value == "pip"
assert EditingMode.VOICE_OVER.value == "voice_over"
assert EditingMode.VOICE_PIP.value == "voice_pip"
def test_from_string(self):
assert EditingMode("one_take") == EditingMode.ONE_TAKE
assert EditingMode("voice_over") == EditingMode.VOICE_OVER
def test_invalid_mode_raises(self):
with pytest.raises(ValueError):
EditingMode("invalid_mode")
def test_is_str_enum(self):
# StrEnum 的值是字符串,可以直接比较
assert EditingMode.ONE_TAKE == "one_take"
class TestTemplateSegment:
def test_create_minimal(self):
seg = TemplateSegment(
id="seg1",
template_id="tpl1",
segment_order=1,
duration_min=5.0,
duration_max=10.0,
)
assert seg.id == "seg1"
assert seg.template_id == "tpl1"
assert seg.segment_order == 1
assert seg.duration_min == 5.0
assert seg.duration_max == 10.0
assert seg.material_type is None
assert isinstance(seg.created_at, datetime)
def test_create_with_material_type(self):
seg = TemplateSegment(
id="seg2",
template_id="tpl1",
segment_order=2,
duration_min=3.0,
duration_max=8.0,
material_type="人物",
)
assert seg.material_type == "人物"
class TestClipType:
def test_basic_types_exist(self):
assert hasattr(ClipType, "MAIN")
assert hasattr(ClipType, "INTRO")
assert hasattr(ClipType, "OUTRO")
assert hasattr(ClipType, "TRANSITION")
def test_values_are_strings(self):
for ct in ClipType:
assert isinstance(ct.value, str)
class TestTransitionEffect:
def test_effects_exist(self):
assert TransitionEffect.CUT.value == "cut"
assert TransitionEffect.FADE.value == "fade"
assert TransitionEffect.DISSOLVE.value == "dissolve"
# 至少有 5 种以上转场效果
assert len(list(TransitionEffect)) >= 5
class TestTemplateClipConfig:
def test_create_minimal(self):
config = TemplateClipConfig.create(
template_id="tpl1",
clip_type=ClipType.MAIN,
order=1,
min_duration=3.0,
max_duration=8.0,
)
assert config.id is not None
assert config.template_id == "tpl1"
assert config.clip_type == ClipType.MAIN
assert config.order == 1
assert config.min_duration == 3.0
assert config.max_duration == 8.0
def test_create_with_string_type(self):
config = TemplateClipConfig.create(
template_id="tpl1",
clip_type="intro",
order=0,
min_duration=2.0,
max_duration=5.0,
)
assert config.clip_type == ClipType.INTRO
def test_has_duration_range_true(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
min_duration=3.0,
max_duration=8.0,
)
assert config.has_duration_range is True
def test_has_duration_range_false_when_both_zero(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
)
assert config.has_duration_range is False
def test_default_duration_midpoint(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
min_duration=4.0,
max_duration=6.0,
)
assert config.default_duration == pytest.approx(5.0)
def test_default_duration_when_only_max(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
max_duration=5.0,
)
assert config.default_duration == 5.0
def test_create_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_create_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_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_default_transition_is_cut(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
)
assert config.transition_effect == TransitionEffect.CUT
def test_custom_transition_effect(self):
config = TemplateClipConfig.create(
template_id="t1",
clip_type=ClipType.MAIN,
order=1,
transition_effect="fade",
)
assert config.transition_effect == TransitionEffect.FADE
class TestEditTemplateVersion:
def test_create_minimal(self):
version = EditTemplateVersion.create(
template_id="tpl1",
version=1,
)
assert version.id is not None
assert len(version.id) == 32
assert version.template_id == "tpl1"
assert version.version == 1
assert version.config == {}
assert version.clip_configs == []
assert version.published_by == ""
assert version.change_note == ""
assert version.name == ""
assert version.editing_mode == "one_take"
assert isinstance(version.created_at, datetime)
def test_create_with_config_and_clip_configs(self):
version = EditTemplateVersion.create(
template_id="tpl1",
version=2,
config={"layout": "one_take"},
clip_configs=[{"clip_id": "c1", "type": "main"}],
published_by="user1",
change_note="添加了片头效果",
)
assert version.config == {"layout": "one_take"}
assert len(version.clip_configs) == 1
assert version.published_by == "user1"
assert version.change_note == "添加了片头效果"
def test_create_with_name_and_mode(self):
version = EditTemplateVersion.create(
template_id="t1",
version=1,
name="v1.0 正式版",
editing_mode="voice_over",
)
assert version.name == "v1.0 正式版"
assert version.editing_mode == "voice_over"
def test_create_unique_ids(self):
v1 = EditTemplateVersion.create("t1", 1)
v2 = EditTemplateVersion.create("t1", 2)
assert v1.id != v2.id
def test_none_config_defaults_to_empty_dict(self):
version = EditTemplateVersion.create("t1", 1, config=None)
assert version.config == {}
def test_none_clip_configs_defaults_to_empty_list(self):
version = EditTemplateVersion.create("t1", 1, clip_configs=None)
assert version.clip_configs == []
class TestVoiceLibraryItem:
def test_create_minimal(self):
item = VoiceLibraryItem(
id="v1",
user_id="u1",
name="我的配音",
)
assert item.id == "v1"
assert item.user_id == "u1"
assert item.name == "我的配音"
assert item.text == ""
assert item.voice_provider == ""
assert item.duration == 0
assert item.status == "completed"
assert item.tags == []
assert item.project_id is None
assert isinstance(item.created_at, datetime)
def test_create_with_all_fields(self):
item = VoiceLibraryItem(
id="v2",
user_id="u1",
name="产品介绍",
text="欢迎来到我们的产品",
voice_provider="cosyvoice",
voice_id="voice_001",
voice_name="温柔女声",
audio_url="https://cdn/v2.mp3",
duration=30.5,
file_size=102400,
status="processing",
project_id="proj1",
tags=["产品", "介绍"],
)
assert item.text == "欢迎来到我们的产品"
assert item.voice_provider == "cosyvoice"
assert item.voice_id == "voice_001"
assert item.audio_url == "https://cdn/v2.mp3"
assert item.duration == 30.5
assert item.file_size == 102400
assert item.status == "processing"
assert item.project_id == "proj1"
assert item.tags == ["产品", "介绍"]
class TestTitleLibraryItem:
def test_create_minimal(self):
item = TitleLibraryItem(
id="t1",
user_id="u1",
name="爆款标题1",
text="这是一个爆款标题",
)
assert item.id == "t1"
assert item.user_id == "u1"
assert item.name == "爆款标题1"
assert item.text == "这是一个爆款标题"
assert item.category == "default"
assert item.description == ""
assert item.tags == []
assert item.usage_count == 0
assert item.is_active is True
def test_create_with_category(self):
item = TitleLibraryItem(
id="t2",
user_id="u1",
name="美食标题",
text="太好吃了!",
category="美食",
)
assert item.category == "美食"
def test_inactive_item(self):
item = TitleLibraryItem(
id="t3",
user_id="u1",
name="旧标题",
text="旧文案",
is_active=False,
)
assert item.is_active is False
def test_usage_count_increment(self):
item = TitleLibraryItem(
id="t4",
user_id="u1",
name="T",
text="T",
)
item.usage_count += 1
assert item.usage_count == 1
class TestRecipeItem:
def test_create_minimal(self):
item = RecipeItem(
id="ri1",
recipe_id="r1",
item_type="asset",
item_id="asset_001",
)
assert item.id == "ri1"
assert item.recipe_id == "r1"
assert item.item_type == "asset"
assert item.item_id == "asset_001"
assert item.position == 0
assert item.metadata_ == {}
def test_create_with_position_and_metadata(self):
item = RecipeItem(
id="ri2",
recipe_id="r1",
item_type="title",
item_id="title_001",
position=2,
metadata_={"style": "bold"},
)
assert item.position == 2
assert item.metadata_ == {"style": "bold"}
def test_item_types_variety(self):
asset_item = RecipeItem(id="a", recipe_id="r", item_type="asset", item_id="i1")
title_item = RecipeItem(id="t", recipe_id="r", item_type="title", item_id="i2")
voice_item = RecipeItem(id="v", recipe_id="r", item_type="voice", item_id="i3")
assert asset_item.item_type == "asset"
assert title_item.item_type == "title"
assert voice_item.item_type == "voice"