feat(#13): S-12c-2 — behandel decide → domain + complete workflow task (#86)
All checks were successful
All checks were successful
## What & why
Second half of **S-12c** (behandel-portal backend), completing the decision path per **ADR-0013**:
- **Domain:** `BeoordeelRegistratie` now, after applying the decision (aggregate + ACL for approval), **completes the open Flowable `Beoordelen` task** for that registration (found by registrationId) with the besluit, so the workflow advances. No open task → the decision still stands (completes nothing); idempotent.
- **BFF:** `POST /behandel/registrations/{id}/decide` behind the medewerker/`behandelaar` policy, forwarding `goedkeuren`/`afwijzen` to the domain. Validates the besluit vocabulary (400 on unknown) without troubling the domain.
Behavior: decide is **401** without a token, **403** without the role, **400** for an unknown besluit, **204** (forwarded) for a behandelaar.
This completes the behandel backend. **S-12d** (the Angular behandel-portal + Playwright e2e) closes umbrella #13 and retires the temporary `/approve`.
## Definition of Done
- [x] Linked issue: #13 (umbrella, `refs`)
- [x] Tests first; red → green per layer
- [x] Unit + acceptance green (`make unit`): domain 79, bff 27, acceptance 9 (acl/event-subscriber unaffected)
- [x] Beoordeling acceptance scenario asserts task completion (goedkeuren + afwijzen)
- [x] openapi.json + api-client regenerated (drift guard passes)
- [x] Mutation ≥ break(90): **domain 100%, bff 100%**
- [ ] CI green (pending)
Part of #13.
Reviewed-on: #86
This commit was merged in pull request #86.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,16 @@ internal sealed class FakeDomainClient : IDomainClient
|
||||
return Task.FromResult(Result);
|
||||
}
|
||||
|
||||
public (string RegistrationId, string Besluit)? Decided { get; private set; }
|
||||
|
||||
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<WerkbakItem>>(Werkbak);
|
||||
|
||||
public Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default)
|
||||
{
|
||||
Decided = (registrationId, besluit);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Serves a configurable set of projection rows.</summary>
|
||||
|
||||
Reference in New Issue
Block a user