Files
document-haness/tests/helpers.py
T

180 lines
5.8 KiB
Python

from __future__ import annotations
import json
import hashlib
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "tests" / "fixtures"
def run_cli(script: str, *args: object, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(ROOT / "scripts" / script), *(str(arg) for arg in args)],
cwd=cwd or ROOT,
text=True,
capture_output=True,
check=False,
)
def read_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def write_json(path: Path, value: Any) -> None:
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def init_run(
tmp_path: Path,
*,
route: str = "light",
mode: str = "write",
with_draft: bool = False,
audience: str | None = None,
) -> Path:
brief = tmp_path / "request.md"
brief.write_text("캐시 실패를 설명하는 문서를 작성해 주세요.\n", encoding="utf-8")
command: list[object] = [
"--brief",
brief,
"--kind",
"explanation",
"--route",
route,
"--mode",
mode,
"--workspace",
tmp_path / "workspace",
"--date",
"2026-07-23",
]
if audience is not None:
command.extend(["--audience", audience])
if with_draft or mode in {"revise", "review"}:
draft = tmp_path / "original.md"
shutil.copyfile(FIXTURES / "good" / "document.md", draft)
command.extend(["--draft", draft])
result = run_cli("init_run.py", *command)
assert result.returncode == 0, result.stderr
return Path(result.stdout.split("\t", 1)[0])
def install_good_contracts(run_dir: Path) -> None:
mapping = {
"reader-contract.json": "02_reader_contract.json",
"logic-map.json": "04_logic_map.json",
"term-ledger.json": "05_term_ledger.json",
"document.md": "07_draft.md",
}
for source, target in mapping.items():
shutil.copyfile(FIXTURES / "good" / source, run_dir / target)
manifest = read_json(run_dir / "00_run.json")
omissions = []
if manifest["route_hint"] == "light":
omissions.append(
{
"artifact": "03_evidence_map.json",
"reason": "light fixture는 별도 evidence curation을 생략한다.",
}
)
if manifest["mode"] != "review":
omissions.extend(
[
{
"artifact": "08_logic_review.json",
"reason": "light write/revise fixture는 별도 logic review를 생략한다.",
},
{
"artifact": "08_reader_review.json",
"reason": "light write/revise fixture는 별도 reader review를 생략한다.",
},
]
)
if manifest["mode"] == "review":
omissions.append(
{
"artifact": "final.md",
"reason": "review mode는 publishable final 문서를 만들지 않는다.",
}
)
manifest["omissions"] = omissions
write_json(run_dir / "00_run.json", manifest)
def write_reviews(run_dir: Path) -> None:
draft_path = run_dir / "07_draft.md"
draft_hash = hashlib.sha256(draft_path.read_bytes()).hexdigest()
review_inputs = {
"input_sha256": hashlib.sha256((run_dir / "01_input.md").read_bytes()).hexdigest(),
"sources_sha256": hashlib.sha256((run_dir / "01_sources.json").read_bytes()).hexdigest(),
"reader_contract_sha256": hashlib.sha256(
(run_dir / "02_reader_contract.json").read_bytes()
).hexdigest(),
"evidence_map_sha256": (
hashlib.sha256((run_dir / "03_evidence_map.json").read_bytes()).hexdigest()
if (run_dir / "03_evidence_map.json").is_file()
else None
),
"logic_map_sha256": hashlib.sha256(
(run_dir / "04_logic_map.json").read_bytes()
).hexdigest(),
"term_ledger_sha256": hashlib.sha256(
(run_dir / "05_term_ledger.json").read_bytes()
).hexdigest(),
}
for review_type, filename in (
("logic", "08_logic_review.json"),
("reader", "08_reader_review.json"),
):
write_json(
run_dir / filename,
{
"schema_version": "1.0",
"review_type": review_type,
"document": {"path": str(draft_path), "sha256": draft_hash},
"inputs": review_inputs,
"verdict": "pass",
"findings": [],
},
)
def write_evidence(run_dir: Path) -> None:
write_json(
run_dir / "03_evidence_map.json",
{
"schema_version": "1.0",
"claims": [
{
"id": "C1",
"statement": "동시 재계산이 부하를 키운다.",
"status": "source_backed",
"source_ids": ["brief"],
"source_locations": [
{"source_id": "brief", "locator": "line 1"}
],
"does_not_support": ["모든 장애 제거"],
"load_bearing": True,
},
{
"id": "C2",
"statement": "검증에는 한계가 있다.",
"status": "assumption",
"source_ids": [],
"source_locations": [],
"does_not_support": ["운영 효과"],
"load_bearing": True,
"label": "가정",
},
],
},
)