From 4322c607cb8afca23732ac54ea1018f17809b034 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Mon, 29 Jun 2026 11:43:27 +0200 Subject: [PATCH] fix(acl): buffer the zaak POST body so OpenZaak accepts it (refs #46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenZaak runs behind uwsgi, which rejects a chunked request body with 400. JsonContent streams without a Content-Length (Transfer-Encoding: chunked), so buffer it first. Only a real OpenZaak surfaces this — the integration test from the previous commit now passes. A unit test asserts a Content-Length is sent (captured before the stub reads/buffers the body), guarding the fix in the fast lane and killing the Stryker mutant that would otherwise survive. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../acl/Acl.Infrastructure/OpenZaakGateway.cs | 5 +++++ .../acl/Acl.Tests/OpenZaakGatewayTests.cs | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs index 0a6e2e4..f9cce31 100644 --- a/services/acl/Acl.Infrastructure/OpenZaakGateway.cs +++ b/services/acl/Acl.Infrastructure/OpenZaakGateway.cs @@ -27,6 +27,11 @@ public sealed class OpenZaakGateway(HttpClient http, OpenZaakOptions options) : // ZRC is a geo API; it requires the CRS headers. message.Headers.Add("Accept-Crs", "EPSG:4326"); message.Content.Headers.Add("Content-Crs", "EPSG:4326"); + // OpenZaak runs behind uwsgi, which rejects a chunked request body with 400. + // JsonContent streams without a known length (→ Transfer-Encoding: chunked), + // so buffer it first to send a Content-Length instead. Only a real OpenZaak + // surfaces this — a stubbed HttpMessageHandler accepts either framing. + await message.Content.LoadIntoBufferAsync(ct); using var response = await http.SendAsync(message, ct); response.EnsureSuccessStatusCode(); diff --git a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs index 5110660..b9f9099 100644 --- a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs +++ b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs @@ -31,6 +31,10 @@ public class OpenZaakGatewayTests return new StubHandler(async req => { c.Seen = req; + // Capture the length BEFORE reading the body: ReadAsStringAsync buffers the + // content and would set ContentLength as a side effect, masking the gateway's + // own buffering. Read here to assert the gateway sent a length (not chunked). + c.ContentLength = req.Content?.Headers.ContentLength; c.Body = req.Content is null ? null : await req.Content.ReadAsStringAsync(); return new HttpResponseMessage(HttpStatusCode.Created) { @@ -43,6 +47,7 @@ public class OpenZaakGatewayTests { public HttpRequestMessage? Seen; public string? Body; + public long? ContentLength; } [Fact] @@ -75,6 +80,20 @@ public class OpenZaakGatewayTests Assert.Equal("EPSG:4326", Assert.Single(capture.Seen.Content!.Headers.GetValues("Content-Crs"))); } + [Fact] + public async Task Sends_the_body_with_a_content_length_so_it_is_not_chunked() + { + // OpenZaak's uwsgi rejects a chunked request body (400). The gateway buffers + // the body so a Content-Length is sent. JsonContent has no length until + // buffered, so this guards the fix the real-OpenZaak integration test found. + var handler = Created(out var capture); + + await Gateway(handler).OpenZaakAsync(SampleRequest()); + + Assert.NotNull(capture.ContentLength); + Assert.True(capture.ContentLength > 0); + } + [Fact] public async Task Mints_a_hs256_jwt_carrying_the_acl_identity_claims() {