#!/usr/bin/env python3 """Self-check for the portals' Caddyfiles — stdlib asserts, no framework. Run: python3 infra/test_portal_caddyfiles.py (also runs in `make unit`). Each portal serves its Angular app and reverse-proxies *its own* BFF endpoint group same-origin, so the browser never sees CORS and the DigiD token rides along (ADR-0010). The four files are near-identical, which makes a copy-paste slip cheap to introduce and expensive to find: proxying another portal's group hands a behandelaar's browser an endpoint its token isn't for, and the failure shows up as a 401 three services away. What is asserted per portal: it proxies exactly its own groups to the BFF service, and it falls back to index.html so Angular's client-side routes survive a deep link / refresh. """ import os import re APPS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "apps") # The self-service portal also renders the public register (S-09), so it proxies both. EXPECTED = { "self-service": {"/self-service/*", "/openbaar/*"}, "openbaar": {"/openbaar/*"}, "behandel": {"/behandel/*"}, "beheer": {"/beheer/*"}, } ALL_GROUPS = {g for groups in EXPECTED.values() for g in groups} def caddyfile(app): with open(os.path.join(APPS, app, "Caddyfile")) as fh: return fh.read() def proxied_groups(text): """The path groups routed to the BFF: `handle { reverse_proxy bff:8080 }`.""" return { m.group(1) for m in re.finditer(r"handle\s+(\S+)\s*\{[^}]*reverse_proxy\s+bff:8080", text) } def test_each_portal_proxies_exactly_its_own_endpoint_groups(): for app, expected in EXPECTED.items(): got = proxied_groups(caddyfile(app)) assert got == expected, f"{app}: proxies {got or '{}'}, expected {expected}" def test_no_portal_proxies_another_portals_group(): for app, expected in EXPECTED.items(): strays = proxied_groups(caddyfile(app)) & (ALL_GROUPS - expected) assert not strays, f"{app}: proxies another portal's group {strays}" def test_every_portal_falls_back_to_index_html(): """Angular routes client-side: an unknown path must serve the app, not a 404.""" for app in EXPECTED: text = caddyfile(app) assert "try_files {path} /index.html" in text, f"{app}: no SPA fallback" assert "file_server" in text, f"{app}: nothing serves the built app" if __name__ == "__main__": for name, fn in sorted(globals().items()): if name.startswith("test_") and callable(fn): fn() print(f" ok {name}") print("portal Caddyfile self-check passed")