From 055474360edb13c2031aa1b07c45015902682b3f Mon Sep 17 00:00:00 2001 From: theshy Date: Tue, 14 Apr 2026 16:44:31 +0800 Subject: [PATCH] fix: unify task workspace directory resolution --- src/biliup_next/app/serializers.py | 5 +- .../app/session_delivery_service.py | 11 +-- src/biliup_next/infra/workspace_cleanup.py | 4 +- src/biliup_next/infra/workspace_paths.py | 10 +++ .../providers/bilibili_collection.py | 3 +- .../comment/providers/bilibili_top_comment.py | 22 ++++-- .../modules/publish/providers/biliup_cli.py | 3 +- src/biliup_next/modules/publish/service.py | 24 +++--- tests/test_bilibili_top_comment_provider.py | 78 +++++++++++++++++-- tests/test_biliup_cli_publish_provider.py | 73 ++++++++++++++--- tests/test_publish_service.py | 15 ++-- 11 files changed, 192 insertions(+), 56 deletions(-) create mode 100644 src/biliup_next/infra/workspace_paths.py diff --git a/src/biliup_next/app/serializers.py b/src/biliup_next/app/serializers.py index 87f6f08..267a4c4 100644 --- a/src/biliup_next/app/serializers.py +++ b/src/biliup_next/app/serializers.py @@ -4,6 +4,7 @@ import json from pathlib import Path from biliup_next.app.retry_meta import retry_meta_for_step +from biliup_next.infra.workspace_paths import resolve_task_work_dir class ControlPlaneSerializer: @@ -78,7 +79,7 @@ class ControlPlaneSerializer: task = task or self.state["repo"].get_task(task_id) if task is None: return {} - session_dir = Path(str(self.state["settings"]["paths"]["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) source_path = Path(task.source_path) split_dir = session_dir / "split_video" @@ -246,7 +247,7 @@ class ControlPlaneSerializer: task = task or self.state["repo"].get_task(task_id) if task is None: return None - session_dir = Path(str(self.state["settings"]["paths"]["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) path = session_dir / filename if not path.exists(): return None diff --git a/src/biliup_next/app/session_delivery_service.py b/src/biliup_next/app/session_delivery_service.py index 411a3c5..f0ce6ea 100644 --- a/src/biliup_next/app/session_delivery_service.py +++ b/src/biliup_next/app/session_delivery_service.py @@ -5,6 +5,7 @@ from pathlib import Path import re from biliup_next.core.models import ActionRecord, SessionBinding, TaskContext, utc_now_iso +from biliup_next.infra.workspace_paths import resolve_task_work_dir class SessionDeliveryService: @@ -222,10 +223,10 @@ class SessionDeliveryService: return None return bvid - def _full_video_bvid_path(self, task_title: str) -> Path: - session_dir = Path(str(self.settings["paths"]["session_dir"])) / task_title - session_dir.mkdir(parents=True, exist_ok=True) - return session_dir / "full_video_bvid.txt" + def _full_video_bvid_path(self, task) -> Path: # type: ignore[no-untyped-def] + work_dir = resolve_task_work_dir(task) + work_dir.mkdir(parents=True, exist_ok=True) + return work_dir / "full_video_bvid.txt" def _upsert_session_binding_for_context(self, context: TaskContext, full_video_bvid: str, now: str) -> None: self.repo.upsert_session_binding( @@ -253,6 +254,6 @@ class SessionDeliveryService: context.updated_at = now self.repo.upsert_task_context(context) self._upsert_session_binding_for_context(context, full_video_bvid, now) - path = self._full_video_bvid_path(task.title) + path = self._full_video_bvid_path(task) path.write_text(full_video_bvid, encoding="utf-8") return path diff --git a/src/biliup_next/infra/workspace_cleanup.py b/src/biliup_next/infra/workspace_cleanup.py index 0df956f..207ff47 100644 --- a/src/biliup_next/infra/workspace_cleanup.py +++ b/src/biliup_next/infra/workspace_cleanup.py @@ -1,9 +1,9 @@ from __future__ import annotations import shutil -from pathlib import Path from biliup_next.infra.task_repository import TaskRepository +from biliup_next.infra.workspace_paths import resolve_task_work_dir class WorkspaceCleanupService: @@ -15,7 +15,7 @@ class WorkspaceCleanupService: if task is None: raise RuntimeError(f"task not found: {task_id}") - session_dir = Path(str(settings["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) removed: list[str] = [] skipped: list[str] = [] diff --git a/src/biliup_next/infra/workspace_paths.py b/src/biliup_next/infra/workspace_paths.py new file mode 100644 index 0000000..8fa2922 --- /dev/null +++ b/src/biliup_next/infra/workspace_paths.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pathlib import Path + + +def resolve_task_work_dir(task) -> Path: # type: ignore[no-untyped-def] + source = Path(task.source_path).resolve() + if source.is_file() or source.suffix: + return source.parent + return source diff --git a/src/biliup_next/modules/collection/providers/bilibili_collection.py b/src/biliup_next/modules/collection/providers/bilibili_collection.py index 72de59e..ce90fc2 100644 --- a/src/biliup_next/modules/collection/providers/bilibili_collection.py +++ b/src/biliup_next/modules/collection/providers/bilibili_collection.py @@ -11,6 +11,7 @@ from biliup_next.core.models import Task from biliup_next.core.providers import ProviderManifest from biliup_next.infra.adapters.bilibili_api import BilibiliApiAdapter from biliup_next.infra.adapters.full_video_locator import resolve_full_video_bvid +from biliup_next.infra.workspace_paths import resolve_task_work_dir class BilibiliCollectionProvider: @@ -29,7 +30,7 @@ class BilibiliCollectionProvider: ) def sync(self, task: Task, target: str, settings: dict[str, Any]) -> dict[str, object]: - session_dir = Path(str(settings["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) cookies = self.bilibili_api.load_cookies(Path(str(settings["cookies_file"]))) csrf = cookies.get("bili_jct") if not csrf: diff --git a/src/biliup_next/modules/comment/providers/bilibili_top_comment.py b/src/biliup_next/modules/comment/providers/bilibili_top_comment.py index 9bcf82e..499e963 100644 --- a/src/biliup_next/modules/comment/providers/bilibili_top_comment.py +++ b/src/biliup_next/modules/comment/providers/bilibili_top_comment.py @@ -11,6 +11,7 @@ from biliup_next.core.models import Task from biliup_next.core.providers import ProviderManifest from biliup_next.infra.adapters.bilibili_api import BilibiliApiAdapter from biliup_next.infra.adapters.full_video_locator import resolve_full_video_bvid +from biliup_next.infra.workspace_paths import resolve_task_work_dir class BilibiliTopCommentProvider: @@ -28,7 +29,7 @@ class BilibiliTopCommentProvider: ) def comment(self, task: Task, settings: dict[str, Any]) -> dict[str, object]: - session_dir = Path(str(settings["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) songs_path = session_dir / "songs.txt" songs_json_path = session_dir / "songs.json" bvid_path = session_dir / "bvid.txt" @@ -164,14 +165,13 @@ class BilibiliTopCommentProvider: def _build_split_comment(self, task: Task, settings: dict[str, Any]) -> tuple[str, str | None]: repo = settings.get("__repo") - session_dir_root = Path(str(settings["session_dir"])) if repo is None or not hasattr(repo, "get_task_context") or not hasattr(repo, "list_task_contexts_by_session_key"): - session_dir = session_dir_root / task.title + session_dir = resolve_task_work_dir(task) return self._build_split_comment_content(session_dir / "songs.json", session_dir / "songs.txt"), None context = repo.get_task_context(task.id) if context is None or not context.session_key or context.session_key.startswith("task:"): - session_dir = session_dir_root / task.title + session_dir = resolve_task_work_dir(task) return self._build_split_comment_content(session_dir / "songs.json", session_dir / "songs.txt"), None ordered_contexts = self._ordered_session_contexts(repo, context.session_key) @@ -183,7 +183,10 @@ class BilibiliTopCommentProvider: blocks: list[str] = [] for index, session_context in enumerate(ordered_contexts, start=1): - task_dir = session_dir_root / session_context.task_id + session_task = repo.get_task(session_context.task_id) + if session_task is None: + continue + task_dir = resolve_task_work_dir(session_task) content = self._build_split_comment_content(task_dir / "songs.json", task_dir / "songs.txt") if not content: continue @@ -195,13 +198,13 @@ class BilibiliTopCommentProvider: def _build_full_comment_content(self, task: Task, settings: dict[str, Any]) -> tuple[str, str | None]: repo = settings.get("__repo") if repo is None or not hasattr(repo, "get_task_context") or not hasattr(repo, "list_task_contexts_by_session_key"): - session_dir = Path(str(settings["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) content = session_dir.joinpath("songs.txt").read_text(encoding="utf-8").strip() return content, None if content else "timeline_comment_empty" context = repo.get_task_context(task.id) if context is None or not context.session_key or context.session_key.startswith("task:"): - session_dir = Path(str(settings["session_dir"])) / task.title + session_dir = resolve_task_work_dir(task) content = session_dir.joinpath("songs.txt").read_text(encoding="utf-8").strip() return content, None if content else "timeline_comment_empty" @@ -214,7 +217,10 @@ class BilibiliTopCommentProvider: blocks: list[str] = [] for index, session_context in enumerate(ordered_contexts, start=1): - task_dir = Path(str(settings["session_dir"])) / session_context.task_id + session_task = repo.get_task(session_context.task_id) + if session_task is None: + continue + task_dir = resolve_task_work_dir(session_task) songs_path = task_dir / "songs.txt" if not songs_path.exists(): continue diff --git a/src/biliup_next/modules/publish/providers/biliup_cli.py b/src/biliup_next/modules/publish/providers/biliup_cli.py index 4f41d09..84608a2 100644 --- a/src/biliup_next/modules/publish/providers/biliup_cli.py +++ b/src/biliup_next/modules/publish/providers/biliup_cli.py @@ -11,6 +11,7 @@ from biliup_next.core.errors import ModuleError from biliup_next.core.models import PublishRecord, Task, utc_now_iso from biliup_next.core.providers import ProviderManifest from biliup_next.infra.adapters.biliup_cli import BiliupCliAdapter +from biliup_next.infra.workspace_paths import resolve_task_work_dir class BiliupCliPublishProvider: @@ -28,7 +29,7 @@ class BiliupCliPublishProvider: ) def publish(self, task: Task, clip_videos: list, settings: dict[str, Any]) -> PublishRecord: - work_dir = Path(str(settings["session_dir"])) / task.title + work_dir = resolve_task_work_dir(task) bvid_file = work_dir / "bvid.txt" upload_done = work_dir / "upload_done.flag" publish_log = work_dir / "publish.log" diff --git a/src/biliup_next/modules/publish/service.py b/src/biliup_next/modules/publish/service.py index 5eee29b..62bf2b3 100644 --- a/src/biliup_next/modules/publish/service.py +++ b/src/biliup_next/modules/publish/service.py @@ -8,6 +8,7 @@ from typing import Any from biliup_next.core.models import Artifact, PublishRecord, TaskContext, utc_now_iso from biliup_next.core.registry import Registry from biliup_next.infra.task_repository import TaskRepository +from biliup_next.infra.workspace_paths import resolve_task_work_dir class PublishService: @@ -26,7 +27,7 @@ class PublishService: if len(session_contexts) <= 1: clip_videos = self._clip_videos_for_task(task_id) record = provider.publish(task, clip_videos, settings) - self._persist_publish_success(task_id, task.title, record, settings) + self._persist_publish_success(task, record) return record anchor_context = session_contexts[0] @@ -41,7 +42,7 @@ class PublishService: title=task.title, published_at=utc_now_iso(), ) - self._persist_publish_success(task_id, task.title, record, settings) + self._persist_publish_success(task, record) return record clip_videos = self._session_clip_videos(session_contexts) @@ -64,7 +65,7 @@ class PublishService: title=record.title, published_at=record.published_at, ) - self._persist_publish_success(context.task_id, session_task.title, session_record, settings) + self._persist_publish_success(session_task, session_record) return PublishRecord( id=None, task_id=task_id, @@ -75,12 +76,13 @@ class PublishService: published_at=record.published_at, ) - def _persist_publish_success(self, task_id: str, task_title: str, record: PublishRecord, settings: dict[str, object]) -> None: + def _persist_publish_success(self, task, record: PublishRecord) -> None: # type: ignore[no-untyped-def] + task_id = task.id self.repo.add_publish_record(record) if record.bvid: - session_dir = Path(str(settings.get("session_dir", "session"))) / task_title - session_dir.mkdir(parents=True, exist_ok=True) - bvid_path_obj = session_dir / "bvid.txt" + work_dir = resolve_task_work_dir(task) + work_dir.mkdir(parents=True, exist_ok=True) + bvid_path_obj = work_dir / "bvid.txt" bvid_path_obj.write_text(record.bvid, encoding="utf-8") self.repo.add_artifact( Artifact( @@ -120,12 +122,11 @@ class PublishService: return aggregated def _shared_session_bvid(self, contexts: list[TaskContext], settings: dict[str, object]) -> str | None: - session_dir_root = Path(str(settings.get("session_dir", "session"))) for context in contexts: task = self.repo.get_task(context.task_id) if task is None: continue - bvid_path = session_dir_root / task.title / "bvid.txt" + bvid_path = resolve_task_work_dir(task) / "bvid.txt" if bvid_path.exists(): bvid = bvid_path.read_text(encoding="utf-8").strip() if bvid.startswith("BV"): @@ -138,8 +139,7 @@ class PublishService: contexts: list[TaskContext], settings: dict[str, object], ) -> dict[str, Any]: # type: ignore[no-untyped-def] - session_dir_root = Path(str(settings.get("session_dir", "session"))) - anchor_work_dir = (session_dir_root / anchor_task.title).resolve() + anchor_work_dir = resolve_task_work_dir(anchor_task) anchor_work_dir.mkdir(parents=True, exist_ok=True) aggregate_txt_lines: list[str] = [] aggregate_songs: list[dict[str, object]] = [] @@ -148,7 +148,7 @@ class PublishService: task = self.repo.get_task(context.task_id) if task is None: continue - task_work_dir = (session_dir_root / task.title).resolve() + task_work_dir = resolve_task_work_dir(task) songs_txt = task_work_dir / "songs.txt" songs_json = task_work_dir / "songs.json" diff --git a/tests/test_bilibili_top_comment_provider.py b/tests/test_bilibili_top_comment_provider.py index 7564943..4c893c3 100644 --- a/tests/test_bilibili_top_comment_provider.py +++ b/tests/test_bilibili_top_comment_provider.py @@ -41,11 +41,11 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): provider = BilibiliTopCommentProvider(bilibili_api=api) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) - task = Task("task-1", "local_file", str(root / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()) task_dir_1 = root / "task-1" task_dir_2 = root / "task-2" task_dir_1.mkdir(parents=True, exist_ok=True) task_dir_2.mkdir(parents=True, exist_ok=True) + task = Task("task-1", "local_file", str(task_dir_1 / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()) (task_dir_1 / "songs.txt").write_text("00:00:00 Song A — Artist A\n", encoding="utf-8") (task_dir_1 / "songs.json").write_text(json.dumps({"songs": [{"title": "Song A", "artist": "Artist A"}]}), encoding="utf-8") (task_dir_1 / "bvid.txt").write_text("BV1SPLIT111", encoding="utf-8") @@ -65,6 +65,13 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): def list_task_contexts_by_session_key(self, session_key): # noqa: ANN001 return [self.get_task_context("task-1"), self.get_task_context("task-2")] + def get_task(self, task_id): # noqa: ANN001 + mapping = { + "task-1": Task("task-1", "local_file", str(task_dir_1 / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()), + "task-2": Task("task-2", "local_file", str(task_dir_2 / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()), + } + return mapping[task_id] + result = provider.comment( task, { @@ -88,9 +95,9 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): provider = BilibiliTopCommentProvider(bilibili_api=api) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) - task = Task("task-2", "local_file", str(root / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()) task_dir = root / "task-2" task_dir.mkdir(parents=True, exist_ok=True) + task = Task("task-2", "local_file", str(task_dir / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()) (task_dir / "songs.txt").write_text("00:00:00 Song B — Artist B\n", encoding="utf-8") (task_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Song B", "artist": "Artist B"}]}), encoding="utf-8") (task_dir / "bvid.txt").write_text("BV1SPLIT222", encoding="utf-8") @@ -108,6 +115,13 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): def list_task_contexts_by_session_key(self, session_key): # noqa: ANN001 return [self.get_task_context("task-1"), self.get_task_context("task-2")] + def get_task(self, task_id): # noqa: ANN001 + mapping = { + "task-1": Task("task-1", "local_file", str(root / "task-1" / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()), + "task-2": Task("task-2", "local_file", str(task_dir / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()), + } + return mapping[task_id] + result = provider.comment( task, { @@ -128,17 +142,17 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): provider = BilibiliTopCommentProvider(bilibili_api=_FakeBilibiliApi()) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) + work_dir = root / "task-1" + work_dir.mkdir(parents=True, exist_ok=True) task = Task( id="task-1", source_type="local_file", - source_path=str(root / "source.mp4"), + source_path=str(work_dir / "source.mp4"), title="task-1", status="published", created_at=utc_now_iso(), updated_at=utc_now_iso(), ) - work_dir = root / task.title - work_dir.mkdir(parents=True, exist_ok=True) (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song", "artist": "Tester"}]}), encoding="utf-8") (work_dir / "bvid.txt").write_text("BV1COMMENT123", encoding="utf-8") @@ -162,16 +176,52 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): self.assertTrue((work_dir / "comment_full_done.flag").exists()) self.assertTrue((work_dir / "comment_done.flag").exists()) + def test_comment_uses_source_path_parent_when_task_title_differs(self) -> None: + provider = BilibiliTopCommentProvider(bilibili_api=_FakeBilibiliApi()) + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + work_dir = root / "task-id-dir" + work_dir.mkdir(parents=True, exist_ok=True) + task = Task( + id="task-id", + source_type="bilibili_url", + source_path=str(work_dir / "task-id.mp4"), + title="display-title", + status="published", + created_at=utc_now_iso(), + updated_at=utc_now_iso(), + ) + (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") + (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song", "artist": "Tester"}]}), encoding="utf-8") + (work_dir / "bvid.txt").write_text("BV1COMMENT123", encoding="utf-8") + cookies_file = root / "cookies.json" + cookies_file.write_text("{}", encoding="utf-8") + + result = provider.comment( + task, + { + "session_dir": str(root), + "cookies_file": str(cookies_file), + "post_split_comment": True, + "post_full_video_timeline_comment": False, + }, + ) + + self.assertEqual(result["status"], "ok") + self.assertEqual(result["split"]["status"], "skipped") + self.assertEqual(result["split"]["reason"], "comment_disabled") + self.assertTrue((work_dir / "comment_done.flag").exists()) + def test_full_comment_aggregates_session_parts_on_anchor_task(self) -> None: api = _FakeBilibiliApi() provider = BilibiliTopCommentProvider(bilibili_api=api) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) - task = Task("task-1", "local_file", str(root / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()) task_dir_1 = root / "task-1" task_dir_2 = root / "task-2" task_dir_1.mkdir(parents=True, exist_ok=True) task_dir_2.mkdir(parents=True, exist_ok=True) + task = Task("task-1", "local_file", str(task_dir_1 / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()) (task_dir_1 / "songs.txt").write_text("00:00:01 Song A\n00:02:00 Song B\n", encoding="utf-8") (task_dir_1 / "songs.json").write_text(json.dumps({"songs": [{"title": "Song A"}]}), encoding="utf-8") (task_dir_1 / "bvid.txt").write_text("BV1SPLIT111", encoding="utf-8") @@ -191,6 +241,13 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): def list_task_contexts_by_session_key(self, session_key): # noqa: ANN001 return [self.get_task_context("task-1"), self.get_task_context("task-2")] + def get_task(self, task_id): # noqa: ANN001 + mapping = { + "task-1": Task("task-1", "local_file", str(task_dir_1 / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()), + "task-2": Task("task-2", "local_file", str(task_dir_2 / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()), + } + return mapping[task_id] + result = provider.comment( task, { @@ -214,9 +271,9 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): provider = BilibiliTopCommentProvider(bilibili_api=api) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) - task = Task("task-2", "local_file", str(root / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()) task_dir = root / "task-2" task_dir.mkdir(parents=True, exist_ok=True) + task = Task("task-2", "local_file", str(task_dir / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()) (task_dir / "songs.txt").write_text("00:00:03 Song C\n", encoding="utf-8") (task_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Song C"}]}), encoding="utf-8") (task_dir / "bvid.txt").write_text("BV1SPLIT222", encoding="utf-8") @@ -235,6 +292,13 @@ class BilibiliTopCommentProviderTests(unittest.TestCase): def list_task_contexts_by_session_key(self, session_key): # noqa: ANN001 return [self.get_task_context("task-1"), self.get_task_context("task-2")] + def get_task(self, task_id): # noqa: ANN001 + mapping = { + "task-1": Task("task-1", "local_file", str(root / "task-1" / "source-1.mp4"), "task-1", "published", utc_now_iso(), utc_now_iso()), + "task-2": Task("task-2", "local_file", str(task_dir / "source-2.mp4"), "task-2", "published", utc_now_iso(), utc_now_iso()), + } + return mapping[task_id] + result = provider.comment( task, { diff --git a/tests/test_biliup_cli_publish_provider.py b/tests/test_biliup_cli_publish_provider.py index 5ec5588..75f3172 100644 --- a/tests/test_biliup_cli_publish_provider.py +++ b/tests/test_biliup_cli_publish_provider.py @@ -50,22 +50,71 @@ class BiliupCliAdapterTests(unittest.TestCase): class BiliupCliPublishProviderTests(unittest.TestCase): + def test_publish_uses_source_path_parent_when_task_title_differs(self) -> None: + adapter = _FakeBiliupAdapter() + provider = BiliupCliPublishProvider(adapter=adapter) + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + work_dir = root / "task-id-dir" + work_dir.mkdir(parents=True, exist_ok=True) + task = Task( + id="task-id", + source_type="bilibili_url", + source_path=str(work_dir / "task-id.mp4"), + title="display-title", + status="split_done", + created_at=utc_now_iso(), + updated_at=utc_now_iso(), + ) + (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") + (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song"}]}), encoding="utf-8") + upload_config = root / "upload_config.json" + upload_config.write_text("{}", encoding="utf-8") + clip_path = work_dir / "clip-1.mp4" + clip_path.write_text("fake", encoding="utf-8") + clip = Artifact( + id=None, + task_id=task.id, + artifact_type="clip_video", + path=str(clip_path), + metadata_json="{}", + created_at=utc_now_iso(), + ) + + record = provider.publish( + task, + [clip], + { + "session_dir": str(root), + "upload_config_file": str(upload_config), + "biliup_path": "runtime/biliup", + "cookie_file": "runtime/cookies.json", + "retry_count": 1, + "command_timeout_seconds": 123, + }, + ) + + self.assertEqual(record.bvid, "BV1TEST12345") + self.assertEqual(adapter.optional_calls[0]["log_path"], work_dir / "publish.log") + self.assertTrue((work_dir / "bvid.txt").exists()) + self.assertTrue((work_dir / "upload_done.flag").exists()) + def test_publish_passes_timeout_and_log_path(self) -> None: adapter = _FakeBiliupAdapter() provider = BiliupCliPublishProvider(adapter=adapter) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) + work_dir = root / "task-1" + work_dir.mkdir(parents=True, exist_ok=True) task = Task( id="task-1", source_type="local_file", - source_path=str(root / "source.mp4"), + source_path=str(work_dir / "source.mp4"), title="task-1", status="split_done", created_at=utc_now_iso(), updated_at=utc_now_iso(), ) - work_dir = root / task.title - work_dir.mkdir(parents=True, exist_ok=True) (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song"}]}), encoding="utf-8") upload_config = root / "upload_config.json" @@ -117,17 +166,17 @@ class BiliupCliPublishProviderTests(unittest.TestCase): provider = BiliupCliPublishProvider(adapter=adapter) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) + work_dir = root / "task-1" + work_dir.mkdir(parents=True, exist_ok=True) task = Task( id="task-1", source_type="local_file", - source_path=str(root / "source.mp4"), + source_path=str(work_dir / "source.mp4"), title="task-1", status="split_done", created_at=utc_now_iso(), updated_at=utc_now_iso(), ) - work_dir = root / task.title - work_dir.mkdir(parents=True, exist_ok=True) (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song"}]}), encoding="utf-8") (work_dir / "bvid.txt").write_text("BVOLD1234567", encoding="utf-8") @@ -165,17 +214,17 @@ class BiliupCliPublishProviderTests(unittest.TestCase): provider = BiliupCliPublishProvider(adapter=adapter) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) + work_dir = root / "task-1" + work_dir.mkdir(parents=True, exist_ok=True) task = Task( id="task-1", source_type="local_file", - source_path=str(root / "source.mp4"), + source_path=str(work_dir / "source.mp4"), title="task-1", status="split_done", created_at=utc_now_iso(), updated_at=utc_now_iso(), ) - work_dir = root / task.title - work_dir.mkdir(parents=True, exist_ok=True) (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song"}]}), encoding="utf-8") (work_dir / "bvid.txt").write_text("BV1RESUME1234", encoding="utf-8") @@ -225,17 +274,17 @@ class BiliupCliPublishProviderTests(unittest.TestCase): provider = BiliupCliPublishProvider(adapter=adapter) with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) + work_dir = root / "task-1" + work_dir.mkdir(parents=True, exist_ok=True) task = Task( id="task-1", source_type="local_file", - source_path=str(root / "source.mp4"), + source_path=str(work_dir / "source.mp4"), title="task-1", status="split_done", created_at=utc_now_iso(), updated_at=utc_now_iso(), ) - work_dir = root / task.title - work_dir.mkdir(parents=True, exist_ok=True) (work_dir / "songs.txt").write_text("00:00:00 Test Song - Tester\n", encoding="utf-8") (work_dir / "songs.json").write_text(json.dumps({"songs": [{"title": "Test Song"}]}), encoding="utf-8") (work_dir / "bvid.txt").write_text("BV1RESUME1234", encoding="utf-8") diff --git a/tests/test_publish_service.py b/tests/test_publish_service.py index 6cdb30d..840ccac 100644 --- a/tests/test_publish_service.py +++ b/tests/test_publish_service.py @@ -75,8 +75,10 @@ class PublishServiceTests(unittest.TestCase): provider = _FakePublishProvider() with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) - task1 = Task("task-1", "local_file", "/tmp/a.mp4", "task-1", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") - task2 = Task("task-2", "local_file", "/tmp/b.mp4", "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + (root / "task-1").mkdir(parents=True, exist_ok=True) + (root / "task-2").mkdir(parents=True, exist_ok=True) + task1 = Task("task-1", "local_file", str(root / "task-1" / "source.mp4"), "task-1", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + task2 = Task("task-2", "local_file", str(root / "task-2" / "source.mp4"), "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") ctx1 = TaskContext(None, "task-1", "session-1", "s", None, "part-1", "2026-04-04T09:23:00+08:00", None, None, task1.created_at, task1.updated_at) ctx2 = TaskContext(None, "task-2", "session-1", "s", None, "part-2", "2026-04-04T09:25:00+08:00", None, None, task2.created_at, task2.updated_at) artifacts = { @@ -108,8 +110,9 @@ class PublishServiceTests(unittest.TestCase): root = Path(tmpdir) (root / "task-1").mkdir(parents=True, exist_ok=True) (root / "task-1" / "bvid.txt").write_text("BV1SESSION123", encoding="utf-8") - task1 = Task("task-1", "local_file", "/tmp/a.mp4", "task-1", "published", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") - task2 = Task("task-2", "local_file", "/tmp/b.mp4", "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + (root / "task-2").mkdir(parents=True, exist_ok=True) + task1 = Task("task-1", "local_file", str(root / "task-1" / "source.mp4"), "task-1", "published", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + task2 = Task("task-2", "local_file", str(root / "task-2" / "source.mp4"), "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") ctx1 = TaskContext(None, "task-1", "session-1", "s", None, "part-1", "2026-04-04T09:23:00+08:00", None, None, task1.created_at, task1.updated_at) ctx2 = TaskContext(None, "task-2", "session-1", "s", None, "part-2", "2026-04-04T09:25:00+08:00", None, None, task2.created_at, task2.updated_at) repo = _FakeRepo([task1, task2], [ctx1, ctx2], {"task-2": []}) @@ -132,8 +135,8 @@ class PublishServiceTests(unittest.TestCase): (root / "task-2" / "songs.txt").write_text("00:00:00 Song B — Artist B\n", encoding="utf-8") (root / "task-1" / "songs.json").write_text('{"songs":[{"title":"Song A"},{"title":"Song A2"}]}\n', encoding="utf-8") (root / "task-2" / "songs.json").write_text('{"songs":[{"title":"Song B"}]}\n', encoding="utf-8") - task1 = Task("task-1", "local_file", "/tmp/a.mp4", "task-1", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") - task2 = Task("task-2", "local_file", "/tmp/b.mp4", "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + task1 = Task("task-1", "local_file", str(root / "task-1" / "source.mp4"), "task-1", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") + task2 = Task("task-2", "local_file", str(root / "task-2" / "source.mp4"), "task-2", "split_done", "2026-01-01T00:00:00+00:00", "2026-01-01T00:00:00+00:00") ctx1 = TaskContext(None, "task-1", "session-1", "s", None, "part-1", "2026-04-04T09:23:00+08:00", None, None, task1.created_at, task1.updated_at) ctx2 = TaskContext(None, "task-2", "session-1", "s", None, "part-2", "2026-04-04T09:25:00+08:00", None, None, task2.created_at, task2.updated_at) artifacts = {