From 3331ba43cdc5d934a3d935ea16700af68ba29fc2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 8 Sep 2026 18:37:14 +0800 Subject: [PATCH] fix(transfer): settle missing library directory as planning rejection --- app/modules/filemanager/module.py | 6 ++--- tests/test_transfer_planning_checkpoint.py | 30 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/modules/filemanager/module.py b/app/modules/filemanager/module.py index 0f2bee66b..414e20e84 100644 --- a/app/modules/filemanager/module.py +++ b/app/modules/filemanager/module.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union from app.adapters.system.host import SystemUtils from app.application.directory import DirectoryHelper from app.application.messaging.message import MessageHelper -from app.application.transfer.execution import TransferStepRunner +from app.application.transfer.execution import TransferPlanningRejectedError, TransferStepRunner from app.application.transfer.workflow import TransferPlanCheckpoint, TransferPlanningInput from app.domain.context import MediaInfo, MusicInfo from app.domain.meta.metabase import MetaBase @@ -492,10 +492,10 @@ class FileManagerModule(_ModuleBase): need_type_folder=library_type_folder, need_category_folder=library_category_folder) else: - # 未找到有效的媒体库目录 + # 目录匹配失败按业务拒绝结算,避免进入异常重试和堆栈日志路径。 logger.error( f"{mediainfo.type.value if mediainfo.type else '未知类型'} {mediainfo.title_year} 未找到有效的媒体库目录,无法整理文件,源路径:{fileitem.path}") - raise ValueError("未找到有效的媒体库目录") + raise TransferPlanningRejectedError("未找到有效的媒体库目录") # 整理方式 if not transfer_type: directory_name = target_directory.name if target_directory else "目标目录" diff --git a/tests/test_transfer_planning_checkpoint.py b/tests/test_transfer_planning_checkpoint.py index b905c0f2d..eb515680d 100644 --- a/tests/test_transfer_planning_checkpoint.py +++ b/tests/test_transfer_planning_checkpoint.py @@ -1372,6 +1372,36 @@ def test_host_planning_value_error_commits_rejection_instead_of_retrying(): repository.record_planning_failure.assert_not_called() +def test_missing_library_directory_commits_rejection_without_file_execution(): + """真实目录匹配失败应提交业务拒绝检查点,无文件副作用或异常重试。""" + repository = Mock() + task = _task() + task.target_path = None + task.mediainfo = MediaInfo(title="Movie", year="2026") + task.bind_admission_task_id("task-missing-library") + _bind_planning_input(task, replace( + _planning_input(), target_directory=None, target_path=None, + )) + repository.checkpoint_plan.side_effect = lambda **kwargs: _planned_admission( + task, kwargs["checkpoint"], + ) + chain = _chain(repository=repository) + module = object.__new__(FileManagerModule) + chain.plan_transfer.side_effect = module.plan_transfer + + with patch("app.modules.filemanager.module.Path.exists", return_value=True): + result = chain._plan_checkpoint_and_execute(task) + + assert result.success is False + assert result.message == "未找到有效的媒体库目录" + assert task.plan_checkpoint.rejection_error == result.message + assert task.plan_checkpoint.items == () + assert task.execution_checkpoint is not None + repository.checkpoint_plan.assert_called_once() + repository.record_planning_failure.assert_not_called() + chain.execute_transfer_plan.assert_not_called() + + def test_post_commit_crash_replays_frozen_plan_without_replanning(): """commit 后执行崩溃仍保留 planned checkpoint,重启后跳过 rename 规划。""" repository = Mock()