from __future__ import annotations
|
|
import hashlib
|
import importlib.util
|
import io
|
import json
|
import os
|
import sys
|
import tempfile
|
import unittest
|
from contextlib import redirect_stdout
|
from datetime import datetime
|
from pathlib import Path
|
from unittest import mock
|
|
|
MODULE_PATH = Path(__file__).resolve().parents[1] / "bili_dynamic_collector.py"
|
SPEC = importlib.util.spec_from_file_location("bili_dynamic_collector", MODULE_PATH)
|
assert SPEC and SPEC.loader
|
collector = importlib.util.module_from_spec(SPEC)
|
sys.modules[SPEC.name] = collector
|
SPEC.loader.exec_module(collector)
|
|
FIXTURE = Path(__file__).resolve().parent / "fixtures" / "bili_dynamic_collector" / "dynamic_items.json"
|
NOW = "2026-08-04T12:00:00+08:00"
|
|
|
class BiliDynamicCollectorTests(unittest.TestCase):
|
def setUp(self) -> None:
|
self.temp = tempfile.TemporaryDirectory()
|
self.root = Path(self.temp.name)
|
self.config_path = self.root / "config.json"
|
self.state_dir = self.root / "state"
|
self.download_dir = self.root / "downloads"
|
self.video_dir = self.root / "videos"
|
self.download_dir.mkdir()
|
self.video_dir.mkdir()
|
self.write_json(
|
self.config_path,
|
{
|
"schema_version": 1,
|
"creator": {
|
"name": "青枫浦上Q",
|
"dynamic_url": "https://space.bilibili.com/1420210197/dynamic",
|
},
|
"timezone": "Asia/Shanghai",
|
"window_hours": 72,
|
"minimum_complete_age_seconds": 0,
|
"title_max_length": 48,
|
"allowed_source_hosts": [
|
"space.bilibili.com",
|
"www.bilibili.com",
|
"t.bilibili.com",
|
"b23.tv",
|
],
|
"allowed_video_extensions": [".mp4", ".mkv", ".mov", ".webm"],
|
"native_handoff": {
|
"project_id": "project-info",
|
"source_ai_id": "video-downloader",
|
"source_thread_id": "019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5",
|
"source_role_instance_id": "case_analysis.video_downloader",
|
"target_ai_id": "media-processor",
|
"target_thread_id": "019fb7a4-bdfd-79f2-bd6b-e67e2b7d8efd",
|
"target_role_instance_id": "case_analysis.media_processor",
|
"reply_thread_id": "019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5",
|
},
|
"paths": {
|
"state_dir": "state",
|
"download_dir": "downloads",
|
"video_dir": "videos",
|
},
|
},
|
)
|
|
def tearDown(self) -> None:
|
self.temp.cleanup()
|
|
@staticmethod
|
def write_json(path: Path, value: object) -> None:
|
path.write_text(json.dumps(value, ensure_ascii=False), encoding="utf-8")
|
|
def run_check(self) -> tuple[int, dict[str, object]]:
|
return collector.run(
|
["--config", str(self.config_path), "check", "--input", str(FIXTURE), "--now", NOW]
|
)
|
|
def run_main_json(self, argv: list[str]) -> tuple[int, dict[str, object], str]:
|
stream = io.StringIO()
|
with redirect_stdout(stream):
|
code = collector.main(argv)
|
text = stream.getvalue()
|
return code, json.loads(text), text
|
|
def queue_video(self) -> dict[str, object]:
|
code, result = self.run_check()
|
self.assertEqual(0, code)
|
self.assertEqual("TODO_GENERATED", result["status"])
|
manifest = collector.load_manifest(self.state_dir / "manifest.jsonl")
|
return next(event for event in manifest if event["content_type"] == "video")
|
|
def test_windows_name_is_safe_and_preserves_timestamp(self) -> None:
|
self.assertEqual("_CON", collector.sanitize_windows_component("CON", 48))
|
self.assertEqual("a_b_c", collector.sanitize_windows_component('a<b>c. ', 48))
|
config = collector.load_config(self.config_path)
|
item = collector.normalize_item(
|
{
|
"dynamic_id": "d1",
|
"content_type": "text",
|
"published_at": "2026-08-04T10:00:00+08:00",
|
"title": "午盘: A/B?",
|
"source_url": "https://www.bilibili.com/opus/d1",
|
},
|
config,
|
0,
|
)
|
self.assertEqual("20260804-100000_文字_午盘_ A_B_", collector.suggested_base(item, config))
|
|
def test_check_filters_window_and_is_idempotent(self) -> None:
|
code, first = self.run_check()
|
self.assertEqual(0, code)
|
self.assertEqual("TODO_GENERATED", first["status"])
|
self.assertEqual(2, first["new_items"])
|
self.assertEqual(1, first["skipped_old"])
|
self.assertEqual(1, first["skipped_future"])
|
queue_path = Path(first["queue_path"])
|
self.assertTrue(queue_path.is_file())
|
queue = json.loads(queue_path.read_text(encoding="utf-8"))
|
self.assertEqual(2, len(queue["items"]))
|
self.assertTrue(queue["browser_interaction_required"])
|
self.assertFalse(queue["authentication_data_allowed"])
|
self.assertEqual(2, len(collector.load_manifest(self.state_dir / "manifest.jsonl")))
|
|
code, second = self.run_check()
|
self.assertEqual(0, code)
|
self.assertEqual("NO_NEW_ITEMS", second["status"])
|
self.assertEqual(0, second["new_items"])
|
self.assertIsNone(second["queue_path"])
|
self.assertEqual([queue_path], list((self.state_dir / "queues").glob("*.json")))
|
self.assertEqual(2, len(collector.load_manifest(self.state_dir / "manifest.jsonl")))
|
|
def test_safe_move_hash_manifest_and_handoff_are_idempotent(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "extension-result.mp4"
|
payload = b"small synthetic completed video fixture"
|
source.write_bytes(payload)
|
completed_mtime = datetime.fromisoformat("2026-08-04T11:30:00+08:00").timestamp()
|
os.utime(source, (completed_mtime, completed_mtime))
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{
|
"schema_version": 1,
|
"items": [{"source_file": source.name, "bvid": "BV1TEST00001"}],
|
},
|
)
|
|
code, moved = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(0, code)
|
self.assertEqual("COMPLETE", moved["status"])
|
self.assertFalse(source.exists())
|
target = Path(moved["items"][0]["target"])
|
self.assertEqual(f"{queued['suggested_stem']}.mp4", target.name)
|
self.assertEqual(payload, target.read_bytes())
|
expected_hash = hashlib.sha256(payload).hexdigest()
|
self.assertEqual(expected_hash, moved["items"][0]["sha256"])
|
latest, _ = collector.latest_entities(collector.load_manifest(self.state_dir / "manifest.jsonl"))
|
event = latest[queued["entity_id"]]
|
self.assertEqual("VIDEO_MOVED", event["status"])
|
self.assertEqual("READY_FOR_HANDOFF", event["video_processing_status"])
|
self.assertEqual(expected_hash, event["sha256"])
|
|
manifest_before_rerun = (self.state_dir / "manifest.jsonl").read_bytes()
|
code, moved_again = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(0, code)
|
self.assertEqual("ALREADY_MOVED", moved_again["items"][0]["status"])
|
self.assertEqual(expected_hash, moved_again["items"][0]["sha256"])
|
self.assertEqual(manifest_before_rerun, (self.state_dir / "manifest.jsonl").read_bytes())
|
self.assertEqual(payload, target.read_bytes())
|
|
code, handoff = collector.run(
|
["--config", str(self.config_path), "handoff", "--now", NOW]
|
)
|
self.assertEqual(0, code)
|
self.assertEqual("GENERATED", handoff["write"])
|
draft = Path(handoff["handoff_path"])
|
text = draft.read_text(encoding="utf-8")
|
self.assertIn("message_type=video_processing_request", text)
|
self.assertIn("status=PROCESSING_REQUESTED", text)
|
self.assertRegex(text, r"handoff_id=HANDOFF-BILI-DYNAMIC-VIDEO-PROCESSING-[0-9A-F]{24}")
|
for field in (
|
"source_ai_id=video-downloader",
|
"source_thread_id=019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5",
|
"source_role_instance_id=case_analysis.video_downloader",
|
"target_ai_id=media-processor",
|
"target_thread_id=019fb7a4-bdfd-79f2-bd6b-e67e2b7d8efd",
|
"target_role_instance_id=case_analysis.media_processor",
|
"reply_thread_id=019fcc5d-798f-7ea1-8325-3a4d1f2dc5a5",
|
"scope:",
|
"evidence:",
|
"expected_action:",
|
):
|
self.assertIn(field, text)
|
self.assertNotIn("generated_at=", text)
|
self.assertIn(json.dumps(str(target), ensure_ascii=False), text)
|
self.assertNotIn("cookie", text.lower())
|
|
code, repeated = collector.run(
|
["--config", str(self.config_path), "handoff", "--now", "2026-08-04T13:00:00+08:00"]
|
)
|
self.assertEqual(0, code)
|
self.assertEqual("REUSED", repeated["write"])
|
self.assertEqual(draft, Path(repeated["handoff_path"]))
|
|
target.write_bytes(b"tampered after manifest")
|
config = collector.load_config(self.config_path)
|
with collector.StateLock(config.lock_path):
|
with self.assertRaises(collector.CollectorError) as caught:
|
collector.generate_handoff(config, None, collector.parse_now(NOW))
|
self.assertEqual("E_HANDOFF_HASH", caught.exception.code)
|
|
def test_existing_target_preflight_has_zero_business_side_effects(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "extension-result.mp4"
|
source.write_bytes(b"new download")
|
completed_mtime = datetime.fromisoformat("2026-08-04T11:30:00+08:00").timestamp()
|
os.utime(source, (completed_mtime, completed_mtime))
|
target = self.video_dir / f"{queued['suggested_stem']}.mp4"
|
target.write_bytes(b"existing unrelated file")
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": source.name, "dynamic_id": "dynamic-video-001"}]},
|
)
|
manifest_before = (self.state_dir / "manifest.jsonl").read_bytes()
|
config = collector.load_config(self.config_path)
|
with collector.StateLock(config.lock_path):
|
with self.assertRaisesRegex(collector.CollectorError, "refusing to overwrite") as caught:
|
collector.move_completed(config, mapping, collector.parse_now(NOW))
|
self.assertEqual("E_TARGET_EXISTS", caught.exception.code)
|
self.assertEqual(b"existing unrelated file", target.read_bytes())
|
self.assertEqual(b"new download", source.read_bytes())
|
self.assertEqual(manifest_before, (self.state_dir / "manifest.jsonl").read_bytes())
|
latest, _ = collector.latest_entities(collector.load_manifest(self.state_dir / "manifest.jsonl"))
|
self.assertEqual("TODO_QUEUED", latest[queued["entity_id"]]["status"])
|
|
code, retry = self.run_check()
|
self.assertEqual(0, code)
|
self.assertEqual("NO_NEW_ITEMS", retry["status"])
|
|
def test_incomplete_or_outside_download_is_a_safety_stop(self) -> None:
|
queued = self.queue_video()
|
incomplete = self.download_dir / "clip.part"
|
incomplete.write_bytes(b"partial")
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": incomplete.name, "bvid": queued["bvid"]}]},
|
)
|
code = collector.main(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(3, code)
|
self.assertTrue(incomplete.exists())
|
|
outside = self.root / "outside.mp4"
|
outside.write_bytes(b"outside")
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": str(outside), "bvid": queued["bvid"]}]},
|
)
|
code = collector.main(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(3, code)
|
self.assertTrue(outside.exists())
|
|
def test_exact_selectors_and_batch_uniqueness_fail_before_business_writes(self) -> None:
|
queued = self.queue_video()
|
events = collector.load_manifest(self.state_dir / "manifest.jsonl")
|
text_event = next(event for event in events if event["content_type"] == "text")
|
source = self.download_dir / "one.mp4"
|
source.write_bytes(b"one physical source")
|
completed_mtime = datetime.fromisoformat("2026-08-04T11:30:00+08:00").timestamp()
|
os.utime(source, (completed_mtime, completed_mtime))
|
manifest_path = self.state_dir / "manifest.jsonl"
|
|
cases = [
|
(
|
"unknown supplied selector",
|
[{"source_file": source.name, "bvid": queued["bvid"], "dynamic_id": "unknown-dynamic"}],
|
"E_MAPPING_SELECTOR_UNKNOWN",
|
),
|
(
|
"selectors disagree",
|
[
|
{
|
"source_file": source.name,
|
"bvid": queued["bvid"],
|
"dynamic_id": text_event["dynamic_id"],
|
}
|
],
|
"E_MAPPING_SELECTOR_CONFLICT",
|
),
|
(
|
"entity repeated",
|
[
|
{"source_file": source.name, "bvid": queued["bvid"]},
|
{"source_file": source.name, "dynamic_id": queued["dynamic_id"]},
|
],
|
"E_ENTITY_DUPLICATE",
|
),
|
]
|
for label, items, expected_code in cases:
|
with self.subTest(label=label):
|
mapping = self.root / "mapping.json"
|
self.write_json(mapping, {"schema_version": 1, "items": items})
|
manifest_before = manifest_path.read_bytes()
|
code, payload, _ = self.run_main_json(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(3, code)
|
self.assertEqual(expected_code, payload["error_code"])
|
self.assertEqual(manifest_before, manifest_path.read_bytes())
|
self.assertEqual(b"one physical source", source.read_bytes())
|
self.assertEqual([], list(self.video_dir.iterdir()))
|
|
second = dict(queued)
|
second.update(
|
{
|
"event_id": "second-video-event",
|
"entity_id": "second-video-entity",
|
"dynamic_id": "dynamic-video-002",
|
"bvid": "BV1TEST00002",
|
"source_url": "https://www.bilibili.com/video/BV1TEST00002",
|
"dedupe_keys": [
|
"bvid:bv1test00002",
|
"dynamic:dynamic-video-002",
|
"url:https://www.bilibili.com/video/BV1TEST00002",
|
],
|
"suggested_stem": str(queued["suggested_stem"]) + "_second",
|
}
|
)
|
collector.append_manifest(manifest_path, [second])
|
mapping = self.root / "same-source.json"
|
self.write_json(
|
mapping,
|
{
|
"schema_version": 1,
|
"items": [
|
{"source_file": source.name, "bvid": queued["bvid"]},
|
{"source_file": str(source), "bvid": second["bvid"]},
|
],
|
},
|
)
|
manifest_before = manifest_path.read_bytes()
|
code, payload, _ = self.run_main_json(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(3, code)
|
self.assertEqual("E_SOURCE_DUPLICATE", payload["error_code"])
|
self.assertEqual(manifest_before, manifest_path.read_bytes())
|
self.assertEqual(b"one physical source", source.read_bytes())
|
self.assertEqual([], list(self.video_dir.iterdir()))
|
|
def test_retained_source_warning_is_sticky_with_or_without_source(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "retained.mp4"
|
payload_bytes = b"retained synthetic bytes"
|
source.write_bytes(payload_bytes)
|
completed_mtime = datetime.fromisoformat("2026-08-04T11:30:00+08:00").timestamp()
|
os.utime(source, (completed_mtime, completed_mtime))
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": source.name, "bvid": queued["bvid"]}]},
|
)
|
path_class = type(source)
|
original_unlink = path_class.unlink
|
|
def fail_only_source(path: Path, *args: object, **kwargs: object) -> None:
|
if path == source:
|
raise PermissionError("injected source delete failure")
|
original_unlink(path, *args, **kwargs)
|
|
with mock.patch.object(path_class, "unlink", fail_only_source):
|
code, first = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(4, code)
|
self.assertEqual("VIDEO_MOVED_SOURCE_RETAINED", first["items"][0]["status"])
|
target = Path(first["items"][0]["target"])
|
expected_hash = hashlib.sha256(payload_bytes).hexdigest()
|
self.assertEqual(expected_hash, first["items"][0]["sha256"])
|
self.assertTrue(source.exists())
|
manifest_before = (self.state_dir / "manifest.jsonl").read_bytes()
|
|
code, second = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(4, code)
|
self.assertEqual("VIDEO_MOVED_SOURCE_RETAINED", second["items"][0]["status"])
|
self.assertEqual(first["items"][0]["source_delete_error"], second["items"][0]["source_delete_error"])
|
self.assertEqual(manifest_before, (self.state_dir / "manifest.jsonl").read_bytes())
|
self.assertEqual(payload_bytes, target.read_bytes())
|
|
source.unlink()
|
code, third = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(4, code)
|
self.assertEqual("VIDEO_MOVED_SOURCE_RETAINED", third["items"][0]["status"])
|
self.assertEqual(expected_hash, third["items"][0]["sha256"])
|
self.assertEqual(first["items"][0]["source_delete_error"], third["items"][0]["source_delete_error"])
|
self.assertEqual(manifest_before, (self.state_dir / "manifest.jsonl").read_bytes())
|
self.assertEqual(payload_bytes, target.read_bytes())
|
|
def test_disallowed_latest_statuses_fail_before_stage_b(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "sentinel.mp4"
|
source.write_bytes(b"sentinel source")
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": source.name, "bvid": queued["bvid"]}]},
|
)
|
manifest_path = self.state_dir / "manifest.jsonl"
|
status_cases: list[object] = [
|
"PROCESSING_HANDOFF_CONFIRMED",
|
"PROCESSING",
|
"PROCESSING_FAILED",
|
None,
|
"",
|
"UNKNOWN_STATE",
|
]
|
for status in status_cases:
|
with self.subTest(status=status):
|
event = dict(queued)
|
if status is None:
|
event.pop("status", None)
|
else:
|
event["status"] = status
|
manifest_path.write_bytes(collector.canonical_json_bytes(event))
|
before = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
code, payload, _ = self.run_main_json(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
after = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
self.assertEqual(3, code)
|
self.assertEqual("SAFETY_STOP", payload["status"])
|
self.assertEqual("E_STATUS", payload["error_code"])
|
self.assertEqual(before, after)
|
self.assertEqual([], list(self.video_dir.iterdir()))
|
self.assertFalse((self.state_dir / ".collector.lock").exists())
|
|
def test_manifest_secret_gate_is_shared_by_all_commands(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "secret-check.mp4"
|
source.write_bytes(b"source remains")
|
mapping = self.root / "mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": source.name, "bvid": queued["bvid"]}]},
|
)
|
manifest_path = self.state_dir / "manifest.jsonl"
|
base_event = dict(queued)
|
commands = [
|
["check", "--input", str(FIXTURE), "--now", NOW],
|
["move-completed", "--mapping", str(mapping), "--now", NOW],
|
["handoff", "--now", NOW],
|
]
|
for index, command in enumerate(commands):
|
with self.subTest(command=command[0]):
|
event = dict(base_event)
|
if index == 0:
|
event["cookie"] = "forbidden-manifest-value"
|
else:
|
event["nested"] = {"token": "forbidden-manifest-value"}
|
manifest_path.write_bytes(collector.canonical_json_bytes(event))
|
before = manifest_path.read_bytes()
|
code, payload, stdout = self.run_main_json(["--config", str(self.config_path), *command])
|
self.assertEqual(3, code)
|
self.assertEqual("E_SECRET_FIELD", payload["error_code"])
|
self.assertNotIn("forbidden-manifest-value", stdout)
|
self.assertEqual(before, manifest_path.read_bytes())
|
self.assertEqual(b"source remains", source.read_bytes())
|
self.assertEqual([], list(self.video_dir.iterdir()))
|
|
def test_terminal_projection_rejects_missing_blank_or_malformed_evidence(self) -> None:
|
queued = self.queue_video()
|
mapping = self.root / "terminal-mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": "source-is-absent.mp4", "bvid": queued["bvid"]}]},
|
)
|
manifest_path = self.state_dir / "manifest.jsonl"
|
valid_target = str(self.video_dir / "persisted-target.mp4")
|
cases = [
|
("moved missing local", "VIDEO_MOVED", "local_file", None, True),
|
("moved blank local", "VIDEO_MOVED", "local_file", " ", False),
|
("moved control local", "VIDEO_MOVED", "local_file", "bad\npath", False),
|
("moved missing sha", "VIDEO_MOVED", "sha256", None, True),
|
("moved blank sha", "VIDEO_MOVED", "sha256", "", False),
|
("moved malformed sha", "VIDEO_MOVED", "sha256", "A" * 64, False),
|
("retained missing reason", "VIDEO_MOVED_SOURCE_RETAINED", "failure_reason", None, True),
|
("retained blank reason", "VIDEO_MOVED_SOURCE_RETAINED", "failure_reason", " ", False),
|
("retained control reason", "VIDEO_MOVED_SOURCE_RETAINED", "failure_reason", "bad\rreason", False),
|
]
|
for label, status, field, value, remove_field in cases:
|
with self.subTest(label=label):
|
event = dict(queued)
|
event.update(
|
{
|
"status": status,
|
"local_file": valid_target,
|
"sha256": "a" * 64,
|
"failure_reason": "PermissionError: retained source",
|
"video_processing_status": "READY_FOR_HANDOFF",
|
}
|
)
|
if remove_field:
|
event.pop(field, None)
|
else:
|
event[field] = value
|
manifest_path.write_bytes(collector.canonical_json_bytes(event))
|
before = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
code, payload, stdout = self.run_main_json(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
after = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
self.assertEqual(3, code)
|
self.assertEqual("SAFETY_STOP", payload["status"])
|
self.assertEqual("E_MANIFEST", payload["error_code"])
|
self.assertEqual(before, after)
|
self.assertNotIn("bad\npath", stdout)
|
self.assertNotIn("bad\rreason", stdout)
|
if isinstance(value, str) and value.strip():
|
self.assertNotIn(value, stdout)
|
self.assertEqual([], list(self.video_dir.iterdir()))
|
self.assertFalse((self.state_dir / ".collector.lock").exists())
|
|
def test_handoff_rejects_control_or_invalid_manifest_scalars_without_writes(self) -> None:
|
queued = self.queue_video()
|
source = self.download_dir / "handoff-source.mp4"
|
source_bytes = b"handoff scalar validation bytes"
|
source.write_bytes(source_bytes)
|
completed_mtime = datetime.fromisoformat("2026-08-04T11:30:00+08:00").timestamp()
|
os.utime(source, (completed_mtime, completed_mtime))
|
mapping = self.root / "handoff-mapping.json"
|
self.write_json(
|
mapping,
|
{"schema_version": 1, "items": [{"source_file": source.name, "bvid": queued["bvid"]}]},
|
)
|
code, moved = collector.run(
|
["--config", str(self.config_path), "move-completed", "--mapping", str(mapping), "--now", NOW]
|
)
|
self.assertEqual(0, code)
|
target = Path(moved["items"][0]["target"])
|
latest, _ = collector.latest_entities(collector.load_manifest(self.state_dir / "manifest.jsonl"))
|
moved_event = dict(latest[queued["entity_id"]])
|
manifest_path = self.state_dir / "manifest.jsonl"
|
cases = [
|
("entity newline", "entity_id", str(moved_event["entity_id"]) + "\nstatus=FORGED"),
|
("entity format", "entity_id", "not-an-internal-id"),
|
("bvid newline", "bvid", str(moved_event["bvid"]) + "\r\nstatus=FORGED"),
|
("bvid format", "bvid", "NOT-A-BVID"),
|
("published newline", "published_at", str(moved_event["published_at"]) + "\nstatus=FORGED"),
|
("published noncanonical", "published_at", "2026-08-04T03:00:00+00:00"),
|
("title newline", "title", str(moved_event["title"]) + "\nstatus=FORGED"),
|
("source url newline", "source_url", str(moved_event["source_url"]) + "\nstatus=FORGED"),
|
("local file newline", "local_file", str(moved_event["local_file"]) + "\nstatus=FORGED"),
|
("sha newline", "sha256", str(moved_event["sha256"]) + "\nstatus=FORGED"),
|
]
|
for label, field, value in cases:
|
with self.subTest(label=label):
|
event = dict(moved_event)
|
event[field] = value
|
manifest_path.write_bytes(collector.canonical_json_bytes(event))
|
before = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
code, payload, stdout = self.run_main_json(
|
["--config", str(self.config_path), "handoff", "--now", NOW]
|
)
|
after = {
|
str(path.relative_to(self.root)): path.read_bytes()
|
for path in self.root.rglob("*")
|
if path.is_file()
|
}
|
self.assertEqual(3, code)
|
self.assertEqual("SAFETY_STOP", payload["status"])
|
self.assertEqual("E_MANIFEST", payload["error_code"])
|
self.assertNotIn("status=FORGED", stdout)
|
self.assertNotIn(value, stdout)
|
self.assertEqual(before, after)
|
self.assertEqual(source_bytes, target.read_bytes())
|
self.assertFalse((self.state_dir / ".collector.lock").exists())
|
|
def test_native_handoff_route_is_required_and_validated_before_state_write(self) -> None:
|
base = json.loads(self.config_path.read_text(encoding="utf-8"))
|
for label, mutate in (
|
("missing field", lambda route: route.pop("target_thread_id")),
|
("invalid thread", lambda route: route.__setitem__("target_thread_id", "NOT-A-THREAD")),
|
("envelope injection", lambda route: route.__setitem__("target_ai_id", "media-processor\nstatus=FORGED")),
|
):
|
with self.subTest(label=label):
|
invalid = json.loads(json.dumps(base))
|
mutate(invalid["native_handoff"])
|
config = self.root / f"invalid-{label}.json"
|
self.write_json(config, invalid)
|
self.assertFalse(self.state_dir.exists())
|
code, payload, _ = self.run_main_json(
|
["--config", str(config), "check", "--input", str(FIXTURE), "--now", NOW]
|
)
|
self.assertEqual(2, code)
|
self.assertEqual("E_CONFIG", payload["error_code"])
|
self.assertFalse(self.state_dir.exists())
|
|
def test_secret_fields_are_rejected_and_not_persisted(self) -> None:
|
exported = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
exported["cookie"] = "forbidden-value"
|
bad = self.root / "bad.json"
|
self.write_json(bad, exported)
|
code = collector.main(
|
["--config", str(self.config_path), "check", "--input", str(bad), "--now", NOW]
|
)
|
self.assertEqual(3, code)
|
self.assertFalse((self.state_dir / "manifest.jsonl").exists())
|
|
|
if __name__ == "__main__":
|
unittest.main()
|