test(acl,verify): cancellation reaches Geannuleerd live (refs #106)
Adds an ACL↔OpenZaak integration test asserting SetZaakToCancellationStatusAsync records the Geannuleerd status + a resultaat against real OpenZaak, and extends the domain verify script to confirm a timed-out registration's zaak is cancelled to Geannuleerd end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -309,10 +309,11 @@ done
|
|||||||
[ -n "$escalated" ] || { echo "FAIL — Beoordelen task not reassigned to teamlead (candidate groups: '$groups')" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
[ -n "$escalated" ] || { echo "FAIL — Beoordelen task not reassigned to teamlead (candidate groups: '$groups')" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
||||||
echo "OK — the 14-day timer escalated the still-open Beoordelen task to the teamlead"
|
echo "OK — the 14-day timer escalated the still-open Beoordelen task to the teamlead"
|
||||||
|
|
||||||
# ── S-10a: document timeout. A registration parks at WachtOpDocumenten and — unlike every block above —
|
# ── S-10a/S-10c: document timeout. A registration parks at WachtOpDocumenten and — unlike every block
|
||||||
# its documents never arrive. We fire its 30-day boundary timer early via the management API; the
|
# above — its documents never arrive. We fire its 30-day boundary timer early via the management API;
|
||||||
# INTERRUPTING timer cancels the wait and routes a token to the RegistratieVerlopen external task. The
|
# the INTERRUPTING timer cancels the wait and routes a token to the RegistratieVerlopen external task.
|
||||||
# domain's timeout worker acquires it and expires the registration to VERLOPEN (ADR-0017). ────────────
|
# The domain's timeout worker acquires it, cancels the ZGW zaak via the ACL (S-10c), and expires the
|
||||||
|
# registration to VERLOPEN (ADR-0017). ─────────────────────────────────────────────────────────────
|
||||||
echo ">> submitting a registration to let its document term lapse"
|
echo ">> submitting a registration to let its document term lapse"
|
||||||
locv="$(docker run --rm --network "$net" curlimages/curl:latest \
|
locv="$(docker run --rm --network "$net" curlimages/curl:latest \
|
||||||
-fsS -D - -o /dev/null -X POST "http://$dom_ip:8080/registrations" \
|
-fsS -D - -o /dev/null -X POST "http://$dom_ip:8080/registrations" \
|
||||||
@@ -354,4 +355,46 @@ for _ in $(seq 1 30); do
|
|||||||
done
|
done
|
||||||
[ -n "$verlopen" ] || { echo "FAIL — registration $reg_idv not VERLOPEN after the document timer fired (body: $body)" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
[ -n "$verlopen" ] || { echo "FAIL — registration $reg_idv not VERLOPEN after the document timer fired (body: $body)" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
||||||
echo "OK — the 30-day document timer expired the registration to VERLOPEN"
|
echo "OK — the 30-day document timer expired the registration to VERLOPEN"
|
||||||
|
|
||||||
|
# S-10c: the worker cancels the ZGW zaak (ACL-first, before it expires the aggregate), so a VERLOPEN
|
||||||
|
# registration must carry a zaak whose current status is "Geannuleerd". Read it back from OpenZaak with
|
||||||
|
# a ZGW token minted like the seed's client (the same client OpenZaak trusts for this stack).
|
||||||
|
zaak_url_v="$(printf '%s' "$body" | grep -oiE 'http://[^"]*/zaken/api/v1/zaken/[a-f0-9-]+' | head -1)"
|
||||||
|
[ -n "$zaak_url_v" ] || { echo "FAIL — VERLOPEN registration $reg_idv exposes no zaak URL (body: $body)" >&2; exit 1; }
|
||||||
|
echo ">> confirming the zaak $zaak_url_v reached the Geannuleerd status in OpenZaak"
|
||||||
|
|
||||||
|
read_zaak_status() {
|
||||||
|
docker run --rm --network "$net" \
|
||||||
|
-e OZ_CLIENT_ID="${OZ_CLIENT_ID:-big-reference-seed}" \
|
||||||
|
-e OZ_SECRET="${OZ_SECRET:-insecure-dev-secret-change-me}" \
|
||||||
|
python:3-slim python - "$1" <<'PY'
|
||||||
|
import base64, hashlib, hmac, json, os, sys, time, urllib.request
|
||||||
|
cid, sec = os.environ["OZ_CLIENT_ID"], os.environ["OZ_SECRET"]
|
||||||
|
b64 = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=")
|
||||||
|
def token():
|
||||||
|
hdr = {"alg": "HS256", "typ": "JWT"}
|
||||||
|
pl = {"iss": cid, "iat": int(time.time()), "client_id": cid, "user_id": "verify", "user_representation": "verify"}
|
||||||
|
seg = b64(json.dumps(hdr, separators=(",", ":")).encode()) + b"." + b64(json.dumps(pl, separators=(",", ":")).encode())
|
||||||
|
return (seg + b"." + b64(hmac.new(sec.encode(), seg, hashlib.sha256).digest())).decode()
|
||||||
|
def get(url):
|
||||||
|
req = urllib.request.Request(url, headers={
|
||||||
|
"Authorization": "Bearer " + token(), "Accept": "application/json", "Accept-Crs": "EPSG:4326"})
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
zaak = get(sys.argv[1])
|
||||||
|
status_url = zaak.get("status")
|
||||||
|
if not status_url:
|
||||||
|
print(""); sys.exit(0)
|
||||||
|
print(get(get(status_url)["statustype"]).get("omschrijving", ""))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
geannuleerd=""
|
||||||
|
for _ in $(seq 1 15); do
|
||||||
|
oms="$(read_zaak_status "$zaak_url_v" 2>/dev/null | tr -d '\r' || true)"
|
||||||
|
[ "$oms" = "Geannuleerd" ] && { geannuleerd=1; break; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
[ -n "$geannuleerd" ] || { echo "FAIL — zaak $zaak_url_v not Geannuleerd after timeout (current status omschrijving: '$oms')" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
||||||
|
echo "OK — the timed-out registration's zaak was cancelled to Geannuleerd in OpenZaak"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -114,6 +114,19 @@ public sealed class OpenZaakFixture : IDisposable
|
|||||||
return fallback ?? throw new InvalidOperationException($"No statustypen for zaaktype {zaaktypeUrl}");
|
return fallback ?? throw new InvalidOperationException($"No statustypen for zaaktype {zaaktypeUrl}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Resolve a statustype by its omschrijving (e.g. the S-10c "Geannuleerd" cancellation status).</summary>
|
||||||
|
public async Task<Uri> FindStatustypeByOmschrijvingAsync(Uri zaaktypeUrl, string omschrijving, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var query = new Uri(BaseUrl,
|
||||||
|
"/catalogi/api/v1/statustypen?status=alles&zaaktype=" + Uri.EscapeDataString(zaaktypeUrl.ToString()));
|
||||||
|
var page = await GetJsonAsync(query, ct);
|
||||||
|
foreach (var st in page.GetProperty("results").EnumerateArray())
|
||||||
|
if (st.TryGetProperty("omschrijving", out var o) && o.GetString() == omschrijving)
|
||||||
|
return new Uri(st.GetProperty("url").GetString()!);
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"No '{omschrijving}' statustype for zaaktype {zaaktypeUrl}");
|
||||||
|
}
|
||||||
|
|
||||||
// A ZGW (vng-api-common) HS256 JWT, mirroring the seed's client. Minted here
|
// A ZGW (vng-api-common) HS256 JWT, mirroring the seed's client. Minted here
|
||||||
// rather than reusing Acl.Infrastructure's internal minter to keep that internal.
|
// rather than reusing Acl.Infrastructure's internal minter to keep that internal.
|
||||||
private string MintToken()
|
private string MintToken()
|
||||||
|
|||||||
@@ -75,6 +75,39 @@ public sealed class OpenZaakGatewayIntegrationTests(OpenZaakFixture stack)
|
|||||||
Assert.Equal(eindstatustype.ToString(), status.GetProperty("statustype").GetString());
|
Assert.Equal(eindstatustype.ToString(), status.GetProperty("statustype").GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Cancelling_a_zaak_records_the_geannuleerd_status_and_a_resultaat()
|
||||||
|
{
|
||||||
|
var zaaktype = await stack.FindPublishedBigZaaktypeAsync();
|
||||||
|
Assert.True(zaaktype is not null,
|
||||||
|
"No published BIG-REGISTRATIE zaaktype found in OpenZaak — bring the stack up and " +
|
||||||
|
"seed it with OZ_PUBLISH=1 (`make integration` does this).");
|
||||||
|
|
||||||
|
var gateway = new OpenZaakGateway(stack.Http, stack.Options);
|
||||||
|
var zaakUrl = await gateway.OpenZaakAsync(new ZaakRequest(
|
||||||
|
Bronorganisatie: "517439943",
|
||||||
|
VerantwoordelijkeOrganisatie: "517439943",
|
||||||
|
Vertrouwelijkheidaanduiding: "openbaar",
|
||||||
|
Zaaktype: zaaktype!,
|
||||||
|
Startdatum: DateOnly.FromDateTime(DateTime.UtcNow),
|
||||||
|
Identificatie: Guid.NewGuid().ToString()));
|
||||||
|
|
||||||
|
await gateway.SetZaakToCancellationStatusAsync(zaakUrl, zaaktype!, DateOnly.FromDateTime(DateTime.UtcNow));
|
||||||
|
|
||||||
|
// The zaak's current status is the Geannuleerd statustype — distinct from the approval eindstatus.
|
||||||
|
var zaak = await stack.GetZaakAsync(zaakUrl);
|
||||||
|
var statusUrl = zaak.GetProperty("status").GetString();
|
||||||
|
Assert.False(string.IsNullOrEmpty(statusUrl), "the cancelled zaak has no current status");
|
||||||
|
|
||||||
|
var status = await stack.GetJsonAsync(new Uri(statusUrl!));
|
||||||
|
var geannuleerd = await stack.FindStatustypeByOmschrijvingAsync(zaaktype!, "Geannuleerd");
|
||||||
|
Assert.Equal(geannuleerd.ToString(), status.GetProperty("statustype").GetString());
|
||||||
|
|
||||||
|
// ...and a resultaat is recorded (OpenZaak requires it before a closing/terminal status).
|
||||||
|
Assert.False(string.IsNullOrEmpty(zaak.GetProperty("resultaat").GetString()),
|
||||||
|
"the cancelled zaak has no resultaat");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Storing_a_diploma_creates_a_real_informatieobject_related_to_the_zaak()
|
public async Task Storing_a_diploma_creates_a_real_informatieobject_related_to_the_zaak()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user