using Acl.Application; namespace Acl.Tests; public class AclServiceTests { private sealed class FakeGateway : IZaakGateway { // The URLs the catalogus resolves the configured identificatie/omschrijving to (S-27). public Uri ResolvedZaaktype { get; } = new("http://openzaak/catalogi/api/v1/zaaktypen/big"); public Uri ResolvedInformatieobjecttype { get; } = new("http://openzaak/catalogi/api/v1/informatieobjecttypen/dip"); public string? ResolvedByIdentificatie; public string? ResolvedByOmschrijving; public ZaakRequest? Captured; public Uri Result { get; } = new("http://openzaak/zaken/api/v1/zaken/abc"); public (Uri Zaak, Uri Zaaktype, DateOnly Datum)? Approved; public Task OpenZaakAsync(ZaakRequest request, CancellationToken ct = default) { Captured = request; return Task.FromResult(Result); } public Task SetZaakToEindstatusAsync(Uri zaakUrl, Uri zaaktypeUrl, DateOnly datumStatusGezet, CancellationToken ct = default) { Approved = (zaakUrl, zaaktypeUrl, datumStatusGezet); return Task.CompletedTask; } public (Uri Zaak, Uri Zaaktype, DateOnly Datum)? Cancelled; public Task SetZaakToCancellationStatusAsync(Uri zaakUrl, Uri zaaktypeUrl, DateOnly datumStatusGezet, CancellationToken ct = default) { Cancelled = (zaakUrl, zaaktypeUrl, datumStatusGezet); return Task.CompletedTask; } public Uri? ReadReferenceFor; public Task GetZaakIdentificatieAsync(Uri zaakUrl, CancellationToken ct = default) { ReadReferenceFor = zaakUrl; return Task.FromResult("REG-FROM-ZAAK"); } public DocumentRequest? StoredDocument; public Uri DocumentResult { get; } = new("http://openzaak/documenten/api/v1/enkelvoudiginformatieobjecten/doc-1"); public Task StoreDocumentAsync(DocumentRequest request, CancellationToken ct = default) { StoredDocument = request; return Task.FromResult(DocumentResult); } public Task ResolveZaaktypeUrlAsync(string identificatie, CancellationToken ct = default) { ResolvedByIdentificatie = identificatie; return Task.FromResult(ResolvedZaaktype); } public Task ResolveInformatieobjecttypeUrlAsync(string omschrijving, CancellationToken ct = default) { ResolvedByOmschrijving = omschrijving; return Task.FromResult(ResolvedInformatieobjecttype); } } private static AclDefaults Defaults() => new() { Bronorganisatie = "517439943", VerantwoordelijkeOrganisatie = "517439943", Vertrouwelijkheidaanduiding = "openbaar", ZaaktypeIdentificatie = "BIG-REGISTRATIE", InformatieobjecttypeOmschrijving = "Diploma", }; private static AclService ServiceWith(FakeGateway gateway, AclDefaults defaults, DateOnly today) => new(gateway, defaults, new CachedZaaktypeCatalog(gateway, defaults), new FixedClock(today)); private sealed class FixedClock(DateOnly today) : IClock { public DateOnly Today { get; } = today; } [Fact] public async Task Opening_a_zaak_default_fills_zgw_fields_and_uses_the_resolved_zaaktype() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); var url = await service.OpenZaakAsync(new DomainRegistration("123456782", "reg-77")); Assert.Equal(gateway.Result, url); var req = Assert.IsType(gateway.Captured); Assert.Equal("517439943", req.Bronorganisatie); Assert.Equal("517439943", req.VerantwoordelijkeOrganisatie); Assert.Equal("openbaar", req.Vertrouwelijkheidaanduiding); // The zaaktype is resolved from the configured identificatie, not a pinned URL (S-27). Assert.Equal("BIG-REGISTRATIE", gateway.ResolvedByIdentificatie); Assert.Equal(gateway.ResolvedZaaktype, req.Zaaktype); Assert.Equal(new DateOnly(2026, 6, 4), req.Startdatum); // The registration reference becomes the zaak identificatie (#78). Assert.Equal("reg-77", req.Identificatie); } [Fact] public async Task Rejects_a_null_registration_without_calling_the_gateway() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); await Assert.ThrowsAsync(() => service.OpenZaakAsync(null!)); Assert.Null(gateway.Captured); } [Fact] public async Task Approving_a_zaak_sets_it_to_its_resolved_zaaktypes_eindstatus_dated_today() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); var zaak = new Uri("http://openzaak/zaken/api/v1/zaken/abc"); await service.ApproveZaakAsync(zaak); Assert.NotNull(gateway.Approved); Assert.Equal(zaak, gateway.Approved!.Value.Zaak); Assert.Equal(gateway.ResolvedZaaktype, gateway.Approved.Value.Zaaktype); Assert.Equal(new DateOnly(2026, 6, 4), gateway.Approved.Value.Datum); } [Fact] public async Task Approving_a_null_zaak_is_rejected_without_touching_the_gateway() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); await Assert.ThrowsAsync(() => service.ApproveZaakAsync(null!)); Assert.Null(gateway.Approved); } [Fact] public async Task Cancelling_a_zaak_sets_it_to_the_cancellation_status_dated_today() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); var zaak = new Uri("http://openzaak/zaken/api/v1/zaken/abc"); await service.CancelZaakAsync(zaak); Assert.NotNull(gateway.Cancelled); Assert.Equal(zaak, gateway.Cancelled!.Value.Zaak); Assert.Equal(gateway.ResolvedZaaktype, gateway.Cancelled.Value.Zaaktype); Assert.Equal(new DateOnly(2026, 6, 4), gateway.Cancelled.Value.Datum); // Cancellation must not touch the approval path. Assert.Null(gateway.Approved); } [Fact] public async Task Cancelling_a_null_zaak_is_rejected_without_touching_the_gateway() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); await Assert.ThrowsAsync(() => service.CancelZaakAsync(null!)); Assert.Null(gateway.Cancelled); } [Fact] public async Task Storing_a_diploma_default_fills_the_document_fields_and_uses_the_resolved_informatieobjecttype() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); var zaak = new Uri("http://openzaak/zaken/api/v1/zaken/abc"); var url = await service.StoreDiplomaAsync(zaak, [1, 2, 3], "diploma.pdf", "application/pdf"); Assert.Equal(gateway.DocumentResult, url); var req = gateway.StoredDocument!; Assert.Equal(zaak, req.Zaak); // The informatieobjecttype is resolved from the configured omschrijving (S-27). Assert.Equal("Diploma", gateway.ResolvedByOmschrijving); Assert.Equal(gateway.ResolvedInformatieobjecttype, req.Informatieobjecttype); Assert.Equal("517439943", req.Bronorganisatie); Assert.Equal("openbaar", req.Vertrouwelijkheidaanduiding); Assert.Equal(new DateOnly(2026, 6, 4), req.Creatiedatum); Assert.Equal("nld", req.Taal); Assert.Equal("diploma.pdf", req.Bestandsnaam); Assert.Equal("application/pdf", req.Formaat); Assert.Equal(new byte[] { 1, 2, 3 }, req.Inhoud); } [Fact] public async Task Storing_a_diploma_rejects_null_or_blank_arguments() { var service = ServiceWith(new FakeGateway(), Defaults(), new DateOnly(2026, 6, 4)); var zaak = new Uri("http://openzaak/zaken/api/v1/zaken/abc"); await Assert.ThrowsAsync(() => service.StoreDiplomaAsync(null!, [1], "d.pdf", "application/pdf")); await Assert.ThrowsAsync(() => service.StoreDiplomaAsync(zaak, null!, "d.pdf", "application/pdf")); await Assert.ThrowsAnyAsync(() => service.StoreDiplomaAsync(zaak, [1], " ", "application/pdf")); await Assert.ThrowsAnyAsync(() => service.StoreDiplomaAsync(zaak, [1], "d.pdf", " ")); } [Fact] public async Task Reading_a_zaak_reference_returns_the_zaaks_identificatie() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); var zaak = new Uri("http://openzaak/zaken/api/v1/zaken/abc"); var reference = await service.GetZaakReferenceAsync(zaak); Assert.Equal("REG-FROM-ZAAK", reference); Assert.Equal(zaak, gateway.ReadReferenceFor); } [Fact] public async Task Reading_a_null_zaak_reference_is_rejected() { var gateway = new FakeGateway(); var service = ServiceWith(gateway, Defaults(), new DateOnly(2026, 6, 4)); await Assert.ThrowsAsync(() => service.GetZaakReferenceAsync(null!)); Assert.Null(gateway.ReadReferenceFor); } }