Files
atomic-design-poc/backend/openzaak/README.md
T
ehoandClaude Sonnet 5 89ad3490b0
CI / changes (push) Successful in 8s
CI / lint (push) Successful in 53s
CI / frontend (push) Successful in 1m42s
CI / backend (push) Successful in 2m11s
CI / e2e (push) Successful in 3m58s
CI / storybook-a11y (push) Successful in 8m8s
CI / semgrep (push) Successful in 1m17s
CI / api-client-drift (push) Successful in 1m50s
feat(openzaak): idempotent catalogus/zaaktype/zaak provisioning (WP-56)
bootstrap-catalogus.sh now looks up every resource by its natural key before
creating it (catalogus by domein+rsin, zaaktype by catalogus+identificatie,
statustype by zaaktype+volgnummer, roltype by zaaktype+omschrijvingGeneriek,
zaaktype-publish by checking `concept` first, zaak by identificatie,
status/rol by existence-under-the-zaak), so rerunning against an
already-seeded instance reuses what's there instead of erroring.

The WP's original plan (move this into OpenZaak's `setup_configuration`
mechanism) turned out not to be achievable: reading the actual
django_setup_configuration steps installed inside the open-zaak image shows
no step exists for Catalogi/Zaken content anywhere in this OpenZaak version
— only sites/credentials/applicaties/selectielijst. Documented as a
deviation; the WP's own Risks section already anticipated this and sanctioned
falling back to an idempotent script.

Verified live: fresh instance -> full run (all created) -> integration test
green -> reran the script twice more against the same instance (all reused,
identical URLs, no duplicates) -> integration test still green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:57:37 +02:00

7.6 KiB

OpenZaak integration harness (WP-54)

A real OpenZaak, for developing/testing the ZGW seam (backend/src/BigRegister.Api/Zgw/) against something that isn't a fixture or a stub HttpMessageHandler. Deliberately not part of the root docker-compose.yml and not wired into npm run ci / CI — see docs/reference/openzaak-integration.md for the full picture; this is just "how to run it".

Bring it up

cd backend/openzaak
docker compose -f docker-compose.openzaak.yml up -d   # postgres, redis, migrate+configure, OpenZaak
./bootstrap-catalogus.sh                               # seeds a catalogus/zaaktype/zaak to read back

bootstrap-catalogus.sh waits for OpenZaak to answer, then over plain REST + a hand-rolled HS256 JWT (same shape as ZgwTokenProvider.cs, matching the bigregister-test client setup_configuration/data.yaml creates): a catalogus, a published zaaktype ("Herregistratie arts", with the statustypen/resultaattype/roltype OpenZaak requires before a zaaktype can be published), and one zaak (BIG-2026-000123) with an initiator rol for the seeded BSN (111222333 — the same fixture BSN OpenZaakZaakSourceTests.cs uses). It writes what it seeded to seeded.env (gitignored) and prints a summary.

Idempotent (WP-56) — every resource is looked up by its natural key (the same field(s) OpenZaak enforces identity on: catalogus by domein+rsin, zaaktype by catalogus+ identificatie, statustype by zaaktype+volgnummer, roltype by zaaktype+ omschrijvingGeneriek, zaak by identificatie) before creating it, so re-running against an already-seeded instance reuses what's there instead of erroring. Safe to run repeatedly against a long-lived instance, not just once per fresh volume — a full reset is still available if you want a truly clean slate:

docker compose -f docker-compose.openzaak.yml down -v && docker compose -f docker-compose.openzaak.yml up -d

This content has no setup_configuration (declarative-YAML) equivalent: reading the django_setup_configuration steps installed inside the openzaak/open-zaak:1.29.1 image itself confirms the only app-registered steps are sites/credentials/applicaties (already used by setup_configuration/data.yaml) and Selectielijst API config — nothing for Catalogi/Zaken content. Hence this stays a script, made safe to rerun instead.

Run the integration test against it

cd backend
dotnet test --filter Category=Integration

OpenZaakIntegrationTests.cs points a WebApplicationFactory<Program> at Zgw:Enabled=true + http://localhost:8000 with the harness's credentials, hits GET /api/v1/admin/cases, and asserts the seeded zaak comes back — through the real HTTP + JWT + Catalogi-label-resolution path, not a mock. This test is tagged Category=Integration and is excluded from the default dotnet test run and from CI (ci.yml, scripts/ci-local.sh both filter Category!=Integration) — it only passes with this harness up, so it never runs where the harness doesn't exist.

