test_footprint_integrity.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Unit tests for the footprint self-integrity hard gate (§KH3.4).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import dataclasses |
| 6 | from pathlib import Path |
| 7 | |
| 8 | import pytest |
| 9 | |
| 10 | from cli.version_lock import ( |
| 11 | ORIGIN_KIT, |
| 12 | ORIGIN_PRESERVED, |
| 13 | FootprintEntry, |
| 14 | build_version_lock_from_entries, |
| 15 | ) |
| 16 | from tools.footprint_integrity import FootprintIntegrityReport, check_footprint_integrity |
| 17 | |
| 18 | |
| 19 | def _lock(entries: list[FootprintEntry]): |
| 20 | return build_version_lock_from_entries( |
| 21 | kit_version="0.1.0", |
| 22 | config_version=1, |
| 23 | entries=entries, |
| 24 | installed_at="2026-01-01T00:00:00Z", |
| 25 | ) |
| 26 | |
| 27 | |
| 28 | def test_no_lock_file_is_not_applicable(tmp_path: Path) -> None: |
| 29 | """A repo with no version.lock at all has nothing declared yet — vacuously fine.""" |
| 30 | report = check_footprint_integrity(tmp_path) |
| 31 | assert report.ok |
| 32 | assert report.state == "not_applicable" |
| 33 | assert report.missing == () |
| 34 | |
| 35 | |
| 36 | def test_corrupt_lock_is_unreadable(tmp_path: Path) -> None: |
| 37 | """A version.lock that exists but cannot be parsed fails closed, not silently ok.""" |
| 38 | overseer = tmp_path / ".overseer" |
| 39 | overseer.mkdir(parents=True) |
| 40 | (overseer / "version.lock").write_text("not: [valid, yaml, :::", encoding="utf-8") |
| 41 | report = check_footprint_integrity(tmp_path) |
| 42 | assert not report.ok |
| 43 | assert report.state == "unreadable" |
| 44 | assert report.remediation == "ok init" |
| 45 | |
| 46 | |
| 47 | def test_all_declared_present_is_ok(tmp_path: Path) -> None: |
| 48 | (tmp_path / "a.mdc").write_text("x", encoding="utf-8") |
| 49 | lock = _lock( |
| 50 | [FootprintEntry(path="a.mdc", source="cursor/rules/a.mdc", sha256="0" * 64, origin=ORIGIN_KIT)] |
| 51 | ) |
| 52 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 53 | assert report.ok |
| 54 | assert report.state == "ok" |
| 55 | assert report.missing == () |
| 56 | |
| 57 | |
| 58 | def test_declared_but_absent_is_missing(tmp_path: Path) -> None: |
| 59 | """The exact frozen trigger: declared in version.lock, absent from disk.""" |
| 60 | lock = _lock( |
| 61 | [FootprintEntry(path="a.mdc", source="cursor/rules/a.mdc", sha256="0" * 64, origin=ORIGIN_KIT)] |
| 62 | ) |
| 63 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 64 | assert not report.ok |
| 65 | assert report.state == "missing" |
| 66 | assert report.missing == ("a.mdc",) |
| 67 | assert report.remediation == "ok sync" |
| 68 | assert "a.mdc" in report.message |
| 69 | |
| 70 | |
| 71 | def test_missing_entries_are_sorted(tmp_path: Path) -> None: |
| 72 | lock = _lock( |
| 73 | [ |
| 74 | FootprintEntry(path="z.mdc", source="s", sha256="0" * 64, origin=ORIGIN_KIT), |
| 75 | FootprintEntry(path="a.mdc", source="s", sha256="0" * 64, origin=ORIGIN_KIT), |
| 76 | ] |
| 77 | ) |
| 78 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 79 | assert report.missing == ("a.mdc", "z.mdc") |
| 80 | |
| 81 | |
| 82 | def test_preserved_origin_absent_is_never_missing(tmp_path: Path) -> None: |
| 83 | """Frozen non-trigger: a preserved living doc is never existence-checked.""" |
| 84 | lock = _lock( |
| 85 | [ |
| 86 | FootprintEntry( |
| 87 | path="docs/ROADMAP.md", source="s", sha256="0" * 64, origin=ORIGIN_PRESERVED |
| 88 | ) |
| 89 | ] |
| 90 | ) |
| 91 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 92 | assert report.ok |
| 93 | assert report.state == "ok" |
| 94 | assert report.missing == () |
| 95 | |
| 96 | |
| 97 | def test_content_mismatch_is_never_missing(tmp_path: Path) -> None: |
| 98 | """Frozen non-trigger: a file that exists but whose content differs is not this gate's job.""" |
| 99 | (tmp_path / "a.mdc").write_text("totally different content now", encoding="utf-8") |
| 100 | lock = _lock( |
| 101 | [FootprintEntry(path="a.mdc", source="s", sha256="0" * 64, origin=ORIGIN_KIT)] |
| 102 | ) |
| 103 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 104 | assert report.ok |
| 105 | assert report.state == "ok" |
| 106 | |
| 107 | |
| 108 | def test_empty_footprint_lock_is_ok(tmp_path: Path) -> None: |
| 109 | """A lock declaring zero entries has nothing to check — vacuously ok.""" |
| 110 | report = check_footprint_integrity(tmp_path, lock=_lock([])) |
| 111 | assert report.ok |
| 112 | assert report.state == "ok" |
| 113 | |
| 114 | |
| 115 | def test_mixed_missing_and_preserved_only_flags_kit_owned(tmp_path: Path) -> None: |
| 116 | (tmp_path / "present.mdc").write_text("x", encoding="utf-8") |
| 117 | lock = _lock( |
| 118 | [ |
| 119 | FootprintEntry(path="present.mdc", source="s", sha256="0" * 64, origin=ORIGIN_KIT), |
| 120 | FootprintEntry(path="absent.mdc", source="s", sha256="0" * 64, origin=ORIGIN_KIT), |
| 121 | FootprintEntry( |
| 122 | path="docs/ROADMAP.md", source="s", sha256="0" * 64, origin=ORIGIN_PRESERVED |
| 123 | ), |
| 124 | ] |
| 125 | ) |
| 126 | report = check_footprint_integrity(tmp_path, lock=lock) |
| 127 | assert report.state == "missing" |
| 128 | assert report.missing == ("absent.mdc",) |
| 129 | |
| 130 | |
| 131 | def test_report_is_frozen_dataclass() -> None: |
| 132 | report = FootprintIntegrityReport(state="ok", message="fine", remediation=None) |
| 133 | assert report.ok |
| 134 | with pytest.raises(dataclasses.FrozenInstanceError): |
| 135 | report.state = "missing" # type: ignore[misc] |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago