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

131 lines
5.2 KiB
Python

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
<!-- section-id: project-decisions -->
## 결정
| Decision ID | Revision | Domain | Decision Summary | Status | Owner | Evidence |
|---|---|---|---|---|---|---|
| `DEC-SAMPLE-PROJECT-API-001` | 2 | api | canonical summary | active | project | C1 |
<!-- section-id: project-work-items -->
## 작업
| 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()