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>
213 lines
13 KiB
Markdown
213 lines
13 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
|
|
```
|
|
|
|
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`.
|