Tear down

docker compose -f docker-compose.openzaak.yml down -v

Production (WP-55)

This dev harness stays dev-only: hardcoded SECRET_KEY, POSTGRES_HOST_AUTH_METHOD=trust, IS_HTTPS: 'no', a client secret checked into setup_configuration/data.yaml. A real deployment layers docker-compose.openzaak.prod.yml on top instead of replacing anything:

export OPENZAAK_SECRET_KEY=...          # Django SECRET_KEY — generate, don't reuse the dev value
export OPENZAAK_DB_PASSWORD=...         # postgres password (switches auth off `trust`)
export OPENZAAK_SITE_DOMAIN=...         # e.g. open-zaak.example.org — no scheme/port
export OPENZAAK_ALLOWED_HOSTS=...       # Django ALLOWED_HOSTS, usually the same domain
export OPENZAAK_CLIENT_ID=...           # the BFF's OpenZaak client id (ZgwOptions:ClientId)
export OPENZAAK_CLIENT_SECRET=...       # the BFF's JWT signing secret (ZgwOptions:Secret)
export OPENZAAK_APPLICATIE_UUID=$(uuidgen)

./render-prod-secrets.sh   # writes the gitignored setup_configuration/data.prod.yaml
docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.prod.yml up -d

Every one of those env vars is ${VAR:?...}-checked — compose (and render-prod-secrets.sh for the client secret) refuses to start rather than silently falling back to a dev-looking default. There is no env var that "means insecure default"; if it's unset, it's a hard error.

TLS: OpenZaak itself does no certificate handling. Put a reverse proxy/ingress (the same one fronting the BFF) in front of web's :8000, terminate TLS there, and forward to http://web:8000 over the compose network. IS_HTTPS: 'yes' in the prod override only tells Django it's being served over HTTPS (secure cookies, SECURE_* redirects) — it does not open a TLS listener itself.

The BFF side needs no code change: ZgwOptions already binds ClientId/Secret/the base URLs from IConfiguration, so pointing it at a production OpenZaak is a config change (Zgw:ClientId/Zgw:Secret/Zgw:ZrcBaseUrl etc. via env vars or a secrets manager), not an app change.

What's in here / what isn't

  • docker-compose.openzaak.yml — postgres (postgis), redis, a one-shot web-init (runs Django migrations then setup_configuration against setup_configuration/data.yaml), and web (the OpenZaak API on :8000). Pinned to openzaak/open-zaak:1.29.1. No celery/celery-beat/celery-flower/nginx — trimmed for a lean, fast-booting harness; add them back only if a later WP needs a real async notification delivery round-trip here (WP-52's webhook is already covered by fixture tests against no live instance). NOTIFICATIONS_DISABLED=true is required, not optional: without it, OpenZaak 500s (and rolls back the whole create) on any notified resource — see the compose file's comment.
  • setup_configuration/data.yaml — the declarative, scripted alternative to clicking through the Django admin (upstream's own documented setup_configuration CLI mechanism): creates the one bigregister-test client (heeft_alle_autorisaties: true — this instance never exists for anything but this harness, so there's no least-privilege boundary worth modeling).
  • bootstrap-catalogus.sh — the business content (catalogus/zaaktype/zaak/…) setup_configuration has no YAML for; every field value here was checked against OpenZaak's own OpenAPI spec and a live run of this exact script, not guessed (two OpenZaak quirks it works around: a zaaktype needs ≥1 resultaattype and 2 statustypen before it can be published, and its selectielijstklasse and the zaaktype's selectielijstProcestype must reference the same procesType on the public VNG selectielijst API). Idempotent (WP-56) — see "Bring it up" above.
  • Not here: Documenten (DRC) / Notificaties (NRC) content — add if a later WP needs to prove those round-trips against a live instance too (WP-51/52 are fixture-tested today).
  • docker-compose.openzaak.prod.yml (WP-55) — production overrides layered on top of docker-compose.openzaak.yml: real SECRET_KEY/DB password/site domain/allowed-hosts from required env vars (fails fast if unset), password DB auth instead of trust, IS_HTTPS: 'yes'. Adds no image/service of its own — see "Production" above for the full flow.
  • setup_configuration/data.prod.yaml.template (WP-55) — the prod counterpart of data.yaml with no secret in it (${OPENZAAK_CLIENT_SECRET} etc. as placeholders); render-prod-secrets.sh fills it in to the gitignored data.prod.yaml, which the prod compose override mounts over the container's data.yaml.