test(infra): verify-registerrecord asserts RegisterRecord objecttype registered + published (refs #141)
Fails against a stack with Objecttypen up but no objecttype seeded yet: "no objecttype named 'RegisterRecord'". Green comes with the seeded registerrecord-init one-shot in the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,6 +207,9 @@ jobs:
|
|||||||
- name: Objecten API up + token authenticates + trusts Objecttypen
|
- name: Objecten API up + token authenticates + trusts Objecttypen
|
||||||
id: objecten
|
id: objecten
|
||||||
run: OBJECTEN_TIMEOUT=120 make verify-objecten
|
run: OBJECTEN_TIMEOUT=120 make verify-objecten
|
||||||
|
- name: RegisterRecord objecttype registered + published
|
||||||
|
id: registerrecord
|
||||||
|
run: REGISTERRECORD_TIMEOUT=120 make verify-registerrecord
|
||||||
- name: ACL ↔ OpenZaak integration tests
|
- name: ACL ↔ OpenZaak integration tests
|
||||||
id: acl
|
id: acl
|
||||||
run: make verify-acl
|
run: make verify-acl
|
||||||
@@ -241,6 +244,7 @@ jobs:
|
|||||||
OBS: ${{ steps.obs.outcome }}
|
OBS: ${{ steps.obs.outcome }}
|
||||||
OBJECTTYPEN: ${{ steps.objecttypen.outcome }}
|
OBJECTTYPEN: ${{ steps.objecttypen.outcome }}
|
||||||
OBJECTEN: ${{ steps.objecten.outcome }}
|
OBJECTEN: ${{ steps.objecten.outcome }}
|
||||||
|
REGISTERRECORD: ${{ steps.registerrecord.outcome }}
|
||||||
ACL: ${{ steps.acl.outcome }}
|
ACL: ${{ steps.acl.outcome }}
|
||||||
NRC: ${{ steps.nrc.outcome }}
|
NRC: ${{ steps.nrc.outcome }}
|
||||||
PROJECTION: ${{ steps.projection.outcome }}
|
PROJECTION: ${{ steps.projection.outcome }}
|
||||||
@@ -261,6 +265,7 @@ jobs:
|
|||||||
echo "| Observability backplane | $(icon "$OBS") |"
|
echo "| Observability backplane | $(icon "$OBS") |"
|
||||||
echo "| Objecttypen API + token | $(icon "$OBJECTTYPEN") |"
|
echo "| Objecttypen API + token | $(icon "$OBJECTTYPEN") |"
|
||||||
echo "| Objecten API + token | $(icon "$OBJECTEN") |"
|
echo "| Objecten API + token | $(icon "$OBJECTEN") |"
|
||||||
|
echo "| RegisterRecord objecttype | $(icon "$REGISTERRECORD") |"
|
||||||
echo "| ACL ↔ OpenZaak | $(icon "$ACL") |"
|
echo "| ACL ↔ OpenZaak | $(icon "$ACL") |"
|
||||||
echo "| OpenZaak → NRC | $(icon "$NRC") |"
|
echo "| OpenZaak → NRC | $(icon "$NRC") |"
|
||||||
echo "| NRC → Event Subscriber → projection | $(icon "$PROJECTION") |"
|
echo "| NRC → Event Subscriber → projection | $(icon "$PROJECTION") |"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export DOCKER_HOST := unix://$(PODMAN_SOCK)
|
|||||||
endif
|
endif
|
||||||
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-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
|
.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-registerrecord 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)
|
## 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).
|
## `verify` is the live-stack stage (full stack up once → ACL + notification checks).
|
||||||
@@ -196,6 +196,11 @@ verify-objecttypen:
|
|||||||
verify-objecten:
|
verify-objecten:
|
||||||
bash infra/run-objecten-check.sh
|
bash infra/run-objecten-check.sh
|
||||||
|
|
||||||
|
## verify-registerrecord: assert the RegisterRecord objecttype is registered + published in the
|
||||||
|
## Objecttypen API (S-18c), against the already-running stack.
|
||||||
|
verify-registerrecord:
|
||||||
|
bash infra/run-registerrecord-check.sh
|
||||||
|
|
||||||
## verify: local mirror of the CI verify-stack job — full stack up once, all checks,
|
## 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`
|
## tear down (always). For fast single-concern local iteration use `integration`
|
||||||
## (oz-only) or `verify-notifications` (oz+nrc) instead.
|
## (oz-only) or `verify-notifications` (oz+nrc) instead.
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""S-18c (#141): prove the RegisterRecord objecttype is registered + published in the Objecttypen API.
|
||||||
|
|
||||||
|
Assert the objecttype named "RegisterRecord" exists, has a **published** version, and that version's
|
||||||
|
jsonSchema carries the public-safe fields (id, status, reference) — i.e. registerrecord-init ran and
|
||||||
|
seeded the schema S-19 will write records against. Stdlib only so it runs in a bare python:3-slim
|
||||||
|
container on the compose network.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
BASE = os.environ["OBJECTTYPEN"] # http://<ip>:8000
|
||||||
|
TOKEN = os.environ["OBJECTTYPEN_TOKEN"]
|
||||||
|
TIMEOUT = int(os.environ.get("REGISTERRECORD_TIMEOUT", "60"))
|
||||||
|
NAME = "RegisterRecord"
|
||||||
|
EXPECTED_FIELDS = {"id", "status", "reference"}
|
||||||
|
|
||||||
|
|
||||||
|
def get(path):
|
||||||
|
req = urllib.request.Request(f"{BASE}{path}", headers={"Authorization": f"Token {TOKEN}"})
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as r:
|
||||||
|
return json.load(r)
|
||||||
|
|
||||||
|
|
||||||
|
def check():
|
||||||
|
"""Return (ok, detail). Raises on transport errors so the caller can retry."""
|
||||||
|
ots = get("/api/v2/objecttypes").get("results", [])
|
||||||
|
match = next((o for o in ots if o.get("name") == NAME), None)
|
||||||
|
if not match:
|
||||||
|
return False, f"no objecttype named {NAME!r} (have: {[o.get('name') for o in ots]})"
|
||||||
|
if not match.get("versions"):
|
||||||
|
return False, f"{NAME} exists but has no versions"
|
||||||
|
# The versions list holds URLs; fetch each to find a published one.
|
||||||
|
for ver_url in match["versions"]:
|
||||||
|
ver = get(ver_url[len(BASE):] if ver_url.startswith(BASE) else ver_url)
|
||||||
|
if ver.get("status") != "published":
|
||||||
|
continue
|
||||||
|
props = set((ver.get("jsonSchema") or {}).get("properties", {}))
|
||||||
|
if not EXPECTED_FIELDS <= props:
|
||||||
|
return False, f"published version missing fields: {EXPECTED_FIELDS - props}"
|
||||||
|
return True, f"{NAME} v{ver.get('version')} published, fields={sorted(props)}"
|
||||||
|
return False, f"{NAME} has versions but none are published"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
deadline = time.time() + TIMEOUT
|
||||||
|
detail = "no attempt"
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
ok, detail = check()
|
||||||
|
if ok:
|
||||||
|
print(f"OK — {detail}")
|
||||||
|
return 0
|
||||||
|
except (urllib.error.URLError, ConnectionError, TimeoutError) as e:
|
||||||
|
detail = f"transport: {e}"
|
||||||
|
time.sleep(3)
|
||||||
|
print(f"FAIL — {detail}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# S-18c (#141): assert the RegisterRecord objecttype is registered + published in the Objecttypen
|
||||||
|
# API, 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/objecttypen/setup_configuration/data.yaml.
|
||||||
|
TOKEN="${OBJECTTYPEN_TOKEN:-0123456789abcdef0123456789abcdef01234567}"
|
||||||
|
|
||||||
|
ot="$(docker ps -q --filter 'name=objecttypen' --filter 'health=healthy' | head -1)"
|
||||||
|
[ -n "$ot" ] || ot="$(docker ps -q --filter 'name=[-_]objecttypen[-_]' | head -1)"
|
||||||
|
[ -n "$ot" ] || { echo "ERROR: no running objecttypen 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 objecttypen=$ip"
|
||||||
|
|
||||||
|
cid="$(docker create --network "$net" \
|
||||||
|
-e "OBJECTTYPEN=http://$ip:8000" -e "OBJECTTYPEN_TOKEN=$TOKEN" \
|
||||||
|
-e "REGISTERRECORD_TIMEOUT=${REGISTERRECORD_TIMEOUT:-60}" \
|
||||||
|
python:3-slim python /registerrecord-check.py)"
|
||||||
|
docker cp "$here/registerrecord-check.py" "$cid:/registerrecord-check.py" >/dev/null
|
||||||
|
rc=0; docker start -a "$cid" || rc=$?
|
||||||
|
docker rm -f "$cid" >/dev/null
|
||||||
|
exit $rc
|
||||||
Reference in New Issue
Block a user