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
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>
239 lines
14 KiB
Markdown
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`.
|