mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-15 19:14:01 +08:00
129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
"""整理任务失活收敛行为测试。"""
|
||
|
||
from app.chain import transfer
|
||
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.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(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(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(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
|