#!/usr/bin/env python3 """参考作品只读适配器的纯数据组装测试。""" from __future__ import annotations import json import inspect import pathlib import sys import unittest from unittest.mock import patch PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3] SCRIPT_DIR = PROJECT_ROOT / ".claude" / "skills" / "freeze-context" / "scripts" sys.path.insert(0, str(SCRIPT_DIR)) import load_reference_work as loader # noqa: E402 from load_reference_work import ( # noqa: E402 AdapterError, build_replay_config, project_card, ) FILE_HASH = "02cf1f8c1ca03c26e0b839d88fe536e83c0af20fd8972235b7aedca6a33becf4" SOURCE_HASH = f"sha256:{FILE_HASH}" SOURCE_VERSION = f"raw-file-v1:{SOURCE_HASH}" WORK = { "id": 8, "title": "深空之影", "revision": 12, "chapter_count": 594, "parse_status": "pending", } REFERENCE = { "declared_chapter_count": 595, "imported_chapter_count": 594, "parse_scope": {"from": 1, "to": 594}, "parse_status": "parsing", "source_file": "深空之影_远瞳.txt", "deleted": False, } IMPORT_TASK = { "id": 19, "status": "succeeded", "command_id": f"import-{FILE_HASH[:16]}", "source_snapshot": {"file": REFERENCE["source_file"]}, "deleted": False, } DOCUMENT = { "id": 7, "file_name": REFERENCE["source_file"], "file_hash": FILE_HASH, "deleted": False, } AUTHORIZATION_ROW = { "id": 1, "snapshot_version": "auth-work-8-v1", "source_hash": SOURCE_HASH, "source_version": SOURCE_VERSION, "copyright_status": "research_only", "source_status": "active", "allowed_purpose": ["offline_evaluation"], "forbidden_purpose": ["external_distribution"], "authorization_basis": "user_authorization", "authorized_by": "user:1", "display_summary": "用户授权仅用于内部离线评测", "checked_at": "2026-07-19T00:00:00Z", "expires_at": None, "revalidation_at": "2099-07-19T00:00:00Z", } def card_row(card_id=11126, name="苏铭"): return { "id": card_id, "status": "pending", "source_type": "upgrade_book", "source_id": 8, "revision": 3, "update_time": "2026-07-19T00:00:00+00:00", "draft_payload": { "type": "character", "名称": name, "别名": [f"{name}别名"], "出场章": [1, 430, 489, 550], "字段": { "演变历程": [ {"章": 420, "台阶": "历史台阶"}, {"章": 489, "台阶": "未来台阶"}, ], "演变概括": "全书终态摘要,不应透传", "境界与能力": "终局能力,不应透传", "说话方式": "可选静态字段", }, "一句话摘要": "终局摘要,不应透传", }, } class FakeQueryResult: """提供 psycopg 查询结果所需的最小 fetch 接口。""" def __init__(self, rows): self.rows = rows if isinstance(rows, list) else [rows] def fetchone(self): return self.rows[0] if self.rows else None def fetchall(self): return self.rows class FakeReadOnlyConnection: """模拟只读查询,并按 SQL 中的原文件条件过滤导入任务。""" def __init__(self, import_tasks): self.import_tasks = import_tasks def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): return False def execute(self, query, params=None): sql = " ".join(query.split()) if sql.startswith("SET TRANSACTION"): return FakeQueryResult([]) if "FROM muse_content_work" in sql: return FakeQueryResult(WORK) if "FROM example_reference_work" in sql: return FakeQueryResult([REFERENCE]) if "FROM muse_content_import_task" in sql: rows = self.import_tasks if "source_snapshot->>'file' = %s" in sql: source_file = params[-1] rows = [ row for row in rows if row.get("source_snapshot", {}).get("file") == source_file ] return FakeQueryResult(rows) if "FROM muse_knowledge_document" in sql: return FakeQueryResult([DOCUMENT]) if "FROM example_reference_authorization_snapshot" in sql: return FakeQueryResult(AUTHORIZATION_ROW) if "FROM example_parse_outline" in sql: return FakeQueryResult([]) if "FROM example_parse_scaffold" in sql and "ch.order_no<=%s" in sql: return FakeQueryResult([]) if "FROM example_parse_scaffold" in sql and "ch.order_no=%s" in sql: return FakeQueryResult( {"id": 11, "chapter": 489, "title": "目标", "outline_text": "目标事实"} ) if "FROM muse_knowledge_draft" in sql: return FakeQueryResult([card_row(), card_row(11127, "赵宁")]) raise AssertionError(f"未处理 SQL: {sql}") class LoadReferenceWorkTest(unittest.TestCase): def setUp(self): self.source = loader.validate_source_records([REFERENCE], [IMPORT_TASK], [DOCUMENT]) self.authorization = loader.project_authorization(AUTHORIZATION_ROW, self.source) def test_window_freeze_uses_absolute_bounds_and_excludes_target_window(self): config = build_replay_config( work=WORK, reference=REFERENCE, outline_rows=[ {"id": 1, "window_no": 20, "from_order": 401, "to_order": 429, "outline_text": "历史窗", "check_status": "revised"}, {"id": 2, "window_no": 19, "from_order": 430, "to_order": 438, "outline_text": "目标后窗", "check_status": "revised"}, ], scaffold_rows=[ {"id": 10, "chapter": 428, "title": "前章", "outline_text": "前情", "entities": None, "pattern_hints": []}, ], target_scaffold={"id": 11, "chapter": 430, "title": "目标", "outline_text": "目标章事实"}, card_rows=[card_row(), card_row(11127, "赵宁")], card_selection={"correctCardIds": [11126], "placeboCardIds": [11127]}, as_of=429, target=430, evaluation_set_version="set-test", strategy_version="strategy-test", source=self.source, authorization=self.authorization, ) windows = config["snapshot"]["data"]["outlineWindows"] self.assertEqual([item["from_order"] for item in windows], [401]) public = json.dumps( { "snapshot": config["snapshot"], "commonContext": config["commonContext"], "arms": config["arms"], "sources": config["sources"], }, ensure_ascii=False, ) self.assertNotIn("目标章事实", public) def test_card_is_eval_draft_and_history_is_frozen(self): result = project_card(card_row(), as_of=488, source_version=SOURCE_VERSION) encoded = json.dumps(result, ensure_ascii=False) self.assertEqual(result["evaluationStatus"], "eval_draft") self.assertFalse(result["productionRetrievalEligible"]) self.assertEqual([item["章"] for item in result["milestones"]], [420]) self.assertNotIn("终局摘要", encoded) self.assertNotIn("终局能力", encoded) self.assertEqual(result["source"]["sourceId"], "eval-draft:11126") self.assertEqual(result["source"]["sourceVersion"], SOURCE_VERSION) def test_missing_history_fails_closed_instead_of_using_static_card_fields(self): row = card_row() row["draft_payload"]["字段"].pop("演变历程") with self.assertRaises(AdapterError): project_card(row, as_of=488, source_version=SOURCE_VERSION) def test_sources_have_source_id_and_version(self): config = build_replay_config( work=WORK, reference=REFERENCE, outline_rows=[{"id": 1, "from_order": 1, "to_order": 10, "outline_text": "历史窗"}], scaffold_rows=[], target_scaffold={"id": 11, "chapter": 489, "title": "目标", "outline_text": "目标事实"}, card_rows=[card_row(), card_row(11127, "赵宁")], card_selection={"correctCardIds": [11126], "placeboCardIds": [11127]}, as_of=488, target=489, evaluation_set_version="set-test", strategy_version="strategy-test", source=self.source, authorization=self.authorization, ) self.assertTrue(config["sources"]) self.assertTrue(all(item["sourceId"] and item["sourceVersion"] for item in config["sources"])) self.assertEqual(config["referenceWork"]["version"], SOURCE_VERSION) self.assertEqual(config["authorization"], self.authorization) def test_source_records_normalize_original_file_version(self): self.assertEqual(self.source["documentId"], "7") self.assertEqual(self.source["fileName"], REFERENCE["source_file"]) self.assertEqual(self.source["sourceHash"], SOURCE_HASH) self.assertEqual(self.source["sourceVersion"], SOURCE_VERSION) def test_missing_source_record_fails_closed(self): for references, tasks, documents in ( ([], [IMPORT_TASK], [DOCUMENT]), ([REFERENCE], [], [DOCUMENT]), ([REFERENCE], [IMPORT_TASK], []), ): with self.subTest(references=len(references), tasks=len(tasks), documents=len(documents)): with self.assertRaises(AdapterError): loader.validate_source_records(references, tasks, documents) def test_duplicate_source_record_fails_closed(self): for references, tasks, documents in ( ([REFERENCE, REFERENCE], [IMPORT_TASK], [DOCUMENT]), ([REFERENCE], [IMPORT_TASK, IMPORT_TASK], [DOCUMENT]), ([REFERENCE], [IMPORT_TASK], [DOCUMENT, DOCUMENT]), ): with self.subTest(references=len(references), tasks=len(tasks), documents=len(documents)): with self.assertRaises(AdapterError): loader.validate_source_records(references, tasks, documents) def test_import_task_query_ignores_successful_task_for_other_source_file(self): unrelated = { **IMPORT_TASK, "id": 20, "source_snapshot": {"file": "无关作品.txt"}, } connection = FakeReadOnlyConnection([IMPORT_TASK, unrelated]) with patch.object(loader.psycopg, "connect", return_value=connection): rows = loader.load_reference_rows( dsn="postgresql://unused", tenant_id=1, work_id=8, as_of=488, target=489, card_selection={"correctCardIds": [11126], "placeboCardIds": [11127]}, ) self.assertEqual(rows["source"]["importTaskId"], "19") def test_import_task_query_blocks_two_tasks_for_same_source_file(self): duplicate = {**IMPORT_TASK, "id": 20} connection = FakeReadOnlyConnection([IMPORT_TASK, duplicate]) with patch.object(loader.psycopg, "connect", return_value=connection): with self.assertRaises(AdapterError): loader.load_reference_rows( dsn="postgresql://unused", tenant_id=1, work_id=8, as_of=488, target=489, card_selection={"correctCardIds": [11126], "placeboCardIds": [11127]}, ) def test_invalid_hash_fails_closed(self): with self.assertRaises(AdapterError): loader.validate_source_records([REFERENCE], [IMPORT_TASK], [{**DOCUMENT, "file_hash": "xyz"}]) def test_filename_mismatch_fails_closed(self): bad_task = {**IMPORT_TASK, "source_snapshot": {"file": "别的文件.txt"}} with self.assertRaises(AdapterError): loader.validate_source_records([REFERENCE], [bad_task], [DOCUMENT]) with self.assertRaises(AdapterError): loader.validate_source_records([REFERENCE], [IMPORT_TASK], [{**DOCUMENT, "file_name": "别的文件.txt"}]) def test_command_prefix_mismatch_fails_closed(self): with self.assertRaises(AdapterError): loader.validate_source_records( [REFERENCE], [{**IMPORT_TASK, "command_id": "import-0000000000000000"}], [DOCUMENT], ) def test_soft_deleted_source_record_fails_closed(self): for references, tasks, documents in ( ([{**REFERENCE, "deleted": True}], [IMPORT_TASK], [DOCUMENT]), ([REFERENCE], [{**IMPORT_TASK, "deleted": True}], [DOCUMENT]), ([REFERENCE], [IMPORT_TASK], [{**DOCUMENT, "deleted": True}]), ): with self.subTest(references=references, tasks=tasks, documents=documents): with self.assertRaises(AdapterError): loader.validate_source_records(references, tasks, documents) def test_authorization_is_projected_or_kept_blocked(self): self.assertEqual(self.authorization["copyrightStatus"], "research_only") self.assertEqual(self.authorization["sourceHash"], SOURCE_HASH) self.assertEqual(self.authorization["authorizationSnapshot"]["sourceVersion"], SOURCE_VERSION) blocked = loader.project_authorization(None, self.source) self.assertEqual(blocked["sourceStatus"], "missing_authorization_snapshot") self.assertEqual(blocked["allowedPurpose"], []) self.assertEqual(blocked["authorizationSnapshot"], {}) def test_work_revision_change_does_not_change_source_version(self): changed_work = {**WORK, "revision": 999} config = build_replay_config( work=changed_work, reference={**REFERENCE, "imported_chapter_count": 593}, outline_rows=[{"id": 1, "from_order": 1, "to_order": 10, "outline_text": "历史窗"}], scaffold_rows=[], target_scaffold={"id": 11, "chapter": 489, "title": "目标", "outline_text": "目标事实"}, card_rows=[card_row(), card_row(11127, "赵宁")], card_selection={"correctCardIds": [11126], "placeboCardIds": [11127]}, as_of=488, target=489, evaluation_set_version="set-test", strategy_version="strategy-test", source=self.source, authorization=self.authorization, ) self.assertEqual(config["referenceWork"]["version"], SOURCE_VERSION) def test_database_transaction_is_repeatable_read_only(self): source = inspect.getsource(loader.load_reference_rows) helper = inspect.getsource(loader.begin_read_snapshot) self.assertIn("begin_read_snapshot(conn)", source) self.assertIn("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY", helper) self.assertIn("ORDER BY checked_at DESC,id DESC\n LIMIT 1", source) if __name__ == "__main__": unittest.main()