test(acl): ACL integration test against real OpenZaak (closes #46) #54

Merged
not merged 7 commits from test/46-acl-openzaak-integration into main 2026-06-29 10:48:01 +00:00
Contributor

What & why

S-04a — the deferred S-04 acceptance criterion: an integration test that drives the real OpenZaakGateway against a real OpenZaak, exercising the live ZGW JWT auth and POST /zaken/api/v1/zaken contract (CRS headers, default-fill, the returned zaak URL) that the stubbed-HttpMessageHandler unit tests cannot.

Closes #46
Closes #53 (adr-proposal — recorded as ADR-0006)
Closes #55 (CI execution — folded into this PR)

Approach (ADR-0006)

The test targets the compose stack, not a Testcontainers graph: Testcontainers .NET has no docker-compose support (it would mean hand-rolling the 5-service stack in C#), and the test can't be hermetic anyway — a published zaaktype pulls in the external selectielijst.openzaak.nl reference API. No new NuGet dependency is added.

  • Gated lane. Acl.IntegrationTests ([Trait("Category","Integration")]) is excluded from the fast lanes: make unit filters Category!=Integration and Stryker is pinned to Acl.Tests. make integration (→ infra/run-integration.sh) owns the stack lifecycle: up → seed → test → always tear down.
  • Publishing is opt-in in the seed. seed_catalogus.py gains an OZ_PUBLISH=1 path that adds the relations OpenZaak's publish requires (two statustypen, a roltype, and a resultaattype with its Selectielijst procestype matched onto the zaaktype). The default seed (ADR-0002) still leaves the zaaktype a concept.

It caught a real bug

Against real OpenZaak the gateway POST returned 400: JsonContent streams the body without a Content-Length, so .NET frames it Transfer-Encoding: chunked, which OpenZaak's uwsgi rejects. A stub handler accepts either framing, so only the integration test surfaced it. Fixed by buffering the body; guarded in the fast lane by a unit test asserting a Content-Length is sent (which also keeps the Stryker score above the ratchet).

Running it on CI

A process on the hosted runner can't reach the stack's published ports (sibling containers via the host daemon — gitea-actions-gotchas.md §5), so infra/run-integration.sh runs both the seed and the test as containers joined to the OpenZaak network, reaching it by container IP (a single-label host like openzaak isn't URL-valid for OpenZaak's own URLValidator; an IPv4 literal is). Code is delivered by image build (services/acl/Dockerfile.integration) and docker cp — never bind-mounts. dotnet runs inside the test image, so the integration CI job needs only Docker.

Definition of Done

  • Linked Gitea issue (above).
  • Failing test committed before the implementation (red test(acl) → green fix(acl)).
  • Implementation makes the test pass.
  • Conventional Commits referencing the issue (refs #46).
  • CI green — all six jobs (lint, build, unit, mutation, integration, compose-smoke).
  • docker compose up smoke unaffected (full stack untouched).
  • Docs updated — ADR-0006, CI runbook, gitea-actions-gotchas §5.
  • ADR added — ADR-0006.
  • [N/A] Demo note — not user-visible (test/infra slice).

Notes for reviewers

  • External dependency: the integration job needs egress to selectielijst.openzaak.nl (from the OpenZaak + seed containers) — the one network touchpoint, overridable via OZ_SELECTIELIJST. Documented in ADR-0006.
  • The chunked-body fix changes OpenZaakGateway production behaviour (buffers before send) — the actual fix the new test drove; worth a careful look.
  • infra/run-integration.sh uses only plain docker primitives (run/create/cp/build) so it works identically under docker compose (CI) and podman-compose (local).
## What & why **S-04a** — the deferred S-04 acceptance criterion: an **integration test that drives the real `OpenZaakGateway` against a real OpenZaak**, exercising the live ZGW JWT auth and `POST /zaken/api/v1/zaken` contract (CRS headers, default-fill, the returned zaak URL) that the stubbed-`HttpMessageHandler` unit tests cannot. Closes #46 Closes #53 (adr-proposal — recorded as ADR-0006) Closes #55 (CI execution — folded into this PR) ## Approach (ADR-0006) The test **targets the compose stack**, not a Testcontainers graph: Testcontainers .NET has no docker-compose support (it would mean hand-rolling the 5-service stack in C#), and the test can't be hermetic anyway — a *published* zaaktype pulls in the external `selectielijst.openzaak.nl` reference API. No new NuGet dependency is added. - **Gated lane.** `Acl.IntegrationTests` (`[Trait("Category","Integration")]`) is excluded from the fast lanes: `make unit` filters `Category!=Integration` and Stryker is pinned to `Acl.Tests`. `make integration` (→ `infra/run-integration.sh`) owns the stack lifecycle: up → seed → test → always tear down. - **Publishing is opt-in in the seed.** `seed_catalogus.py` gains an `OZ_PUBLISH=1` path that adds the relations OpenZaak's publish requires (two statustypen, a roltype, and a resultaattype with its Selectielijst procestype matched onto the zaaktype). The default seed (ADR-0002) still leaves the zaaktype a concept. ## It caught a real bug Against real OpenZaak the gateway POST returned `400`: `JsonContent` streams the body without a `Content-Length`, so .NET frames it `Transfer-Encoding: chunked`, which OpenZaak's uwsgi rejects. A stub handler accepts either framing, so only the integration test surfaced it. Fixed by buffering the body; guarded in the fast lane by a unit test asserting a `Content-Length` is sent (which also keeps the Stryker score above the ratchet). ## Running it on CI A process *on* the hosted runner can't reach the stack's published ports (sibling containers via the host daemon — gitea-actions-gotchas.md §5), so `infra/run-integration.sh` runs **both the seed and the test as containers joined to the OpenZaak network**, reaching it by **container IP** (a single-label host like `openzaak` isn't URL-valid for OpenZaak's own `URLValidator`; an IPv4 literal is). Code is delivered by image build (`services/acl/Dockerfile.integration`) and `docker cp` — never bind-mounts. dotnet runs inside the test image, so the `integration` CI job needs only Docker. ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation (red `test(acl)` → green `fix(acl)`). - [x] Implementation makes the test pass. - [x] Conventional Commits referencing the issue (`refs #46`). - [x] CI green — all six jobs (lint, build, unit, mutation, **integration**, compose-smoke). - [x] `docker compose up` smoke unaffected (full stack untouched). - [x] Docs updated — ADR-0006, CI runbook, gitea-actions-gotchas §5. - [x] ADR added — ADR-0006. - [N/A] Demo note — not user-visible (test/infra slice). ## Notes for reviewers - **External dependency:** the `integration` job needs egress to `selectielijst.openzaak.nl` (from the OpenZaak + seed containers) — the one network touchpoint, overridable via `OZ_SELECTIELIJST`. Documented in ADR-0006. - The chunked-body fix changes `OpenZaakGateway` production behaviour (buffers before send) — the actual fix the new test drove; worth a careful look. - `infra/run-integration.sh` uses only plain docker primitives (run/create/cp/build) so it works identically under docker compose (CI) and podman-compose (local).
not added this to the Iteration 1 — Walking Skeleton milestone 2026-06-29 09:44:22 +00:00
not added 5 commits 2026-06-29 09:44:23 +00:00
OpenZaak rejects a zaak against a concept zaaktype (not-published). Add an
opt-in OZ_PUBLISH path that creates the relations publish requires — two
statustypen, a roltype, and a resultaattype whose Selectielijst procestype is
matched onto the zaaktype — then publishes. Default stays concept (ADR-0002);
only the ACL integration test flips it on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
S-04a: the deferred S-04 acceptance criterion. A gated Acl.IntegrationTests
project (Category=Integration) drives the real OpenZaakGateway against the
running compose stack — real ZGW JWT auth and the real POST /zaken contract a
stubbed HttpMessageHandler cannot exercise. The lane is kept out of the fast
checks: make unit filters Category!=Integration, Stryker is pinned to Acl.Tests,
and a new make integration target brings the stack up, seeds a published zaaktype
and tears down.

Red: against real OpenZaak the gateway POST fails 400 — JsonContent streams the
body chunked and OpenZaak's uwsgi rejects it. Fixed in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenZaak runs behind uwsgi, which rejects a chunked request body with 400.
JsonContent streams without a Content-Length (Transfer-Encoding: chunked), so
buffer it first. Only a real OpenZaak surfaces this — the integration test from
the previous commit now passes. A unit test asserts a Content-Length is sent
(captured before the stub reads/buffers the body), guarding the fix in the fast
lane and killing the Stryker mutant that would otherwise survive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records why the integration test targets the running compose stack rather than a
Testcontainers graph (no .NET compose support; not hermetic anyway due to the
Selectielijst dependency), the opt-in publish seed, and the chunked-body bug the
test caught. Proposed in #53.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ci(acl): run the ACL integration test as a Gitea Actions job (refs #46)
Some checks failed
CI / lint (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 46s
CI / mutation (pull_request) Successful in 1m37s
CI / build (pull_request) Successful in 41s
CI / integration (pull_request) Failing after 5m16s
CI / compose-smoke (pull_request) Successful in 4m11s
855a5565fe
New integration job: setup-dotnet + make integration (stack up, OZ_PUBLISH=1 seed,
Integration-category tests, tear down), with on-failure log dump + teardown like
compose-smoke. Documents the job and the new make target in the CI runbook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added the type:slicearea:aclarea:infra labels 2026-06-29 09:44:41 +00:00
not added 1 commit 2026-06-29 10:04:47 +00:00
ci(acl): keep the integration lane local-only; document the runner gap (refs #46)
All checks were successful
CI / lint (pull_request) Successful in 49s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 50s
CI / mutation (pull_request) Successful in 1m31s
CI / compose-smoke (pull_request) Successful in 3m54s
3829cb0b68
The hosted Gitea runner starts the OpenZaak stack as sibling containers via the
host daemon, so a process on the runner can't reach the published ports — the seed
and dotnet test get Connection refused on localhost:8000. Drop the (non-working)
integration CI job; make integration stays the local / host-runner gate. Document
the limitation in gitea-actions-gotchas.md §5 and the CI runbook, and track running
it inside the compose network in #55. ADR-0006 updated accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not added 1 commit 2026-06-29 10:28:47 +00:00
ci(acl): run the ACL integration test in CI inside the compose network (closes #55) (refs #46)
All checks were successful
CI / lint (pull_request) Successful in 50s
CI / build (pull_request) Successful in 42s
CI / unit (pull_request) Successful in 47s
CI / mutation (pull_request) Successful in 1m31s
CI / integration (pull_request) Successful in 3m43s
CI / compose-smoke (pull_request) Successful in 4m1s
4474585606
The hosted runner can't reach the stack's published ports (sibling containers),
so run the seed and the test as containers joined to the OpenZaak network,
reaching it by container IP — a single-label host like 'openzaak' isn't URL-valid
for OpenZaak's own URLValidator, but an IPv4 literal is. Code is delivered via
image build / docker cp (bind mounts don't reach the daemon either).

- infra/run-integration.sh: up -> wait healthy (docker inspect) -> seed published
  zaaktype (python container on the net) -> build + run the test image on the net
  -> always tear down. Plain docker primitives only (portable docker/podman).
- services/acl/Dockerfile.integration: builds + runs Acl.IntegrationTests; dotnet
  lives in the image, so the CI job needs only Docker (no setup-dotnet).
- make integration now delegates to the script; re-added the Gitea Actions job.

Supersedes the local-only gap documented earlier; #55 is no longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not merged commit 86cc65f4d9 into main 2026-06-29 10:48:01 +00:00
Sign in to join this conversation.