diff --git a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs index af568c8..e5974b1 100644 --- a/services/acl/Acl.Tests/OpenZaakGatewayTests.cs +++ b/services/acl/Acl.Tests/OpenZaakGatewayTests.cs @@ -432,4 +432,111 @@ public class OpenZaakGatewayTests b64 = (b64.Length % 4) switch { 2 => b64 + "==", 3 => b64 + "=", _ => b64 }; return Encoding.UTF8.GetString(Convert.FromBase64String(b64)); } + + // --- StoreDocumentAsync (diploma upload / S-10b) --- + + private static readonly Uri Informatieobjecttype = + new("http://openzaak/catalogi/api/v1/informatieobjecttypen/dip"); + + private static DocumentRequest SampleDocument(byte[]? inhoud = null) => new( + Bronorganisatie: "517439943", + Informatieobjecttype: Informatieobjecttype, + Vertrouwelijkheidaanduiding: "openbaar", + Zaak: new Uri(ZaakUrl), + Creatiedatum: new DateOnly(2026, 6, 4), + Titel: "Diploma", + Auteur: "zorgprofessional", + Taal: "nld", + Bestandsnaam: "diploma.pdf", + Formaat: "application/pdf", + Inhoud: inhoud ?? [1, 2, 3, 4]); + + // Routes the two document calls: POST /enkelvoudiginformatieobjecten (documenten) then + // POST /zaakinformatieobjecten (zaken). + private static StubHandler DocumentStub(Recorder rec) => new(async req => + { + rec.Requests.Add(req); + rec.ContentLengths.Add(req.Content?.Headers.ContentLength); + rec.Bodies.Add(req.Content is null ? null : await req.Content.ReadAsStringAsync()); + + return req.RequestUri!.ToString().Contains("/enkelvoudiginformatieobjecten") + ? Json(HttpStatusCode.Created, """{"url":"http://openzaak/documenten/api/v1/enkelvoudiginformatieobjecten/doc-1"}""") + : Json(HttpStatusCode.Created, """{"url":"http://openzaak/zaken/api/v1/zaakinformatieobjecten/rel-1"}"""); + }); + + [Fact] + public async Task Storing_a_document_creates_the_informatieobject_then_relates_it_to_the_zaak() + { + var rec = new Recorder(); + + var url = await Gateway(DocumentStub(rec)).StoreDocumentAsync(SampleDocument([10, 20, 30])); + + Assert.Equal("http://openzaak/documenten/api/v1/enkelvoudiginformatieobjecten/doc-1", url.ToString()); + + // 1. Create the enkelvoudiginformatieobject in the Documenten API. + var create = rec.Sent("/enkelvoudiginformatieobjecten"); + Assert.Equal(HttpMethod.Post, create.Request.Method); + Assert.Equal("http://openzaak/documenten/api/v1/enkelvoudiginformatieobjecten", + create.Request.RequestUri!.ToString()); + Assert.Equal("Bearer", create.Request.Headers.Authorization!.Scheme); + Assert.Contains("\"bronorganisatie\":\"517439943\"", create.Body); + Assert.Contains("\"informatieobjecttype\":\"http://openzaak/catalogi/api/v1/informatieobjecttypen/dip\"", create.Body); + Assert.Contains("\"creatiedatum\":\"2026-06-04\"", create.Body); + Assert.Contains("\"titel\":\"Diploma\"", create.Body); + Assert.Contains("\"auteur\":\"zorgprofessional\"", create.Body); + Assert.Contains("\"taal\":\"nld\"", create.Body); + Assert.Contains("\"bestandsnaam\":\"diploma.pdf\"", create.Body); + Assert.Contains("\"formaat\":\"application/pdf\"", create.Body); + Assert.Contains("\"vertrouwelijkheidaanduiding\":\"openbaar\"", create.Body); + Assert.Contains("\"status\":\"definitief\"", create.Body); + // The file content is base64-encoded into `inhoud`, with its byte length in `bestandsomvang`. + Assert.Contains($"\"inhoud\":\"{Convert.ToBase64String([10, 20, 30])}\"", create.Body); + Assert.Contains("\"bestandsomvang\":3", create.Body); + + // 2. Relate that informatieobject to the zaak (Zaken API — no CRS). + var relate = rec.Sent("/zaakinformatieobjecten"); + Assert.Equal(HttpMethod.Post, relate.Request.Method); + Assert.Equal("http://openzaak/zaken/api/v1/zaakinformatieobjecten", + relate.Request.RequestUri!.ToString()); + Assert.Contains($"\"zaak\":\"{ZaakUrl}\"", relate.Body); + Assert.Contains("\"informatieobject\":\"http://openzaak/documenten/api/v1/enkelvoudiginformatieobjecten/doc-1\"", relate.Body); + } + + [Fact] + public async Task Storing_a_document_buffers_the_body_and_sends_no_crs_headers() + { + // uwsgi rejects a chunked body (Content-Length must be present); the Documenten API is not a + // geo API, so no CRS headers (unlike the Zaken zaak-create). + var rec = new Recorder(); + + await Gateway(DocumentStub(rec)).StoreDocumentAsync(SampleDocument()); + + var create = rec.Sent("/enkelvoudiginformatieobjecten"); + Assert.NotNull(create.Length); + Assert.True(create.Length > 0); + Assert.False(create.Request.Headers.Contains("Accept-Crs")); + Assert.False(create.Request.Content!.Headers.Contains("Content-Crs")); + } + + [Fact] + public async Task Storing_a_document_surfaces_an_openzaak_rejection() + { + var handler = new StubHandler(_ => + Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("""{"detail":"bad"}""", Encoding.UTF8, "application/json"), + })); + + var ex = await Assert.ThrowsAsync( + () => Gateway(handler).StoreDocumentAsync(SampleDocument())); + Assert.Contains("bad", ex.Message); + } + + [Fact] + public async Task Storing_a_document_rejects_a_null_request() + { + var handler = new StubHandler(_ => throw new InvalidOperationException("should not be sent")); + + await Assert.ThrowsAsync(() => Gateway(handler).StoreDocumentAsync(null!)); + } }