generated from coulomb/repo-seed
feat(explain): complete ATLAS-WP-0004 — graph edges, blast-radius, determinism tests
Some checks failed
validate-registry / validate (push) Has been cancelled
Some checks failed
validate-registry / validate (push) Has been cancelled
T05: tests/test_effective_config.py (6 tests) — order-independence, most-specific winner, no value/secret leak; wired into make validate + CI. T04: tools/blast_radius.py + make blast-radius — consumers, transitive dependents (cycle-safe), secret refs, fan-out risk band. T03: tools/config_graph.py + make graph/graph-query — emit config-typed edges to registry/indexes/graph.yaml (queryable by surface id); staleness check in the gate. WP-0004 finished (5/5). Read-first control-plane MVP complete: explain, graph, and blast-radius over the seeded surfaces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
97
tests/test_effective_config.py
Normal file
97
tests/test_effective_config.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Determinism and no-value-leak tests for the effective-config resolver
|
||||
(ATLAS-WP-0004-T05).
|
||||
|
||||
Runnable with `python3 -m unittest discover -s tests` (stdlib) or pytest.
|
||||
Proves the override path is deterministic / order-independent (consistent with
|
||||
the merge-rule + CUE-unification rationale, research §3.3) and that no live or
|
||||
secret value ever appears in explain output.
|
||||
"""
|
||||
import random
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from effective_config import LAYER_INDEX, find_entry, load_entry, resolve_path # noqa: E402
|
||||
from config_explain import render # noqa: E402
|
||||
|
||||
SURFACES_DIR = ROOT / "registry" / "surfaces"
|
||||
|
||||
|
||||
def all_surface_ids():
|
||||
return sorted(p.stem for p in SURFACES_DIR.glob("*.md"))
|
||||
|
||||
|
||||
def path_signature(op):
|
||||
return [(c.layer, c.ref, c.winning, c.overrides) for c in op.contributions]
|
||||
|
||||
|
||||
class TestResolverDeterminism(unittest.TestCase):
|
||||
def test_order_independent(self):
|
||||
"""Shuffling sources never changes the resolved path."""
|
||||
for sid in all_surface_ids():
|
||||
base = path_signature(resolve_path(find_entry(sid)))
|
||||
for _ in range(16):
|
||||
entry = find_entry(sid)
|
||||
random.shuffle(entry["sources"])
|
||||
self.assertEqual(
|
||||
path_signature(resolve_path(entry)), base,
|
||||
f"{sid}: path changed under source reordering",
|
||||
)
|
||||
|
||||
def test_winning_is_most_specific(self):
|
||||
"""The winning layer is the highest-rank (most specific) layered source."""
|
||||
for sid in all_surface_ids():
|
||||
op = resolve_path(find_entry(sid))
|
||||
layered = [c for c in op.contributions if c.rank >= 0]
|
||||
if not layered:
|
||||
continue
|
||||
winner = op.winner
|
||||
self.assertIsNotNone(winner, f"{sid}: no winner among layered sources")
|
||||
self.assertEqual(
|
||||
winner.rank, max(c.rank for c in layered),
|
||||
f"{sid}: winner is not the most specific layer",
|
||||
)
|
||||
|
||||
def test_all_seeded_surfaces_resolve(self):
|
||||
ids = all_surface_ids()
|
||||
self.assertGreaterEqual(len(ids), 1)
|
||||
for sid in ids:
|
||||
resolve_path(find_entry(sid)) # must not raise
|
||||
|
||||
|
||||
class TestNoValueLeak(unittest.TestCase):
|
||||
def test_schema_default_never_rendered(self):
|
||||
"""A surface's declared schema default is contract metadata and must not
|
||||
appear as an effective value in explain output."""
|
||||
for sid in all_surface_ids():
|
||||
entry = find_entry(sid)
|
||||
default = (entry.get("schema") or {}).get("default")
|
||||
if default is None:
|
||||
continue
|
||||
out = render(resolve_path(entry))
|
||||
self.assertNotIn(
|
||||
str(default), out,
|
||||
f"{sid}: schema default leaked into explain output",
|
||||
)
|
||||
|
||||
def test_safety_note_present(self):
|
||||
for sid in all_surface_ids():
|
||||
out = render(resolve_path(find_entry(sid)))
|
||||
self.assertIn("no values shown", out, f"{sid}: missing no-values safety note")
|
||||
|
||||
def test_secret_refs_are_references_only(self):
|
||||
"""depends_on_secret ids appear only on the explicit reference line, never
|
||||
as a resolved value elsewhere."""
|
||||
for sid in all_surface_ids():
|
||||
op = resolve_path(find_entry(sid))
|
||||
if not op.secret_deps:
|
||||
continue
|
||||
out = render(op)
|
||||
self.assertIn("depends on secret (ref)", out, f"{sid}: secret ref line missing")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user