Files
atomic-design-poc/scripts/openzaak-ui-up.sh
T
ehoandClaude Opus 5 6cfd70eeeb fix(backend): resolve besluit endpoint's id via Referentie, not local PK
POST /beoordeling/{id}/besluit always 404'd against a real OpenZaak: {id} is the
FE-facing case id from IZaakSource.ListCases, which under OpenZaakZaakSource is the
ZGW zaak's own uuid, not ApplicationStore's primary key. Resolve the case through
ListCases first (same seam the GET sibling already uses), then to the local Aanvraag
via its Referentie — the one identifier stable across both sources.

Adds ApplicationStore.GetByReferentie and a regression test that reproduces the
divergence with a decorating IZaakSource test double instead of a live OpenZaak.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:20:36 +02:00

151 lines
8.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# One command to test the Angular UI end-to-end against a real OpenZaak instead of the local
# SQLite store: brings up the OpenZaak harness (backend/openzaak/), seeds its catalogus, wires
# it onto the root app's docker network (docker-compose.openzaak.bff.yml /
# docker-compose.openzaak.yml — see those files for the full "why", it's non-obvious), grants
# the extra authorization scope the container alias needs, and brings the FE+BFF up pointed at
# OpenZaak.
#
# Safe to re-run: every step this chains is already idempotent (bootstrap-catalogus.sh,
# `docker compose up -d`, and the grant-replace below).
#
# Also confirmed empirically, many repeated trials: some freshly-(re)started `api`
# containers have EVERY outbound ZGW POST fail with what looks like an empty body reaching
# OpenZaak ("all fields required"), for that container's entire lifetime — while a plain curl
# to the exact same URL, from inside the exact same container, never fails, even hammered in a
# loop. Ruled out as the cause: HttpClient connection pooling settings, Expect-100-Continue,
# content pre-buffering — none of it made a measurable difference. Best lead so far, and
# reproduced live on this dev host (7.5/8GB swap from long-idle unrelated containers): this
# correlates with the HOST being under heavy memory pressure — a heavier managed runtime
# (dotnet's JIT + GC) is plausibly far more sensitive to that than a lightweight one-shot
# `curl` process. If you hit this, try freeing host memory (stop unrelated containers) before
# assuming it's a code regression. The preflight check and self-check below warn about and
# retry around it either way; set ZGW_DEBUG_HTTP=1 on the `api` container (see
# docker-compose.openzaak.yml) next time it reproduces to log Content-Length vs. actual bytes
# sent, which would confirm (or rule out) client-side body corruption.
set -euo pipefail
cd "$(dirname "$0")/.."
step() { printf '\n\033[1;36m▶ %s\033[0m\n' "$1"; }
check_memory_pressure() {
local total used pct
read -r total used <<< "$(free -m | awk '/^Swap:/{print $2, $3}')"
[ "${total:-0}" -gt 0 ] || return 0
pct=$(( used * 100 / total ))
if [ "$pct" -ge 50 ]; then
echo "⚠ host swap ${pct}% used (${used}MiB/${total}MiB) — this correlates with the" >&2
echo " known per-container ZGW flake (see comment up top). Consider 'docker ps' and" >&2
echo " stopping unrelated long-running stacks before continuing." >&2
fi
}
check_memory_pressure
step "root app (creates the docker network the OpenZaak harness joins below)"
docker compose up -d
step "OpenZaak harness + bff overlay"
( cd backend/openzaak && docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.bff.yml up -d )
step "seed catalogus/zaaktype/zaak (idempotent)"
bootstrap_output=$(cd backend/openzaak && ./bootstrap-catalogus.sh)
echo "$bootstrap_output"
zaaktype_line=$(printf '%s\n' "$bootstrap_output" | grep -A1 '^Zaaktype (concept)\.\.\.$' | tail -n1)
zaaktype_url=$(printf '%s\n' "$zaaktype_line" | sed -E 's/^ *(exists|created): //')
zaaktype_uuid=$(printf '%s\n' "$zaaktype_url" | sed 's#.*/##')
if [ -z "$zaaktype_uuid" ]; then
echo "Could not find the seeded zaaktype's URL in bootstrap-catalogus.sh's output — see above." >&2
exit 1
fi
# bootstrap-catalogus.sh queried (and granted scope) via http://localhost:8000 (run from the
# host); the containerized BFF reaches the same resource via the `openzaak.local` alias
# (docker-compose.openzaak.bff.yml) instead — only the UUID suffix is what actually matters.
container_zaaktype_url="http://openzaak.local:8000/catalogi/api/v1/zaaktypen/${zaaktype_uuid}"
export OPENZAAK_ZAAKTYPE_URL="$container_zaaktype_url"
step "grant the container-alias zaaktype scope (REPLACES bootstrap-catalogus.sh's own localhost-scoped grant, doesn't add to it — OpenZaak's own zaken-list authorization filter 500s with a RuntimeError, 'are you sure that all paths point to the same resource?', when an Applicatie has two zrc grants for what's really the same zaaktype under two different hostnames; confirmed empirically. One consequence: dotnet test --filter Category=Integration needs bootstrap-catalogus.sh rerun afterward to restore its localhost-scoped grant — it's idempotent, so that's a plain rerun, not a reset)"
( cd backend/openzaak && docker compose -f docker-compose.openzaak.yml exec -T --workdir /app/src web python manage.py shell ) <<PY
from vng_api_common.authorizations.models import Applicatie
app = Applicatie.objects.get(client_ids__contains=["bigregister-test"])
app.autorisaties.filter(component="zrc").delete()
app.autorisaties.create(
component="zrc",
scopes=["zaken.aanmaken", "zaken.bijwerken", "zaken.lezen", "zaken.statussen.toevoegen"],
zaaktype="$container_zaaktype_url",
max_vertrouwelijkheidaanduiding="openbaar",
)
print("granted:", "$container_zaaktype_url")
PY
step "root app again (now pointed at OpenZaak)"
docker compose -f docker-compose.yml -f docker-compose.openzaak.yml up -d
step "verify the BFF can actually reach OpenZaak (retries api if not — see the flake note up top)"
wait_for_api() {
# -f: a response has to actually be 2xx — dotnet run's own build/start (and, on a retry, a
# full restart) can leave the port accepting-but-erroring for a few seconds first, which a
# plain curl (no -f) would misread as "ready".
for _ in $(seq 1 60); do
curl -sS -f -m 2 -o /dev/null http://localhost:5000/api/v1/me && return 0
sleep 1
done
return 1
}
# A throwaway herregistratie submit: does the resulting zaak actually show up in OpenZaak
# (via the BFF's own /admin/cases)?
zgw_write_reaches_openzaak() {
local create id ref cases
create=$(curl -sS -m 10 -X POST http://localhost:5000/api/v1/applications -H "Content-Type: application/json" -d '{"type":"herregistratie"}') || return 1
id=$(printf '%s' "$create" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])') || return 1
ref=$(curl -sS -m 15 -X POST "http://localhost:5000/api/v1/applications/$id/submit" -H "Content-Type: application/json" -d '{"uren":1}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["referentie"])') || return 1
cases=$(curl -sS -m 10 -H "X-Role: admin" http://localhost:5000/api/v1/admin/cases)
printf '%s' "$cases" | python3 -c "
import json, sys
refs = [c['status']['referentie'] for c in json.load(sys.stdin)]
sys.exit(0 if '$ref' in refs else 1)
"
}
verified=false
attempts=5
for attempt in $(seq 1 "$attempts"); do
wait_for_api || { echo "api never came up listening — see 'docker logs atomic-design-poc-api-1'." >&2; break; }
if zgw_write_reaches_openzaak; then
verified=true
break
fi
echo " attempt $attempt/$attempts: a write didn't reach OpenZaak — restarting api and giving the network a moment to settle (known flake, see comment up top)..."
sleep 3
docker compose -f docker-compose.yml -f docker-compose.openzaak.yml restart api
done
if [ "$verified" = true ]; then
echo " verified: a write reaches OpenZaak."
else
check_memory_pressure
echo " Could not verify after $attempts tries. This is the known flake, not necessarily a real failure —" >&2
echo " keep retrying by hand: 'docker compose -f docker-compose.yml -f docker-compose.openzaak.yml restart api'," >&2
echo " wait for 'Application started' in 'docker logs -f atomic-design-poc-api-1', then try the UI again." >&2
fi
printf '\n\033[1;32m✔ up\033[0m\n'
cat <<EOF
App: http://localhost:4200
Admin cases (proof an aanvraag landed in OpenZaak): http://localhost:4200/beheer/zaken?role=admin
Audit trail (should show no zgw:divergence rows): http://localhost:4200/beheer/audit?role=admin
OpenZaak directly: http://localhost:8000
Only "herregistratie" has a seeded zaaktype in this harness — submit that wizard to see a real
write land in OpenZaak. registratie/intake submissions still succeed locally but their ZGW
write is flagged (Aanvraag.ZgwError / a zgw:divergence audit row), not surfaced as a UI error.
Document uploads also won't register in ZGW (no Documenten content seeded) — pick "per post"
in the wizard's document step to keep the demo clean, or ignore it, it's non-fatal.
Tear down (plain "down", no -f overlay needed — overlay files only add env/network config to
services docker-compose.yml already defines, and the required-var syntax in
docker-compose.openzaak.yml would otherwise block "down" too once OPENZAAK_ZAAKTYPE_URL falls
out of your shell):
docker compose down
( cd backend/openzaak && docker compose -f docker-compose.openzaak.yml down -v )
EOF