Files
MoviePilot/tests/test_transfer_stale_tasks.py
T
jxxghp 7e851dbfa7 refactor(chain): 处理链功能域 mixin 化,清理未使用导入并根治兼容层循环导入
- ChainBase 拆分为 RecognitionMixin/MessageProcessingMixin/NotificationMixin
- TransferChain 拆分为 7 个功能 mixin(_mixins.py),SubscribeChain 音乐订阅域拆出 _music.py
- 斜杠命令交互四件套收敛为 InteractionChainMixin 委托,会话管理器移至 application 层,chain 层不再 re-export
- 模块基础类收敛到 app/modules/_base(notification/mediaserver 语义重命名)
- 清理 app/chain/__init__.py 24 个未使用导入,修正 49 处测试 patch 目标到实际命名空间
- 兼容层 legacy 符号不再并入 __all__,根治 schemas 初始化反向拉起 application.transfer 的循环导入
- 修复 bangumi 集数为字符串时 set_bangumi_info 抛 TypeError
- 新增重复代码等架构门禁测试;capability 清单校验排除下划线内部目录
2026-08-16 16:30:16 +08:00

129 lines
4.2 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.
"""整理任务失活收敛行为测试。"""
from app.chain.transfer import JobManager
from app.domain.meta.metabase import MetaBase
from app.schemas import FileItem
from app.application.transfer import TransferTask
from app.application import transfer as app_transfer
from app.schemas.types import MediaType
class _FakeMeta(MetaBase):
"""
提供整理任务分组需要的最小元数据。
继承 MetaBase 而非鸭子类型:TransferTask.meta 已标注为 MetaBasepydantic 会做
isinstance 校验。三个类级属性用来遮蔽 MetaBase 上的同名 property。
"""
name = None
episode_list = None
season_episode = None
def __init__(self):
super().__init__(title="Test Show S01E01")
self.name = "Test Show"
self.title = "Test Show S01E01"
self.year = "2026"
self.type = MediaType.TV
self.begin_season = 1
self.end_season = None
self.total_season = 1
self.begin_episode = 1
self.end_episode = None
self.total_episode = 1
self.episode_list = [1]
self.season_episode = "S01E01"
self.part = None
def to_dict(self):
"""返回 TransferJobTask 所需的元数据字典。"""
return {
"title": self.title,
"name": self.name,
"year": self.year,
"type": self.type.value,
"begin_season": self.begin_season,
"end_season": self.end_season,
"total_season": self.total_season,
"begin_episode": self.begin_episode,
"end_episode": self.end_episode,
"total_episode": self.total_episode,
"season_episode": self.season_episode,
"episode_list": self.episode_list,
"part": self.part,
}
def _make_task(name: str = "Test.Show.S01E01.mkv") -> TransferTask:
"""创建失活检测使用的整理任务。"""
return TransferTask(
fileitem=FileItem(
storage="local",
path=f"/downloads/{name}",
type="file",
name=name,
basename=name.removesuffix(".mkv"),
extension="mkv",
size=1024,
),
meta=_FakeMeta(),
)
def test_external_running_task_expires_without_heartbeat(monkeypatch):
"""外部接管的运行中任务超过心跳期限后应被标记失败并清理。"""
clock = [100.0]
monkeypatch.setattr(app_transfer, "monotonic", lambda: clock[0])
manager = JobManager()
task = _make_task()
assert manager.add_task(task)
manager.running_task(task)
clock[0] = 221.0
expired = manager.expire_stale_running_tasks(timeout_seconds=120)
assert expired == [(task.fileitem, 121)]
assert manager.list_jobs() == []
def test_main_thread_execution_is_not_expired(monkeypatch):
"""主程序整理线程仍在执行的任务不应被失活检测伪清理。"""
clock = [100.0]
monkeypatch.setattr(app_transfer, "monotonic", lambda: clock[0])
manager = JobManager()
task = _make_task()
assert manager.add_task(task)
manager.start_execution(task)
manager.running_task(task)
clock[0] = 500.0
assert manager.expire_stale_running_tasks(timeout_seconds=120) == []
assert manager.total() == 1
manager.finish_execution(task)
expired = manager.expire_stale_running_tasks(timeout_seconds=120)
assert expired == [(task.fileitem, 400)]
assert manager.list_jobs() == []
def test_waiting_task_and_refreshed_heartbeat_do_not_expire(monkeypatch):
"""等待中任务不受失活期限影响,重复运行状态更新可刷新外部心跳。"""
clock = [100.0]
monkeypatch.setattr(app_transfer, "monotonic", lambda: clock[0])
manager = JobManager()
waiting_task = _make_task("Test.Show.S01E01.waiting.mkv")
running_task = _make_task("Test.Show.S01E02.running.mkv")
running_task.meta.begin_episode = 2
running_task.meta.episode_list = [2]
assert manager.add_task(waiting_task)
assert manager.add_task(running_task)
manager.running_task(running_task)
clock[0] = 180.0
manager.running_task(running_task)
clock[0] = 250.0
assert manager.expire_stale_running_tasks(timeout_seconds=120) == []
assert manager.total() == 2