mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.site.mutation import SiteMutationCommand
|
||||
|
||||
|
||||
def _command(**overrides):
|
||||
"""构造可观察站点写用例及其依赖。"""
|
||||
repository = Mock()
|
||||
repository.get_by_id = AsyncMock(return_value=object())
|
||||
repository.get_by_domain = AsyncMock(return_value=None)
|
||||
repository.stage_create = AsyncMock()
|
||||
repository.stage_update = AsyncMock(return_value=True)
|
||||
repository.stage_delete = AsyncMock()
|
||||
repository.stage_priorities = AsyncMock()
|
||||
unit_of_work = Mock()
|
||||
unit_of_work.commit = AsyncMock()
|
||||
unit_of_work.rollback = AsyncMock()
|
||||
dependencies = {
|
||||
"repository": repository,
|
||||
"unit_of_work": unit_of_work,
|
||||
"auth_level_provider": Mock(return_value=2),
|
||||
"indexer_loader": AsyncMock(return_value={"name": "Demo", "public": True}),
|
||||
"domain_extractor": lambda value: "demo.example",
|
||||
"url_normalizer": lambda value: "https://demo.example/",
|
||||
"publish_updated": AsyncMock(),
|
||||
"publish_deleted": AsyncMock(),
|
||||
}
|
||||
dependencies.update(overrides)
|
||||
return SiteMutationCommand(**dependencies), dependencies
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_site_commits_before_updated_event():
|
||||
"""新增站点必须先提交,再发布站点更新事件。"""
|
||||
calls = []
|
||||
command, dependencies = _command(
|
||||
unit_of_work=Mock(
|
||||
commit=AsyncMock(side_effect=lambda: calls.append("commit")),
|
||||
rollback=AsyncMock(),
|
||||
),
|
||||
publish_updated=AsyncMock(side_effect=lambda _payload: calls.append("event")),
|
||||
)
|
||||
|
||||
result = await command.create({"url": "https://demo.example/path"})
|
||||
|
||||
assert result.success is True
|
||||
assert calls == ["commit", "event"]
|
||||
payload = dependencies["repository"].stage_create.await_args.args[0]
|
||||
assert payload["domain"] == "demo.example"
|
||||
assert payload["url"] == "https://demo.example/"
|
||||
assert payload["name"] == "Demo"
|
||||
assert payload["public"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_site_returns_legacy_not_found_without_writes():
|
||||
"""更新不存在站点时保持失败响应且不产生事务或事件。"""
|
||||
repository = Mock()
|
||||
repository.get_by_id = AsyncMock(return_value=None)
|
||||
command, dependencies = _command(repository=repository)
|
||||
|
||||
result = await command.update({"id": 7, "url": "https://demo.example"})
|
||||
|
||||
assert result.success is False
|
||||
assert result.message == "站点不存在"
|
||||
dependencies["unit_of_work"].commit.assert_not_awaited()
|
||||
dependencies["publish_updated"].assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_site_commit_failure_rolls_back_without_event():
|
||||
"""删除提交失败时必须回滚且不得发送 SiteDeleted。"""
|
||||
unit_of_work = Mock()
|
||||
unit_of_work.commit = AsyncMock(side_effect=RuntimeError("commit failed"))
|
||||
unit_of_work.rollback = AsyncMock()
|
||||
command, dependencies = _command(unit_of_work=unit_of_work)
|
||||
|
||||
with pytest.raises(RuntimeError, match="commit failed"):
|
||||
await command.delete(7)
|
||||
|
||||
unit_of_work.rollback.assert_awaited_once_with()
|
||||
dependencies["publish_deleted"].assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_priorities_uses_one_transaction():
|
||||
"""批量站点优先级必须由一个请求级事务统一提交。"""
|
||||
command, dependencies = _command()
|
||||
priorities = [{"id": 1, "pri": 2}, {"id": 2, "pri": 1}]
|
||||
|
||||
result = await command.update_priorities(priorities)
|
||||
|
||||
assert result.success is True
|
||||
dependencies["repository"].stage_priorities.assert_awaited_once_with(priorities)
|
||||
dependencies["unit_of_work"].commit.assert_awaited_once_with()
|
||||
Reference in New Issue
Block a user