refactor(transfer): complete durable recovery state machine

This commit is contained in:
jxxghp
2026-08-28 00:03:36 +08:00
parent 513d867dd6
commit e9de149dbf
48 changed files with 5421 additions and 1140 deletions
+54 -1
View File
@@ -1,5 +1,8 @@
import threading
from types import SimpleNamespace
from unittest.mock import Mock
from app.application.transfer.execution import TransferExecutionCheckpoint
from app.application.transfer.workflow import TransferTask
from app.chain.transfer import TransferChain
from app.domain.context import MediaInfo
@@ -76,6 +79,43 @@ def test_transfer_stops_when_automatic_category_has_no_tmdb_result(monkeypatch)
"""启用自动类别目录时,缺少 TMDB 分类必须在文件操作前明确失败。"""
chain = object.__new__(TransferChain)
chain.jobview = SimpleNamespace(try_remove_job=lambda _task: None)
chain._transfer_admissions = Mock()
chain._worker_owner_id = "category-owner"
chain._owned_leases = {
"task-before-category": ("lease-before-category", float("inf"))
}
chain._worker_state_lock = threading.RLock()
chain.durable_event_writer = Mock()
chain.runtime_config = SimpleNamespace(
scrape_follow_tmdb=True,
ai_agent_enable=True,
ai_agent_retry_transfer=True,
)
chain.queue_failed_transfer_notification = Mock()
chain._TransferChain__mark_torrent_completed_if_done = Mock()
record_transfer_failure = Mock()
add_transfer_fail = Mock()
monkeypatch.setattr(
"app.chain.transfer.record_transfer_failure",
record_transfer_failure,
)
monkeypatch.setattr("app.chain.transfer.add_transfer_fail", add_transfer_fail)
chain._transfer_admissions.checkpoint_plan.side_effect = (
lambda **kwargs: SimpleNamespace(checkpoint=kwargs["checkpoint"])
)
step_runner = Mock()
step_runner.checkpoint.side_effect = lambda transferinfo: (
TransferExecutionCheckpoint.create(
payload={
"outcome": "failed",
"transferinfo": transferinfo.model_dump(mode="json"),
},
operation_ids=("planning-reject",),
)
)
chain._TransferChain__build_durable_step_runner = Mock(
return_value=step_runner
)
monkeypatch.setattr(
"app.chain.transfer.get_chain_transfer_history_port",
lambda: SimpleNamespace(),
@@ -114,7 +154,12 @@ def test_transfer_stops_when_automatic_category_has_no_tmdb_result(monkeypatch)
library_category_folder=True,
),
library_category_folder=True,
preview=True,
preview=False,
)
task.bind_admission_task_id("task-before-category")
task.bind_execution_lease(
owner_id="category-owner",
lease_token="lease-before-category",
)
state, message = chain._TransferChain__handle_transfer(task)
@@ -123,3 +168,11 @@ def test_transfer_stops_when_automatic_category_has_no_tmdb_result(monkeypatch)
assert message == "未识别到 TMDB 辅助信息,无法按媒体类别整理"
assert task.mediainfo.media_source == MediaSource.AniList
assert task.mediainfo.media_id == "1234"
assert task.plan_checkpoint is not None
assert task.plan_checkpoint.rejection_error == message
assert task.execution_checkpoint is not None
chain._transfer_admissions.record_planning_failure.assert_not_called()
record_transfer_failure.assert_not_called()
add_transfer_fail.assert_not_called()
chain.queue_failed_transfer_notification.assert_not_called()
chain._TransferChain__mark_torrent_completed_if_done.assert_not_called()