mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.history import (
|
||||
DownloadHistoryMutationCommand,
|
||||
TransferHistoryMutationCommand,
|
||||
)
|
||||
|
||||
|
||||
def _transfer_command(*, history=None, delete_result=True, commit_error=None):
|
||||
"""构造可观察整理历史事务和外部副作用的命令。"""
|
||||
repository = Mock()
|
||||
repository.get.return_value = history
|
||||
download_repository = Mock()
|
||||
unit_of_work = Mock()
|
||||
unit_of_work.commit.side_effect = commit_error
|
||||
dependencies = {
|
||||
"repository": repository,
|
||||
"download_repository": download_repository,
|
||||
"unit_of_work": unit_of_work,
|
||||
"file_item_factory": lambda payload: SimpleNamespace(**payload),
|
||||
"delete_media_file": Mock(return_value=delete_result),
|
||||
"publish_download_file_deleted": Mock(),
|
||||
"clear_failures": Mock(),
|
||||
}
|
||||
return TransferHistoryMutationCommand(**dependencies), dependencies
|
||||
|
||||
|
||||
def _history():
|
||||
"""构造包含源和目标文件信息的整理历史快照。"""
|
||||
return SimpleNamespace(
|
||||
id=7,
|
||||
src="/downloads/demo.mkv",
|
||||
src_storage="local",
|
||||
download_hash="abc",
|
||||
src_fileitem={"path": "/downloads/demo.mkv"},
|
||||
dest_fileitem={"path": "/media/demo.mkv"},
|
||||
)
|
||||
|
||||
|
||||
def test_download_history_delete_rolls_back_commit_failure():
|
||||
"""下载历史提交失败时必须回滚请求级事务。"""
|
||||
repository = Mock()
|
||||
unit_of_work = Mock()
|
||||
unit_of_work.commit.side_effect = RuntimeError("commit failed")
|
||||
command = DownloadHistoryMutationCommand(
|
||||
repository=repository,
|
||||
unit_of_work=unit_of_work,
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="commit failed"):
|
||||
command.delete(8)
|
||||
|
||||
repository.stage_delete_history.assert_called_once_with(8)
|
||||
unit_of_work.rollback.assert_called_once_with()
|
||||
|
||||
|
||||
def test_transfer_source_delete_failure_keeps_database_unchanged():
|
||||
"""源文件删除失败时不得删除历史或更新下载文件状态。"""
|
||||
command, dependencies = _transfer_command(
|
||||
history=_history(),
|
||||
delete_result=False,
|
||||
)
|
||||
|
||||
result = command.delete(7, delete_source=True)
|
||||
|
||||
assert result.success is False
|
||||
assert result.message == "/downloads/demo.mkv 删除失败"
|
||||
dependencies["repository"].stage_delete.assert_not_called()
|
||||
dependencies["download_repository"].stage_delete_file_by_fullpath.assert_not_called()
|
||||
dependencies["unit_of_work"].commit.assert_not_called()
|
||||
|
||||
|
||||
def test_transfer_delete_commits_before_event_and_retry_cleanup():
|
||||
"""整理记录提交成功后才发送文件删除事件并清理失败计数。"""
|
||||
calls = []
|
||||
command, dependencies = _transfer_command(history=_history())
|
||||
dependencies["unit_of_work"].commit.side_effect = lambda: calls.append("commit")
|
||||
dependencies["publish_download_file_deleted"].side_effect = (
|
||||
lambda _payload: calls.append("event")
|
||||
)
|
||||
dependencies["clear_failures"].side_effect = lambda *_args: calls.append("clear")
|
||||
|
||||
result = command.delete(
|
||||
7,
|
||||
delete_source=True,
|
||||
delete_destination=True,
|
||||
)
|
||||
|
||||
assert result.success is True
|
||||
dependencies["download_repository"].stage_delete_file_by_fullpath.assert_called_once_with(
|
||||
"/downloads/demo.mkv"
|
||||
)
|
||||
dependencies["repository"].stage_delete.assert_called_once_with(7)
|
||||
assert calls == ["commit", "event", "clear"]
|
||||
|
||||
|
||||
def test_transfer_commit_failure_suppresses_event_and_retry_cleanup():
|
||||
"""数据库提交失败时不得发布已删除事件或清除重试状态。"""
|
||||
command, dependencies = _transfer_command(
|
||||
history=_history(),
|
||||
commit_error=RuntimeError("commit failed"),
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="commit failed"):
|
||||
command.delete(7, delete_source=True)
|
||||
|
||||
dependencies["unit_of_work"].rollback.assert_called_once_with()
|
||||
dependencies["publish_download_file_deleted"].assert_not_called()
|
||||
dependencies["clear_failures"].assert_not_called()
|
||||
|
||||
|
||||
def test_transfer_truncate_uses_single_transaction():
|
||||
"""清空整理历史只暂存一次并统一提交。"""
|
||||
command, dependencies = _transfer_command()
|
||||
|
||||
result = command.truncate()
|
||||
|
||||
assert result.success is True
|
||||
dependencies["repository"].stage_truncate.assert_called_once_with()
|
||||
dependencies["unit_of_work"].commit.assert_called_once_with()
|
||||
Reference in New Issue
Block a user