Files
MoviePilot/tests/test_transfer_failed_retry_budget.py

135 lines
5.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.
"""
有界重试预算的端到端行为验证。
app/application/history.py 的查重闸真值表与计数器 API 已在
tests/test_transfer_history_gate.py 逐项覆盖,本文件换一个角度:把「同一源路径
连续多个监控事件」串成一条时间线,验证瞬时故障能在预算内自愈、耗尽预算后被拦、
以及删除整理记录会让预算重新满额,贴近真实使用场景。
"""
from types import SimpleNamespace
from app import schemas
from app.runtime.config import settings
from app.application.history import (
HistoryGateAction,
clear_transfer_failures,
evaluate_history_gate,
failed_retry_count,
record_transfer_failure,
)
def _reset_failed_retries(src_path, storage=None):
"""清空失败重试计数,隔离用例之间共享的模块级计数缓存。"""
clear_transfer_failures(src_path, storage)
def _failed_history(history_id: int, src_path: str, storage: str):
"""构造一条持续失败的整理记录替身,模拟同一源路径屡次整理失败后落库的状态。"""
return SimpleNamespace(id=history_id, status=False, src_fileitem=None,
src=src_path, src_storage=storage)
def test_transient_failures_self_heal_within_retry_budget(monkeypatch):
"""
瞬时故障自愈:上限为 3 时,连续 2 次失败后第 3 个事件仍应放行重试;
第 3 次也失败后计数达到上限,第 4 个事件应被拦截。
"""
monkeypatch.setattr(settings, "TRANSFER_MAX_FAILED_RETRIES", 3)
src_path = "/downloads/retry-budget-self-heal.mkv"
storage = "local"
_reset_failed_retries(src_path, storage)
try:
history = _failed_history(1, src_path, storage)
# 事件 1整理失败登记第 1 次失败
record_transfer_failure(src_path, storage)
assert failed_retry_count(src_path, storage) == 1
# 事件 2计数为 1未达上限放行重试本次也失败登记第 2 次失败
assert evaluate_history_gate(history, file_size=None) == HistoryGateAction.PASS_FAILED
record_transfer_failure(src_path, storage)
assert failed_retry_count(src_path, storage) == 2
# 事件 3连续失败 2 次后,计数为 2 仍未达上限3第 3 个事件仍应放行重试
assert evaluate_history_gate(history, file_size=None) == HistoryGateAction.PASS_FAILED
# 第 3 次尝试也失败,登记第 3 次失败,计数达到上限
record_transfer_failure(src_path, storage)
assert failed_retry_count(src_path, storage) == 3
# 事件 4计数达到上限3应被拦截不再自动重试
assert evaluate_history_gate(history, file_size=None) == HistoryGateAction.SKIP_RETRY_EXHAUSTED
finally:
_reset_failed_retries(src_path, storage)
def test_clearing_transfer_history_restores_full_retry_budget(monkeypatch):
"""删除整理记录清零:耗尽重试预算后调用 clear_transfer_failures应重新获得放行资格。"""
monkeypatch.setattr(settings, "TRANSFER_MAX_FAILED_RETRIES", 2)
src_path = "/downloads/retry-budget-cleared-by-delete.mkv"
storage = "local"
_reset_failed_retries(src_path, storage)
try:
history = _failed_history(2, src_path, storage)
record_transfer_failure(src_path, storage)
record_transfer_failure(src_path, storage)
assert failed_retry_count(src_path, storage) == 2
assert evaluate_history_gate(history, file_size=None) == HistoryGateAction.SKIP_RETRY_EXHAUSTED
# 用户删除整理记录,显式要求重来
clear_transfer_failures(src_path, storage)
assert failed_retry_count(src_path, storage) == 0
assert evaluate_history_gate(history, file_size=None) == HistoryGateAction.PASS_FAILED
finally:
_reset_failed_retries(src_path, storage)
def test_delete_transfer_history_endpoint_clears_retry_count(monkeypatch):
"""
app/api/endpoints/history.py::delete_transfer_history 是用户删除整理记录的入口,
删除时应连带清空失败重试计数,否则重整仍会受上一轮次数限制。
该端点依赖 SQLAlchemy Session 与鉴权依赖,这里按仓库内既有做法(参见
tests/test_manual_transfer_history.py 对 app.api.endpoints.transfer 端点的用法)
直接以关键字参数调用端点函数本身,绕开 FastAPI 的依赖注入,只替换端点内部
实际用到的 TransferHistory.get / TransferHistory.delete 两个类方法。
"""
from app.api.endpoints.history import delete_transfer_history
src_path = "/downloads/retry-budget-delete-endpoint.mkv"
storage = "local"
history = SimpleNamespace(
id=101,
src=src_path,
src_storage=storage,
dest_fileitem=None,
src_fileitem=None,
download_hash=None,
)
monkeypatch.setattr("app.api.endpoints.history.TransferHistory.get",
lambda db, history_id: history)
monkeypatch.setattr("app.api.endpoints.history.TransferHistory.delete",
lambda db, history_id: None)
_reset_failed_retries(src_path, storage)
try:
record_transfer_failure(src_path, storage)
record_transfer_failure(src_path, storage)
assert failed_retry_count(src_path, storage) == 2
response = delete_transfer_history(
history_in=schemas.TransferHistory(id=101),
deletesrc=False,
deletedest=False,
db=object(),
_="token",
)
assert response.success is True
assert failed_retry_count(src_path, storage) == 0
finally:
_reset_failed_retries(src_path, storage)