From d50a60c98bc3821c000520fc7614701ce9f3166f Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Wed, 15 Jul 2026 11:58:45 +0200 Subject: [PATCH] test(bff): /behandel/registrations/{id}/decide auth + forwarding + besluit validation (refs #13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Red — the decide endpoint does not exist yet. Co-Authored-By: Claude Opus 4.8 (1M context) --- services/bff/Bff.Api/DownstreamClients.cs | 10 ++++ .../bff/Bff.Tests/BehandelEndpointTests.cs | 57 +++++++++++++++++++ services/bff/Bff.Tests/BffFactory.cs | 8 +++ 3 files changed, 75 insertions(+) diff --git a/services/bff/Bff.Api/DownstreamClients.cs b/services/bff/Bff.Api/DownstreamClients.cs index 8571641..5c21c85 100644 --- a/services/bff/Bff.Api/DownstreamClients.cs +++ b/services/bff/Bff.Api/DownstreamClients.cs @@ -24,6 +24,9 @@ public interface IDomainClient /// The behandelaar's werkbak — registrations awaiting beoordeling. Task> GetWerkbakAsync(CancellationToken ct = default); + + /// Apply a behandelaar's decision (goedkeuren/afwijzen) to a registration. + Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default); } /// Port to the read projection. @@ -47,6 +50,13 @@ public sealed class DomainClient(HttpClient http) : IDomainClient public async Task> GetWerkbakAsync(CancellationToken ct = default) => await http.GetFromJsonAsync>("behandel/werkbak", ct) ?? []; + public async Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default) + { + using var response = await http.PostAsJsonAsync( + $"registrations/{registrationId}/decide", new { besluit }, ct); + response.EnsureSuccessStatusCode(); + } + private sealed record DomainResponse(string RegistrationId, string Status, string? ZaakUrl); } diff --git a/services/bff/Bff.Tests/BehandelEndpointTests.cs b/services/bff/Bff.Tests/BehandelEndpointTests.cs index 3d39147..c3e3dfb 100644 --- a/services/bff/Bff.Tests/BehandelEndpointTests.cs +++ b/services/bff/Bff.Tests/BehandelEndpointTests.cs @@ -54,4 +54,61 @@ public class BehandelEndpointTests Assert.Equal("reg-1", item.RegistrationId); Assert.Equal("123456782", item.Bsn); } + + private static HttpRequestMessage Decide(string? bearer, string id = "reg-1", string besluit = "goedkeuren") + { + var request = new HttpRequestMessage(HttpMethod.Post, $"/behandel/registrations/{id}/decide") + { + Content = JsonContent.Create(new { besluit }), + }; + if (bearer is not null) + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearer); + return request; + } + + [Fact] + public async Task Rejects_a_decision_without_a_token() + { + using var factory = new BffFactory(); + + var response = await factory.CreateClient().SendAsync(Decide(bearer: null)); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + Assert.Null(factory.Domain.Decided); + } + + [Fact] + public async Task Rejects_a_decision_from_a_medewerker_without_the_behandelaar_role() + { + using var factory = new BffFactory(); + + var response = await factory.CreateClient().SendAsync(Decide(TestTokens.Medewerker("teamlead"))); + + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + Assert.Null(factory.Domain.Decided); + } + + [Fact] + public async Task Forwards_a_behandelaar_decision_to_the_domain() + { + using var factory = new BffFactory(); + + var response = await factory.CreateClient() + .SendAsync(Decide(TestTokens.Medewerker("behandelaar"), id: "reg-42", besluit: "afwijzen")); + + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.Equal(("reg-42", "afwijzen"), factory.Domain.Decided); + } + + [Fact] + public async Task Rejects_an_unknown_besluit_without_calling_the_domain() + { + using var factory = new BffFactory(); + + var response = await factory.CreateClient() + .SendAsync(Decide(TestTokens.Medewerker("behandelaar"), besluit: "misschien")); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Null(factory.Domain.Decided); + } } diff --git a/services/bff/Bff.Tests/BffFactory.cs b/services/bff/Bff.Tests/BffFactory.cs index 5e74fb9..3cf1e67 100644 --- a/services/bff/Bff.Tests/BffFactory.cs +++ b/services/bff/Bff.Tests/BffFactory.cs @@ -82,8 +82,16 @@ internal sealed class FakeDomainClient : IDomainClient return Task.FromResult(Result); } + public (string RegistrationId, string Besluit)? Decided { get; private set; } + public Task> GetWerkbakAsync(CancellationToken ct = default) => Task.FromResult>(Werkbak); + + public Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default) + { + Decided = (registrationId, besluit); + return Task.CompletedTask; + } } /// Serves a configurable set of projection rows.