mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-28 19:06:36 +08:00
169 lines
5.2 KiB
Python
169 lines
5.2 KiB
Python
"""
|
|
验证整理队列的分页、排序与结果数量。
|
|
"""
|
|
|
|
import asyncio
|
|
import inspect
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from app.adapters.web.security.access import verify_token
|
|
from app.api.endpoints import transfer as transfer_endpoint
|
|
from app.application.transfer.jobs import JobManager
|
|
from app.application.transfer.workflow import TransferQueueService
|
|
from app.schemas.file import FileItem
|
|
from app.schemas.transfer import TransferJob, TransferJobTask
|
|
from tests.test_transfer_job_manager import make_task
|
|
|
|
|
|
def _job(*paths: str) -> TransferJob:
|
|
"""
|
|
构造只包含队列分页测试所需字段的整理作业。
|
|
"""
|
|
return TransferJob(
|
|
season=1,
|
|
tasks=[
|
|
TransferJobTask(
|
|
fileitem=FileItem(
|
|
name=path.rsplit("/", 1)[-1],
|
|
path=path,
|
|
size=1,
|
|
storage="local",
|
|
type="file",
|
|
children=[
|
|
FileItem(
|
|
name="cloud-child.mkv",
|
|
path=f"{path}.child",
|
|
size=1,
|
|
storage="local",
|
|
type="file",
|
|
)
|
|
],
|
|
),
|
|
state="waiting",
|
|
)
|
|
for path in paths
|
|
],
|
|
)
|
|
|
|
|
|
def test_job_manager_page_keeps_flat_order_and_only_projects_the_requested_window():
|
|
"""
|
|
大队列分页必须保持任务顺序,并按窗口复制作业投影。
|
|
"""
|
|
manager = JobManager()
|
|
manager._job_view = {
|
|
("first", 1): _job("/downloads/first-1.mkv", "/downloads/first-2.mkv"),
|
|
("second", 1): _job(
|
|
"/downloads/second-1.mkv",
|
|
"/downloads/second-2.mkv",
|
|
"/downloads/second-3.mkv",
|
|
),
|
|
}
|
|
|
|
first_page, total = manager.list_jobs_page(page=1, count=3)
|
|
second_page, second_total = manager.list_jobs_page(page=2, count=3)
|
|
|
|
assert total == second_total == 5
|
|
assert [len(job.tasks or []) for job in first_page] == [2, 1]
|
|
assert not hasattr(first_page[0].tasks[0].fileitem, "children")
|
|
assert [task.fileitem.path for job in first_page for task in job.tasks or []] == [
|
|
"/downloads/first-1.mkv",
|
|
"/downloads/first-2.mkv",
|
|
"/downloads/second-1.mkv",
|
|
]
|
|
assert [task.fileitem.path for job in second_page for task in job.tasks or []] == [
|
|
"/downloads/second-2.mkv",
|
|
"/downloads/second-3.mkv",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("page,count", [(0, 1), (1, 0), (-1, 10)])
|
|
def test_job_manager_page_rejects_invalid_windows(page: int, count: int):
|
|
"""
|
|
分页窗口不接受零或负数,避免异常切片产生隐性全量查询。
|
|
"""
|
|
with pytest.raises(ValueError, match="队列分页参数必须为正数"):
|
|
JobManager().list_jobs_page(page, count)
|
|
|
|
|
|
def test_transfer_queue_service_uses_bounded_projection_after_expiring_tasks():
|
|
"""
|
|
应用服务先清理失活任务,再调用可选的受限投影依赖。
|
|
"""
|
|
expire_tasks = Mock()
|
|
list_tasks_page = Mock(return_value=([_job("/downloads/page.mkv")], 101))
|
|
service = TransferQueueService(
|
|
register_task=lambda _task: True,
|
|
admit_task=lambda _task: None,
|
|
enqueue=lambda _queue: None,
|
|
before_enqueue=lambda _task: None,
|
|
enqueue_failed=lambda _task, _error: None,
|
|
remove_task=lambda _fileitem: None,
|
|
list_tasks=lambda: [],
|
|
expire_tasks=expire_tasks,
|
|
list_tasks_page=list_tasks_page,
|
|
)
|
|
|
|
items, total = service.list_page(page=3, count=50)
|
|
|
|
assert len(items) == 1
|
|
assert total == 101
|
|
expire_tasks.assert_called_once_with()
|
|
list_tasks_page.assert_called_once_with(3, 50)
|
|
|
|
|
|
def test_queue_page_endpoint_exposes_bounded_projection_with_existing_transfer_auth(monkeypatch):
|
|
"""
|
|
轻量队列端点必须复用整理模块的鉴权并转发分页窗口。
|
|
"""
|
|
calls = []
|
|
|
|
class StubTransferChain:
|
|
"""
|
|
提供端点测试所需的受限队列查询替身。
|
|
"""
|
|
|
|
def get_queue_tasks_page(self, page: int, count: int):
|
|
"""
|
|
记录分页参数并返回最小合法快照。
|
|
"""
|
|
calls.append((page, count))
|
|
return ([], 7)
|
|
|
|
monkeypatch.setattr(transfer_endpoint, "TransferChain", StubTransferChain)
|
|
|
|
result = asyncio.run(
|
|
transfer_endpoint.query_queue_page(
|
|
_=object(),
|
|
page=2,
|
|
count=3,
|
|
)
|
|
)
|
|
|
|
dependency = inspect.signature(transfer_endpoint.query_queue_page).parameters["_"].default.dependency
|
|
assert dependency is verify_token
|
|
assert calls == [(2, 3)]
|
|
assert result.model_dump() == {
|
|
"items": [],
|
|
"total": 7,
|
|
"page": 2,
|
|
"count": 3,
|
|
}
|
|
|
|
|
|
def test_job_manager_duplicate_index_is_released_when_a_cloud_task_is_removed():
|
|
"""
|
|
批量云盘任务的 O(1) 去重索引不能阻止同一源文件后续重新入队。
|
|
"""
|
|
manager = JobManager()
|
|
task = make_task(1)
|
|
|
|
assert manager.add_task(task) is True
|
|
assert manager.add_task(make_task(1)) is False
|
|
|
|
manager.remove_task(task.fileitem)
|
|
|
|
assert manager.add_task(make_task(1)) is True
|