Adds the backend-owned Aanvraag aggregate (PRD 0001, phase A) — the system of
record the dashboard will read. In-memory static store mirroring DocumentStore.
- ApplicationStore: create/get/list/draft-sync/cancel/submit; status COMPUTED ON
READ (Mappers.ToStatusDto(now)) so auto-approval is pure timestamp arithmetic,
no timers/jobs (ProcessingWindow = 8s).
- Endpoints: GET /applications, GET/POST/PUT/DELETE /applications/{id},
POST /applications/{id}/submit.
- Lifecycle: registratie duo -> auto (Goedgekeurd after window), handmatig ->
manual pending (no 422); herregistratie/intake 0 uren -> Afgewezen else auto.
Cancel blocks submitted aanvragen (409, no withdrawal in scope).
- Old /registrations endpoint + RejectRegistratie 422 left intact (retire in E).
- ApplicationTests: lifecycle + auto-approve window boundary (pure). 54/54 green.
Also checks in the PRD (docs/prd/0001).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
5.8 KiB
C#
137 lines
5.8 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using BigRegister.Api.Contracts;
|
|
using BigRegister.Api.Data;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
|
|
namespace BigRegister.Tests;
|
|
|
|
public class ApplicationTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
|
|
{
|
|
private readonly HttpClient _client = factory.CreateClient();
|
|
|
|
private async Task<ApplicationDetailDto> Create(string type = "registratie")
|
|
{
|
|
var res = await _client.PostAsJsonAsync("/api/v1/applications", new { type });
|
|
Assert.Equal(HttpStatusCode.Created, res.StatusCode);
|
|
return (await res.Content.ReadFromJsonAsync<ApplicationDetailDto>())!;
|
|
}
|
|
|
|
private Task<List<ApplicationSummaryDto>?> List() =>
|
|
_client.GetFromJsonAsync<List<ApplicationSummaryDto>>("/api/v1/applications");
|
|
|
|
// --- Lifecycle over HTTP ---
|
|
|
|
[Fact]
|
|
public async Task Create_then_list_shows_a_concept_with_step_progress()
|
|
{
|
|
var a = await Create();
|
|
await _client.PutAsJsonAsync($"/api/v1/applications/{a.Id}",
|
|
new { draft = new { beroep = "arts" }, stepIndex = 1, stepCount = 4 });
|
|
|
|
var mine = (await List())!.Single(x => x.Id == a.Id);
|
|
Assert.Equal("Concept", mine.Status.Tag);
|
|
Assert.Equal(1, mine.Status.StepIndex);
|
|
Assert.Equal(4, mine.Status.StepCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Draft_sync_is_readable_back_from_detail()
|
|
{
|
|
var a = await Create();
|
|
await _client.PutAsJsonAsync($"/api/v1/applications/{a.Id}",
|
|
new { draft = new { beroep = "verpleegkundige" }, stepIndex = 2, stepCount = 4 });
|
|
|
|
var detail = await _client.GetFromJsonAsync<ApplicationDetailDto>($"/api/v1/applications/{a.Id}");
|
|
Assert.NotNull(detail!.Draft);
|
|
Assert.Equal("verpleegkundige", detail.Draft!.Value.GetProperty("beroep").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Submit_duo_registratie_is_in_behandeling_and_auto()
|
|
{
|
|
var a = await Create("registratie");
|
|
var res = await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "duo" });
|
|
res.EnsureSuccessStatusCode();
|
|
var body = (await res.Content.ReadFromJsonAsync<SubmitApplicationResponse>())!;
|
|
Assert.StartsWith("BIG-2026-", body.Referentie);
|
|
Assert.Equal("InBehandeling", body.Status.Tag);
|
|
Assert.False(body.Status.Manual); // auto-approvable → not a manual case
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Submit_handmatig_registratie_succeeds_as_manual_case()
|
|
{
|
|
var a = await Create("registratie");
|
|
var res = await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "handmatig" });
|
|
res.EnsureSuccessStatusCode(); // no longer a 422
|
|
var body = (await res.Content.ReadFromJsonAsync<SubmitApplicationResponse>())!;
|
|
Assert.Equal("InBehandeling", body.Status.Tag);
|
|
Assert.True(body.Status.Manual);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Submit_herregistratie_with_zero_uren_is_afgewezen()
|
|
{
|
|
var a = await Create("herregistratie");
|
|
var res = await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { uren = 0 });
|
|
res.EnsureSuccessStatusCode(); // the submission is accepted...
|
|
var body = (await res.Content.ReadFromJsonAsync<SubmitApplicationResponse>())!;
|
|
Assert.Equal("Afgewezen", body.Status.Tag); // ...but resolves to rejected
|
|
Assert.NotNull(body.Status.Reden);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Submitting_twice_conflicts()
|
|
{
|
|
var a = await Create("registratie");
|
|
(await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "duo" })).EnsureSuccessStatusCode();
|
|
var again = await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "duo" });
|
|
Assert.Equal(HttpStatusCode.Conflict, again.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Cancel_concept_removes_it()
|
|
{
|
|
var a = await Create();
|
|
Assert.Equal(HttpStatusCode.NoContent, (await _client.DeleteAsync($"/api/v1/applications/{a.Id}")).StatusCode);
|
|
Assert.Equal(HttpStatusCode.NotFound, (await _client.GetAsync($"/api/v1/applications/{a.Id}")).StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Cancel_submitted_aanvraag_conflicts()
|
|
{
|
|
var a = await Create("registratie");
|
|
(await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "duo" })).EnsureSuccessStatusCode();
|
|
Assert.Equal(HttpStatusCode.Conflict, (await _client.DeleteAsync($"/api/v1/applications/{a.Id}")).StatusCode);
|
|
}
|
|
|
|
// --- Auto-approval is computed on read: exercise the window boundary without waiting. ---
|
|
|
|
private static Aanvraag Accepted(bool autoApprovable) => new()
|
|
{
|
|
Id = "x", Type = "registratie", Owner = "test",
|
|
Submitted = true, AutoApprovable = autoApprovable, Referentie = "BIG-2026-1",
|
|
SubmittedAt = DateTimeOffset.UtcNow, CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
|
|
[Fact]
|
|
public void AutoApprovable_flips_to_goedgekeurd_after_the_window()
|
|
{
|
|
var a = Accepted(autoApprovable: true);
|
|
var t0 = a.SubmittedAt!.Value;
|
|
Assert.Equal("InBehandeling", a.ToStatusDto(t0 + ApplicationStore.ProcessingWindow - TimeSpan.FromSeconds(1)).Tag);
|
|
Assert.Equal("Goedgekeurd", a.ToStatusDto(t0 + ApplicationStore.ProcessingWindow + TimeSpan.FromSeconds(1)).Tag);
|
|
}
|
|
|
|
[Fact]
|
|
public void Manual_case_never_auto_advances()
|
|
{
|
|
var a = Accepted(autoApprovable: false);
|
|
var far = a.SubmittedAt!.Value + ApplicationStore.ProcessingWindow + TimeSpan.FromDays(1);
|
|
var status = a.ToStatusDto(far);
|
|
Assert.Equal("InBehandeling", status.Tag);
|
|
Assert.True(status.Manual);
|
|
}
|
|
}
|