From 32c3d314075f34f4ae94abc5213de7fc9407994d Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Mon, 27 Jul 2026 10:57:05 +0200 Subject: [PATCH] test(infra): verify-objecten asserts Objecten API up + token auth + trusts Objecttypen (refs #140) Fails against the current stack (no objecten service yet) with a clear "no running objecten container" message. Green comes with the compose service + seeded setup_configuration in the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/ci.yaml | 5 ++++ Makefile | 7 +++++- infra/objecten-check.py | 49 +++++++++++++++++++++++++++++++++++++ infra/run-objecten-check.sh | 28 +++++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 infra/objecten-check.py create mode 100755 infra/run-objecten-check.sh diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 84fbaea..8d4a012 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -204,6 +204,9 @@ jobs: - name: Objecttypen API up + token authenticates id: objecttypen run: OBJECTTYPEN_TIMEOUT=120 make verify-objecttypen + - name: Objecten API up + token authenticates + trusts Objecttypen + id: objecten + run: OBJECTEN_TIMEOUT=120 make verify-objecten - name: ACL ↔ OpenZaak integration tests id: acl run: make verify-acl @@ -237,6 +240,7 @@ jobs: UP: ${{ steps.up.outcome }} OBS: ${{ steps.obs.outcome }} OBJECTTYPEN: ${{ steps.objecttypen.outcome }} + OBJECTEN: ${{ steps.objecten.outcome }} ACL: ${{ steps.acl.outcome }} NRC: ${{ steps.nrc.outcome }} PROJECTION: ${{ steps.projection.outcome }} @@ -256,6 +260,7 @@ jobs: echo "| Bring up + health | $(icon "$UP") |" echo "| Observability backplane | $(icon "$OBS") |" echo "| Objecttypen API + token | $(icon "$OBJECTTYPEN") |" + echo "| Objecten API + token | $(icon "$OBJECTEN") |" echo "| ACL ↔ OpenZaak | $(icon "$ACL") |" echo "| OpenZaak → NRC | $(icon "$NRC") |" echo "| NRC → Event Subscriber → projection | $(icon "$PROJECTION") |" diff --git a/Makefile b/Makefile index 0a5dd05..7006158 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ export DOCKER_HOST := unix://$(PODMAN_SOCK) endif endif -.PHONY: ci lint build unit mutation frontend integration verify verify-up verify-acl verify-nrc verify-projection verify-bff verify-domain verify-observability verify-tracing verify-metrics verify-objecttypen verify-notifications smoke up down local verify-local local-down changelog openzaak-up openzaak-smoke openzaak-seed openzaak-down stack-up stack-smoke stack-down keycloak-up keycloak-smoke keycloak-down flowable-up flowable-smoke flowable-down help +.PHONY: ci lint build unit mutation frontend integration verify verify-up verify-acl verify-nrc verify-projection verify-bff verify-domain verify-observability verify-tracing verify-metrics verify-objecttypen verify-objecten verify-notifications smoke up down local verify-local local-down changelog openzaak-up openzaak-smoke openzaak-seed openzaak-down stack-up stack-smoke stack-down keycloak-up keycloak-smoke keycloak-down flowable-up flowable-smoke flowable-down help ## ci: run the full pipeline — lint, build, unit, mutation, frontend, verify (mirrors Gitea Actions) ## `verify` is the live-stack stage (full stack up once → ACL + notification checks). @@ -191,6 +191,11 @@ verify-metrics: verify-objecttypen: bash infra/run-objecttypen-check.sh +## verify-objecten: assert the Objecten API is up + its static token authenticates and it +## trusts the Objecttypen API (S-18b), against the already-running stack. +verify-objecten: + bash infra/run-objecten-check.sh + ## verify: local mirror of the CI verify-stack job — full stack up once, all checks, ## tear down (always). For fast single-concern local iteration use `integration` ## (oz-only) or `verify-notifications` (oz+nrc) instead. diff --git a/infra/objecten-check.py b/infra/objecten-check.py new file mode 100644 index 0000000..3d5d1bc --- /dev/null +++ b/infra/objecten-check.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""S-18b (#140): prove the Objecten API is up and its static token authenticates. + +Assert an unauthenticated call to /api/v2/objects is 401 and an authenticated one (the seeded dev +token) is 200 — i.e. the service migrated, booted, and setup_configuration provisioned the token +and the Objecttypen service it trusts. Stdlib only so it runs in a bare python:3-slim container on +the compose network. +""" +import os +import sys +import time +import urllib.error +import urllib.request + +BASE = os.environ["OBJECTEN"] # http://:8000 +TOKEN = os.environ["OBJECTEN_TOKEN"] +TIMEOUT = int(os.environ.get("OBJECTEN_TIMEOUT", "60")) + + +def status(url, token=None): + req = urllib.request.Request(url) + if token: + req.add_header("Authorization", f"Token {token}") + try: + with urllib.request.urlopen(req, timeout=10) as r: + return r.status + except urllib.error.HTTPError as e: + return e.code + except Exception: + return 0 + + +def main(): + url = f"{BASE}/api/v2/objects" + deadline = time.time() + TIMEOUT + while time.time() < deadline: + unauth = status(url) + authed = status(url, TOKEN) + if unauth == 401 and authed == 200: + print(f"OK — {url}: no-auth {unauth}, token {authed}") + return 0 + time.sleep(3) + print(f"FAIL — {url}: expected no-auth 401 + token 200, got {status(url)} / {status(url, TOKEN)}", + file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/infra/run-objecten-check.sh b/infra/run-objecten-check.sh new file mode 100755 index 0000000..dbfc1bc --- /dev/null +++ b/infra/run-objecten-check.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# S-18b (#140): assert the Objecten API is healthy + its static token authenticates, against an +# ALREADY-RUNNING stack. Runs the check in a python:3-slim container on the stack network (the +# service is reached by container IP; the runner can't reach published ports — gitea-actions-gotchas.md +# §5/§6). Does NOT manage the stack lifecycle. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# The dev token provisioned by infra/objecten/setup_configuration/data.yaml. +TOKEN="${OBJECTEN_TOKEN:-1234567890abcdef1234567890abcdef12345678}" + +ot="$(docker ps -q --filter 'name=objecten' --filter 'health=healthy' | head -1)" +[ -n "$ot" ] || ot="$(docker ps -q --filter 'name=[-_]objecten[-_]' | head -1)" +[ -n "$ot" ] || { echo "ERROR: no running objecten container — bring the stack up first" >&2; exit 1; } +net="$(docker inspect -f '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$ot" | head -1)" +ip="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$ot")" +echo ">> network=$net objecten=$ip" + +cid="$(docker create --network "$net" \ + -e "OBJECTEN=http://$ip:8000" -e "OBJECTEN_TOKEN=$TOKEN" \ + -e "OBJECTEN_TIMEOUT=${OBJECTEN_TIMEOUT:-60}" \ + python:3-slim python /objecten-check.py)" +docker cp "$here/objecten-check.py" "$cid:/objecten-check.py" >/dev/null +rc=0; docker start -a "$cid" || rc=$? +docker rm -f "$cid" >/dev/null +exit $rc