from __future__ import annotations import json from pathlib import Path import sys import tempfile import unittest REPO_ROOT = Path(__file__).resolve().parents[2] RUNTIME = REPO_ROOT / "harness/runtime" sys.path.insert(0, str(RUNTIME)) import branch_contract_check # noqa: E402 import branch_from_project # noqa: E402 PROJECT = """--- title: sample source_type: project-note id: sample-project project_revision: 3 --- # Sample ## 결정 | Decision ID | Revision | Domain | Decision Summary | Status | Owner | Evidence | |---|---|---|---|---|---|---| | `DEC-SAMPLE-PROJECT-API-001` | 2 | api | canonical summary | active | project | C1 | ## 작업 | Work Item ID | branch slug | 완료 조건 (측정가능) | Applies Decisions | Dependencies | Status | |---|---|---|---|---|---| | `WI-SAMPLE-PROJECT-001` | `feature-sample-api-contract-runtime` | 검사가 통과한다 | `DEC-SAMPLE-PROJECT-API-001@2` | - | `planned` | ## 묶음 """ class BranchContractCheckTest(unittest.TestCase): def setUp(self) -> None: temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) for path in ( "raw/project-notes", "raw/branch-notes", "harness/source", "harness/adapters", "harness/runtime", "harness/tests", "vault/10-projects", ): (self.root / path).mkdir(parents=True, exist_ok=True) (self.root / "raw/project-notes/sample-project.md").write_text(PROJECT, encoding="utf-8") (self.root / "harness/source/vault-layout.json").write_text( json.dumps({ "schema_version": "vault-layout/v1", "mode": "compatibility", "vault_root": "vault", "canonical_mapping": { "schema_version": "project-first-paths/v1", "project_relation": "branch-to-project", "project_member_pattern": "{vault_root}/{area}/{project}/{category}/{relative_path}", "default_pattern": "{vault_root}/{area}/{category}/{relative_path}", }, "write_roots": {"compatibility": ["raw", "wiki"], "shadow": ["raw", "wiki"], "canonical": ["vault"]}, "migration_manifest": {"schema_version": "vault-migration/v1", "entries": []}, "rollback_mapping": {"schema_version": "vault-rollback/v1", "entries": []}, "areas": {"10-projects": ["raw/project-notes", "raw/branch-notes"]}, }), encoding="utf-8", ) changes, result = branch_from_project.prepare( self.root, "sample-project", "WI-SAMPLE-PROJECT-001" ) for path, content in changes.items(): path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(content) self.branch = self.root / result["target"] def test_result_exposes_contract_and_generated_hashes(self) -> None: result = branch_contract_check.validate(self.root, self.branch) self.assertEqual(result["status"], "PASS", result) self.assertEqual(result["project"], "sample-project") self.assertEqual(result["work_item"], "WI-SAMPLE-PROJECT-001") self.assertEqual(result["project_revision"], 3) self.assertEqual(result["inherits"], ["DEC-SAMPLE-PROJECT-API-001@2"]) self.assertEqual(result["editable_sections"], ["branch-parent", "branch-goal", "branch-scope"]) self.assertEqual( result["generated_hashes"]["declared_sha256"], result["generated_hashes"]["observed_sha256"], ) def test_failed_candidate_postflight_leaves_target_unchanged(self) -> None: original = self.branch.read_bytes() candidate = self.root / "candidate.md" candidate.write_text( original.decode("utf-8").replace("canonical summary", "tampered summary", 1), encoding="utf-8", ) result = branch_contract_check.validate_candidate( self.root, self.branch, candidate, postflight=True ) self.assertEqual(result["status"], "FAIL") self.assertIn("GENERATED_REGION_DRIFT", {item["code"] for item in result["findings"]}) self.assertEqual(self.branch.read_bytes(), original) self.assertTrue(result["staged"]) def test_override_mismatch_exposes_legacy_and_alias_codes(self) -> None: candidate = self.root / "candidate.md" candidate.write_text( self.branch.read_text(encoding="utf-8").replace( "overrides: []", "overrides: [DEC-SAMPLE-PROJECT-API-001@2]", 1 ), encoding="utf-8", ) result = branch_contract_check.validate_candidate(self.root, self.branch, candidate) codes = {item["code"] for item in result["findings"]} self.assertIn("OVERRIDE_APPROVAL_MISMATCH", codes) self.assertIn("UNDECLARED_OVERRIDE", codes) self.assertEqual( result["failure_code_aliases"]["OVERRIDE_APPROVAL_MISMATCH"], ["UNDECLARED_OVERRIDE"], ) if __name__ == "__main__": unittest.main()