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
212 lines
6.6 KiB
Python
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")
|