from __future__ import annotations import tempfile import unittest from pathlib import Path from types import SimpleNamespace from unittest.mock import patch import subprocess from biliup_next.infra.adapters.full_video_locator import fetch_biliup_list from biliup_next.infra.video_links import link_context_for_task class VideoLinksTests(unittest.TestCase): def test_fetch_biliup_list_keeps_pubing_videos(self) -> None: output = ( "2026-04-22 15:56:43 INFO biliup_cli::uploader: user: test\n" "BVREVIEW\t王海颖唱歌录播 04月22日 15时56分\t审核中\n" "BVPUB\t王海颖唱歌录播 04月20日 22时08分\t开放浏览\n" "BVPRIVATE\t私密视频\t仅自己可见\n" ) with patch( "biliup_next.infra.adapters.full_video_locator.subprocess.run", return_value=subprocess.CompletedProcess(["biliup"], 0, stdout=output, stderr=""), ): videos = fetch_biliup_list({"biliup_path": "biliup", "cookie_file": "cookies.json"}, max_pages=1) self.assertEqual( videos, [ {"bvid": "BVREVIEW", "title": "王海颖唱歌录播 04月22日 15时56分"}, {"bvid": "BVPUB", "title": "王海颖唱歌录播 04月20日 22时08分"}, ], ) def test_previous_live_falls_back_to_biliup_list(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: source_path = Path(tmpdir) / "source.mp4" source_path.write_bytes(b"") task = SimpleNamespace( id="task-current", title="王海颖唱歌录播 04月19日 22时10分", source_path=str(source_path), ) repo = SimpleNamespace(get_task_context=lambda task_id: None) settings = {"biliup_path": "biliup", "cookie_file": "cookies.json"} with patch( "biliup_next.infra.video_links.fetch_biliup_list", return_value=[ {"bvid": "BVPURE", "title": "【王海颖 (歌曲纯享版)】 04月18日 22时06分 共10首歌"}, {"bvid": "BVNEWER", "title": "王海颖唱歌录播 04月20日 22时00分"}, {"bvid": "BVPREV", "title": "王海颖唱歌录播 04月18日 22时06分"}, {"bvid": "BVOLDER", "title": "王海颖唱歌录播 04月17日 22时00分"}, ], ): context = link_context_for_task(task, repo, settings) self.assertEqual(context["previous_full_video_bvid"], "BVPREV") self.assertEqual(context["previous_full_video_link"], "https://www.bilibili.com/video/BVPREV") self.assertEqual(context["previous_pure_video_bvid"], "BVPURE") self.assertEqual(context["previous_pure_video_link"], "https://www.bilibili.com/video/BVPURE") def test_previous_live_merges_repo_and_biliup_list_links(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) current_path = root / "current" / "source.mp4" previous_path = root / "previous" / "source.mp4" current_path.parent.mkdir() previous_path.parent.mkdir() current_path.write_bytes(b"") previous_path.write_bytes(b"") (previous_path.parent / "full_video_bvid.txt").write_text("BVLOCALFULL", encoding="utf-8") task = SimpleNamespace( id="task-current", title="王海颖唱歌录播 04月19日 22时10分", source_path=str(current_path), ) previous_task = SimpleNamespace( id="task-previous", title="王海颖唱歌录播 04月18日 22时06分", source_path=str(previous_path), ) current_context = SimpleNamespace( task_id=task.id, streamer="王海颖", session_key="王海颖-0419", segment_started_at="2026-04-19T22:10:00", ) previous_context = SimpleNamespace( task_id=previous_task.id, streamer="王海颖", session_key="王海颖-0418", segment_started_at="2026-04-18T22:06:00", full_video_bvid="BVLOCALFULL", ) tasks = {task.id: task, previous_task.id: previous_task} contexts = {task.id: current_context, previous_task.id: previous_context} repo = SimpleNamespace( get_task_context=lambda task_id: contexts.get(task_id), get_task=lambda task_id: tasks.get(task_id), find_recent_task_contexts=lambda streamer, limit=50: [current_context, previous_context], ) settings = {"biliup_path": "biliup", "cookie_file": "cookies.json"} with patch( "biliup_next.infra.video_links.fetch_biliup_list", return_value=[ {"bvid": "BVPURE", "title": "【王海颖(歌曲纯享版)】04月18日 22时06分 共18首歌"}, ], ): context = link_context_for_task(task, repo, settings) self.assertEqual(context["previous_full_video_bvid"], "BVLOCALFULL") self.assertEqual(context["previous_full_video_link"], "https://www.bilibili.com/video/BVLOCALFULL") self.assertEqual(context["previous_pure_video_bvid"], "BVPURE") self.assertEqual(context["previous_pure_video_link"], "https://www.bilibili.com/video/BVPURE") def test_previous_live_biliup_list_handles_year_boundary(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: source_path = Path(tmpdir) / "source.mp4" source_path.write_bytes(b"") task = SimpleNamespace( id="task-current", title="王海颖唱歌录播 01月01日 22时10分", source_path=str(source_path), ) repo = SimpleNamespace(get_task_context=lambda task_id: None) settings = {"biliup_path": "biliup", "cookie_file": "cookies.json"} with patch( "biliup_next.infra.video_links.fetch_biliup_list", return_value=[ {"bvid": "BVPREV", "title": "王海颖唱歌录播 12月31日 22时06分"}, ], ): context = link_context_for_task(task, repo, settings) self.assertEqual(context["previous_full_video_bvid"], "BVPREV") def test_current_full_video_falls_back_to_biliup_list(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: source_path = Path(tmpdir) / "source.mp4" source_path.write_bytes(b"") task = SimpleNamespace( id="task-current", title="王海颖唱歌录播 04月22日 15时56分", source_path=str(source_path), ) repo = SimpleNamespace(get_task_context=lambda task_id: None) settings = {"biliup_path": "biliup", "cookie_file": "cookies.json"} with patch( "biliup_next.infra.adapters.full_video_locator.fetch_biliup_list", return_value=[ {"bvid": "BVFULL", "title": "王海颖唱歌录播 04月22日 15时56分"}, {"bvid": "BVPURE", "title": "【王海颖 (歌曲纯享版)】 04月22日 15时56分 共20首歌"}, ], ): context = link_context_for_task(task, repo, settings) self.assertEqual(context["current_full_video_bvid"], "BVFULL") self.assertEqual(context["current_full_video_link"], "https://www.bilibili.com/video/BVFULL") self.assertEqual((source_path.parent / "full_video_bvid.txt").read_text(encoding="utf-8"), "BVFULL") if __name__ == "__main__": unittest.main()