Files
llm-wiki/harness/tests/test_contract_projection.py
T

94 lines
4.2 KiB
Python

from __future__ import annotations
from pathlib import Path
import shutil
import sys
import tempfile
import unittest
from unittest import mock
ROOT = Path(__file__).resolve().parents[2]
RUNTIME = ROOT / "harness/runtime"
sys.path.insert(0, str(RUNTIME))
import contract_projection # noqa: E402
import fs_transaction # noqa: E402
import typed_contract_check # noqa: E402
class ContractProjectionTest(unittest.TestCase):
def setUp(self) -> None:
self.tempdir = tempfile.TemporaryDirectory()
self.addCleanup(self.tempdir.cleanup)
self.root = Path(self.tempdir.name)
(self.root / "harness/source").mkdir(parents=True)
shutil.copy2(ROOT / "harness/source/typed-contracts.json", self.root / "harness/source/typed-contracts.json")
(self.root / "raw/project-notes").mkdir(parents=True)
branches = self.root / "raw/branch-notes"
branches.mkdir(parents=True)
(self.root / "schemas").mkdir()
(self.root / "schemas/a.json").write_text("{}\n", encoding="utf-8")
(self.root / "raw/project-notes/demo.md").write_text(
"""---
title: demo
---
<!-- section-id: artifact-registry -->
## Artifact Registry
| Artifact ID | Revision | Name | Schema Owner | Producer | Consumers | Schema Ref | Status |
|---|---:|---|---|---|---|---|---|
| `ART-DEMO-A-001` | 1 | `a.json` | `feature-owner-contract` | `feature-owner-contract` | `feature-consumer-one`, `feature-consumer-two` | `schemas/a.json` | active |
""",
encoding="utf-8",
)
(branches / "feature-owner-contract.md").write_text("---\ntitle: owner\n---\n# owner\n", encoding="utf-8")
for slug in ("feature-consumer-one", "feature-consumer-two"):
(branches / f"{slug}.md").write_text(
f"---\ntitle: {slug}\nimports: [ART-DEMO-A-001@1]\n---\n# {slug}\n",
encoding="utf-8",
)
def test_all_consumer_projections_are_one_transaction_and_idempotent(self) -> None:
updates, result = contract_projection.build_updates(self.root)
self.assertEqual(result["status"], "DRIFT")
self.assertEqual(len(updates), 2)
with mock.patch.object(contract_projection, "replace_many") as replace:
code = contract_projection.main(["--root", str(self.root), "--write"])
self.assertEqual(code, 0)
replace.assert_called_once()
self.assertEqual(len(replace.call_args.args[0]), 2)
fs_transaction.replace_many({path: text.encode("utf-8") for path, text in updates.items()})
second, current = contract_projection.build_updates(self.root)
self.assertEqual((second, current["status"]), ({}, "CURRENT"))
self.assertEqual(typed_contract_check.check(self.root)["status"], "PASS")
def test_mid_commit_failure_restores_every_consumer(self) -> None:
updates, _result = contract_projection.build_updates(self.root)
before = {path: path.read_bytes() for path in updates}
original = fs_transaction.os.replace
calls = 0
def fail_second(source: object, target: object) -> None:
nonlocal calls
calls += 1
if calls == 2:
raise OSError("injected projection failure")
original(source, target)
with mock.patch.object(fs_transaction.os, "replace", side_effect=fail_second):
with self.assertRaises(fs_transaction.TransactionError):
fs_transaction.replace_many({path: text.encode("utf-8") for path, text in updates.items()})
self.assertEqual({path: path.read_bytes() for path in updates}, before)
def test_manual_projection_edit_reports_artifact_drift(self) -> None:
updates, _result = contract_projection.build_updates(self.root)
fs_transaction.replace_many({path: text.encode("utf-8") for path, text in updates.items()})
target = self.root / "raw/branch-notes/feature-consumer-one.md"
target.write_text(target.read_text(encoding="utf-8").replace("a.json", "other.json"), encoding="utf-8")
result = typed_contract_check.check(self.root)
self.assertIn("ARTIFACT_PROJECTION_DRIFT", {item["code"] for item in result["findings"]})
if __name__ == "__main__":
unittest.main()