"""Recipe use cases unit tests.""" from __future__ import annotations from datetime import datetime, timezone from unittest.mock import Mock import pytest from packages.application.recipe.commands import ( CreateRecipeCommand, RecipeItemCommand, UpdateRecipeCommand, ) from packages.application.recipe.use_cases import ( CreateRecipeUseCase, DeleteRecipeUseCase, FeatureDisabledError, GetRecipeUseCase, ListRecipesUseCase, NotFoundError, UpdateRecipeUseCase, UseRecipeUseCase, ) from packages.domain.recipe import Recipe, RecipeItem def _make_recipe(**kwargs) -> Recipe: defaults = dict( id="recipe001", user_id="user001", name="测试配方", description="描述", template_id="tpl001", generation_params={"mode": "one_take"}, items=[], is_active=True, metadata_={}, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) defaults.update(kwargs) return Recipe(**defaults) def _make_item(**kwargs) -> RecipeItem: defaults = dict( id="item001", recipe_id="recipe001", item_type="asset", item_id="asset001", position=0, metadata_={}, ) defaults.update(kwargs) return RecipeItem(**defaults) class TestCreateRecipeUseCase: @pytest.fixture def mock_repo(self): repo = Mock() repo.create = Mock(side_effect=lambda r: r) repo.create_items = Mock(side_effect=lambda items: items) return repo def test_create_basic(self, mock_repo): uc = CreateRecipeUseCase(mock_repo) cmd = CreateRecipeCommand( user_id="user001", name="我的配方", description="desc", template_id="tpl001", generation_params={"mode": "one_take"}, ) result = uc.execute(cmd) assert result.name == "我的配方" assert result.user_id == "user001" mock_repo.create.assert_called_once() def test_create_with_items(self, mock_repo): uc = CreateRecipeUseCase(mock_repo) cmd = CreateRecipeCommand( user_id="user001", name="带素材配方", items=[ RecipeItemCommand(item_type="asset", item_id="a1", position=0), RecipeItemCommand(item_type="title", item_id="t1", position=1), RecipeItemCommand(item_type="voice", item_id="v1", position=2), ], ) result = uc.execute(cmd) assert len(result.items) == 3 mock_repo.create_items.assert_called_once() items_arg = mock_repo.create_items.call_args[0][0] assert items_arg[0].item_type == "asset" assert items_arg[1].item_type == "title" assert items_arg[2].item_type == "voice" class TestListRecipesUseCase: def test_list(self): repo = Mock() repo.list_by_user = Mock(return_value=[_make_recipe()]) uc = ListRecipesUseCase(repo) result = uc.execute("user001", skip=0, limit=10) assert len(result) == 1 repo.list_by_user.assert_called_once_with("user001", skip=0, limit=10) class TestGetRecipeUseCase: def test_get_found(self): repo = Mock() repo.get = Mock(return_value=_make_recipe()) uc = GetRecipeUseCase(repo) result = uc.execute("recipe001", "user001") assert result is not None assert result.id == "recipe001" def test_get_not_found(self): repo = Mock() repo.get = Mock(return_value=None) uc = GetRecipeUseCase(repo) result = uc.execute("recipe999", "user001") assert result is None class TestUpdateRecipeUseCase: @pytest.fixture def mock_repo(self): repo = Mock() repo.get = Mock(return_value=_make_recipe()) repo.update = Mock(side_effect=lambda r: r) repo.list_items = Mock(return_value=[]) repo.delete_items_by_recipe = Mock(return_value=0) repo.create_items = Mock(side_effect=lambda items: items) return repo def test_update_name(self, mock_repo): uc = UpdateRecipeUseCase(mock_repo) cmd = UpdateRecipeCommand( recipe_id="recipe001", user_id="user001", name="新名字", ) result = uc.execute(cmd) assert result.name == "新名字" def test_update_not_found(self): repo = Mock() repo.get = Mock(return_value=None) uc = UpdateRecipeUseCase(repo) cmd = UpdateRecipeCommand(recipe_id="xxx", user_id="user001", name="x") with pytest.raises(NotFoundError): uc.execute(cmd) def test_update_replace_items(self, mock_repo): uc = UpdateRecipeUseCase(mock_repo) cmd = UpdateRecipeCommand( recipe_id="recipe001", user_id="user001", items=[RecipeItemCommand(item_type="voice", item_id="v2", position=0)], ) result = uc.execute(cmd) mock_repo.delete_items_by_recipe.assert_called_once_with("recipe001") mock_repo.create_items.assert_called_once() assert len(result.items) == 1 class TestDeleteRecipeUseCase: def test_delete_success(self): repo = Mock() repo.delete = Mock(return_value=True) uc = DeleteRecipeUseCase(repo) assert uc.execute("recipe001", "user001") is True def test_delete_not_found(self): repo = Mock() repo.delete = Mock(return_value=False) uc = DeleteRecipeUseCase(repo) assert uc.execute("recipe999", "user001") is False class TestUseRecipeUseCase: def test_use_success_basic_plan(self): repo = Mock() repo.get = Mock(return_value=_make_recipe()) uc = UseRecipeUseCase(repo) result = uc.execute("recipe001", "user001", user_plan="basic") assert result.recipe.id == "recipe001" assert result.warnings == [] def test_use_success_premium_plan(self): repo = Mock() repo.get = Mock(return_value=_make_recipe()) uc = UseRecipeUseCase(repo) result = uc.execute("recipe001", "user001", user_plan="premium") assert result.recipe.id == "recipe001" def test_use_free_plan_forbidden(self): repo = Mock() uc = UseRecipeUseCase(repo) with pytest.raises(FeatureDisabledError): uc.execute("recipe001", "user001", user_plan="free") def test_use_not_found(self): repo = Mock() repo.get = Mock(return_value=None) uc = UseRecipeUseCase(repo) with pytest.raises(NotFoundError): uc.execute("recipe999", "user001", user_plan="basic")