feat(zgw): docker OpenZaak integration-test harness (WP-54)

Opt-in docker-compose (postgres+redis+OpenZaak, no celery/nginx) +
bootstrap-catalogus.sh seed a real OpenZaak instance; OpenZaakIntegrationTests
(Category=Integration, excluded from default dotnet test/CI) proves the ZGW
seam against it for the first time. That live run caught a real bug:
ZgwHttpClient never sent Content-Crs/Accept-Crs headers, so every write would
412 against a spec-compliant OpenZaak — fixed alongside the harness.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-30 09:08:35 +02:00
co-authored by Claude Sonnet 5
parent 73172510ea
commit 5cb3e1a9f0
12 changed files with 471 additions and 15 deletions
+28 -7
View File
@@ -101,7 +101,11 @@ confidentiality level would matter for production but isn't needed to prove the
`user_representation`). No refresh flow — OpenZaak expires tokens 1h past `iat`, so per-call
minting is the recommended pattern. Hand-rolled (no `Microsoft.IdentityModel.*` dependency).
- `ZgwHttpClient.cs` — shared GET/POST-with-bearer-JWT plumbing used by both
`OpenZaakZaakSource` and `OpenZaakDocumentSource`.
`OpenZaakZaakSource` and `OpenZaakDocumentSource`. Every request also carries
`Accept-Crs`/`Content-Crs: EPSG:4326` — every ZGW call must declare a coordinate reference
system even when no geometry is involved, or a real OpenZaak 412s ("Content-Crs header
ontbreekt"). This was missing until WP-54's live harness caught it — the stub-handler tests
never modelled the header, so it had shipped silently since WP-49/50.
- `ZgwZaakMapper.cs` — the anti-corruption map: ZGW Zaak → `ApplicationSummaryDto`. This is
where **URL identity** becomes the trailing uuid and the **zaaktype URL** is resolved to a
human label (the cross-service join).
@@ -204,6 +208,22 @@ FE or the contract. Watch the **sync-over-async** `ponytail:` note in `OpenZaakZ
its `OpenZaakDocumentSource` sibling) — make the read/write paths async if OpenZaak becomes the
default.
## Run against real OpenZaak (WP-54)
Everything above was, until WP-54, only proven against fixtures + a stub `HttpMessageHandler`
no live OpenZaak. `backend/openzaak/` is a **separate**, opt-in docker-compose harness (never
merged into the root `docker-compose.yml`, which stays FE+BFF-only) that brings up a real
OpenZaak, seeds a minimal catalogus/zaaktype/zaak via a bootstrap script, and backs one
xunit test (`OpenZaakIntegrationTests.cs`, tagged `Category=Integration`) that points the BFF at
it with `Zgw:Enabled=true`. See `backend/openzaak/README.md` for the exact commands; the test is
excluded from the default `dotnet test` run and from CI (`--filter Category!=Integration`) since
it only passes with the harness up.
This is also where the `Content-Crs`/`Accept-Crs` header gap above was found: a real OpenZaak
enforces ZGW's geo-header requirement in a way no stub-based test could catch, since a stub
never rejects an unexpected (or missing) header. That is the harness's whole point — proving
the seam against real protocol behaviour, not just the shapes we already assumed.
## Config
```jsonc
@@ -270,15 +290,16 @@ Caveat: `IZaakSource` covers the cases **read (admin + citizen-scoped) + create*
(WP-49/50/53), `IDocumentSource` covers **upload + zaak-link** (WP-51), the inbound
`POST /zgw/notificaties` webhook (WP-52) closes the read/write/document/notify arc, and WP-53
threaded a real per-request `CallerIdentity` through all of it (ownership + the ZGW audit
claims). Other BFF endpoints (reference data like `SeedData`'s BRP/DUO mimics) still read static
stores directly — ACL-ready (the DTO seam exists) but not yet swappable, and not part of this
arc. What's left is **WP-54**: a docker OpenZaak harness + opt-in integration test — today
everything is fixture/mock-tested against no live instance.
claims), and WP-54 added a docker OpenZaak harness + opt-in integration test proving the seam
against a live instance (and, in doing so, caught the missing `Content-Crs`/`Accept-Crs`
headers noted above). Other BFF endpoints (reference data like `SeedData`'s BRP/DUO mimics)
still read static stores directly — ACL-ready (the DTO seam exists) but not yet swappable, and
not part of this arc. That closes the phase-9 OpenZaak/ZGW arc (WP-49..54).
## See also
- [ADR-0005 — OpenZaak behind the BFF](architecture/0005-openzaak-behind-bff.md) — the decision.
- [ADR-0001 — BFF-lite + decision DTOs](architecture/0001-bff-lite-decision-dtos.md) — why the FE doesn't change.
- [WP-49](../project/backlog/WP-49-openzaak-zaken-read-seam.md) (this), WP-50/51 (CRUD arc so far), WP-52 (notificaties), WP-53 (identity seam + citizen-scoping), WP-54 (integration harness, open).
- `backend/src/BigRegister.Api/Zgw/` — the client; `Data/IZaakSource.cs`/`Data/IDocumentSource.cs` — the seams.
- [WP-49](../project/backlog/WP-49-openzaak-zaken-read-seam.md) (this), WP-50/51 (CRUD arc so far), WP-52 (notificaties), WP-53 (identity seam + citizen-scoping), [WP-54](../project/backlog/WP-54-openzaak-integration-harness.md) (integration harness).
- `backend/src/BigRegister.Api/Zgw/` — the client; `Data/IZaakSource.cs`/`Data/IDocumentSource.cs` — the seams; `backend/openzaak/` — the live-OpenZaak test harness (WP-54).
- [ZGW standard (VNG)](https://vng-realisatie.github.io/gemma-zaken/) · [OpenZaak auth docs](https://open-zaak.readthedocs.io/en/stable/client-development/authentication.html).