import contextlib import io import tempfile import unittest from pathlib import Path from ampg.cli import main from ampg.config import load_config from ampg.route_policy import route_exposures, route_issues class RoutePolicyTest(unittest.TestCase): def test_route_exposure_explains_exposed_denied_and_tier_skips(self): with tempfile.TemporaryDirectory() as tmp: config = load_config(_write_app_config(Path(tmp))) exposures = route_exposures(config) by_protocol_route = { (exposure.protocol, exposure.match): exposure for exposure in exposures } self.assertEqual("route-manifest", by_protocol_route[("tor", "/play/*")].source) self.assertEqual("exposed", by_protocol_route[("tor", "/play/*")].status) self.assertEqual("exposed", by_protocol_route[("gemini", "/play/*")].status) self.assertEqual("skipped", by_protocol_route[("ipfs", "/play/*")].status) self.assertEqual( "tier interactive-lite exceeds protocol max_tier static", by_protocol_route[("ipfs", "/play/*")].reason, ) self.assertEqual("skipped", by_protocol_route[("tor", "/checkout/*")].status) self.assertEqual( "tier transactional exceeds protocol max_tier interactive-lite", by_protocol_route[("tor", "/checkout/*")].reason, ) self.assertEqual("skipped", by_protocol_route[("tor", "/admin/*")].status) self.assertEqual( "matched deny_route /admin/*", by_protocol_route[("tor", "/admin/*")].reason, ) issues = route_issues(config) self.assertEqual(["/checkout/*"], [issue.match for issue in issues]) self.assertEqual("no-compatible-protocol", issues[0].code) def test_route_validate_exits_nonzero_for_unsupported_public_route(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_app_config(Path(tmp)) stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main(["--config", str(config_path), "routes", "validate"]) output = stdout.getvalue() self.assertEqual(1, status) self.assertIn("AMPG_ROUTE site=app protocol=tor route_index=0 route=\"/play/*\"", output) self.assertIn("AMPG_ROUTE_ISSUE site=app route_index=1 route=\"/checkout/*\"", output) self.assertIn("AMPG_ROUTE_SUMMARY sites=1 routes=3 decisions=9 exposed=2 skipped=7 issues=1", output) def test_route_explain_exits_zero_with_issues(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_app_config(Path(tmp)) stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main(["--config", str(config_path), "routes", "explain"]) self.assertEqual(0, status) self.assertIn("issues=1", stdout.getvalue()) def test_route_manifest_requires_routes_array(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) source = root / "site" source.mkdir() (source / "index.html").write_text("