#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Tests for scripts/debug_route_gate.py (R-400). Run from controller/: python3 scripts/test_debug_route_gate.py WHY THE RED-PROOFS ARE THE POINT. A gate that has never been seen failing is a gate that has not been shown to gate anything. This is the second time that sentence has earned its place in this project, so both directions are proven by MUTATING a real copy of the tree and watching the gate convict. """ import io import os import shutil import subprocess import sys import tempfile import unittest SCRIPTS = os.path.dirname(os.path.abspath(__file__)) CTRL = os.path.dirname(SCRIPTS) GATE = os.path.join(SCRIPTS, "debug_route_gate.py") TEMPLATE = os.path.join("internal", "web", "templates", "debug.html") DISPATCH = os.path.join("internal", "web", "handler_debug.go") RUNNER = os.path.join(SCRIPTS, "controller_gates.py") def run_gate(cwd): p = subprocess.run([sys.executable, GATE], cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return p.returncode, p.stdout.decode("utf-8", "replace") def sandbox(): """A copy of just the two files the gate reads, in a throwaway tree. Mutating the real tree and reverting is the version of this that leaves a broken repo behind when an assertion fails mid-test. """ tmp = tempfile.mkdtemp(prefix="debugroutegate-") for rel in (TEMPLATE, DISPATCH): dst = os.path.join(tmp, rel) os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.copyfile(os.path.join(CTRL, rel), dst) return tmp class DebugRouteGateTest(unittest.TestCase): # C1 — the gate passes on the shipped tree. def test_passes_on_the_shipped_tree(self): rc, out = run_gate(CTRL) self.assertEqual(rc, 0, out) self.assertIn("debug route gate OK", out) # C2 — Scenario G, the mandatory red-proof: a reference with no handler convicts, and is NAMED. def test_fails_on_an_unwired_reference(self): tmp = sandbox() try: path = os.path.join(tmp, TEMPLATE) src = io.open(path, encoding="utf-8").read() io.open(path, "w", encoding="utf-8").write( src + '\nx\n') rc, out = run_gate(tmp) self.assertEqual(rc, 1, "a dead control did not convict the gate:\n" + out) self.assertIn("storage/simulate-disconnect", out, "the gate convicted without NAMING the reference:\n" + out) finally: shutil.rmtree(tmp) # C3 — the mirror image: a handler nothing reaches is the same defect. def test_fails_on_an_unreached_handler(self): tmp = sandbox() try: path = os.path.join(tmp, DISPATCH) src = io.open(path, encoding="utf-8").read() marker = 'case subpath == "dump" && r.Method == http.MethodGet:' self.assertIn(marker, src, "fixture drifted: the dispatch shape changed") io.open(path, "w", encoding="utf-8").write(src.replace( marker, 'case subpath == "ghost/handler" && r.Method == http.MethodGet:\n\t\ts.debugDump(w, r)\n\t' + marker, 1)) rc, out = run_gate(tmp) self.assertEqual(rc, 1, "an unreached handler did not convict the gate:\n" + out) self.assertIn("ghost/handler", out, "the gate convicted without NAMING the handler:\n" + out) finally: shutil.rmtree(tmp) # C4 — registration, read from the runner's own GATES table rather than by matching text. # A commented-out row still contains the string; an entry in the parsed list does not. def test_gate_is_registered_in_the_runner(self): import ast tree = ast.parse(io.open(RUNNER, encoding="utf-8").read()) labels = [] for node in ast.walk(tree): if isinstance(node, ast.Assign): for tgt in node.targets: if isinstance(tgt, ast.Name) and tgt.id == "GATES": for elt in node.value.elts: first = elt.elts[0] labels.append(first.value if hasattr(first, "value") else first.s) self.assertTrue(labels, "the runner's GATES table could not be parsed") self.assertIn("debug-routes", labels, "debug_route_gate.py exists but the runner never lists it — a gate nothing runs " "is the inert seam this repo has shipped four times. Listed: %r" % (labels,)) if __name__ == "__main__": unittest.main(verbosity=2)