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 --- ## 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()