Files
xiaoxia-saas/tests/unit/test_recipe_use_cases.py
xiaoxia 52ff2f80ad
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
style: apply black formatting to pass CI validation (#126)
2026-06-30 17:23:08 +08:00

212 lines
6.6 KiB
Python

"""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")