"""domain层小模块批量单测. 覆盖:recipe / tag / editing_mode / voice_library / title_library / template / edit_template 共 7 个模块,纯逻辑 0 外部依赖。 """ from __future__ import annotations from packages.domain.edit_template import EditTemplate, EditTemplateStatus from packages.domain.editing_mode import EditingMode from packages.domain.recipe import Recipe, RecipeItem from packages.domain.tag import Tag from packages.domain.template import Template, TemplateCategory, TemplateSegment from packages.domain.title_library import TitleLibraryItem from packages.domain.voice_library import VoiceLibraryItem class TestEditingMode: """EditingMode 枚举测试.""" def test_four_modes(self): """四种剪辑模式.""" assert len(EditingMode) == 4 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_all_values_unique(self): values = [m.value for m in EditingMode] assert len(values) == len(set(values)) class TestTag: """Tag 测试.""" def test_create_valid(self): """正常创建.""" tag = Tag.create(user_id="u1", name=" 搞笑 ") assert tag.user_id == "u1" assert tag.name == "搞笑" assert isinstance(tag.id, str) assert len(tag.id) > 0 assert tag.created_at is not None def test_create_empty_name(self): """空名称无效.""" try: Tag.create("u1", "") assert False except ValueError as e: assert "不能为空" in str(e) def test_create_whitespace_name(self): """纯空白名称无效.""" try: Tag.create("u1", " ") assert False except ValueError as e: assert "不能为空" in str(e) def test_create_unique_id(self): t1 = Tag.create("u", "t1") t2 = Tag.create("u", "t2") assert t1.id != t2.id class TestRecipeItem: """RecipeItem 测试.""" def test_create_asset_item(self): """素材配方项.""" item = RecipeItem( id="item1", recipe_id="r1", item_type="asset", item_id="a1", position=0, ) assert item.item_type == "asset" assert item.item_id == "a1" assert item.position == 0 def test_create_title_item(self): """标题配方项.""" item = RecipeItem(id="i2", recipe_id="r1", item_type="title", item_id="t1", position=1) assert item.item_type == "title" assert item.position == 1 def test_default_metadata(self): """默认 metadata 为空 dict.""" item = RecipeItem(id="i1", recipe_id="r1", item_type="voice", item_id="v1") assert item.metadata_ == {} class TestRecipe: """Recipe 测试.""" def test_create_minimal(self): """最简配方.""" recipe = Recipe(id="r1", user_id="u1", name="我的配方") assert recipe.name == "我的配方" assert recipe.description == "" assert recipe.template_id == "" assert recipe.generation_params == {} assert recipe.items == [] assert recipe.is_active is True assert recipe.created_at is not None assert recipe.updated_at is not None def test_create_with_items(self): """带配方项.""" items = [ RecipeItem(id="i1", recipe_id="r1", item_type="asset", item_id="a1", position=0), RecipeItem(id="i2", recipe_id="r1", item_type="title", item_id="t1", position=1), ] recipe = Recipe(id="r1", user_id="u1", name="配方", items=items) assert len(recipe.items) == 2 assert recipe.items[0].item_type == "asset" assert recipe.items[1].position == 1 def test_default_items_empty_list(self): """默认 items 为空列表.""" r1 = Recipe(id="r1", user_id="u1", name="r1") r2 = Recipe(id="r2", user_id="u2", name="r2") r1.items.append("fake") assert r2.items == [] class TestVoiceLibraryItem: """VoiceLibraryItem 测试.""" def test_create_minimal(self): """最简创建.""" item = VoiceLibraryItem(id="v1", user_id="u1", name="我的配音") assert item.name == "我的配音" assert item.text == "" assert item.voice_provider == "" assert item.voice_id == "" assert item.voice_name == "" assert item.audio_url == "" assert item.duration == 0 assert item.file_size == 0 assert item.status == "completed" assert item.project_id is None assert item.tags == [] assert item.metadata_ == {} def test_create_full(self): """带全部字段.""" item = VoiceLibraryItem( id="v1", user_id="u1", name="旁白", text="大家好", voice_provider="xf", voice_id="v1", voice_name="小云", audio_url="http://x/a.mp3", duration=10.5, file_size=102400, status="processing", project_id="p1", tags=["旁白", "正式"], ) assert item.text == "大家好" assert item.duration == 10.5 assert item.file_size == 102400 assert item.project_id == "p1" assert item.tags == ["旁白", "正式"] def test_tags_independent(self): """不同实例的 tags 独立.""" i1 = VoiceLibraryItem(id="v1", user_id="u", name="n1") i2 = VoiceLibraryItem(id="v2", user_id="u", name="n2") i1.tags.append("x") assert i2.tags == [] class TestTitleLibraryItem: """TitleLibraryItem 测试.""" def test_create_required(self): """必填字段.""" item = TitleLibraryItem(id="t1", user_id="u1", name="爆款标题", text="这也太牛了") assert item.name == "爆款标题" assert item.text == "这也太牛了" assert item.category == "default" assert item.description == "" assert item.tags == [] assert item.usage_count == 0 assert item.is_active is True assert item.metadata_ == {} def test_create_full(self): """带全部字段.""" item = TitleLibraryItem( id="t1", user_id="u1", name="科技标题", text="震惊!", category="tech", description="科技类标题", tags=["科技", "爆款"], usage_count=100, is_active=False, ) assert item.category == "tech" assert item.usage_count == 100 assert item.is_active is False assert item.tags == ["科技", "爆款"] def test_tags_independent(self): i1 = TitleLibraryItem(id="t1", user_id="u", name="n", text="t") i2 = TitleLibraryItem(id="t2", user_id="u", name="n", text="t") i1.tags.append("x") assert i2.tags == [] class TestTemplateSegment: """TemplateSegment 测试.""" def test_create(self): """正常创建.""" seg = TemplateSegment( id="s1", template_id="t1", segment_order=0, duration_min=2.0, duration_max=5.0, ) assert seg.segment_order == 0 assert seg.duration_min == 2.0 assert seg.duration_max == 5.0 assert seg.material_type is None def test_with_material_type(self): """带素材类型(voice_over模式).""" seg = TemplateSegment( id="s1", template_id="t1", segment_order=0, duration_min=3.0, duration_max=8.0, material_type="person", ) assert seg.material_type == "person" def test_has_timestamps(self): seg = TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=1, duration_max=2) assert seg.created_at is not None assert seg.updated_at is not None class TestTemplate: """Template 测试.""" def test_create_minimal(self): """最简模板.""" tpl = Template(id="t1", user_id="u1", name="通用模板", mode="one_take") assert tpl.name == "通用模板" assert tpl.mode == "one_take" assert tpl.category == "" assert tpl.tags == [] assert tpl.title_config == {} assert tpl.subtitle_config == {} assert tpl.bgm_config == {} assert tpl.estimated_duration == 0.0 assert tpl.segments == [] assert tpl.is_active is True def test_create_with_segments(self): """带片段.""" segs = [ TemplateSegment(id="s1", template_id="t1", segment_order=0, duration_min=2, duration_max=5), TemplateSegment(id="s2", template_id="t1", segment_order=1, duration_min=3, duration_max=7), ] tpl = Template(id="t1", user_id="u1", name="模板", mode="pip", segments=segs) assert len(tpl.segments) == 2 assert tpl.segments[0].segment_order == 0 def test_segments_independent(self): t1 = Template(id="t1", user_id="u", name="n1", mode="one_take") t2 = Template(id="t2", user_id="u", name="n2", mode="pip") t1.segments.append("fake") assert t2.segments == [] class TestTemplateCategory: """TemplateCategory 测试.""" def test_create(self): cat = TemplateCategory(id="c1", user_id="u1", name="科技") assert cat.name == "科技" assert cat.created_at is not None class TestEditTemplateStatus: """EditTemplateStatus 枚举测试.""" def test_two_statuses(self): assert len(EditTemplateStatus) == 2 def test_active(self): assert EditTemplateStatus.ACTIVE == "active" def test_inactive(self): assert EditTemplateStatus.INACTIVE == "inactive" class TestEditTemplate: """EditTemplate 测试.""" def test_create_minimal(self): """最简创建.""" tpl = EditTemplate.create(name=" 通用模板 ") assert tpl.name == "通用模板" assert tpl.description == "" assert tpl.template_type == "default" assert tpl.config == {} assert tpl.preview_url == "" assert tpl.sort_weight == 0 assert tpl.status == EditTemplateStatus.ACTIVE assert isinstance(tpl.id, str) assert len(tpl.id) > 0 def test_create_full(self): """带全部字段.""" tpl = EditTemplate.create( name="口播模板", description="口播类模板", template_type="voice_over", config={"style": "formal"}, preview_url="https://x/preview.mp4", sort_weight=100, status=EditTemplateStatus.INACTIVE, ) assert tpl.name == "口播模板" assert tpl.description == "口播类模板" assert tpl.template_type == "voice_over" assert tpl.config == {"style": "formal"} assert tpl.preview_url == "https://x/preview.mp4" assert tpl.sort_weight == 100 assert tpl.status == EditTemplateStatus.INACTIVE def test_create_empty_name(self): """空名称无效.""" try: EditTemplate.create(name="") assert False except ValueError as e: assert "不能为空" in str(e) def test_create_whitespace_name(self): """空白名称无效.""" try: EditTemplate.create(name=" ") assert False except ValueError as e: assert "不能为空" in str(e) def test_empty_template_type_defaults(self): """空 template_type 默认 default.""" tpl = EditTemplate.create(name="t", template_type="") assert tpl.template_type == "default" def test_whitespace_template_type_defaults(self): """空白 template_type 默认 default.""" tpl = EditTemplate.create(name="t", template_type=" ") assert tpl.template_type == "default" def test_config_none_defaults_empty(self): """config=None 默认为空 dict.""" tpl = EditTemplate.create(name="t", config=None) assert tpl.config == {} def test_activate(self): """激活.""" tpl = EditTemplate.create(name="t", status=EditTemplateStatus.INACTIVE) old = tpl.updated_at tpl.activate() assert tpl.is_active is True assert tpl.status == EditTemplateStatus.ACTIVE assert tpl.updated_at >= old def test_deactivate(self): """停用.""" tpl = EditTemplate.create(name="t") old = tpl.updated_at tpl.deactivate() assert tpl.is_active is False assert tpl.status == EditTemplateStatus.INACTIVE assert tpl.updated_at >= old def test_is_active_property(self): """is_active 属性.""" tpl = EditTemplate.create(name="t") assert tpl.is_active is True tpl.deactivate() assert tpl.is_active is False tpl.activate() assert tpl.is_active is True def test_unique_id(self): t1 = EditTemplate.create(name="t1") t2 = EditTemplate.create(name="t2") assert t1.id != t2.id def test_config_independent(self): t1 = EditTemplate.create(name="t1") t2 = EditTemplate.create(name="t2") t1.config["k"] = "v" assert "k" not in t2.config