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>
90 lines
5.8 KiB
Markdown
90 lines
5.8 KiB
Markdown
# ADR-0006: Provision the ACL integration test against the compose stack
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-06-29
|
|
- **Deciders:** Respellion engineering
|
|
- **Relates to:** S-04a (#46); proposed in #53; builds on ADR-0001 (loose coupling), ADR-0002 (catalogus design), ADR-0003 (default-fill); supports CLAUDE.md §11 (integration tests via real containers)
|
|
|
|
## Context
|
|
|
|
S-04 delivered the ACL's one operation — `OpenZaakGateway.OpenZaakAsync` — with unit
|
|
tests against a stubbed `HttpMessageHandler` and a Reqnroll scenario over an in-memory
|
|
stand-in. The deferred S-04 acceptance criterion (S-04a) is the one a stub cannot meet:
|
|
|
|
> Integration test using Testcontainers against real OpenZaak passes.
|
|
|
|
The test must drive the gateway against a **real** OpenZaak — real ZGW JWT auth, the real
|
|
`POST /zaken/api/v1/zaken` contract, real CRS handling — and assert a zaak comes back.
|
|
|
|
Two ways to stand OpenZaak up were considered (the issue's open question): (a) a full
|
|
**Testcontainers** graph started by the test, or (b) target the **running compose stack**
|
|
the repo already defines (`infra/openzaak/docker-compose.yml`, `make openzaak-up`).
|
|
|
|
Investigation reversed the initially-favoured Testcontainers option:
|
|
|
|
1. **Testcontainers .NET has no docker-compose support.** OpenZaak needs PostGIS + Redis +
|
|
a `setup_configuration` one-shot (the JWT client) + the API. Honouring "full graph" would
|
|
mean re-implementing that five-service stack — init ordering, the config volume, health
|
|
gating — by hand in C#, duplicating the maintained compose file and rotting with it. That
|
|
rubs against CLAUDE.md §13 ("if a test is hard to write, the design is wrong").
|
|
2. **The test cannot be hermetic anyway.** OpenZaak's Zaken API rejects a zaak against a
|
|
*concept* zaaktype (`not-published`), and a *published* zaaktype requires ≥1 resultaattype,
|
|
which OpenZaak validates by fetching the external **Selectielijst** reference API
|
|
(`selectielijst.openzaak.nl`). So a real zaak POST already depends on outbound internet
|
|
from the OpenZaak container — the self-containment that motivated Testcontainers is lost
|
|
regardless of how the containers are started.
|
|
|
|
## Decision
|
|
|
|
**The ACL integration test targets the running compose stack; it does not start containers
|
|
itself. No new test dependency is added.**
|
|
|
|
- A gated test project `Acl.IntegrationTests` (`[Trait("Category","Integration")]`) talks to
|
|
OpenZaak with a plain `HttpClient`, reusing the same endpoint + JWT-client config the seed
|
|
uses (`OZ_BASE` / `OZ_CLIENT_ID` / `OZ_SECRET`, defaulting to the local stack). It locates
|
|
the published `BIG-REGISTRATIE` zaaktype via the Catalogi API and exercises the real
|
|
`OpenZaakGateway` against it.
|
|
- **The lane is kept out of the fast checks.** `make unit` runs with
|
|
`--filter "Category!=Integration"`; Stryker is pinned to `Acl.Tests` (`test-projects`), so
|
|
neither the unit nor the mutation lane needs a live stack. A new `make integration` target
|
|
brings the stack up, seeds, runs the lane, and always tears down. This matches `make` being
|
|
the single source of truth (ADR-0005).
|
|
- **Publishing is opt-in in the seed.** `infra/openzaak/seed_catalogus.py` gains an
|
|
`OZ_PUBLISH=1` path that adds the relations OpenZaak's publish requires — two statustypen
|
|
(begin/eind), a roltype, and a resultaattype whose Selectielijst procestype is matched onto
|
|
the zaaktype — then publishes. The default seed (S-01 / ADR-0002) still leaves the zaaktype
|
|
a concept; only `make integration` flips the switch.
|
|
|
|
## Consequences
|
|
|
|
- **Positive:** a small, honest test over the real ZGW contract with no bespoke orchestration
|
|
to maintain; the compose stack is exercised exactly as operators run it; no new dependency.
|
|
- **It caught a real bug.** The gateway sent the zaak body via `JsonContent` without a
|
|
`Content-Length`, so .NET framed it as `Transfer-Encoding: chunked`, which OpenZaak's uwsgi
|
|
rejects with 400. A stubbed handler accepts either framing, so only a real OpenZaak surfaced
|
|
it. Fixed by buffering the body (`LoadIntoBufferAsync`); guarded in the fast lane by a unit
|
|
test asserting a `Content-Length` is set. This is the concrete justification for §11's
|
|
integration tier.
|
|
- **External dependency:** the integration job needs the OpenZaak container to reach
|
|
`selectielijst.openzaak.nl`. It is a stable public reference API (the same one OpenZaak uses
|
|
in production) but it is a network touchpoint, and a CI environment without egress would need
|
|
a local Selectielijst service or a recorded fixture. `OZ_SELECTIELIJST` overrides the base URL.
|
|
- **Cost:** the lane needs the stack up first, so it is separate from the fast lanes.
|
|
- **Not yet a hosted-runner job.** `make integration` passes locally and on a host-executing
|
|
runner, but on Gitea's hosted runner a process *on the runner* cannot reach the stack's
|
|
published ports — Compose starts sibling containers via the host daemon, so the ports bind to
|
|
the host, not the runner (gitea-actions-gotchas.md §5, the same split as §1). Running the test
|
|
and seed *inside* the compose network (via a built image, not bind mounts) to gate it in CI is
|
|
tracked in **#55**. Until then the lane runs locally and is not a merge gate.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Full Testcontainers graph** — rejected: re-implements the compose stack in C# (brittle,
|
|
duplicative) for no hermeticity gain, since the Selectielijst dependency remains.
|
|
- **Single OpenZaak container (sqlite/locmem)** — rejected: diverges from the real
|
|
PostGIS-backed, Redis-cached deployment; the Zaken API is a geo API and the divergence would
|
|
undermine the contract the test exists to verify.
|
|
- **Mock OpenZaak / record-replay** — rejected: that is what the existing stubbed-handler unit
|
|
tests already do; it cannot exercise the real contract, and would not have caught the chunked
|
|
body bug.
|