Files
atomic-design-poc/backend/openzaak/README.md
T
ehoandClaude Opus 5 faad772f85
CI / changes (push) Successful in 9s
CI / storybook-a11y (push) Successful in 16s
CI / lint (push) Successful in 11s
CI / frontend (push) Successful in 14s
CI / backend (push) Successful in 2m7s
CI / e2e (push) Successful in 2m42s
CI / semgrep (push) Successful in 1m4s
CI / api-client-drift (push) Successful in 1m44s
docs: explain how to check OpenZaak is running
docker compose up at the repo root does not start OpenZaak. A new reader
could easily assume it does. Add a short section to
backend/openzaak/README.md that shows how to check the four containers and
how to curl OpenZaak directly, using the same probe
bootstrap-catalogus.sh already relies on. Add one line to the root README
pointing there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 07:45:01 +02:00

239 lines
14 KiB
Markdown

# 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
```
## Check it is running
**`docker compose up` at the repo root does not start OpenZaak.** OpenZaak is a separate
stack. Bring it up with the command above, or with `scripts/openzaak-ui-up.sh` below.
Check the containers:
```bash
docker compose -f docker-compose.openzaak.yml ps
```
Look for four containers: `db`, `redis`, `web-init`, `web`. `db` and `redis` show a health
status. `web-init` runs once and exits. Its exit code must be `0`. `web` has no health
status. Check it directly instead:
```bash
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8000/catalogi/api/v1/catalogussen
```
A `200`, `401`, or `403` response means OpenZaak is up and answering. This is the same
check `bootstrap-catalogus.sh` uses to wait for OpenZaak before it seeds anything.
To confirm the BFF (not just OpenZaak) is wired up and can write to it, see "Testing the
Angular UI against this harness" below — `scripts/openzaak-ui-up.sh` submits a real test
case and confirms OpenZaak received it.
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<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 + zaaktype→aanvraag-type mapping 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='<a secret>' 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='<the same secret>' ./bootstrap-notificaties.sh
BFF_AUTH='<the same secret>' ./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'`).
## Testing the Angular UI against this harness
The base harness above and the app's own root `docker-compose.yml` are independent projects on
purpose (see the top of this file) — this is the opt-in bridge between them, for when you want
to click through the real UI and see an aanvraag land in a real OpenZaak instead of just
running `dotnet test`. One command from the repo root:
```bash
scripts/openzaak-ui-up.sh
```
It brings up the root app (so its docker network exists), brings up this harness plus
`docker-compose.openzaak.bff.yml` (gives this harness's `web` service a dotted alias,
`openzaak.local`, on its own network — the root project's `api` container joins THIS network,
in the opposite direction from the notifications overlay below, to avoid a real alias
collision: the root project's frontend service is also called `web`. The alias needs a dot
because Django's URLValidator rejects a bare hostname in a URL field; this environment's
rootless Podman also can't route container→host-port traffic through `host.docker.internal`,
so container-to-container is the only reliable path either way — see that file's header
comment for the full, empirically-confirmed reasoning), seeds the catalogus, additively
replaces the zrc authorization grant to match the alias (ZGW authorization is scoped by the
*exact* zaaktype URL string, not just the resource; see `scripts/openzaak-ui-up.sh`'s own
comment for why this is a replace, not an add), and brings the root app back up pointed at
OpenZaak (`docker-compose.openzaak.yml` at the repo root). A final self-check submits a
throwaway aanvraag and confirms it actually lands in OpenZaak, restarting `api` (up to 5
times) if not — see that script for a caveat about an intermittent per-container networking
flake this environment can hit under memory pressure (the script now warns if host swap is
already high going in; `ZGW_DEBUG_HTTP=1` on `api`, see `docker-compose.openzaak.yml`, logs
diagnostics to help nail the cause next time it reproduces). Prints the URLs to check
afterward and the teardown commands.
Two caveats, both non-fatal (WP-60 catches and flags rather than surfacing an error):
**only `herregistratie` has a seeded zaaktype** here, so submit that wizard to prove a real
write; and **no Documenten content is seeded**, so a document upload's ZGW half no-ops (pick
"per post" in the wizard's document step, or ignore it).
## 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`.