Files
MoviePilot/tests/test_transfer_overwrite_declined.py

224 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
覆盖「不覆盖裁决不应降级已有成功记录」的行为。
查重闸放行同路径新版本后,若 overwrite_mode 最终裁定不覆盖,媒体库中原有的
成功版本仍然在位——这是一次正常策略裁决而非整理故障。TransferChain 内部的
__is_overwrite_declined 用于识别这一场景__default_callback 失败分支据此
决定是否写失败历史、发送失败事件与失败通知。本文件覆盖两者。
"""
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from app.chain.transfer import TransferChain
from app.schemas import TransferInfo
from app.schemas.types import EventType
from tests.test_transfer_job_manager import FakeMedia, make_task, make_transfer_chain
def make_history_oper(history=None, success_history=None, raise_on_query: bool = False):
"""构造 __is_overwrite_declined 查询整理历史使用的替身。"""
def get_by_src(src, storage=None):
if raise_on_query:
raise RuntimeError("boom")
return history
def get_success_by_src(src, storage=None):
return success_history
return SimpleNamespace(
get_by_src=get_by_src,
get_success_by_src=get_success_by_src,
)
def make_fail_recorder(calls):
"""替换整理链的失败历史写入函数:只记录调用,不做字段翻译也不落库。"""
def add_transfer_fail(**kwargs):
calls.append(kwargs)
return SimpleNamespace(id=1)
return add_transfer_fail
# ---------------------------------------------------------------------------
# TransferChain.__is_overwrite_declined
# ---------------------------------------------------------------------------
def test_overwrite_declined_false_when_flag_not_set():
"""overwrite_skipped 为假时直接判定为 False且不应触发历史查询。"""
task = make_task(1)
transferinfo = TransferInfo(success=False, overwrite_skipped=False)
transferhis = make_history_oper(raise_on_query=True)
result = TransferChain._TransferChain__is_overwrite_declined(
task, transferinfo, transferhis
)
assert result is False
def test_overwrite_declined_true_when_success_history_exists():
"""overwrite_skipped 为真且同源已有成功记录时,应判定为保护场景。"""
task = make_task(1)
success_history = SimpleNamespace(id=1, status=True)
transferinfo = TransferInfo(success=False, overwrite_skipped=True)
transferhis = make_history_oper(history=success_history)
result = TransferChain._TransferChain__is_overwrite_declined(
task, transferinfo, transferhis
)
assert result is True
def test_overwrite_declined_false_when_no_history():
"""overwrite_skipped 为真但没有任何整理记录时,不应判定为保护场景。"""
task = make_task(1)
transferinfo = TransferInfo(success=False, overwrite_skipped=True)
transferhis = make_history_oper(history=None)
result = TransferChain._TransferChain__is_overwrite_declined(
task, transferinfo, transferhis
)
assert result is False
def test_overwrite_declined_false_when_only_failed_history():
"""overwrite_skipped 为真但只有失败记录时,不应判定为保护场景。"""
task = make_task(1)
failed_history = SimpleNamespace(id=2, status=False)
transferinfo = TransferInfo(success=False, overwrite_skipped=True)
transferhis = make_history_oper(history=failed_history, success_history=None)
result = TransferChain._TransferChain__is_overwrite_declined(
task, transferinfo, transferhis
)
assert result is False
def test_overwrite_declined_false_when_query_raises():
"""查询整理历史异常时应保守返回 False不阻断原有失败语义。"""
task = make_task(1)
transferinfo = TransferInfo(success=False, overwrite_skipped=True)
transferhis = make_history_oper(raise_on_query=True)
result = TransferChain._TransferChain__is_overwrite_declined(
task, transferinfo, transferhis
)
assert result is False
# ---------------------------------------------------------------------------
# __default_callback 失败分支
# ---------------------------------------------------------------------------
def _make_failed_task():
"""构造一个失败回调测试所需的最小整理任务。"""
task = make_task(1)
task.mediainfo = FakeMedia()
# __default_callback 失败通知路径需要读取海报图FakeMedia 本身不提供该接口
task.mediainfo.get_message_image = lambda: "poster.jpg"
task.background = False
task.manual = True
return task
def test_default_callback_skips_history_and_notification_when_overwrite_declined():
"""
同源已有成功记录时,覆盖裁决不覆盖不应写失败历史、不应发送失败事件与通知。
"""
chain = make_transfer_chain()
chain.eventmanager = MagicMock()
chain.post_message = MagicMock()
task = _make_failed_task()
success_history = SimpleNamespace(id=99, status=True)
add_fail_calls = []
transfer_history_oper = make_history_oper(history=success_history)
transferinfo = TransferInfo(
success=False,
fileitem=task.fileitem,
message="目标已存在,按覆盖策略跳过覆盖",
transfer_type="copy",
overwrite_skipped=True,
need_notify=False,
)
with patch(
"app.chain.transfer.TransferHistoryOper",
return_value=transfer_history_oper,
), patch(
"app.chain.transfer.add_transfer_fail",
make_fail_recorder(add_fail_calls),
), patch(
"app.chain.transfer.settings.AI_AGENT_ENABLE", False
), patch(
"app.chain.transfer.settings.AI_AGENT_RETRY_TRANSFER", False
):
state, errmsg = chain._TransferChain__default_callback(task, transferinfo)
assert state is False
assert errmsg == transferinfo.message
assert add_fail_calls == []
assert chain.post_message.call_count == 0
transfer_failed_events = [
call
for call in chain.eventmanager.send_event.call_args_list
if call.args[0] == EventType.TransferFailed
]
assert transfer_failed_events == []
def test_default_callback_keeps_original_failure_semantics_without_success_history():
"""
没有已有成功记录时(即使 overwrite_skipped 为真),仍应按原有语义写失败历史并通知。
"""
chain = make_transfer_chain()
chain.eventmanager = MagicMock()
chain.post_message = MagicMock()
task = _make_failed_task()
add_fail_calls = []
transfer_history_oper = make_history_oper(history=None)
transferinfo = TransferInfo(
success=False,
fileitem=task.fileitem,
message="目标已存在,按覆盖策略跳过覆盖",
transfer_type="copy",
overwrite_skipped=True,
need_notify=False,
)
with patch(
"app.chain.transfer.TransferHistoryOper",
return_value=transfer_history_oper,
), patch(
"app.chain.transfer.add_transfer_fail",
make_fail_recorder(add_fail_calls),
), patch(
"app.chain.transfer.settings.AI_AGENT_ENABLE", False
), patch(
"app.chain.transfer.settings.AI_AGENT_RETRY_TRANSFER", False
):
state, errmsg = chain._TransferChain__default_callback(task, transferinfo)
assert state is False
assert errmsg == transferinfo.message
assert len(add_fail_calls) == 1
assert chain.post_message.call_count == 1
transfer_failed_events = [
call
for call in chain.eventmanager.send_event.call_args_list
if call.args[0] == EventType.TransferFailed
]
assert len(transfer_failed_events) == 1