Compare commits
7 Commits
feat/13-be
...
feat/13-be
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e65738c04 | |||
| e0db3ace6e | |||
| 6f5a61dea6 | |||
| 1a6daecc47 | |||
| 08515b9bba | |||
| 69db103e0e | |||
| ffd59c0aac |
@@ -24,10 +24,6 @@ import {
|
|||||||
Observable
|
Observable
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
export interface DecideRequest {
|
|
||||||
besluit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OpenbaarEntry {
|
export interface OpenbaarEntry {
|
||||||
id: string;
|
id: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -256,42 +252,4 @@ export class BffApiV1Service {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
||||||
decideRequest: DecideRequest, options?: HttpClientBodyOptions): Observable<TData>;
|
|
||||||
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
||||||
decideRequest: DecideRequest, options?: HttpClientEventOptions): Observable<HttpEvent<TData>>;
|
|
||||||
postBehandelRegistrationsIdDecide<TData = void>(id: string,
|
|
||||||
decideRequest: DecideRequest, options?: HttpClientResponseOptions): Observable<AngularHttpResponse<TData>>;
|
|
||||||
postBehandelRegistrationsIdDecide<TData = void>(
|
|
||||||
id: string,
|
|
||||||
decideRequest: DecideRequest, options?: HttpClientObserveOptions): Observable<TData | HttpEvent<TData> | AngularHttpResponse<TData>> {
|
|
||||||
if (options?.observe === 'events') {
|
|
||||||
return this.http.post<TData>(
|
|
||||||
`/behandel/registrations/${id}/decide`,
|
|
||||||
decideRequest,{
|
|
||||||
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
||||||
observe: 'events',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.observe === 'response') {
|
|
||||||
return this.http.post<TData>(
|
|
||||||
`/behandel/registrations/${id}/decide`,
|
|
||||||
decideRequest,{
|
|
||||||
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
||||||
observe: 'response',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.post<TData>(
|
|
||||||
`/behandel/registrations/${id}/decide`,
|
|
||||||
decideRequest,{
|
|
||||||
...(options as Omit<NonNullable<typeof options>, 'observe'>),
|
|
||||||
observe: 'body',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ public interface IDomainClient
|
|||||||
|
|
||||||
/// <summary>The behandelaar's werkbak — registrations awaiting beoordeling.</summary>
|
/// <summary>The behandelaar's werkbak — registrations awaiting beoordeling.</summary>
|
||||||
Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default);
|
Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
/// <summary>Apply a behandelaar's decision (<c>goedkeuren</c>/<c>afwijzen</c>) to a registration.</summary>
|
|
||||||
Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Port to the read projection.</summary>
|
/// <summary>Port to the read projection.</summary>
|
||||||
@@ -50,13 +47,6 @@ public sealed class DomainClient(HttpClient http) : IDomainClient
|
|||||||
public async Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
public async Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||||
=> await http.GetFromJsonAsync<List<WerkbakItem>>("behandel/werkbak", ct) ?? [];
|
=> await http.GetFromJsonAsync<List<WerkbakItem>>("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);
|
private sealed record DomainResponse(string RegistrationId, string Status, string? ZaakUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,28 +103,8 @@ app.MapGet("/behandel/werkbak", async (IDomainClient domain, CancellationToken c
|
|||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces(StatusCodes.Status403Forbidden);
|
.Produces(StatusCodes.Status403Forbidden);
|
||||||
|
|
||||||
// A behandelaar's beoordeling on a registration (goedkeuren/afwijzen). Forwarded to the domain, which
|
|
||||||
// applies the decision and completes the workflow task (ADR-0013). Same medewerker/behandelaar gate.
|
|
||||||
app.MapPost("/behandel/registrations/{id}/decide",
|
|
||||||
async (string id, DecideRequest body, IDomainClient domain, CancellationToken ct) =>
|
|
||||||
{
|
|
||||||
if (!BehandelAuth.IsKnownBesluit(body.Besluit))
|
|
||||||
return Results.BadRequest(new { error = $"Unknown besluit '{body.Besluit}'. Expected 'goedkeuren' or 'afwijzen'." });
|
|
||||||
|
|
||||||
await domain.DecideAsync(id, body.Besluit, ct);
|
|
||||||
return Results.NoContent();
|
|
||||||
})
|
|
||||||
.RequireAuthorization(BehandelAuth.Policy)
|
|
||||||
.Produces(StatusCodes.Status204NoContent)
|
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
|
||||||
.Produces(StatusCodes.Status403Forbidden);
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
/// <summary>The behandelaar's decision on a registration.</summary>
|
|
||||||
public sealed record DecideRequest(string Besluit);
|
|
||||||
|
|
||||||
// Behandel (medewerker-realm) authentication + authorization wiring (ADR-0013).
|
// Behandel (medewerker-realm) authentication + authorization wiring (ADR-0013).
|
||||||
internal static class BehandelAuth
|
internal static class BehandelAuth
|
||||||
{
|
{
|
||||||
@@ -132,12 +112,6 @@ internal static class BehandelAuth
|
|||||||
public const string Policy = "behandelaar";
|
public const string Policy = "behandelaar";
|
||||||
public const string BehandelaarRole = "behandelaar";
|
public const string BehandelaarRole = "behandelaar";
|
||||||
|
|
||||||
/// <summary>The beoordeling vocabulary the BFF accepts (case-insensitive); an unknown besluit is a
|
|
||||||
/// 400 without troubling the domain. Mirrors the domain's <c>BeoordelingsBesluit</c>.</summary>
|
|
||||||
public static bool IsKnownBesluit(string? besluit) =>
|
|
||||||
string.Equals(besluit, "goedkeuren", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(besluit, "afwijzen", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
/// <summary>Lift Keycloak's realm roles (the nested <c>realm_access.roles</c> claim) onto the
|
/// <summary>Lift Keycloak's realm roles (the nested <c>realm_access.roles</c> claim) onto the
|
||||||
/// principal as role claims, so <c>RequireRole</c> can authorize on them.</summary>
|
/// principal as role claims, so <c>RequireRole</c> can authorize on them.</summary>
|
||||||
public static void AddRealmRoles(ClaimsPrincipal? principal)
|
public static void AddRealmRoles(ClaimsPrincipal? principal)
|
||||||
|
|||||||
@@ -54,61 +54,4 @@ public class BehandelEndpointTests
|
|||||||
Assert.Equal("reg-1", item.RegistrationId);
|
Assert.Equal("reg-1", item.RegistrationId);
|
||||||
Assert.Equal("123456782", item.Bsn);
|
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,16 +82,8 @@ internal sealed class FakeDomainClient : IDomainClient
|
|||||||
return Task.FromResult(Result);
|
return Task.FromResult(Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (string RegistrationId, string Besluit)? Decided { get; private set; }
|
|
||||||
|
|
||||||
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||||
=> Task.FromResult<IReadOnlyList<WerkbakItem>>(Werkbak);
|
=> 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>
|
/// <summary>Serves a configurable set of projection rows.</summary>
|
||||||
|
|||||||
@@ -88,62 +88,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/behandel/registrations/{id}/decide": {
|
|
||||||
"post": {
|
|
||||||
"tags": [
|
|
||||||
"Bff.Api"
|
|
||||||
],
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/DecideRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"204": {
|
|
||||||
"description": "No Content"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request"
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "Forbidden"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"DecideRequest": {
|
|
||||||
"required": [
|
|
||||||
"besluit"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"besluit": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"OpenbaarEntry": {
|
"OpenbaarEntry": {
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
@@ -20,12 +20,10 @@ public sealed record BeoordeelRegistratieCommand(RegistrationId RegistrationId,
|
|||||||
/// <see cref="BeoordelingsBesluit.Goedkeuren"/> sets the zaak's final status via the ACL (§8.1) and
|
/// <see cref="BeoordelingsBesluit.Goedkeuren"/> sets the zaak's final status via the ACL (§8.1) and
|
||||||
/// advances the aggregate to INGESCHREVEN; <see cref="BeoordelingsBesluit.Afwijzen"/> advances it to
|
/// advances the aggregate to INGESCHREVEN; <see cref="BeoordelingsBesluit.Afwijzen"/> advances it to
|
||||||
/// AFGEWEZEN in the domain (propagating a rejection to the zaak, so the openbaar projection reflects
|
/// AFGEWEZEN in the domain (propagating a rejection to the zaak, so the openbaar projection reflects
|
||||||
/// it, is a later sub-slice of S-12). After applying the decision it completes the Flowable
|
/// it, is a later sub-slice of S-12). Both decisions are idempotent — a repeated or redelivered
|
||||||
/// <c>Beoordelen</c> task (found by registrationId) so the workflow advances (ADR-0013). Both
|
/// decision that matches the current terminal state is a no-op, so the ACL is not called twice.
|
||||||
/// decisions are idempotent — a repeated or redelivered decision that matches the current terminal
|
|
||||||
/// state is a no-op, so the ACL is not called and the task not completed twice.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class BeoordeelRegistratie(IRegistrationStore store, IAclClient acl, IUserTaskClient tasks)
|
public sealed class BeoordeelRegistratie(IRegistrationStore store, IAclClient acl)
|
||||||
{
|
{
|
||||||
public async Task HandleAsync(BeoordeelRegistratieCommand command, CancellationToken ct = default)
|
public async Task HandleAsync(BeoordeelRegistratieCommand command, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -55,17 +53,5 @@ public sealed class BeoordeelRegistratie(IRegistrationStore store, IAclClient ac
|
|||||||
}
|
}
|
||||||
|
|
||||||
await store.SaveAsync(registration, ct);
|
await store.SaveAsync(registration, ct);
|
||||||
await CompleteWorkflowTaskAsync(command.RegistrationId, command.Besluit, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance the workflow: complete the open Beoordelen task for this registration. If none is open
|
|
||||||
// (already completed, or the process hasn't parked yet) the decision still stands — we complete
|
|
||||||
// nothing rather than fail.
|
|
||||||
private async Task CompleteWorkflowTaskAsync(RegistrationId registrationId, BeoordelingsBesluit besluit, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var open = await tasks.GetOpenBeoordelingenAsync(ct);
|
|
||||||
var task = open.FirstOrDefault(t => t.RegistrationId == registrationId);
|
|
||||||
if (task is not null)
|
|
||||||
await tasks.CompleteBeoordelingAsync(task.TaskId, besluit, ct);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ namespace Big.Tests;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The beoordeling use case (S-12): a behandelaar's decision on a registration. Goedkeuren sets the
|
/// The beoordeling use case (S-12): a behandelaar's decision on a registration. Goedkeuren sets the
|
||||||
/// zaak's final status via the ACL (§8.1) and marks the aggregate INGESCHREVEN; Afwijzen marks it
|
/// zaak's final status via the ACL (§8.1) and marks the aggregate INGESCHREVEN; Afwijzen marks it
|
||||||
/// AFGEWEZEN. Either way the decision also completes the Flowable Beoordelen task (found by
|
/// AFGEWEZEN in the domain (propagating a rejection to the zaak is a later sub-slice). Both are
|
||||||
/// registrationId) so the process advances. All idempotent — a repeated decision is a no-op.
|
/// idempotent so a repeated or redelivered decision is a no-op.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BeoordeelRegistratieTests
|
public class BeoordeelRegistratieTests
|
||||||
{
|
{
|
||||||
@@ -18,9 +18,6 @@ public class BeoordeelRegistratieTests
|
|||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FakeUserTaskClient TaskFor(Registration registration) =>
|
|
||||||
new([new BeoordelingTask("task-1", registration.Id)]);
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Goedkeuren_sets_the_zaak_status_via_the_acl_and_marks_the_registration_ingeschreven()
|
public async Task Goedkeuren_sets_the_zaak_status_via_the_acl_and_marks_the_registration_ingeschreven()
|
||||||
{
|
{
|
||||||
@@ -28,8 +25,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var registration = WithZaak();
|
var registration = WithZaak();
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var tasks = TaskFor(registration);
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, tasks);
|
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
||||||
|
|
||||||
@@ -38,8 +34,6 @@ public class BeoordeelRegistratieTests
|
|||||||
Assert.Equal(FakeAclClient.DefaultZaakUrl, acl.ApprovedZaakUrl);
|
Assert.Equal(FakeAclClient.DefaultZaakUrl, acl.ApprovedZaakUrl);
|
||||||
Assert.Equal(1, acl.ApproveCallCount);
|
Assert.Equal(1, acl.ApproveCallCount);
|
||||||
Assert.Equal(1, store.SaveCount);
|
Assert.Equal(1, store.SaveCount);
|
||||||
// The behandelaar's decision advances the workflow: the Beoordelen task is completed.
|
|
||||||
Assert.Equal(("task-1", BeoordelingsBesluit.Goedkeuren), tasks.Completed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -49,8 +43,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var registration = WithZaak();
|
var registration = WithZaak();
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var tasks = TaskFor(registration);
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, tasks);
|
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
||||||
|
|
||||||
@@ -58,7 +51,6 @@ public class BeoordeelRegistratieTests
|
|||||||
Assert.Equal(RegistrationStatus.Afgewezen, saved!.Status);
|
Assert.Equal(RegistrationStatus.Afgewezen, saved!.Status);
|
||||||
Assert.Equal(0, acl.ApproveCallCount);
|
Assert.Equal(0, acl.ApproveCallCount);
|
||||||
Assert.Equal(1, store.SaveCount);
|
Assert.Equal(1, store.SaveCount);
|
||||||
Assert.Equal(("task-1", BeoordelingsBesluit.Afwijzen), tasks.Completed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -69,7 +61,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var registration = WithZaak();
|
var registration = WithZaak();
|
||||||
registration.TakeIntoBehandeling();
|
registration.TakeIntoBehandeling();
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, TaskFor(registration));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
||||||
|
|
||||||
@@ -81,7 +73,7 @@ public class BeoordeelRegistratieTests
|
|||||||
{
|
{
|
||||||
var store = new FakeRegistrationStore();
|
var store = new FakeRegistrationStore();
|
||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var handler = new BeoordeelRegistratie(store, acl, new FakeUserTaskClient([]));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(() => handler.HandleAsync(null!));
|
await Assert.ThrowsAsync<ArgumentNullException>(() => handler.HandleAsync(null!));
|
||||||
Assert.Equal(0, acl.ApproveCallCount);
|
Assert.Equal(0, acl.ApproveCallCount);
|
||||||
@@ -93,7 +85,7 @@ public class BeoordeelRegistratieTests
|
|||||||
{
|
{
|
||||||
var store = new FakeRegistrationStore();
|
var store = new FakeRegistrationStore();
|
||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var handler = new BeoordeelRegistratie(store, acl, new FakeUserTaskClient([]));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
handler.HandleAsync(new BeoordeelRegistratieCommand(RegistrationId.New(), BeoordelingsBesluit.Goedkeuren)));
|
handler.HandleAsync(new BeoordeelRegistratieCommand(RegistrationId.New(), BeoordelingsBesluit.Goedkeuren)));
|
||||||
@@ -108,7 +100,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var registration = Registration.Submit("123456782"); // no zaak yet
|
var registration = Registration.Submit("123456782"); // no zaak yet
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, TaskFor(registration));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren)));
|
handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren)));
|
||||||
@@ -123,7 +115,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var registration = WithZaak();
|
var registration = WithZaak();
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, TaskFor(registration));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
||||||
@@ -139,7 +131,7 @@ public class BeoordeelRegistratieTests
|
|||||||
var acl = new FakeAclClient();
|
var acl = new FakeAclClient();
|
||||||
var registration = WithZaak();
|
var registration = WithZaak();
|
||||||
store.Seed(registration);
|
store.Seed(registration);
|
||||||
var handler = new BeoordeelRegistratie(store, acl, TaskFor(registration));
|
var handler = new BeoordeelRegistratie(store, acl);
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Afwijzen));
|
||||||
@@ -147,22 +139,4 @@ public class BeoordeelRegistratieTests
|
|||||||
Assert.Equal(1, store.SaveCount);
|
Assert.Equal(1, store.SaveCount);
|
||||||
Assert.Equal(RegistrationStatus.Afgewezen, (await store.GetAsync(registration.Id))!.Status);
|
Assert.Equal(RegistrationStatus.Afgewezen, (await store.GetAsync(registration.Id))!.Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Deciding_completes_no_task_when_none_is_open_for_the_registration()
|
|
||||||
{
|
|
||||||
// The task may already be gone (redelivery / manual completion). The decision still applies
|
|
||||||
// and simply completes nothing rather than failing.
|
|
||||||
var store = new FakeRegistrationStore();
|
|
||||||
var acl = new FakeAclClient();
|
|
||||||
var registration = WithZaak();
|
|
||||||
store.Seed(registration);
|
|
||||||
var tasks = new FakeUserTaskClient([]); // no open task for this registration
|
|
||||||
var handler = new BeoordeelRegistratie(store, acl, tasks);
|
|
||||||
|
|
||||||
await handler.HandleAsync(new BeoordeelRegistratieCommand(registration.Id, BeoordelingsBesluit.Goedkeuren));
|
|
||||||
|
|
||||||
Assert.Equal(RegistrationStatus.Ingeschreven, (await store.GetAsync(registration.Id))!.Status);
|
|
||||||
Assert.Null(tasks.Completed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ Feature: Een registratie beoordelen
|
|||||||
When the behandelaar decides "goedkeuren"
|
When the behandelaar decides "goedkeuren"
|
||||||
Then the registration has status "INGESCHREVEN"
|
Then the registration has status "INGESCHREVEN"
|
||||||
And the zaak's final status is set via the ACL
|
And the zaak's final status is set via the ACL
|
||||||
And the beoordeling task is completed with "goedkeuren"
|
|
||||||
|
|
||||||
Scenario: Afwijzen wijst de registratie af zonder de ACL
|
Scenario: Afwijzen wijst de registratie af zonder de ACL
|
||||||
Given a submitted registration with an opened zaak
|
Given a submitted registration with an opened zaak
|
||||||
@@ -23,4 +22,3 @@ Feature: Een registratie beoordelen
|
|||||||
And the behandelaar decides "afwijzen"
|
And the behandelaar decides "afwijzen"
|
||||||
Then the registration has status "AFGEWEZEN"
|
Then the registration has status "AFGEWEZEN"
|
||||||
And the ACL is not asked to set the zaak status
|
And the ACL is not asked to set the zaak status
|
||||||
And the beoordeling task is completed with "afwijzen"
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ public sealed class EenRegistratieBeoordelenSteps
|
|||||||
{
|
{
|
||||||
private readonly InMemoryAclClient _acl = new();
|
private readonly InMemoryAclClient _acl = new();
|
||||||
private readonly InMemoryRegistrationStore _store = new();
|
private readonly InMemoryRegistrationStore _store = new();
|
||||||
private readonly InMemoryUserTaskClient _tasks = new();
|
|
||||||
private RegistrationId _id;
|
private RegistrationId _id;
|
||||||
|
|
||||||
[Given("a submitted registration with an opened zaak")]
|
[Given("a submitted registration with an opened zaak")]
|
||||||
@@ -26,8 +25,6 @@ public sealed class EenRegistratieBeoordelenSteps
|
|||||||
registration.AttachZaak(InMemoryAclClient.OpenedZaakUrl);
|
registration.AttachZaak(InMemoryAclClient.OpenedZaakUrl);
|
||||||
await _store.SaveAsync(registration);
|
await _store.SaveAsync(registration);
|
||||||
_id = registration.Id;
|
_id = registration.Id;
|
||||||
// The process has parked at the Beoordelen user task awaiting the behandelaar.
|
|
||||||
_tasks.Open(_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[When("the behandelaar takes it into behandeling")]
|
[When("the behandelaar takes it into behandeling")]
|
||||||
@@ -40,7 +37,7 @@ public sealed class EenRegistratieBeoordelenSteps
|
|||||||
|
|
||||||
[When("the behandelaar decides \"(.*)\"")]
|
[When("the behandelaar decides \"(.*)\"")]
|
||||||
public async Task WhenTheBehandelaarDecides(string besluit)
|
public async Task WhenTheBehandelaarDecides(string besluit)
|
||||||
=> await new BeoordeelRegistratie(_store, _acl, _tasks).HandleAsync(
|
=> await new BeoordeelRegistratie(_store, _acl).HandleAsync(
|
||||||
new BeoordeelRegistratieCommand(_id, Enum.Parse<BeoordelingsBesluit>(besluit, ignoreCase: true)));
|
new BeoordeelRegistratieCommand(_id, Enum.Parse<BeoordelingsBesluit>(besluit, ignoreCase: true)));
|
||||||
|
|
||||||
[Then("the registration has status \"(.*)\"")]
|
[Then("the registration has status \"(.*)\"")]
|
||||||
@@ -58,8 +55,4 @@ public sealed class EenRegistratieBeoordelenSteps
|
|||||||
[Then("the ACL is not asked to set the zaak status")]
|
[Then("the ACL is not asked to set the zaak status")]
|
||||||
public void ThenTheAclIsNotAskedToSetTheZaakStatus()
|
public void ThenTheAclIsNotAskedToSetTheZaakStatus()
|
||||||
=> Assert.Null(_acl.ApprovedZaakUrl);
|
=> Assert.Null(_acl.ApprovedZaakUrl);
|
||||||
|
|
||||||
[Then("the beoordeling task is completed with \"(.*)\"")]
|
|
||||||
public void ThenTheBeoordelingTaskIsCompletedWith(string besluit)
|
|
||||||
=> Assert.Equal(Enum.Parse<BeoordelingsBesluit>(besluit, ignoreCase: true), _tasks.Completed?.Besluit);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,6 @@ public sealed class CapturingDomainClient : IDomainClient
|
|||||||
|
|
||||||
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||||
=> Task.FromResult<IReadOnlyList<WerkbakItem>>([]);
|
=> Task.FromResult<IReadOnlyList<WerkbakItem>>([]);
|
||||||
|
|
||||||
public Task DecideAsync(string registrationId, string besluit, CancellationToken ct = default)
|
|
||||||
=> Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Serves configurable projection rows.</summary>
|
/// <summary>Serves configurable projection rows.</summary>
|
||||||
|
|||||||
@@ -43,29 +43,6 @@ public sealed class InMemoryAclClient : IAclClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>An in-memory user-task client for the beoordeling acceptance scenario: it holds one open
|
|
||||||
/// Beoordelen task per registration and records the besluit each is completed with.</summary>
|
|
||||||
public sealed class InMemoryUserTaskClient : IUserTaskClient
|
|
||||||
{
|
|
||||||
private readonly List<BeoordelingTask> _open = [];
|
|
||||||
|
|
||||||
public (string TaskId, BeoordelingsBesluit Besluit)? Completed { get; private set; }
|
|
||||||
|
|
||||||
public void Open(RegistrationId registrationId) => _open.Add(new BeoordelingTask($"task-{registrationId}", registrationId));
|
|
||||||
|
|
||||||
public Task<IReadOnlyList<BeoordelingTask>> GetOpenBeoordelingenAsync(CancellationToken ct = default)
|
|
||||||
=> Task.FromResult<IReadOnlyList<BeoordelingTask>>(_open);
|
|
||||||
|
|
||||||
public Task ClaimAsync(string taskId, string behandelaar, CancellationToken ct = default) => Task.CompletedTask;
|
|
||||||
|
|
||||||
public Task CompleteBeoordelingAsync(string taskId, BeoordelingsBesluit besluit, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
Completed = (taskId, besluit);
|
|
||||||
_open.RemoveAll(t => t.TaskId == taskId);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>An in-memory registration store for the domain acceptance scenario.</summary>
|
/// <summary>An in-memory registration store for the domain acceptance scenario.</summary>
|
||||||
public sealed class InMemoryRegistrationStore : IRegistrationStore
|
public sealed class InMemoryRegistrationStore : IRegistrationStore
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user