112 lines
4.7 KiB
Python
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")
|