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
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
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>
This commit is contained in:
@@ -63,11 +63,22 @@ jobs:
|
|||||||
path: services/acl/StrykerOutput/**/reports/mutation-report.html
|
path: services/acl/StrykerOutput/**/reports/mutation-report.html
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
# NOTE: there is deliberately no `integration` job here yet. `make integration`
|
integration:
|
||||||
# runs the ACL ↔ real-OpenZaak test locally / on a host-executing runner, but on
|
runs-on: ubuntu-latest
|
||||||
# the hosted runner a process on the runner cannot reach the stack's published
|
steps:
|
||||||
# ports (sibling containers — see gitea-actions-gotchas.md §5). Running it inside
|
- uses: https://github.com/actions/checkout@v4
|
||||||
# the compose network is tracked in #55.
|
# No setup-dotnet: `make integration` runs the seed and the test as containers
|
||||||
|
# *inside* the OpenZaak compose network (reaching it by container IP), so dotnet
|
||||||
|
# lives in the test image and the runner needs only Docker. This sidesteps the
|
||||||
|
# runner being unable to reach published ports (gitea-actions-gotchas.md §5).
|
||||||
|
# Needs egress to pull base images + nuget + selectielijst.openzaak.nl.
|
||||||
|
- run: make integration
|
||||||
|
- name: dump OpenZaak logs on failure
|
||||||
|
if: failure()
|
||||||
|
run: docker compose -f infra/openzaak/docker-compose.yml logs --no-color --tail=80 oz-init openzaak 2>&1 || true
|
||||||
|
- name: tear down on failure
|
||||||
|
if: failure()
|
||||||
|
run: docker compose -f infra/openzaak/docker-compose.yml down --volumes 2>&1 || true
|
||||||
|
|
||||||
compose-smoke:
|
compose-smoke:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
22
Makefile
22
Makefile
@@ -106,20 +106,14 @@ local-down:
|
|||||||
changelog:
|
changelog:
|
||||||
git-cliff --output CHANGELOG.md
|
git-cliff --output CHANGELOG.md
|
||||||
|
|
||||||
## integration: ACL integration tests against a real OpenZaak (S-04a, #46). Brings
|
## integration: ACL integration tests against a real OpenZaak (S-04a, #46). The
|
||||||
## the stack up, seeds a PUBLISHED BIG zaaktype (OZ_PUBLISH=1, so OpenZaak accepts a
|
## seed and the test run inside the compose network (reaching http://openzaak:8000),
|
||||||
## real zaak POST), runs the Integration-category tests, then always tears down.
|
## so this works on the hosted CI runner where a runner process can't reach the
|
||||||
## Kept out of `unit`/`mutation` because it needs the live stack. See ADR-0006.
|
## stack's published ports. Brings the stack up, seeds a PUBLISHED BIG zaaktype,
|
||||||
integration: openzaak-up
|
## runs the Integration-category tests, then always tears down. Kept out of
|
||||||
@bash -c 'set -e; \
|
## `unit`/`mutation` because it needs the live stack. See infra/run-integration.sh + ADR-0006.
|
||||||
for i in $$(seq 1 60); do \
|
integration:
|
||||||
c=$$(curl -s -o /dev/null -w "%{http_code}" $(OZ_BASE)/catalogi/api/v1/ || true); \
|
bash infra/run-integration.sh
|
||||||
[ "$$c" = "200" ] && break; sleep 3; done; echo "OpenZaak ready ($$c)"; \
|
|
||||||
OZ_PUBLISH=1 python3 infra/openzaak/seed_catalogus.py; \
|
|
||||||
rc=0; dotnet test $(SLN) -c Release --filter "Category=Integration" || rc=$$?; \
|
|
||||||
docker compose -f $(OZ_COMPOSE) down --volumes >/dev/null 2>&1 || true; \
|
|
||||||
docker volume rm -f rr-oz-config >/dev/null 2>&1 || true; \
|
|
||||||
exit $$rc'
|
|
||||||
|
|
||||||
## openzaak-up: start the OpenZaak stack (migrations run on first start)
|
## openzaak-up: start the OpenZaak stack (migrations run on first start)
|
||||||
openzaak-up:
|
openzaak-up:
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ itself. No new test dependency is added.**
|
|||||||
- **The lane is kept out of the fast checks.** `make unit` runs with
|
- **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
|
`--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
|
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
|
(`infra/run-integration.sh`) brings the stack up, seeds, runs the lane, and always tears down
|
||||||
the single source of truth (ADR-0005).
|
— mirrored by a Gitea Actions `integration` job. 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
|
- **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
|
`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
|
(begin/eind), a roltype, and a resultaattype whose Selectielijst procestype is matched onto
|
||||||
@@ -70,12 +71,13 @@ itself. No new test dependency is added.**
|
|||||||
in production) but it is a network touchpoint, and a CI environment without egress would need
|
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.
|
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.
|
- **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
|
- **Runs on the hosted runner.** A process *on* the runner can't reach the stack's published
|
||||||
runner, but on Gitea's hosted runner a process *on the runner* cannot reach the stack's
|
ports (Compose starts sibling containers via the host daemon — gitea-actions-gotchas.md §5,
|
||||||
published ports — Compose starts sibling containers via the host daemon, so the ports bind to
|
same split as §1), so `infra/run-integration.sh` runs both the seed and the test as containers
|
||||||
the host, not the runner (gitea-actions-gotchas.md §5, the same split as §1). Running the test
|
*joined to the OpenZaak network*, reaching it by **container IP** (a single-label host like
|
||||||
and seed *inside* the compose network (via a built image, not bind mounts) to gate it in CI is
|
`openzaak` isn't URL-valid for OpenZaak's own `URLValidator`; an IPv4 literal is). Code is
|
||||||
tracked in **#55**. Until then the lane runs locally and is not a merge gate.
|
delivered by image build / `docker cp`, never bind mounts. The CI job therefore needs only
|
||||||
|
Docker — no `setup-dotnet`. (This closed the follow-up that was originally split out as #55.)
|
||||||
|
|
||||||
## Alternatives considered
|
## Alternatives considered
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ and CI cannot drift:
|
|||||||
| `build` | `make build` → `dotnet build … -c Release` | .NET 10 SDK |
|
| `build` | `make build` → `dotnet build … -c Release` | .NET 10 SDK |
|
||||||
| `unit` | `make unit` → `dotnet test … -c Release --filter "Category!=Integration"` | .NET 10 SDK |
|
| `unit` | `make unit` → `dotnet test … -c Release --filter "Category!=Integration"` | .NET 10 SDK |
|
||||||
| `mutation` | `make mutation` → `dotnet tool restore` → `dotnet stryker` (ACL); uploads the HTML report as an artifact | .NET 10 SDK |
|
| `mutation` | `make mutation` → `dotnet tool restore` → `dotnet stryker` (ACL); uploads the HTML report as an artifact | .NET 10 SDK |
|
||||||
|
| `integration` | `make integration` → `infra/run-integration.sh`: OpenZaak up → seed a **published** BIG zaaktype + run `Acl.IntegrationTests` **as containers inside the compose network** → tear down | container engine + egress (base images, nuget, `selectielijst.openzaak.nl`) |
|
||||||
| `compose-smoke` | `make smoke` → seed config volumes → `up -d` (full stack) → `up --wait` durable services → `down` | container engine + compose v2 |
|
| `compose-smoke` | `make smoke` → seed config volumes → `up -d` (full stack) → `up --wait` durable services → `down` | container engine + compose v2 |
|
||||||
|
|
||||||
> **`make integration` is not a hosted-runner job yet.** The ACL ↔ real-OpenZaak
|
> **The `integration` job needs no `setup-dotnet`.** dotnet runs inside the test
|
||||||
> test (`make integration`, ADR-0006) passes locally and on a host-executing runner,
|
> image, and both the seed and the test join the OpenZaak network and reach it by
|
||||||
> but a process on the hosted runner can't reach the stack's published ports
|
> container IP — so the runner never has to reach a published port
|
||||||
> (sibling containers — see [gitea-actions-gotchas.md §5](gitea-actions-gotchas.md)).
|
> (see [gitea-actions-gotchas.md §5](gitea-actions-gotchas.md)).
|
||||||
> Run it inside the compose network to gate it in CI — tracked in **#55**.
|
|
||||||
|
|
||||||
All `uses:` references are absolute, tag-pinned URLs (`https://github.com/actions/checkout@v4`,
|
All `uses:` references are absolute, tag-pinned URLs (`https://github.com/actions/checkout@v4`,
|
||||||
`https://github.com/actions/setup-dotnet@v4`) per CLAUDE.md §8.7 and §15 — Gitea
|
`https://github.com/actions/setup-dotnet@v4`) per CLAUDE.md §8.7 and §15 — Gitea
|
||||||
|
|||||||
@@ -152,6 +152,8 @@ service by name (`http://openzaak:8000`). For a test/seed that needs the repo's
|
|||||||
code, deliver it via a **built image** (not a bind mount — §1), then
|
code, deliver it via a **built image** (not a bind mount — §1), then
|
||||||
`docker run --network <stack>_cg …`.
|
`docker run --network <stack>_cg …`.
|
||||||
|
|
||||||
**Status** — `make integration` (the ACL ↔ real-OpenZaak test, ADR-0006) therefore
|
**Applied** — `make integration` (the ACL ↔ real-OpenZaak test, ADR-0006) does
|
||||||
runs locally / on a host-executing runner only. Gating it on the hosted runner via
|
exactly this: `infra/run-integration.sh` runs the seed and the test as containers on
|
||||||
the compose network is tracked in **#55**.
|
the OpenZaak network and reaches it by **container IP** (a single-label service name
|
||||||
|
like `openzaak` isn't URL-valid — OpenZaak echoes the request host into the URLs it
|
||||||
|
returns and then rejects them with Django's `URLValidator`; an IPv4 literal passes).
|
||||||
|
|||||||
68
infra/run-integration.sh
Executable file
68
infra/run-integration.sh
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Run the ACL ↔ real-OpenZaak integration test (S-04a / #46) end to end.
|
||||||
|
#
|
||||||
|
# Everything that talks to OpenZaak runs *inside* the compose network and reaches
|
||||||
|
# it by service name (http://openzaak:8000) — the hosted CI runner can't reach the
|
||||||
|
# stack's published ports (sibling containers) and bind mounts don't reach the
|
||||||
|
# daemon either (gitea-actions-gotchas.md §1/§5). So we use only plain docker
|
||||||
|
# primitives (run / create / cp / build) — portable across docker compose (CI) and
|
||||||
|
# podman-compose (local), exactly like infra/seed-config.sh. See ADR-0006.
|
||||||
|
#
|
||||||
|
# Steps: bring OpenZaak up → wait for it healthy → seed a PUBLISHED BIG zaaktype
|
||||||
|
# (a seed container on the network) → build + run the test container on the network
|
||||||
|
# → always tear down.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
root="$(cd "$here/.." && pwd)"
|
||||||
|
OZ_COMPOSE="$here/openzaak/docker-compose.yml"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
docker compose -f "$OZ_COMPOSE" down --volumes >/dev/null 2>&1 || true
|
||||||
|
docker volume rm -f rr-oz-config >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo ">> bringing OpenZaak up"
|
||||||
|
bash "$here/seed-config.sh" oz
|
||||||
|
docker compose -f "$OZ_COMPOSE" up -d
|
||||||
|
|
||||||
|
echo ">> waiting for the OpenZaak API container to be healthy"
|
||||||
|
# Match the API container under both docker compose (openzaak-openzaak-1) and
|
||||||
|
# podman-compose (openzaak_openzaak_1) naming; the regex excludes oz-db / oz-redis.
|
||||||
|
api=""
|
||||||
|
for _ in $(seq 1 140); do
|
||||||
|
api="$(docker ps -q --filter 'name=openzaak[-_]openzaak' | head -1)"
|
||||||
|
if [ -n "$api" ]; then
|
||||||
|
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$api" 2>/dev/null || true)"
|
||||||
|
[ "$status" = "healthy" ] && break
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
[ -n "$api" ] || { echo "ERROR: OpenZaak API container never appeared" >&2; exit 1; }
|
||||||
|
[ "${status:-}" = "healthy" ] || { echo "ERROR: OpenZaak not healthy (status=${status:-none})" >&2; exit 1; }
|
||||||
|
|
||||||
|
# The network the API container is attached to — joined by the seed + test below.
|
||||||
|
net="$(docker inspect -f '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$api" | head -1)"
|
||||||
|
# Reach OpenZaak by container IP, not by the service name. OpenZaak echoes its
|
||||||
|
# request Host into the self-referential URLs it returns, then validates those URLs
|
||||||
|
# with Django's URLValidator — which rejects a single-label host like `openzaak`
|
||||||
|
# ("Voer een geldige URL in") while accepting an IPv4 literal (and `localhost`).
|
||||||
|
oz_ip="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$api")"
|
||||||
|
oz_base="http://${oz_ip}:8000"
|
||||||
|
echo ">> OpenZaak healthy on network $net at $oz_base"
|
||||||
|
|
||||||
|
echo ">> seeding a published BIG zaaktype (OZ_PUBLISH=1, inside the network)"
|
||||||
|
sid="$(docker create --network "$net" \
|
||||||
|
-e "OZ_BASE=$oz_base" -e OZ_PUBLISH=1 \
|
||||||
|
python:3-slim python /seed_catalogus.py)"
|
||||||
|
docker cp "$here/openzaak/seed_catalogus.py" "$sid:/seed_catalogus.py"
|
||||||
|
docker start -a "$sid"
|
||||||
|
docker rm -f "$sid" >/dev/null
|
||||||
|
|
||||||
|
echo ">> building the integration test image"
|
||||||
|
docker build -f "$root/services/acl/Dockerfile.integration" -t rr-acl-integration "$root/services/acl"
|
||||||
|
|
||||||
|
echo ">> running the integration tests (inside the network)"
|
||||||
|
docker run --rm --network "$net" -e "OZ_BASE=$oz_base" rr-acl-integration
|
||||||
26
services/acl/Dockerfile.integration
Normal file
26
services/acl/Dockerfile.integration
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Runs the ACL integration tests (Category=Integration) from *inside* the compose
|
||||||
|
# network, so they reach OpenZaak at http://openzaak:8000 by service name. On the
|
||||||
|
# hosted CI runner a process on the runner can't reach the stack's published ports
|
||||||
|
# (sibling containers — gitea-actions-gotchas.md §5), so the test runs as a
|
||||||
|
# container joined to that network instead. See ADR-0006 / #55.
|
||||||
|
#
|
||||||
|
# Build context is services/acl (like the service Dockerfile). dotnet lives in this
|
||||||
|
# image, so the CI `integration` job needs only Docker — no setup-dotnet step.
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Restore first (cached unless the .csproj files change). The integration test
|
||||||
|
# project pulls in Acl.Application + Acl.Infrastructure via its ProjectReferences.
|
||||||
|
COPY Acl.Application/Acl.Application.csproj Acl.Application/
|
||||||
|
COPY Acl.Infrastructure/Acl.Infrastructure.csproj Acl.Infrastructure/
|
||||||
|
COPY Acl.IntegrationTests/Acl.IntegrationTests.csproj Acl.IntegrationTests/
|
||||||
|
RUN dotnet restore Acl.IntegrationTests/Acl.IntegrationTests.csproj
|
||||||
|
|
||||||
|
COPY Acl.Application/ Acl.Application/
|
||||||
|
COPY Acl.Infrastructure/ Acl.Infrastructure/
|
||||||
|
COPY Acl.IntegrationTests/ Acl.IntegrationTests/
|
||||||
|
|
||||||
|
# OZ_BASE is supplied at run time (the OpenZaak container IP — see run-integration.sh,
|
||||||
|
# which passes `-e OZ_BASE=http://<ip>:8000`; a single-label host is not URL-valid).
|
||||||
|
ENTRYPOINT ["dotnet", "test", "Acl.IntegrationTests/Acl.IntegrationTests.csproj", \
|
||||||
|
"-c", "Release", "--filter", "Category=Integration"]
|
||||||
Reference in New Issue
Block a user