From e5eef71a574cc0f2ea673aabc590b41fa9834955 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Tue, 21 Jul 2026 15:13:36 +0200 Subject: [PATCH] test(acl): kill cancellation-path mutants to clear the mutation ratchet (refs #106) The new SetZaakToCancellationStatusAsync added surviving mutants (uncovered null-guards and the failure-action messages) that dropped the ACL mutation score below the 90% break threshold. Add null-argument and rejected-POST tests for the cancellation path; score back to 93.46%. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../acl/Acl.Tests/OpenZaakGatewayTests.cs | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs index a94f783..f167f75 100644 --- a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs +++ b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs @@ -310,14 +310,12 @@ public class OpenZaakGatewayTests Assert.Contains("\"zaak\":\"" + ZaakUrl + "\"", resultaat.Body); // The cancellation resultaat (Vervallen) is chosen by name — not the approval one (Geregistreerd). Assert.Contains("\"resultaattype\":\"http://openzaak/catalogi/api/v1/resultaattypen/vervallen\"", resultaat.Body); - Assert.True(resultaat.Length > 0); var status = rec.Sent("/statussen"); Assert.Contains("\"zaak\":\"" + ZaakUrl + "\"", status.Body); // The Geannuleerd statustype is chosen by name — not the Afgehandeld eindstatus (approval). Assert.Contains("\"statustype\":\"http://openzaak/catalogi/api/v1/statustypen/geannuleerd\"", status.Body); Assert.Contains("\"datumStatusGezet\":\"2026-06-04T00:00:00Z\"", status.Body); - Assert.True(status.Length > 0); } [Fact] @@ -336,6 +334,56 @@ public class OpenZaakGatewayTests Assert.Contains("Geannuleerd", ex.Message); } + [Fact] + public async Task Cancelling_rejects_a_null_zaak_without_calling_openzaak() + { + var handler = new StubHandler(_ => throw new InvalidOperationException("should not be sent")); + await Assert.ThrowsAsync(() => + Gateway(handler).SetZaakToCancellationStatusAsync(null!, Zaaktype, new DateOnly(2026, 6, 4))); + } + + [Fact] + public async Task Cancelling_rejects_a_null_zaaktype_without_calling_openzaak() + { + var handler = new StubHandler(_ => throw new InvalidOperationException("should not be sent")); + await Assert.ThrowsAsync(() => + Gateway(handler).SetZaakToCancellationStatusAsync(new Uri(ZaakUrl), null!, new DateOnly(2026, 6, 4))); + } + + [Fact] + public async Task Cancelling_surfaces_the_failure_when_recording_the_resultaat_is_rejected() + { + var rec = new Recorder(); + + var ex = await Assert.ThrowsAsync(() => + Gateway(ApprovalStub(rec, new OzRoutes + { + StatustypenJson = CancellationStatustypenJson, + ResultaattypenJson = CancellationResultaattypenJson, + ResultaatPostStatus = HttpStatusCode.BadRequest, + })).SetZaakToCancellationStatusAsync(new Uri(ZaakUrl), Zaaktype, new DateOnly(2026, 6, 4))); + + Assert.Contains("cancellation resultaat", ex.Message); + // It fails on the resultaat, before it ever posts the status. + Assert.Equal(-1, rec.IndexOf("/statussen")); + } + + [Fact] + public async Task Cancelling_surfaces_the_failure_when_recording_the_status_is_rejected() + { + var rec = new Recorder(); + + var ex = await Assert.ThrowsAsync(() => + Gateway(ApprovalStub(rec, new OzRoutes + { + StatustypenJson = CancellationStatustypenJson, + ResultaattypenJson = CancellationResultaattypenJson, + StatusPostStatus = HttpStatusCode.BadRequest, + })).SetZaakToCancellationStatusAsync(new Uri(ZaakUrl), Zaaktype, new DateOnly(2026, 6, 4))); + + Assert.Contains("cancellation status", ex.Message); + } + [Fact] public async Task Approving_falls_back_to_the_highest_volgnummer_when_no_eindstatus_is_flagged() {