feat: professionalize control plane and standalone delivery
This commit is contained in:
111
tests/test_control_plane_post_dispatcher.py
Normal file
111
tests/test_control_plane_post_dispatcher.py
Normal file
@ -0,0 +1,111 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user