|
2 | 2 | changes anywhere in the dep closure bust the cache; failure summaries stay |
3 | 3 | within the token budget.""" |
4 | 4 |
|
| 5 | +import json |
| 6 | + |
| 7 | +import pytest |
| 8 | + |
5 | 9 | from hashloom import api, tokens |
| 10 | +from hashloom.config import config_path, resolve_strict_provenance |
| 11 | +from hashloom.errors import HashloomError |
6 | 12 | from hashloom.indexer import index |
7 | 13 | from hashloom.verify import SUMMARY_MAX_TOKENS |
8 | 14 |
|
@@ -202,6 +208,70 @@ def test_confirming_inferred_contract_keeps_cached_green(project): |
202 | 208 | assert all("inferred" not in r for r in out["results"]) |
203 | 209 |
|
204 | 210 |
|
| 211 | +def _strict_on(root) -> None: |
| 212 | + config_path(root).write_text(json.dumps({"strict_provenance": True}), encoding="utf-8") |
| 213 | + |
| 214 | + |
| 215 | +def test_strict_provenance_refuses_inferred_unit(project): |
| 216 | + root, store = project |
| 217 | + text = (root / "contracts" / "total.yaml").read_text() |
| 218 | + api.put_contract(root, store, "total", text + "status: inferred\n") |
| 219 | + _strict_on(root) |
| 220 | + out = api.verify(root, store, ["total"]) |
| 221 | + assert out["ok"] is False |
| 222 | + r = out["results"][0] |
| 223 | + assert r["status"] == "error" |
| 224 | + assert r["error"]["code"] == "inferred_contract" |
| 225 | + assert "total" in r["error"]["message"] |
| 226 | + assert r["inferred"] == ["total"] |
| 227 | + # refused before verify_one: no pytest ran, no verdict was cached |
| 228 | + assert store.counters().get("test_runs", 0) == 0 |
| 229 | + |
| 230 | + |
| 231 | +def test_strict_provenance_refuses_dependent_of_inferred(project): |
| 232 | + root, store = project |
| 233 | + text = (root / "contracts" / "total.yaml").read_text() |
| 234 | + api.put_contract(root, store, "total", text + "status: inferred\n") |
| 235 | + _strict_on(root) |
| 236 | + # report is confirmed, but its closure rests on the unvetted total |
| 237 | + out = api.verify(root, store, ["report"]) |
| 238 | + assert out["ok"] is False |
| 239 | + assert out["results"][0]["error"]["code"] == "inferred_contract" |
| 240 | + assert out["results"][0]["inferred"] == ["total"] |
| 241 | + |
| 242 | + |
| 243 | +def test_strict_refusal_preserves_cached_green(project): |
| 244 | + root, store = project |
| 245 | + assert statuses(api.verify(root, store, ["total"])) == {"total": "pass"} |
| 246 | + runs_before = store.counters()["test_runs"] |
| 247 | + text = (root / "contracts" / "total.yaml").read_text() |
| 248 | + api.put_contract(root, store, "total", text + "status: inferred\n") |
| 249 | + _strict_on(root) |
| 250 | + assert api.verify(root, store, ["total"])["ok"] is False # refused, not failed |
| 251 | + # the review flip: the refusal wrote nothing, so the old green revives |
| 252 | + api.put_contract(root, store, "total", text) |
| 253 | + out = api.verify(root, store, ["total"]) |
| 254 | + assert statuses(out) == {"total": "cached-pass"} |
| 255 | + assert store.counters()["test_runs"] == runs_before |
| 256 | + |
| 257 | + |
| 258 | +def test_strict_unknown_name_still_errors_as_unknown(project): |
| 259 | + root, store = project |
| 260 | + _strict_on(root) |
| 261 | + out = api.verify(root, store, ["nope"]) |
| 262 | + assert out["ok"] is False |
| 263 | + assert out["results"][0]["error"]["code"] == "unknown_contract" |
| 264 | + |
| 265 | + |
| 266 | +def test_strict_provenance_resolver_defaults_off_and_rejects_bad_values(project): |
| 267 | + root, _ = project |
| 268 | + assert resolve_strict_provenance(root) is False # no key -> advisory as before |
| 269 | + config_path(root).write_text(json.dumps({"strict_provenance": "yes"}), encoding="utf-8") |
| 270 | + with pytest.raises(HashloomError) as exc: |
| 271 | + resolve_strict_provenance(root) |
| 272 | + assert exc.value.code == "bad_config" |
| 273 | + |
| 274 | + |
205 | 275 | def test_status_reports_dirty_and_hit_rate(project): |
206 | 276 | root, store = project |
207 | 277 | s = api.status(root, store) |
|
0 commit comments