# 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](../../docs/reference/openzaak-integration.md) for the full picture; this is just "how to run it". ## Bring it up ```bash 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: ```bash 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 ```bash cd backend dotnet test --filter Category=Integration ``` `OpenZaakIntegrationTests.cs` points a `WebApplicationFactory` 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. ## Notifications-enabled profile (WP-58) The base harness above never delivers a real notification (`NOTIFICATIONS_DISABLED: 'true'`, no celery worker) — fine for the read/write ZGW seam, not for proving a live webhook round-trip. An opt-in overlay adds the one celery worker needed, flips that flag, and points OpenZaak straight at this repo's own BFF webhook (no real Notificaties API/NRC in this harness — see [docs/reference/openzaak-integration.md](../../docs/reference/openzaak-integration.md)'s "Notifications-enabled profile" section for why and how). Needs the repo root's own `docker compose up` (or an equivalent `api` container) running too, since the celery worker reaches the BFF by container name on that network: ```bash docker compose run --rm -d --name atomic-design-poc-api-1 --service-ports \ -e Zgw__NotificatieAuthorization='' api # repo root cd backend/openzaak docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.notificaties.yml up -d ./bootstrap-catalogus.sh BFF_AUTH='' ./bootstrap-notificaties.sh BFF_AUTH='' ./verify-notificatie.sh # proves a real delivery, end to end ``` To go back to the fast, no-notifications default: `docker compose -f docker-compose.openzaak.yml up -d --remove-orphans` (drops the celery worker, restores `NOTIFICATIONS_DISABLED: 'true'`). ## Tear down ```bash 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: ```bash 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; layer `docker-compose.openzaak.notificaties.yml` (WP-58) on top for a real async notification delivery round-trip. `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 with `heeft_alle_autorisaties: false` — this YAML mechanism (`vng_api_common`'s `ApplicatieConfigurationModel`) has no field for granular scopes at all, so the client starts with zero Autorisaties; `bootstrap-catalogus.sh` grants the exact ones it needs (WP-57). - `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. Also grants `bigregister-test`'s Autorisaties via `manage.py shell` (WP-57, see the script's top comment): `ztc` scopes (`catalogi.lezen`/`catalogi.schrijven`, this script's own content-creation needs) up front, `zrc` scopes (`zaken.aanmaken`/`zaken.bijwerken`/ `zaken.lezen`, scoped to the one zaaktype the BFF and this script both use) once that zaaktype exists. No `documenten`/DRC grant — `Zgw:InformatieobjecttypeUrls` is empty in this harness's `appsettings.json`, so `OpenZaakDocumentSource` isn't reachable here yet; add the grant (scoped to a real `informatieobjecttype`, which this script would also need to seed) when a later WP wires DRC content into this harness. - **Not here**: Documenten (DRC) content, or a real Notificaties API (NRC) — add DRC content if a later WP needs to prove that round-trip against a live instance too (WP-51 is fixture-tested today). A real NRC is a separate application (`open-notificaties`) this harness deliberately doesn't stand up — WP-58's notifications-enabled profile (below) proves live delivery without one, since this harness only ever has one subscriber. - `docker-compose.openzaak.notificaties.yml` (WP-58) — opt-in overlay: one celery worker for OpenZaak (async notification delivery needs it) + `NOTIFICATIONS_DISABLED: 'false'`, joined to the repo root's own compose network so it can reach the `api` container by name (tried `host.docker.internal:host-gateway` first; this environment's rootless Podman doesn't route container→host-port traffic through it). See "Notifications-enabled profile" below. - `bootstrap-notificaties.sh` (WP-58) — points OpenZaak's `NotificationsConfig` at the BFF's webhook via a `zgw_consumers.Service` (`update_or_create`, idempotent) instead of provisioning a real NRC `abonnement`; preflights that the BFF is reachable with the right secret first (a misconfigured target here means every write to a notified resource 500s and rolls back). - `verify-notificatie.sh` (WP-58) — the runnable end-to-end check: PATCHes the seeded zaak, polls the BFF's own `/admin/audit` (WP-41) for the resulting `zgw:notificatie`/`allow` row. - `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`.