""" Recipe 配方领域模型单元测试 """ from packages.domain.recipe import Recipe, RecipeItem class TestRecipeItem: """RecipeItem 测试""" def test_create_item(self): item = RecipeItem( id="item-1", recipe_id="recipe-1", item_type="asset", item_id="asset-123", position=0, ) assert item.id == "item-1" assert item.recipe_id == "recipe-1" assert item.item_type == "asset" assert item.item_id == "asset-123" assert item.position == 0 assert item.metadata_ == {} def test_item_with_metadata(self): item = RecipeItem( id="item-1", recipe_id="r1", item_type="voice", item_id="voice-1", position=2, metadata_={"speed": 1.0, "pitch": 0}, ) assert item.metadata_["speed"] == 1.0 assert item.metadata_["pitch"] == 0 class TestRecipe: """Recipe 测试""" def test_create_minimal(self): r = Recipe(id="r1", user_id="u1", name="我的配方") assert r.id == "r1" assert r.user_id == "u1" assert r.name == "我的配方" def test_default_values(self): r = Recipe(id="r1", user_id="u1", name="n") assert r.description == "" assert r.template_id == "" assert r.generation_params == {} assert r.items == [] assert r.is_active is True assert r.metadata_ == {} def test_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), ] r = Recipe(id="r1", user_id="u1", name="n", items=items) assert len(r.items) == 2 assert r.items[0].item_type == "asset" assert r.items[1].item_type == "title" def test_with_generation_params(self): params = {"mode": "one_take", "duration": 30} r = Recipe(id="r1", user_id="u1", name="n", generation_params=params) assert r.generation_params["mode"] == "one_take" def test_recipe_inactive(self): r = Recipe(id="r1", user_id="u1", name="n", is_active=False) assert r.is_active is False def test_has_timestamps(self): r = Recipe(id="r1", user_id="u1", name="n") assert r.created_at is not None assert r.updated_at is not None def test_all_item_types(self): for itype in ["asset", "title", "voice"]: item = RecipeItem(id="i1", recipe_id="r1", item_type=itype, item_id="x", position=0) assert item.item_type == itype def test_item_metadata_independence(self): """不同 RecipeItem 的 metadata_ 互不影响""" item1 = RecipeItem(id="i1", recipe_id="r1", item_type="asset", item_id="a1") item2 = RecipeItem(id="i2", recipe_id="r1", item_type="asset", item_id="a2") item1.metadata_["key"] = "val" assert "key" not in item2.metadata_ def test_item_position_negative(self): """负数 position 也能存""" item = RecipeItem(id="i1", recipe_id="r1", item_type="asset", item_id="a1", position=-5) assert item.position == -5 def test_item_position_large(self): item = RecipeItem(id="i1", recipe_id="r1", item_type="asset", item_id="a1", position=9999) assert item.position == 9999 def test_item_empty_item_id(self): item = RecipeItem(id="i1", recipe_id="r1", item_type="title", item_id="") assert item.item_id == "" def test_item_type_voice(self): item = RecipeItem(id="i1", recipe_id="r1", item_type="voice", item_id="v1") assert item.item_type == "voice" def test_item_type_title(self): item = RecipeItem(id="i1", recipe_id="r1", item_type="title", item_id="t1") assert item.item_type == "title" class TestRecipeExtended: """Recipe 深度补充测试""" def test_items_order_preserved(self): items = [ RecipeItem(id="i3", recipe_id="r1", item_type="asset", item_id="a1", position=2), RecipeItem(id="i1", recipe_id="r1", item_type="title", item_id="t1", position=0), RecipeItem(id="i2", recipe_id="r1", item_type="voice", item_id="v1", position=1), ] r = Recipe(id="r1", user_id="u1", name="n", items=items) assert len(r.items) == 3 assert r.items[0].position == 2 assert r.items[1].position == 0 assert r.items[2].position == 1 def test_empty_items_list(self): r = Recipe(id="r1", user_id="u1", name="n", items=[]) assert r.items == [] def test_many_items(self): items = [ RecipeItem(id=f"i{i}", recipe_id="r1", item_type="asset", item_id=f"a{i}", position=i) for i in range(50) ] r = Recipe(id="r1", user_id="u1", name="n", items=items) assert len(r.items) == 50 assert r.items[0].position == 0 assert r.items[49].position == 49 def test_generation_params_independence(self): params = {"mode": "one_take", "duration": 30} r1 = Recipe(id="r1", user_id="u1", name="n1", generation_params=params) r2 = Recipe(id="r2", user_id="u1", name="n2") r1.generation_params["new_key"] = "new_val" # 传入同一个 dict 会共享,但默认生成的互不影响 assert r2.generation_params == {} def test_metadata_independence_default(self): r1 = Recipe(id="r1", user_id="u1", name="n1") r2 = Recipe(id="r2", user_id="u1", name="n2") r1.metadata_["key"] = "val" assert "key" not in r2.metadata_ def test_with_description(self): r = Recipe(id="r1", user_id="u1", name="n", description="这是一个测试配方") assert r.description == "这是一个测试配方" def test_with_template_id(self): r = Recipe(id="r1", user_id="u1", name="n", template_id="tpl-123") assert r.template_id == "tpl-123" def test_empty_name(self): r = Recipe(id="r1", user_id="u1", name="") assert r.name == "" def test_long_name(self): long_name = "配方" * 200 r = Recipe(id="r1", user_id="u1", name=long_name) assert r.name == long_name assert len(r.name) == 400 def test_special_characters_in_name(self): special = "配!@#$%方" r = Recipe(id="r1", user_id="u1", name=special) assert r.name == special def test_unicode_name(self): r = Recipe(id="r1", user_id="u1", name="🎬 一键生成配方 · 美食探店") assert "🎬" in r.name assert "美食探店" in r.name