feat: professionalize control plane and standalone delivery

This commit is contained in:
theshy
2026-04-07 10:46:30 +08:00
parent d0cf1fd0df
commit 862db502b0
100 changed files with 8313 additions and 1483 deletions

View File

@ -0,0 +1,92 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from biliup_next.app.session_delivery_service import SessionDeliveryService
from biliup_next.core.models import Task, TaskContext
class FakeRepo:
def __init__(self, task: Task, context: TaskContext | None = None, contexts: list[TaskContext] | None = None) -> None:
self.task = task
self.context = context
self.contexts = contexts or ([] if context is None else [context])
self.task_context_upserts: list[TaskContext] = []
self.session_binding_upserts = []
self.action_records = []
self.updated_session_bvid: tuple[str, str, str] | None = None
def get_task(self, task_id: str) -> Task | None:
return self.task if task_id == self.task.id else None
def get_task_context(self, task_id: str) -> TaskContext | None:
return self.context if task_id == self.task.id else None
def upsert_task_context(self, context: TaskContext) -> None:
self.context = context
self.task_context_upserts.append(context)
def upsert_session_binding(self, binding) -> None: # type: ignore[no-untyped-def]
self.session_binding_upserts.append(binding)
def add_action_record(self, record) -> None: # type: ignore[no-untyped-def]
self.action_records.append(record)
def list_task_contexts_by_session_key(self, session_key: str) -> list[TaskContext]:
return [context for context in self.contexts if context.session_key == session_key]
def update_session_full_video_bvid(self, session_key: str, full_video_bvid: str, updated_at: str) -> int:
self.updated_session_bvid = (session_key, full_video_bvid, updated_at)
return len(self.list_task_contexts_by_session_key(session_key))
def list_task_contexts_by_source_title(self, source_title: str) -> list[TaskContext]:
return [context for context in self.contexts if context.source_title == source_title]
class SessionDeliveryServiceTests(unittest.TestCase):
def test_receive_full_video_webhook_updates_binding_context_and_action_record(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
task = Task("task-1", "local_file", "/tmp/source.mp4", "task-title", "published", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00")
context = TaskContext(
id=None,
task_id="task-1",
session_key="task:task-1",
streamer="streamer",
room_id="room",
source_title="task-title",
segment_started_at=None,
segment_duration_seconds=None,
full_video_bvid=None,
created_at="2026-01-01T00:00:00+00:00",
updated_at="2026-01-01T00:00:00+00:00",
)
repo = FakeRepo(task, context=context, contexts=[context])
state = {"repo": repo, "settings": {"paths": {"session_dir": str(Path(tmpdir) / "session")}}}
result = SessionDeliveryService(state).receive_full_video_webhook(
{"session_key": "session-1", "source_title": "task-title", "full_video_bvid": "BVWEBHOOK123"}
)
self.assertEqual(result["updated_count"], 1)
self.assertEqual(repo.context.session_key, "session-1")
self.assertEqual(repo.context.full_video_bvid, "BVWEBHOOK123")
self.assertEqual(repo.session_binding_upserts[-1].full_video_bvid, "BVWEBHOOK123")
self.assertEqual(repo.action_records[-1].action_name, "webhook_full_video_uploaded")
persisted_path = Path(result["tasks"][0]["path"])
self.assertTrue(persisted_path.exists())
self.assertEqual(persisted_path.read_text(encoding="utf-8"), "BVWEBHOOK123")
def test_merge_session_returns_error_when_task_ids_empty(self) -> None:
task = Task("task-1", "local_file", "/tmp/source.mp4", "task-title", "created", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00")
repo = FakeRepo(task)
state = {"repo": repo, "settings": {"paths": {"session_dir": "/tmp/session"}}}
result = SessionDeliveryService(state).merge_session("session-1", ["", " "])
self.assertEqual(result["error"]["code"], "TASK_IDS_EMPTY")
if __name__ == "__main__":
unittest.main()