Files
biliup-next/tests/test_control_plane_post_dispatcher.py

112 lines
4.7 KiB
Python

from __future__ import annotations
import io
import tempfile
import unittest
from http import HTTPStatus
from pathlib import Path
from types import SimpleNamespace
from biliup_next.app.control_plane_post_dispatcher import ControlPlanePostDispatcher
from biliup_next.core.models import Task
class FakeRepo:
def __init__(self) -> None:
self.actions = []
def add_action_record(self, action) -> None: # type: ignore[no-untyped-def]
self.actions.append(action)
class ModuleError(Exception):
def to_dict(self) -> dict[str, object]:
return {"error": "conflict"}
class ControlPlanePostDispatcherTests(unittest.TestCase):
def _dispatcher(self, tmpdir: str, repo: FakeRepo, *, ingest_service: object | None = None) -> ControlPlanePostDispatcher:
state = {
"repo": repo,
"root": Path(tmpdir),
"settings": {
"paths": {"stage_dir": str(Path(tmpdir) / "stage"), "session_dir": str(Path(tmpdir) / "session")},
"ingest": {"stage_min_free_space_mb": 100},
},
"ingest_service": ingest_service or SimpleNamespace(
create_task_from_file=lambda path, settings: Task(
"task-1",
"local_file",
str(path),
"task-title",
"created",
"2026-01-01T00:00:00+00:00",
"2026-01-01T00:00:00+00:00",
)
),
}
return ControlPlanePostDispatcher(
state,
bind_full_video_action=lambda task_id, bvid: {"task_id": task_id, "full_video_bvid": bvid},
merge_session_action=lambda session_key, task_ids: {"session_key": session_key, "task_ids": task_ids},
receive_full_video_webhook=lambda payload: {"ok": True, **payload},
rebind_session_full_video_action=lambda session_key, bvid: {"session_key": session_key, "full_video_bvid": bvid},
reset_to_step_action=lambda task_id, step_name: {"task_id": task_id, "step_name": step_name},
retry_step_action=lambda task_id, step_name: {"task_id": task_id, "step_name": step_name},
run_task_action=lambda task_id: {"task_id": task_id},
run_once=lambda: {"scheduler": {"scan_count": 1}, "worker": {"picked": 1}},
stage_importer_factory=lambda: SimpleNamespace(
import_file=lambda source, dest, min_free_bytes=0: {"imported_to": str(dest / source.name)},
import_upload=lambda filename, fileobj, dest, min_free_bytes=0: {"filename": filename, "dest": str(dest)},
),
systemd_runtime_factory=lambda: SimpleNamespace(act=lambda service, action: {"service": service, "action": action, "command_ok": True}),
)
def test_handle_bind_full_video_maps_missing_bvid(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
dispatcher = self._dispatcher(tmpdir, FakeRepo())
body, status = dispatcher.handle_bind_full_video("task-1", {})
self.assertEqual(status, HTTPStatus.BAD_REQUEST)
self.assertEqual(body["error"], "missing full_video_bvid")
def test_handle_worker_run_once_records_action(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
repo = FakeRepo()
dispatcher = self._dispatcher(tmpdir, repo)
body, status = dispatcher.handle_worker_run_once()
self.assertEqual(status, HTTPStatus.ACCEPTED)
self.assertEqual(body["worker"]["picked"], 1)
self.assertEqual(repo.actions[-1].action_name, "worker_run_once")
def test_handle_stage_upload_returns_created(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
dispatcher = self._dispatcher(tmpdir, FakeRepo())
file_item = SimpleNamespace(filename="incoming.mp4", file=io.BytesIO(b"video"))
body, status = dispatcher.handle_stage_upload(file_item)
self.assertEqual(status, HTTPStatus.CREATED)
self.assertEqual(body["filename"], "incoming.mp4")
def test_handle_create_task_maps_module_error_to_conflict(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
repo = FakeRepo()
def raise_module_error(path, settings): # type: ignore[no-untyped-def]
raise ModuleError()
dispatcher = self._dispatcher(
tmpdir,
repo,
ingest_service=SimpleNamespace(create_task_from_file=raise_module_error),
)
body, status = dispatcher.handle_create_task({"source_path": str(Path(tmpdir) / "source.mp4")})
self.assertEqual(status, HTTPStatus.CONFLICT)
self.assertEqual(body["error"], "conflict")