All checks were successful
## What & why Second sub-slice of **S-11 · Withdrawal (Flow 3)** (#12). S-11a (#88) made a withdrawal advance the aggregate to INGETROKKEN; this sub-slice **cancels the running Flowable process** so the withdrawn case leaves the behandelaar's werkbak. - **BPMN** (`registratie.bpmn`): an interrupting message boundary event (`RegistratieIngetrokken`) on the `Beoordelen` task, routing to a dedicated "Registratie ingetrokken" end event. - **Workflow Client**: `WithdrawBeoordelingAsync(executionId)` delivers `messageEventReceived` to the task's execution (PUT); `BeoordelingTask` now carries its `executionId`. - **`WithdrawRegistration` handler**: after the domain transition, finds the open `Beoordelen` task for the registration and delivers the withdrawal message — best-effort, mirroring how the beoordeling completes its task. - **Werkbak**: also filters out registrations that are no longer open, so a withdrawn case never surfaces even in the brief window before cancellation lands. - **ADR-0014** records the decision (message event in BPMN vs. deleting the instance from code). - **verify (`run-domain-check.sh`)**: a second registration parks at `Beoordelen`, is withdrawn via the domain, and the check asserts its `Beoordelen` task disappears — so verify-stack validates the live Flowable message correlation. Refs #12 (S-11c — the BFF + self-service "trek aanvraag in" button + e2e — closes it). ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation (red → green per commit). - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected (BPMN redeploys on a fresh CI DB via flowable-init). - [x] ADR added (ADR-0014). - [x] Docs — the user-visible demo note lands with S-11c. ## Notes for reviewers - Verified locally: `Big.Tests` 94/94 pass; `Big.Api` builds; `registratie.bpmn` is well-formed. - The Flowable message-correlation REST shape is validated **live** by verify-stack (the Workflow Client unit tests stub the exchange and assert only the request shape, per ADR-0009) — the new `run-domain-check.sh` withdrawal step is that live check. - Known gap (ADR-0014): a withdrawal that races ahead of the process reaching `Beoordelen` finds no task to cancel; the aggregate is still INGETROKKEN and the werkbak filter hides it, but that instance parks unattended. A process-level event subprocess would close the gap — deferred. Reviewed-on: #89
159 lines
8.4 KiB
Bash
Executable File
159 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Verify the BIG Domain Service end-to-end (S-05) against an ALREADY-RUNNING full stack:
|
|
# domain → Flowable (start the registratie process + external-task worker) → ACL → OpenZaak.
|
|
# Submits a registration to the domain and asserts the worker opens a zaak in OpenZaak and
|
|
# records its URL on the aggregate (ADR-0009).
|
|
#
|
|
# The seeded zaaktype URL is server-assigned, so it isn't knowable at initial bring-up. This
|
|
# script therefore seeds a published BIG zaaktype and recreates the `acl` service configured to
|
|
# default-fill it — pointing the ACL at the SAME OpenZaak host that owns the URL, so zaak creation
|
|
# is host-consistent (exactly the configuration the ACL integration test proves, ADR-0006). That
|
|
# one recreate aside, the caller owns stack bring-up + teardown.
|
|
#
|
|
# All in-network, reaching services by container IP (a single-label host isn't URL-valid; the
|
|
# runner can't reach published ports — gitea-actions-gotchas.md §5/§6). Plain docker primitives.
|
|
set -euo pipefail
|
|
|
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
root="$(cd "$here/.." && pwd)"
|
|
compose="$root/infra/docker-compose.yml"
|
|
|
|
ip() { docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$1"; }
|
|
|
|
oz="$(docker ps -q --filter 'name=[-_]openzaak[-_]' | head -1)"
|
|
dom="$(docker ps -q --filter 'name=domain' | head -1)"
|
|
[ -n "$oz" ] || { echo "ERROR: no running OpenZaak container — bring the stack up first" >&2; exit 1; }
|
|
[ -n "$dom" ] || { echo "ERROR: no running domain container — bring the stack up first" >&2; exit 1; }
|
|
net="$(docker inspect -f '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$oz" | head -1)"
|
|
oz_ip="$(ip "$oz")"; dom_ip="$(ip "$dom")"
|
|
oz_base="http://$oz_ip:8000"
|
|
echo ">> openzaak=$oz_ip domain=$dom_ip network=$net"
|
|
|
|
echo ">> seeding a published BIG zaaktype (idempotent) and capturing its URL"
|
|
sid="$(docker create --network "$net" -e "OZ_BASE=$oz_base" -e OZ_PUBLISH=1 python:3-slim python /seed.py)"
|
|
docker cp "$here/openzaak/seed_catalogus.py" "$sid:/seed.py" >/dev/null
|
|
zt_url="$(docker start -a "$sid" | sed -n 's/^ZAAKTYPE_URL //p' | head -1)"
|
|
docker rm -f "$sid" >/dev/null
|
|
[ -n "$zt_url" ] || { echo "ERROR: seed did not report a ZAAKTYPE_URL" >&2; exit 1; }
|
|
echo ">> zaaktype: $zt_url"
|
|
|
|
echo ">> recreating the acl service pointed at the seeded zaaktype (host-consistent)"
|
|
ACL_ZAAKTYPE_URL="$zt_url" ACL_OPENZAAK_BASEURL="$oz_base/" docker compose -f "$compose" up -d acl
|
|
WAIT_TIMEOUT="${WAIT_TIMEOUT:-120}" bash "$here/wait-healthy.sh" acl
|
|
|
|
echo ">> submitting a registration to the domain"
|
|
loc="$(docker run --rm --network "$net" curlimages/curl:latest \
|
|
-fsS -D - -o /dev/null -X POST "http://$dom_ip:8080/registrations" \
|
|
-H 'Content-Type: application/json' -d '{"bsn":"123456782"}' \
|
|
| sed -n 's/\r$//; s/^[Ll]ocation: //p' | head -1)"
|
|
[ -n "$loc" ] || { echo "ERROR: POST /registrations returned no Location" >&2; exit 1; }
|
|
echo ">> registration accepted at $loc"
|
|
|
|
echo ">> polling the domain until the worker records the opened zaak"
|
|
zaak_ok=""
|
|
for _ in $(seq 1 30); do
|
|
body="$(docker run --rm --network "$net" curlimages/curl:latest \
|
|
-fsS "http://$dom_ip:8080$loc" 2>/dev/null || true)"
|
|
if echo "$body" | grep -q '/zaken/api/v1/zaken/'; then
|
|
echo "OK — the domain opened a zaak and recorded it on the registration:"
|
|
echo "$body" | cut -c1-300
|
|
zaak_ok=1
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
if [ -z "$zaak_ok" ]; then
|
|
echo "FAIL — the registration never received a zaak URL" >&2
|
|
echo "--- domain log ---" >&2; docker logs "$dom" 2>&1 | tail -15 >&2
|
|
acl="$(docker ps -q --filter 'name=[-_]acl[-_]' | head -1)"
|
|
[ -n "$acl" ] && { echo "--- acl log ---" >&2; docker logs "$acl" 2>&1 | tail -15 >&2; }
|
|
exit 1
|
|
fi
|
|
|
|
# ── S-12b: the process now parks at the Beoordelen user task. Exercise the exact Flowable REST
|
|
# contract the Workflow Client uses (query/claim/complete) against the live engine, reaching
|
|
# flowable-rest by container IP (same in-network constraint as above). ──────────────────────────
|
|
fl="$(docker ps -q --filter 'name=flowable-rest' | head -1)"
|
|
[ -n "$fl" ] || { echo "ERROR: no running flowable-rest container" >&2; exit 1; }
|
|
fl_base="http://$(ip "$fl"):8080/flowable-rest/service"
|
|
reg_id="${loc##*/}"
|
|
|
|
# Extracts the Beoordelen task id for a given registration from a Flowable task-query response on
|
|
# stdin. Tolerates an empty/non-JSON body (a transient failure during the poll) by printing nothing.
|
|
task_for_reg() { REG_ID="$1" python3 -c "import os,sys,json
|
|
try:
|
|
d=json.load(sys.stdin)
|
|
except Exception:
|
|
d={}
|
|
rid=os.environ['REG_ID']
|
|
# Flowable's task-query returns the included process variables under 'variables'.
|
|
print(next((t['id'] for t in (d.get('data') or [])
|
|
if any(v.get('name')=='registrationId' and v.get('value')==rid for v in (t.get('variables') or []))), ''))"; }
|
|
|
|
flcurl() { docker run --rm --network "$net" curlimages/curl:latest -fsS -u rest-admin:test "$@"; }
|
|
query='{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen","includeProcessVariables":true}'
|
|
|
|
echo ">> polling Flowable for the Beoordelen user task (werkbak)"
|
|
task_id=""
|
|
for _ in $(seq 1 30); do
|
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query" 2>/dev/null || true)"
|
|
task_id="$(printf '%s' "$resp" | task_for_reg "$reg_id")"
|
|
[ -n "$task_id" ] && break
|
|
sleep 2
|
|
done
|
|
[ -n "$task_id" ] || { echo "FAIL — no Beoordelen task appeared for registration $reg_id" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
|
echo ">> Beoordelen task $task_id is waiting"
|
|
|
|
echo ">> claiming the task as merel-behandelaar"
|
|
flcurl -X POST "$fl_base/runtime/tasks/$task_id" -H 'Content-Type: application/json' \
|
|
-d '{"action":"claim","assignee":"merel-behandelaar"}' >/dev/null
|
|
|
|
echo ">> completing the beoordeling (goedkeuren)"
|
|
flcurl -X POST "$fl_base/runtime/tasks/$task_id" -H 'Content-Type: application/json' \
|
|
-d '{"action":"complete","variables":[{"name":"besluit","type":"string","value":"goedkeuren"}]}' >/dev/null
|
|
|
|
echo ">> asserting the process finished (no Beoordelen task remains for the registration)"
|
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query")"
|
|
still="$(printf '%s' "$resp" | task_for_reg "$reg_id")"
|
|
[ -z "$still" ] || { echo "FAIL — Beoordelen task $still still active after completion" >&2; exit 1; }
|
|
echo "OK — behandelaar claimed and completed the Beoordelen task; the registratie process finished"
|
|
|
|
# ── S-11: withdrawal. A second registration parks at Beoordelen; the citizen withdraws it via the
|
|
# domain, which delivers the RegistratieIngetrokken message to the task's execution, tripping the
|
|
# BPMN boundary event so the process ends and the Beoordelen task disappears (ADR-0014). ────────────
|
|
echo ">> submitting a second registration to withdraw"
|
|
loc2="$(docker run --rm --network "$net" curlimages/curl:latest \
|
|
-fsS -D - -o /dev/null -X POST "http://$dom_ip:8080/registrations" \
|
|
-H 'Content-Type: application/json' -d '{"bsn":"123456782"}' \
|
|
| sed -n 's/\r$//; s/^[Ll]ocation: //p' | head -1)"
|
|
[ -n "$loc2" ] || { echo "FAIL — second POST /registrations returned no Location" >&2; exit 1; }
|
|
reg_id2="${loc2##*/}"
|
|
echo ">> second registration $reg_id2"
|
|
|
|
echo ">> polling Flowable for its Beoordelen task"
|
|
task_id2=""
|
|
for _ in $(seq 1 30); do
|
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query" 2>/dev/null || true)"
|
|
task_id2="$(printf '%s' "$resp" | task_for_reg "$reg_id2")"
|
|
[ -n "$task_id2" ] && break
|
|
sleep 2
|
|
done
|
|
[ -n "$task_id2" ] || { echo "FAIL — no Beoordelen task appeared for registration $reg_id2" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
|
echo ">> Beoordelen task $task_id2 is waiting; withdrawing the registration via the domain"
|
|
|
|
docker run --rm --network "$net" curlimages/curl:latest \
|
|
-fsS -X POST "http://$dom_ip:8080/registrations/$reg_id2/withdraw" >/dev/null
|
|
|
|
echo ">> asserting the process was cancelled (no Beoordelen task remains for the registration)"
|
|
gone=""
|
|
for _ in $(seq 1 15); do
|
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query" 2>/dev/null || true)"
|
|
still2="$(printf '%s' "$resp" | task_for_reg "$reg_id2")"
|
|
[ -z "$still2" ] && { gone=1; break; }
|
|
sleep 2
|
|
done
|
|
[ -n "$gone" ] || { echo "FAIL — Beoordelen task for $reg_id2 still active after withdrawal" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
|
echo "OK — withdrawal cancelled the Beoordelen task; the registratie process ended (ingetrokken)"
|
|
exit 0
|