"""Phase 4 自檢:沙盒植入五種 source_ref 岔開型別(hash 不符、路徑正規化、 檔名 hash 後綴岔開、檔在磁碟未登記、完全無對應)+一個正常解得開的 ref, 驗證 diag_refs 逐一分類正確、報告涵蓋各型別、結束碼 1、且不修改任何檔案。 另驗全部解得開時結束碼 0。 執行:.venv/Scripts/python tools/selfcheck_diag_refs.py """ import io import json import pathlib import shutil import sys import tempfile from contextlib import redirect_stdout sys.path.insert(0, str(pathlib.Path(__file__).parent)) import kb import diag_refs SHA_A = "abcd1234" + "0" * 56 SHA_B = "beef5678" + "0" * 56 # 帳本 key 一律 posix 正斜線+中文/連字號檔名(真實 convert 產出的形狀) K_OK = "raw/converted/www-pluspay-faq-06b02fd9.md" K_STEM = "raw/converted/www-pluspay-terms-11112222.md" # 同幹、不同 hash 後綴 FILES = { K_OK: {"kind": "converted", "sha256": SHA_A, "original_path": "raw/originals/faq.html", "original_sha256": SHA_A, "converter": "from_web", "converted_at": "2026-07-01T00:00:00"}, K_STEM: {"kind": "converted", "sha256": SHA_B, "original_path": "raw/originals/terms.html", "original_sha256": SHA_B, "converter": "from_web", "converted_at": "2026-07-01T00:00:00"}, } # ref → 預期分類。unregistered 的檔會實際落地;absent 的不落地。 CASES = { "resolved": f"{K_OK}#abcd1234", "hash-mismatch": f"{K_OK}#deadbeef", "normalize": r"raw\converted\www-pluspay-faq-06b02fd9.md#abcd1234", # 反斜線 "stem": "raw/converted/www-pluspay-terms-99998888.md#cccccccc", # 同幹、異後綴、非 key "unregistered": "raw/converted/www-pluspay-instructions-77776666.md#77777777", "absent": "raw/converted/www-pluspay-ghost-00001111.md#cafebabe", } def unit(): """classify_ref 純函式逐型別驗證。""" on_disk = {"raw/converted/www-pluspay-instructions-77776666.md"} exists = lambda p: p in on_disk for expect, ref in CASES.items(): cat, detail = diag_refs.classify_ref(ref, FILES, exists) assert cat == expect, f"{ref!r} 應分類為 {expect},實得 {cat}({detail})" print("PASS: classify_ref 六型別(含 resolved)分類正確") def build(root): for sub in ("raw/converted", "wiki/summaries", "wiki/entities", "wiki/concepts"): (root / sub).mkdir(parents=True) (root / "raw/manifest.json").write_text( json.dumps({"version": 1, "files": FILES}), encoding="utf-8") # unregistered 的檔要真的存在於磁碟,才會被判為「檔在、帳本無」而非 absent (root / "raw/converted/www-pluspay-instructions-77776666.md").write_text( "# 未登記\n", encoding="utf-8") today = "2026-07-01" for i, (name, ref) in enumerate(CASES.items()): meta = {"type": "entity", "title": f"頁{name}", "description": "測試頁", "tags": ["t"], "timestamp": today, "sources": [ref], "status": "draft"} (root / f"wiki/entities/p{i}.md").write_text(kb.dump_page(meta, "內文。"), encoding="utf-8") def run(argv): buf = io.StringIO() code = 0 try: with redirect_stdout(buf): diag_refs.main(argv) except SystemExit as e: code = e.code return code, buf.getvalue() def main(): unit() tmp = pathlib.Path(tempfile.mkdtemp(prefix="ppqa-diag-")) try: root = tmp / "repo" build(root) before = {p: p.read_bytes() for p in root.rglob("*") if p.is_file()} code, out = run(["--root", str(root)]) assert code == 1, f"有岔開應以結束碼 1,實得 {code}\n{out}" for cat in ("hash-mismatch", "normalize", "stem", "unregistered", "absent"): assert diag_refs.CAT_TITLE[cat].split("(")[0] in out, f"報告缺型別 {cat}\n{out}" assert "對不上:5" in out, out after = {p: p.read_bytes() for p in root.rglob("*") if p.is_file()} assert before == after, "diag_refs 修改了檔案!" print("PASS: 整合——五型別全報、結束碼 1、零寫檔") # 全部解得開 → 結束碼 0 good = {"type": "entity", "title": "好頁", "description": "d", "tags": ["t"], "timestamp": "2026-07-01", "sources": [f"{K_OK}#abcd1234"], "status": "draft"} for p in (root / "wiki/entities").glob("*.md"): p.unlink() (root / "wiki/entities/ok.md").write_text(kb.dump_page(good, "內文。"), encoding="utf-8") code, out = run(["--root", str(root)]) assert code == 0, f"全部解得開應以結束碼 0,實得 {code}\n{out}" assert "全部解得開" in out, out print("PASS: 全部解得開時結束碼 0") print("ALL PASS") finally: shutil.rmtree(tmp, ignore_errors=True) if __name__ == "__main__": main()