mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
- 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 清单校验排除下划线内部目录
129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
"""整理任务失活收敛行为测试。"""
|
||
|
||
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 已标注为 MetaBase,pydantic 会做
|
||
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
|