The four portal proxy configs are near-identical, so a copy-paste slip is cheap to introduce and expensive to find: proxying another portal's endpoint group hands a browser an endpoint its token is not for, and the failure surfaces as a 401 three services away. Asserts each portal proxies exactly its own groups to the BFF and keeps the SPA fallback for Angular's client-side routes. Red: the Caddyfiles it reads do not exist yet.
69 lines
2.6 KiB
Python
69 lines
2.6 KiB
Python
#!/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 <path> { 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")
|