test_lt_unit.py python
209 lines 7.6 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Unit tests for LT loop tightening (§LT.10)."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 import pytest
8
9 from adapters.config import load_config
10 from adapters.errors import ConfigError
11 from cli.version_lock import ORIGIN_KIT, FootprintEntry, build_version_lock_from_entries
12 from tools.footprint_coverage import check_footprint_coverage
13 from tools.handover_compact import compact_handover_change_log
14 from tools.optional_feature_tips import build_optional_feature_tips
15 from tools.verification_evidence_gate import build_verification_evidence_gate
16 from tests.support import FIXTURES, KIT_ROOT, load_fixture_config, write_config
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_coverage_empty_lock_not_applicable(tmp_path: Path) -> None:
29 config = load_fixture_config(tmp_path, "config-git-only.yaml")
30 report = check_footprint_coverage(tmp_path, config, lock=_lock([]), rendered=[])
31 assert report.ok
32 assert report.state == "not_applicable"
33
34
35 def test_coverage_missing_from_lock(tmp_path: Path) -> None:
36 config = load_fixture_config(tmp_path, "config-git-only.yaml")
37 from cli.footprint import resolve_footprint
38
39 files = resolve_footprint(config, kit=KIT_ROOT)
40 lock = _lock(
41 [
42 FootprintEntry(
43 path=files[0].destination,
44 source=files[0].source,
45 sha256="0" * 64,
46 origin=ORIGIN_KIT,
47 )
48 ]
49 )
50 report = check_footprint_coverage(tmp_path, config, lock=lock, rendered=files, kit=KIT_ROOT)
51 assert not report.ok
52 assert report.state == "missing_from_lock"
53 assert report.remediation == "ok sync"
54 assert len(report.missing) == len(files) - 1
55
56
57 def test_coverage_all_dests_ok(tmp_path: Path) -> None:
58 config = load_fixture_config(tmp_path, "config-git-only.yaml")
59 from cli.footprint import resolve_footprint
60
61 files = resolve_footprint(config, kit=KIT_ROOT)
62 entries = [
63 FootprintEntry(path=f.destination, source=f.source, sha256="0" * 64, origin=ORIGIN_KIT)
64 for f in files
65 ]
66 report = check_footprint_coverage(
67 tmp_path, config, lock=_lock(entries), rendered=files, kit=KIT_ROOT
68 )
69 assert report.ok
70 assert report.state == "ok"
71
72
73 def test_session_bookends_default_off(tmp_path: Path) -> None:
74 config = load_fixture_config(tmp_path, "config-git-only.yaml")
75 assert config.session_bookends.enabled is False
76
77
78 def test_session_bookends_unknown_key(tmp_path: Path) -> None:
79 dest = tmp_path / ".overseer" / "config.yaml"
80 dest.parent.mkdir(parents=True, exist_ok=True)
81 text = (FIXTURES / "config-git-only.yaml").read_text(encoding="utf-8")
82 dest.write_text(text + "\nsession_bookends:\n enabled: false\n extra: true\n", encoding="utf-8")
83 with pytest.raises(ConfigError, match="unknown session_bookends"):
84 load_config(dest)
85
86
87 def test_session_bookends_enabled_non_bool(tmp_path: Path) -> None:
88 dest = tmp_path / ".overseer" / "config.yaml"
89 dest.parent.mkdir(parents=True, exist_ok=True)
90 text = (FIXTURES / "config-git-only.yaml").read_text(encoding="utf-8")
91 dest.write_text(text + "\nsession_bookends:\n enabled: not-a-bool\n", encoding="utf-8")
92 with pytest.raises(ConfigError, match="must be a boolean"):
93 load_config(dest)
94
95
96 def test_compact_keep_too_low(tmp_path: Path) -> None:
97 config = load_fixture_config(tmp_path, "config-git-only.yaml")
98 write_config(tmp_path, "config-git-only.yaml")
99 handover = tmp_path / "docs" / "OVERSEER-HANDOVER.md"
100 handover.parent.mkdir(parents=True, exist_ok=True)
101 handover.write_text(
102 "<!-- overseer:anchor:change-log -->\n"
103 "- **2026-01-01** — one\n"
104 "<!-- /overseer:anchor:change-log -->\n",
105 encoding="utf-8",
106 )
107 from cli.main import main
108 from cli.context import CliContext
109 from cli.output import OutputContext
110 from tests.support import make_runner
111
112 ctx = CliContext.create(
113 runner=make_runner({}),
114 cwd=tmp_path,
115 kit=KIT_ROOT,
116 output=OutputContext(json_mode=False),
117 )
118 import os
119
120 old = os.getcwd()
121 os.chdir(tmp_path)
122 try:
123 code = main(["handover-compact", "--keep", "4"], ctx=ctx)
124 finally:
125 os.chdir(old)
126 assert code == 2
127
128
129 def test_compact_missing_anchor(tmp_path: Path) -> None:
130 config = load_fixture_config(tmp_path, "config-git-only.yaml")
131 handover = tmp_path / "docs" / "OVERSEER-HANDOVER.md"
132 handover.parent.mkdir(parents=True, exist_ok=True)
133 handover.write_text("# no anchor\n", encoding="utf-8")
134 report = compact_handover_change_log(config, tmp_path, keep=15, write=False)
135 assert not report.ok
136 assert report.reason == "change_log_anchor_missing"
137
138
139 def test_compact_no_op_when_within_keep(tmp_path: Path) -> None:
140 config = load_fixture_config(tmp_path, "config-git-only.yaml")
141 handover = tmp_path / "docs" / "OVERSEER-HANDOVER.md"
142 handover.parent.mkdir(parents=True, exist_ok=True)
143 handover.write_text(
144 "<!-- overseer:anchor:change-log -->\n"
145 "- **2026-01-01** — one\n"
146 "- **2026-01-02** — two\n"
147 "<!-- /overseer:anchor:change-log -->\n",
148 encoding="utf-8",
149 )
150 report = compact_handover_change_log(config, tmp_path, keep=15, write=False)
151 assert report.ok
152 assert report.compacted == 0
153
154
155 def test_verification_gate_skips_when_honesty_off(tmp_path: Path) -> None:
156 config = load_fixture_config(tmp_path, "config-git-only.yaml")
157 report = build_verification_evidence_gate(config, tmp_path)
158 assert report.skipped
159 assert report.ok
160
161
162 def test_verification_gate_skips_active_auto_todo(tmp_path: Path) -> None:
163 write_config(tmp_path, "config-git-only.yaml")
164 config_path = tmp_path / ".overseer" / "config.yaml"
165 text = config_path.read_text(encoding="utf-8")
166 config_path.write_text(
167 text
168 + "\nhonesty:\n enabled: true\n ledger: .overseer/honesty/VERDICT-LEDGER.jsonl\n"
169 + " require_verification_evidence: warn\n",
170 encoding="utf-8",
171 )
172 config = load_config(config_path)
173 docs = tmp_path / "docs"
174 docs.mkdir(parents=True, exist_ok=True)
175 (docs / "ROADMAP.md").write_text(
176 "| Phase | Model | Status | Deliverable |\n"
177 "| --- | --- | --- | --- |\n"
178 "| **LT-b Loop tightening build** | Auto | **TODO** | build |\n",
179 encoding="utf-8",
180 )
181 (docs / "OVERSEER-HANDOVER.md").write_text(
182 "## NEXT SESSION — LT-b\n\n| | |\n| **ID** | **LT-b** |\n",
183 encoding="utf-8",
184 )
185 report = build_verification_evidence_gate(config, tmp_path)
186 assert report.skipped
187
188
189 def test_optional_feature_tips_default_off(tmp_path: Path) -> None:
190 config = load_fixture_config(tmp_path, "config-git-only.yaml")
191 tips = build_optional_feature_tips(config)
192 assert len(tips) == 2
193 assert any("session_bookends off" in tip for tip in tips)
194 assert any("honesty off" in tip for tip in tips)
195
196
197 def test_optional_feature_tips_empty_when_enabled(tmp_path: Path) -> None:
198 write_config(tmp_path, "config-git-only.yaml")
199 config_path = tmp_path / ".overseer" / "config.yaml"
200 text = config_path.read_text(encoding="utf-8")
201 config_path.write_text(
202 text
203 + "\nsession_bookends:\n enabled: true\n"
204 + "honesty:\n enabled: true\n ledger: .overseer/honesty/VERDICT-LEDGER.jsonl\n"
205 + " require_independent_second_reviewer: warn\n",
206 encoding="utf-8",
207 )
208 config = load_config(config_path)
209 assert build_optional_feature_tips(config) == []
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago