style: format backend with dotnet format

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 13:39:31 +02:00
parent e82309786d
commit 1137f59f7b
17 changed files with 1135 additions and 1129 deletions

View File

@@ -8,129 +8,135 @@ namespace BigRegister.Tests;
public class ApplicationTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client = factory.CreateClient();
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 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");
private Task<List<ApplicationSummaryDto>?> List() =>
_client.GetFromJsonAsync<List<ApplicationSummaryDto>>("/api/v1/applications");
// --- Lifecycle over HTTP ---
// --- 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 });
[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);
}
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 });
[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());
}
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_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_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 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 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_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);
}
[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. ---
// --- 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,
};
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 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);
}
[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);
}
}

View File

@@ -13,150 +13,150 @@ namespace BigRegister.Tests;
/// </summary>
public class BriefEndpointTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client = factory.CreateClient();
private readonly HttpClient _client = factory.CreateClient();
private static LetterBlockDto FreeText(string id) =>
new("freeText", id, new RichTextBlockDto(new[] { new ParagraphDto(new[] { new RichTextNodeDto("text", Text: "inhoud") }) }));
private static LetterBlockDto FreeText(string id) =>
new("freeText", id, new RichTextBlockDto(new[] { new ParagraphDto(new[] { new RichTextNodeDto("text", Text: "inhoud") }) }));
// Fill every REQUIRED section with a block so submit is allowed.
private static SaveBriefRequest FilledFrom(BriefDto brief)
{
var i = 0;
var sections = brief.Sections
.Select(s => new LetterSectionDto(s.SectionKey, s.Title, s.Required, s.Required ? new[] { FreeText($"local-{++i}") } : s.Blocks))
.ToList();
return new SaveBriefRequest(sections);
}
// Fill every REQUIRED section with a block so submit is allowed.
private static SaveBriefRequest FilledFrom(BriefDto brief)
{
var i = 0;
var sections = brief.Sections
.Select(s => new LetterSectionDto(s.SectionKey, s.Title, s.Required, s.Required ? new[] { FreeText($"local-{++i}") } : s.Blocks))
.ToList();
return new SaveBriefRequest(sections);
}
private async Task<BriefDto> Get()
{
BriefStore.Reset();
var view = await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief");
return view!.Brief;
}
private async Task<BriefDto> Get()
{
BriefStore.Reset();
var view = await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief");
return view!.Brief;
}
private HttpRequestMessage Post(string path, string? role = null, object? body = null)
{
var req = new HttpRequestMessage(HttpMethod.Post, path);
if (role is not null) req.Headers.Add("X-Role", role);
if (body is not null) req.Content = JsonContent.Create(body);
return req;
}
private HttpRequestMessage Post(string path, string? role = null, object? body = null)
{
var req = new HttpRequestMessage(HttpMethod.Post, path);
if (role is not null) req.Headers.Add("X-Role", role);
if (body is not null) req.Content = JsonContent.Create(body);
return req;
}
[Fact]
public async Task Get_creates_a_draft_from_the_template_with_scoped_passages()
{
var brief = await Get();
Assert.Equal("draft", brief.Status.Tag);
Assert.Equal(new[] { "aanhef", "kern", "slot" }, brief.Sections.Select(s => s.SectionKey));
// aanhef + slot are locked, predefined and prefilled; only kern is editable + empty.
var aanhef = brief.Sections.Single(s => s.SectionKey == "aanhef");
Assert.True(aanhef.Locked);
Assert.NotEmpty(aanhef.Blocks);
Assert.True(brief.Sections.Single(s => s.SectionKey == "slot").Locked);
var kern = brief.Sections.Single(s => s.SectionKey == "kern");
Assert.False(kern.Locked);
Assert.Empty(kern.Blocks);
[Fact]
public async Task Get_creates_a_draft_from_the_template_with_scoped_passages()
{
var brief = await Get();
Assert.Equal("draft", brief.Status.Tag);
Assert.Equal(new[] { "aanhef", "kern", "slot" }, brief.Sections.Select(s => s.SectionKey));
// aanhef + slot are locked, predefined and prefilled; only kern is editable + empty.
var aanhef = brief.Sections.Single(s => s.SectionKey == "aanhef");
Assert.True(aanhef.Locked);
Assert.NotEmpty(aanhef.Blocks);
Assert.True(brief.Sections.Single(s => s.SectionKey == "slot").Locked);
var kern = brief.Sections.Single(s => s.SectionKey == "kern");
Assert.False(kern.Locked);
Assert.Empty(kern.Blocks);
var view = await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief");
// global passages + the arts-scoped one; no other-beroep passages leak in.
Assert.Contains(view!.AvailablePassages, p => p.PassageId == "p-kern-arts");
Assert.All(view.AvailablePassages, p => Assert.True(p.Scope == "global" || p.Beroep == "arts"));
}
var view = await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief");
// global passages + the arts-scoped one; no other-beroep passages leak in.
Assert.Contains(view!.AvailablePassages, p => p.PassageId == "p-kern-arts");
Assert.All(view.AvailablePassages, p => Assert.True(p.Scope == "global" || p.Beroep == "arts"));
}
[Fact]
public async Task Save_is_drafter_only()
{
var brief = await Get();
var save = FilledFrom(brief);
[Fact]
public async Task Save_is_drafter_only()
{
var brief = await Get();
var save = FilledFrom(brief);
var approver = Post("/api/v1/brief", role: "approver");
approver.Method = HttpMethod.Put;
approver.Content = JsonContent.Create(save);
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(approver)).StatusCode);
var approver = Post("/api/v1/brief", role: "approver");
approver.Method = HttpMethod.Put;
approver.Content = JsonContent.Create(save);
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(approver)).StatusCode);
Assert.Equal(HttpStatusCode.OK, (await _client.PutAsJsonAsync("/api/v1/brief", save)).StatusCode);
}
Assert.Equal(HttpStatusCode.OK, (await _client.PutAsJsonAsync("/api/v1/brief", save)).StatusCode);
}
[Fact]
public async Task Submit_blocks_on_empty_required_section_then_succeeds_when_filled()
{
await Get();
// Nothing filled yet → required sections empty → 409.
Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/submit"))).StatusCode);
[Fact]
public async Task Submit_blocks_on_empty_required_section_then_succeeds_when_filled()
{
await Get();
// Nothing filled yet → required sections empty → 409.
Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/submit"))).StatusCode);
var brief = (await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief"))!.Brief;
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
var brief = (await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief"))!.Brief;
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
var res = await _client.SendAsync(Post("/api/v1/brief/submit"));
res.EnsureSuccessStatusCode();
var submitted = await res.Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("submitted", submitted!.Status.Tag);
}
var res = await _client.SendAsync(Post("/api/v1/brief/submit"));
res.EnsureSuccessStatusCode();
var submitted = await res.Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("submitted", submitted!.Status.Tag);
}
[Fact]
public async Task Drafter_cannot_approve_own_letter_but_a_different_reviewer_can()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
[Fact]
public async Task Drafter_cannot_approve_own_letter_but_a_different_reviewer_can()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
// drafter role approving own letter → 403
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(Post("/api/v1/brief/approve"))).StatusCode);
// drafter role approving own letter → 403
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(Post("/api/v1/brief/approve"))).StatusCode);
var res = await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver"));
res.EnsureSuccessStatusCode();
Assert.Equal("approved", (await res.Content.ReadFromJsonAsync<BriefDto>())!.Status.Tag);
}
var res = await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver"));
res.EnsureSuccessStatusCode();
Assert.Equal("approved", (await res.Content.ReadFromJsonAsync<BriefDto>())!.Status.Tag);
}
[Fact]
public async Task Reject_returns_comments_and_editing_reopens_to_draft()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
[Fact]
public async Task Reject_returns_comments_and_editing_reopens_to_draft()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
var rejected = await (await _client.SendAsync(
Post("/api/v1/brief/reject", role: "approver", body: new RejectBriefRequest("Graag aanvullen.")))).Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("rejected", rejected!.Status.Tag);
Assert.Equal("Graag aanvullen.", rejected.Status.Comments);
var rejected = await (await _client.SendAsync(
Post("/api/v1/brief/reject", role: "approver", body: new RejectBriefRequest("Graag aanvullen.")))).Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("rejected", rejected!.Status.Tag);
Assert.Equal("Graag aanvullen.", rejected.Status.Comments);
// A drafter save on a rejected letter reopens it to draft.
var reopened = await (await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief))).Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("draft", reopened!.Status.Tag);
}
// A drafter save on a rejected letter reopens it to draft.
var reopened = await (await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief))).Content.ReadFromJsonAsync<BriefDto>();
Assert.Equal("draft", reopened!.Status.Tag);
}
[Fact]
public async Task Send_only_from_approved()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
[Fact]
public async Task Send_only_from_approved()
{
var brief = await Get();
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
// submitted (not approved) → send 409
Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/send"))).StatusCode);
// submitted (not approved) → send 409
Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/send"))).StatusCode);
await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver"));
var res = await _client.SendAsync(Post("/api/v1/brief/send"));
res.EnsureSuccessStatusCode();
Assert.Equal("sent", (await res.Content.ReadFromJsonAsync<BriefDto>())!.Status.Tag);
}
await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver"));
var res = await _client.SendAsync(Post("/api/v1/brief/send"));
res.EnsureSuccessStatusCode();
Assert.Equal("sent", (await res.Content.ReadFromJsonAsync<BriefDto>())!.Status.Tag);
}
[Fact]
public async Task Reset_recreates_a_fresh_draft_with_locked_prefilled_sections()
{
var brief = await Get();
// Advance out of draft so the reset back to draft is observable.
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
[Fact]
public async Task Reset_recreates_a_fresh_draft_with_locked_prefilled_sections()
{
var brief = await Get();
// Advance out of draft so the reset back to draft is observable.
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Post("/api/v1/brief/submit"));
var res = await _client.SendAsync(Post("/api/v1/brief/reset"));
res.EnsureSuccessStatusCode();
var view = await res.Content.ReadFromJsonAsync<BriefViewDto>();
Assert.Equal("draft", view!.Brief.Status.Tag);
var aanhef = view.Brief.Sections.Single(s => s.SectionKey == "aanhef");
Assert.True(aanhef.Locked);
Assert.NotEmpty(aanhef.Blocks);
Assert.Empty(view.Brief.Sections.Single(s => s.SectionKey == "kern").Blocks);
}
var res = await _client.SendAsync(Post("/api/v1/brief/reset"));
res.EnsureSuccessStatusCode();
var view = await res.Content.ReadFromJsonAsync<BriefViewDto>();
Assert.Equal("draft", view!.Brief.Status.Tag);
var aanhef = view.Brief.Sections.Single(s => s.SectionKey == "aanhef");
Assert.True(aanhef.Locked);
Assert.NotEmpty(aanhef.Blocks);
Assert.Empty(view.Brief.Sections.Single(s => s.SectionKey == "kern").Blocks);
}
}

