from __future__ import annotations import unittest from types import SimpleNamespace from biliup_next.core.models import Task, TaskStep, utc_now_iso from biliup_next.modules.collection.service import CollectionService class _FakeRegistry: def __init__(self, provider) -> None: # type: ignore[no-untyped-def] self.provider = provider def get(self, provider_type: str, provider_id: str): # type: ignore[no-untyped-def] return self.provider class _FakeProvider: def sync(self, task, target: str, settings: dict[str, object]) -> dict[str, object]: # type: ignore[no-untyped-def] return {"status": "skipped", "target": target} class _FakeRepo: def __init__(self) -> None: now = utc_now_iso() self.task = Task("task-1", "local_file", "/tmp/source.mp4", "task-1", "running", now, now) self.steps = { "collection_a": TaskStep(None, "task-1", "collection_a", "pending", None, None, 0, None, None), "collection_b": TaskStep(None, "task-1", "collection_b", "pending", None, None, 0, None, None), } self.task_status_updates: list[tuple[str, str]] = [] def get_task(self, task_id: str): # type: ignore[no-untyped-def] return self.task if task_id == self.task.id else None def update_step_status(self, task_id: str, step_name: str, status: str, **kwargs) -> None: # type: ignore[no-untyped-def] step = self.steps[step_name] self.steps[step_name] = TaskStep( step.id, step.task_id, step.step_name, status, kwargs.get("error_code", step.error_code), kwargs.get("error_message", step.error_message), kwargs.get("retry_count", step.retry_count), kwargs.get("started_at", step.started_at), kwargs.get("finished_at", step.finished_at), ) def list_steps(self, task_id: str) -> list[TaskStep]: return list(self.steps.values()) def update_task_status(self, task_id: str, status: str, updated_at: str) -> None: self.task_status_updates.append((task_id, status)) class CollectionServiceTests(unittest.TestCase): def test_collection_a_restores_commented_status_so_collection_b_can_run(self) -> None: repo = _FakeRepo() service = CollectionService(_FakeRegistry(_FakeProvider()), repo) # type: ignore[arg-type] service.cleanup = SimpleNamespace(cleanup_task_outputs=lambda task_id, settings: {}) # type: ignore[assignment] result = service.run("task-1", "a", {"provider": "fake"}) self.assertEqual(result["status"], "skipped") self.assertEqual(repo.steps["collection_a"].status, "succeeded") self.assertEqual(repo.steps["collection_b"].status, "pending") self.assertEqual(repo.task_status_updates[-1], ("task-1", "commented")) def test_collection_b_marks_collection_synced_when_both_steps_succeeded(self) -> None: repo = _FakeRepo() repo.steps["collection_a"] = TaskStep(None, "task-1", "collection_a", "succeeded", None, None, 0, None, utc_now_iso()) service = CollectionService(_FakeRegistry(_FakeProvider()), repo) # type: ignore[arg-type] service.cleanup = SimpleNamespace(cleanup_task_outputs=lambda task_id, settings: {"deleted": []}) # type: ignore[assignment] result = service.run("task-1", "b", {"provider": "fake"}) self.assertEqual(result["status"], "skipped") self.assertEqual(repo.steps["collection_b"].status, "succeeded") self.assertEqual(repo.task_status_updates[-1], ("task-1", "collection_synced")) self.assertEqual(result["cleanup"], {"deleted": []}) if __name__ == "__main__": unittest.main()