View File

@@ -9,223 +9,223 @@ namespace BigRegister.Tests;
public class EndpointTests(WebApplicationFactory<Program> factory) : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client = factory.CreateClient();
private readonly HttpClient _client = factory.CreateClient();
[Fact]
public async Task DashboardView_computes_eligibility_decision()
{
var dto = await _client.GetFromJsonAsync<DashboardViewDto>("/api/v1/dashboard-view");
Assert.NotNull(dto);
Assert.Equal("19012345601", dto.Registration.BigNummer);
Assert.Equal("Geregistreerd", dto.Registration.Status.Tag);
// seed deadline 2027-03-01 is within 12 months of "today" (2026) → eligible
Assert.True(dto.Decisions.EligibleForHerregistratie);
Assert.NotNull(dto.Decisions.HerregistratieReason);
}
[Fact]
public async Task DashboardView_computes_eligibility_decision()
{
var dto = await _client.GetFromJsonAsync<DashboardViewDto>("/api/v1/dashboard-view");
Assert.NotNull(dto);
Assert.Equal("19012345601", dto.Registration.BigNummer);
Assert.Equal("Geregistreerd", dto.Registration.Status.Tag);
// seed deadline 2027-03-01 is within 12 months of "today" (2026) → eligible
Assert.True(dto.Decisions.EligibleForHerregistratie);
Assert.NotNull(dto.Decisions.HerregistratieReason);
}
[Fact]
public async Task Notes_returns_seeded_aantekeningen()
{
var notes = await _client.GetFromJsonAsync<List<AantekeningDto>>("/api/v1/notes");
Assert.Equal(3, notes!.Count);
}
[Fact]
public async Task Notes_returns_seeded_aantekeningen()
{
var notes = await _client.GetFromJsonAsync<List<AantekeningDto>>("/api/v1/notes");
Assert.Equal(3, notes!.Count);
}
[Fact]
public async Task Brp_returns_address()
{
var dto = await _client.GetFromJsonAsync<BrpAddressDto>("/api/v1/brp/address");
Assert.True(dto!.Gevonden);
Assert.Equal("2514 EA", dto.Adres!.Postcode);
}
[Fact]
public async Task Brp_returns_address()
{
var dto = await _client.GetFromJsonAsync<BrpAddressDto>("/api/v1/brp/address");
Assert.True(dto!.Gevonden);
Assert.Equal("2514 EA", dto.Adres!.Postcode);
}
[Fact]
public async Task Duo_lookup_carries_server_decided_questions_and_professions()
{
var dto = await _client.GetFromJsonAsync<DuoLookupDto>("/api/v1/duo/diplomas");
Assert.NotNull(dto);
[Fact]
public async Task Duo_lookup_carries_server_decided_questions_and_professions()
{
var dto = await _client.GetFromJsonAsync<DuoLookupDto>("/api/v1/duo/diplomas");
Assert.NotNull(dto);
var english = dto.Diplomas.Single(d => d.Id == "d2");
Assert.Equal("Arts", english.Beroep);
Assert.Contains(english.PolicyQuestions, q => q.Id == "nl-taalvaardigheid");
var english = dto.Diplomas.Single(d => d.Id == "d2");
Assert.Equal("Arts", english.Beroep);
Assert.Contains(english.PolicyQuestions, q => q.Id == "nl-taalvaardigheid");
var dutch = dto.Diplomas.Single(d => d.Id == "d1");
Assert.Empty(dutch.PolicyQuestions);
var dutch = dto.Diplomas.Single(d => d.Id == "d1");
Assert.Empty(dutch.PolicyQuestions);
// DUO "not found" fallback: an unlisted diploma → user uses the manual path,
// which the same lookup provides (maximal question set + declarable professions).
Assert.DoesNotContain(dto.Diplomas, d => d.Id == "unknown-id");
Assert.Equal(3, dto.Handmatig.PolicyQuestions.Count);
Assert.Equal(5, dto.Handmatig.Beroepen.Count);
}
// DUO "not found" fallback: an unlisted diploma → user uses the manual path,
// which the same lookup provides (maximal question set + declarable professions).
Assert.DoesNotContain(dto.Diplomas, d => d.Id == "unknown-id");
Assert.Equal(3, dto.Handmatig.PolicyQuestions.Count);
Assert.Equal(5, dto.Handmatig.Beroepen.Count);
}
[Fact]
public async Task IntakePolicy_returns_scholing_threshold()
{
var dto = await _client.GetFromJsonAsync<IntakePolicyDto>("/api/v1/intake/policy");
Assert.Equal(1000, dto!.ScholingThreshold);
}
[Fact]
public async Task IntakePolicy_returns_scholing_threshold()
{
var dto = await _client.GetFromJsonAsync<IntakePolicyDto>("/api/v1/intake/policy");
Assert.Equal(1000, dto!.ScholingThreshold);
}
[Fact]
public async Task Registration_with_duo_diploma_succeeds()
{
var res = await _client.PostAsJsonAsync("/api/v1/registrations", new RegistratieRequest("duo"));
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<ReferentieResponse>();
Assert.StartsWith("BIG-2026-", body!.Referentie);
}
[Fact]
public async Task Registration_with_duo_diploma_succeeds()
{
var res = await _client.PostAsJsonAsync("/api/v1/registrations", new RegistratieRequest("duo"));
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<ReferentieResponse>();
Assert.StartsWith("BIG-2026-", body!.Referentie);
}
[Fact]
public async Task Registration_with_manual_diploma_is_rejected_with_problem_details()
{
var res = await _client.PostAsJsonAsync("/api/v1/registrations", new RegistratieRequest("handmatig"));
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
Assert.Contains("application/problem+json", res.Content.Headers.ContentType!.ToString());
}
[Fact]
public async Task Registration_with_manual_diploma_is_rejected_with_problem_details()
{
var res = await _client.PostAsJsonAsync("/api/v1/registrations", new RegistratieRequest("handmatig"));
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
Assert.Contains("application/problem+json", res.Content.Headers.ContentType!.ToString());
}
[Theory]
[InlineData("/api/v1/intakes")]
[InlineData("/api/v1/herregistraties")]
public async Task Zero_hours_submission_is_rejected(string route)
{
var res = await _client.PostAsJsonAsync(route, new { uren = 0 });
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
}
[Theory]
[InlineData("/api/v1/intakes")]
[InlineData("/api/v1/herregistraties")]
public async Task Zero_hours_submission_is_rejected(string route)
{
var res = await _client.PostAsJsonAsync(route, new { uren = 0 });
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
}
[Theory]
[InlineData("/api/v1/intakes")]
[InlineData("/api/v1/herregistraties")]
public async Task Worked_hours_submission_succeeds(string route)
{
var res = await _client.PostAsJsonAsync(route, new { uren = 40 });
res.EnsureSuccessStatusCode();
}
[Theory]
[InlineData("/api/v1/intakes")]
[InlineData("/api/v1/herregistraties")]
public async Task Worked_hours_submission_succeeds(string route)
{
var res = await _client.PostAsJsonAsync(route, new { uren = 40 });
res.EnsureSuccessStatusCode();
}
[Fact]
public async Task Change_request_with_valid_address_succeeds()
{
var res = await _client.PostAsJsonAsync("/api/v1/change-requests",
new { straat = "Lange Voorhout 9", postcode = "2514 EA", woonplaats = "Den Haag" });
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<ReferentieResponse>();
Assert.StartsWith("BIG-2026-", body!.Referentie);
}
[Fact]
public async Task Change_request_with_valid_address_succeeds()
{
var res = await _client.PostAsJsonAsync("/api/v1/change-requests",
new { straat = "Lange Voorhout 9", postcode = "2514 EA", woonplaats = "Den Haag" });
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<ReferentieResponse>();
Assert.StartsWith("BIG-2026-", body!.Referentie);
}
[Fact]
public async Task Change_request_with_bad_postcode_is_rejected()
{
var res = await _client.PostAsJsonAsync("/api/v1/change-requests",
new { straat = "Straat 1", postcode = "nope", woonplaats = "Den Haag" });
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
}
[Fact]
public async Task Change_request_with_bad_postcode_is_rejected()
{
var res = await _client.PostAsJsonAsync("/api/v1/change-requests",
new { straat = "Straat 1", postcode = "nope", woonplaats = "Den Haag" });
Assert.Equal(HttpStatusCode.UnprocessableEntity, res.StatusCode);
}
[Fact]
public async Task Health_endpoint_is_ok()
{
var res = await _client.GetAsync("/health");
res.EnsureSuccessStatusCode();
}
[Fact]
public async Task Health_endpoint_is_ok()
{
var res = await _client.GetAsync("/health");
res.EnsureSuccessStatusCode();
}
// --- Document upload ---
// --- Document upload ---
private static MultipartFormDataContent UploadForm(string localId, string categoryId, string wizardId, string fileName, string contentType)
{
var content = new MultipartFormDataContent();
var file = new ByteArrayContent(new byte[] { 1, 2, 3 });
file.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(file, "file", fileName);
content.Add(new StringContent(categoryId), "categoryId");
content.Add(new StringContent(localId), "localId");
content.Add(new StringContent(wizardId), "wizardId");
return content;
}
private static MultipartFormDataContent UploadForm(string localId, string categoryId, string wizardId, string fileName, string contentType)
{
var content = new MultipartFormDataContent();
var file = new ByteArrayContent(new byte[] { 1, 2, 3 });
file.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(file, "file", fileName);
content.Add(new StringContent(categoryId), "categoryId");
content.Add(new StringContent(localId), "localId");
content.Add(new StringContent(wizardId), "wizardId");
return content;
}
private async Task<UploadResponse> Upload(string localId, string categoryId = "diploma", string type = "application/pdf", string file = "d.pdf")
{
var res = await _client.PostAsync("/api/v1/uploads", UploadForm(localId, categoryId, "registratie", file, type));
Assert.Equal(HttpStatusCode.Created, res.StatusCode);
return (await res.Content.ReadFromJsonAsync<UploadResponse>())!;
}
private async Task<UploadResponse> Upload(string localId, string categoryId = "diploma", string type = "application/pdf", string file = "d.pdf")
{
var res = await _client.PostAsync("/api/v1/uploads", UploadForm(localId, categoryId, "registratie", file, type));
Assert.Equal(HttpStatusCode.Created, res.StatusCode);
return (await res.Content.ReadFromJsonAsync<UploadResponse>())!;
}
[Fact]
public async Task Categories_are_server_owned_config()
{
// A manual diploma requires a diploma upload; identiteit is always required.
var dto = await _client.GetFromJsonAsync<UploadCategoriesDto>("/api/v1/uploads/categories?wizardId=registratie&diplomaHerkomst=handmatig");
Assert.Contains(dto!.Categories, c => c.CategoryId == "diploma" && c.Required && !c.AllowPostDelivery);
Assert.Contains(dto.Categories, c => c.CategoryId == "identiteit" && c.AllowPostDelivery);
}
[Fact]
public async Task Categories_are_server_owned_config()
{
// A manual diploma requires a diploma upload; identiteit is always required.
var dto = await _client.GetFromJsonAsync<UploadCategoriesDto>("/api/v1/uploads/categories?wizardId=registratie&diplomaHerkomst=handmatig");
Assert.Contains(dto!.Categories, c => c.CategoryId == "diploma" && c.Required && !c.AllowPostDelivery);
Assert.Contains(dto.Categories, c => c.CategoryId == "identiteit" && c.AllowPostDelivery);
}
[Fact]
public async Task Upload_then_status_reports_complete_for_known_localId()
{
var localId = Guid.NewGuid().ToString();
var doc = await Upload(localId);
var status = await _client.GetFromJsonAsync<UploadStatusDto>($"/api/v1/uploads/status?localIds={localId},onbekend");
Assert.Contains(status!.Results, r => r.LocalId == localId && r.Status == "complete" && r.DocumentId == doc.DocumentId);
Assert.Contains(status.Results, r => r.LocalId == "onbekend" && r.Status == "unknown");
}
[Fact]
public async Task Upload_then_status_reports_complete_for_known_localId()
{
var localId = Guid.NewGuid().ToString();
var doc = await Upload(localId);
var status = await _client.GetFromJsonAsync<UploadStatusDto>($"/api/v1/uploads/status?localIds={localId},onbekend");
Assert.Contains(status!.Results, r => r.LocalId == localId && r.Status == "complete" && r.DocumentId == doc.DocumentId);
Assert.Contains(status.Results, r => r.LocalId == "onbekend" && r.Status == "unknown");
}
[Fact]
public async Task Upload_content_is_served_back_with_its_type_inline_for_pdf()
{
var doc = await Upload(Guid.NewGuid().ToString());
var res = await _client.GetAsync($"/api/v1/uploads/{doc.DocumentId}/content");
res.EnsureSuccessStatusCode();
Assert.Equal("application/pdf", res.Content.Headers.ContentType!.MediaType);
Assert.Equal(new byte[] { 1, 2, 3 }, await res.Content.ReadAsByteArrayAsync());
// pdf/image → inline (no attachment disposition) so the browser previews it
Assert.NotEqual("attachment", res.Content.Headers.ContentDisposition?.DispositionType);
}
[Fact]
public async Task Upload_content_is_served_back_with_its_type_inline_for_pdf()
{
var doc = await Upload(Guid.NewGuid().ToString());
var res = await _client.GetAsync($"/api/v1/uploads/{doc.DocumentId}/content");
res.EnsureSuccessStatusCode();
Assert.Equal("application/pdf", res.Content.Headers.ContentType!.MediaType);
Assert.Equal(new byte[] { 1, 2, 3 }, await res.Content.ReadAsByteArrayAsync());
// pdf/image → inline (no attachment disposition) so the browser previews it
Assert.NotEqual("attachment", res.Content.Headers.ContentDisposition?.DispositionType);
}
[Fact]
public async Task Upload_content_404_for_unknown_document()
{
Assert.Equal(HttpStatusCode.NotFound, (await _client.GetAsync("/api/v1/uploads/demo-nope/content")).StatusCode);
}
[Fact]
public async Task Upload_content_404_for_unknown_document()
{
Assert.Equal(HttpStatusCode.NotFound, (await _client.GetAsync("/api/v1/uploads/demo-nope/content")).StatusCode);
}
[Fact]
public async Task Upload_rejects_wrong_type()
{
var res = await _client.PostAsync("/api/v1/uploads",
UploadForm(Guid.NewGuid().ToString(), "diploma", "registratie", "d.txt", "text/plain"));
Assert.Equal(HttpStatusCode.BadRequest, res.StatusCode);
}
[Fact]
public async Task Upload_rejects_wrong_type()
{
var res = await _client.PostAsync("/api/v1/uploads",
UploadForm(Guid.NewGuid().ToString(), "diploma", "registratie", "d.txt", "text/plain"));
Assert.Equal(HttpStatusCode.BadRequest, res.StatusCode);
}
[Fact]
public async Task User_delete_succeeds_then_404()
{
var doc = await Upload(Guid.NewGuid().ToString());
Assert.Equal(HttpStatusCode.NoContent, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
Assert.Equal(HttpStatusCode.NotFound, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
}
[Fact]
public async Task User_delete_succeeds_then_404()
{
var doc = await Upload(Guid.NewGuid().ToString());
Assert.Equal(HttpStatusCode.NoContent, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
Assert.Equal(HttpStatusCode.NotFound, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
}
[Fact]
public async Task User_delete_blocked_with_409_once_linked_to_submission()
{
var doc = await Upload(Guid.NewGuid().ToString());
var submit = await _client.PostAsJsonAsync("/api/v1/registrations",
new RegistratieRequest("duo", new[] { new DocumentRefDto("diploma", "digital", doc.DocumentId) }));
submit.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.Conflict, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
}
[Fact]
public async Task User_delete_blocked_with_409_once_linked_to_submission()
{
var doc = await Upload(Guid.NewGuid().ToString());
var submit = await _client.PostAsJsonAsync("/api/v1/registrations",
new RegistratieRequest("duo", new[] { new DocumentRefDto("diploma", "digital", doc.DocumentId) }));
submit.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.Conflict, (await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}")).StatusCode);
}
[Fact]
public async Task Admin_delete_requires_admin_role()
{
var doc = await Upload(Guid.NewGuid().ToString());
Assert.Equal(HttpStatusCode.Forbidden, (await _client.DeleteAsync($"/api/v1/admin/uploads/{doc.DocumentId}")).StatusCode);
[Fact]
public async Task Admin_delete_requires_admin_role()
{
var doc = await Upload(Guid.NewGuid().ToString());
Assert.Equal(HttpStatusCode.Forbidden, (await _client.DeleteAsync($"/api/v1/admin/uploads/{doc.DocumentId}")).StatusCode);
var req = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/admin/uploads/{doc.DocumentId}");
req.Headers.Add("X-Admin", "true");
Assert.Equal(HttpStatusCode.NoContent, (await _client.SendAsync(req)).StatusCode);
}
var req = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/admin/uploads/{doc.DocumentId}");
req.Headers.Add("X-Admin", "true");
Assert.Equal(HttpStatusCode.NoContent, (await _client.SendAsync(req)).StatusCode);
}
[Fact]
public async Task Audit_log_records_upload_and_delete_metadata_only()
{
var doc = await Upload(Guid.NewGuid().ToString());
await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}");
Assert.Contains(DocumentStore.AuditLog, a => a.DocumentId == doc.DocumentId && a.Action == "upload");
Assert.Contains(DocumentStore.AuditLog, a => a.DocumentId == doc.DocumentId && a.Action == "delete-user");
}
[Fact]
public async Task Audit_log_records_upload_and_delete_metadata_only()
{
var doc = await Upload(Guid.NewGuid().ToString());
await _client.DeleteAsync($"/api/v1/uploads/{doc.DocumentId}");
Assert.Contains(DocumentStore.AuditLog, a => a.DocumentId == doc.DocumentId && a.Action == "upload");
Assert.Contains(DocumentStore.AuditLog, a => a.DocumentId == doc.DocumentId && a.Action == "delete-user");
}
}

View File

@@ -7,174 +7,174 @@ namespace BigRegister.Tests;
public class DocumentRuleTests
{
[Fact]
public void Rejects_unknown_category() =>
Assert.NotNull(DocumentRules.RejectUpload(null, "application/pdf", 1));
[Fact]
public void Rejects_unknown_category() =>
Assert.NotNull(DocumentRules.RejectUpload(null, "application/pdf", 1));
[Fact]
public void Rejects_disallowed_type()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.NotNull(DocumentRules.RejectUpload(c, "text/plain", 1));
}
[Fact]
public void Rejects_disallowed_type()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.NotNull(DocumentRules.RejectUpload(c, "text/plain", 1));
}
[Fact]
public void Rejects_oversized_file()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.NotNull(DocumentRules.RejectUpload(c, "application/pdf", 11L * 1024 * 1024));
}
[Fact]
public void Rejects_oversized_file()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.NotNull(DocumentRules.RejectUpload(c, "application/pdf", 11L * 1024 * 1024));
}
[Fact]
public void Accepts_valid_file()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.Null(DocumentRules.RejectUpload(c, "application/pdf", 5L * 1024 * 1024));
}
[Fact]
public void Accepts_valid_file()
{
var c = DocumentRules.Find("registratie", "diploma");
Assert.Null(DocumentRules.RejectUpload(c, "application/pdf", 5L * 1024 * 1024));
}
private static IReadOnlyList<string> Ids(string? herkomst, string? taalvaardigheid) =>
DocumentRules.CategoriesFor("registratie", herkomst, taalvaardigheid).Select(c => c.CategoryId).ToList();
private static IReadOnlyList<string> Ids(string? herkomst, string? taalvaardigheid) =>
DocumentRules.CategoriesFor("registratie", herkomst, taalvaardigheid).Select(c => c.CategoryId).ToList();
[Fact]
public void First_load_has_no_diploma_upload() => // no diploma chosen yet
Assert.Equal(new[] { "identiteit" }, Ids(null, null));
[Fact]
public void First_load_has_no_diploma_upload() => // no diploma chosen yet
Assert.Equal(new[] { "identiteit" }, Ids(null, null));
[Fact]
public void Manual_diploma_needs_a_diploma_upload() =>
Assert.Equal(new[] { "diploma", "identiteit" }, Ids("handmatig", null));
[Fact]
public void Manual_diploma_needs_a_diploma_upload() =>
Assert.Equal(new[] { "diploma", "identiteit" }, Ids("handmatig", null));
[Fact]
public void Duo_diploma_skips_diploma_upload() =>
Assert.DoesNotContain("diploma", Ids("duo", null));
[Fact]
public void Duo_diploma_skips_diploma_upload() =>
Assert.DoesNotContain("diploma", Ids("duo", null));
[Fact]
public void Confirmed_dutch_proficiency_requires_taalvaardigheid_proof() =>
Assert.Contains("taalvaardigheid", Ids("handmatig", "ja"));
[Fact]
public void Confirmed_dutch_proficiency_requires_taalvaardigheid_proof() =>
Assert.Contains("taalvaardigheid", Ids("handmatig", "ja"));
[Fact]
public void Unconfirmed_proficiency_requires_no_taalvaardigheid_proof() =>
Assert.DoesNotContain("taalvaardigheid", Ids("handmatig", "nee"));
[Fact]
public void Unconfirmed_proficiency_requires_no_taalvaardigheid_proof() =>
Assert.DoesNotContain("taalvaardigheid", Ids("handmatig", "nee"));
[Fact]
public void Find_resolves_taalvaardigheid_for_upload_validation() =>
Assert.NotNull(DocumentRules.Find("registratie", "taalvaardigheid"));
[Fact]
public void Find_resolves_taalvaardigheid_for_upload_validation() =>
Assert.NotNull(DocumentRules.Find("registratie", "taalvaardigheid"));
}
public class HerregistratieRuleTests
{
private static Registration Active(DateOnly deadline) => new(
"19012345601", "Test", "Arts",
new DateOnly(2012, 9, 1), new DateOnly(1985, 3, 14),
new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: deadline));
private static Registration Active(DateOnly deadline) => new(
"19012345601", "Test", "Arts",
new DateOnly(2012, 9, 1), new DateOnly(1985, 3, 14),
new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: deadline));
[Fact]
public void Eligible_within_window()
{
var (eligible, reason) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2026, 6, 26));
Assert.True(eligible);
Assert.Contains("12 maanden", reason);
}
[Fact]
public void Eligible_within_window()
{
var (eligible, reason) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2026, 6, 26));
Assert.True(eligible);
Assert.Contains("12 maanden", reason);
}
[Fact]
public void Not_eligible_before_window()
{
var (eligible, _) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2025, 1, 1));
Assert.False(eligible);
}
[Fact]
public void Not_eligible_before_window()
{
var (eligible, _) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2025, 1, 1));
Assert.False(eligible);
}
[Fact]
public void Eligible_on_window_boundary()
{
// window opens exactly 12 months before the deadline
var (eligible, _) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2026, 3, 1));
Assert.True(eligible);
}
[Fact]
public void Eligible_on_window_boundary()
{
// window opens exactly 12 months before the deadline
var (eligible, _) = HerregistratieRule.Evaluate(
Active(new DateOnly(2027, 3, 1)), today: new DateOnly(2026, 3, 1));
Assert.True(eligible);
}
[Fact]
public void Suspended_is_not_eligible()
[Fact]
public void Suspended_is_not_eligible()
{
var reg = Active(new DateOnly(2027, 3, 1)) with
{
var reg = Active(new DateOnly(2027, 3, 1)) with
{
Status = new RegistrationStatus(StatusTag.Geschorst, GeschorstTot: new DateOnly(2027, 1, 1), Reden: "x"),
};
var (eligible, _) = HerregistratieRule.Evaluate(reg, today: new DateOnly(2026, 6, 26));
Assert.False(eligible);
}
Status = new RegistrationStatus(StatusTag.Geschorst, GeschorstTot: new DateOnly(2027, 1, 1), Reden: "x"),
};
var (eligible, _) = HerregistratieRule.Evaluate(reg, today: new DateOnly(2026, 6, 26));
Assert.False(eligible);
}
[Fact]
public void Status_consistency_invariant()
{
Assert.True(HerregistratieRule.IsStatusConsistent(
new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: new DateOnly(2027, 3, 1))));
Assert.False(HerregistratieRule.IsStatusConsistent(
new RegistrationStatus(StatusTag.Geregistreerd)));
}
[Fact]
public void Status_consistency_invariant()
{
Assert.True(HerregistratieRule.IsStatusConsistent(
new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: new DateOnly(2027, 3, 1))));
Assert.False(HerregistratieRule.IsStatusConsistent(
new RegistrationStatus(StatusTag.Geregistreerd)));
}
}
public class DiplomaRuleTests
{
private static Diploma Diploma(string opleiding, bool engelstalig) =>
new("x", "naam", "instelling", 2011, opleiding, engelstalig);
private static Diploma Diploma(string opleiding, bool engelstalig) =>
new("x", "naam", "instelling", 2011, opleiding, engelstalig);
[Theory]
[InlineData("geneeskunde", "Arts")]
[InlineData("verpleegkunde", "Verpleegkundige")]
[InlineData("onbekend-programma", "Onbekend")]
public void Profession_is_derived_from_program(string opleiding, string expected) =>
Assert.Equal(expected, DiplomaRules.ProfessionFor(Diploma(opleiding, false)));
[Theory]
[InlineData("geneeskunde", "Arts")]
[InlineData("verpleegkunde", "Verpleegkundige")]
[InlineData("onbekend-programma", "Onbekend")]
public void Profession_is_derived_from_program(string opleiding, string expected) =>
Assert.Equal(expected, DiplomaRules.ProfessionFor(Diploma(opleiding, false)));
[Fact]
public void English_diploma_requires_dutch_proficiency()
{
var questions = DiplomaRules.QuestionsFor(Diploma("geneeskunde", engelstalig: true));
Assert.Single(questions);
Assert.Equal("nl-taalvaardigheid", questions[0].Id);
}
[Fact]
public void English_diploma_requires_dutch_proficiency()
{
var questions = DiplomaRules.QuestionsFor(Diploma("geneeskunde", engelstalig: true));
Assert.Single(questions);
Assert.Equal("nl-taalvaardigheid", questions[0].Id);
}
[Fact]
public void Dutch_diploma_has_no_policy_questions() =>
Assert.Empty(DiplomaRules.QuestionsFor(Diploma("geneeskunde", engelstalig: false)));
[Fact]
public void Dutch_diploma_has_no_policy_questions() =>
Assert.Empty(DiplomaRules.QuestionsFor(Diploma("geneeskunde", engelstalig: false)));
[Fact]
public void Manual_diploma_gets_maximal_set()
{
var questions = DiplomaRules.ManualQuestions();
Assert.Equal(3, questions.Count);
Assert.Equal(new[] { "nl-taalvaardigheid", "diploma-erkend", "toelichting" },
questions.Select(q => q.Id));
}
[Fact]
public void Manual_diploma_gets_maximal_set()
{
var questions = DiplomaRules.ManualQuestions();
Assert.Equal(3, questions.Count);
Assert.Equal(new[] { "nl-taalvaardigheid", "diploma-erkend", "toelichting" },
questions.Select(q => q.Id));
}
[Fact]
public void Manual_professions_match_known_programs() =>
Assert.Equal(new[] { "Arts", "Verpleegkundige", "Fysiotherapeut", "Apotheker", "Tandarts" },
DiplomaRules.ManualProfessions());
[Fact]
public void Manual_professions_match_known_programs() =>
Assert.Equal(new[] { "Arts", "Verpleegkundige", "Fysiotherapeut", "Apotheker", "Tandarts" },
DiplomaRules.ManualProfessions());
}
public class SubmissionRuleTests
{
[Fact]
public void Manual_diploma_is_rejected() =>
Assert.NotNull(SubmissionRules.RejectRegistratie("handmatig"));
[Fact]
public void Manual_diploma_is_rejected() =>
Assert.NotNull(SubmissionRules.RejectRegistratie("handmatig"));
[Fact]
public void Duo_diploma_is_accepted() =>
Assert.Null(SubmissionRules.RejectRegistratie("duo"));
[Fact]
public void Duo_diploma_is_accepted() =>
Assert.Null(SubmissionRules.RejectRegistratie("duo"));
[Fact]
public void Zero_hours_is_rejected() =>
Assert.NotNull(SubmissionRules.RejectZeroUren(0));
[Fact]
public void Zero_hours_is_rejected() =>
Assert.NotNull(SubmissionRules.RejectZeroUren(0));
[Fact]
public void Worked_hours_are_accepted() =>
Assert.Null(SubmissionRules.RejectZeroUren(40));
[Fact]
public void Worked_hours_are_accepted() =>
Assert.Null(SubmissionRules.RejectZeroUren(40));
[Theory]
[InlineData("Lange Voorhout 9", "2514 EA", null)] // valid
[InlineData("", "2514 EA", "Vul straat en huisnummer in.")]
[InlineData("Straat 1", "nope", "Voer een geldige postcode in, bijv. 1234 AB.")]
public void Change_request_is_validated(string straat, string postcode, string? expected) =>
Assert.Equal(expected, SubmissionRules.RejectChangeRequest(straat, postcode));
[Theory]
[InlineData("Lange Voorhout 9", "2514 EA", null)] // valid
[InlineData("", "2514 EA", "Vul straat en huisnummer in.")]
[InlineData("Straat 1", "nope", "Voer een geldige postcode in, bijv. 1234 AB.")]
public void Change_request_is_validated(string straat, string postcode, string? expected) =>
Assert.Equal(expected, SubmissionRules.RejectChangeRequest(straat, postcode));
}