Mappers.ToAdminSummaryDto set Owner to the raw BSN. Both consumers are
cross-owner lists read by someone who is not the subject — GET /admin/cases
and GET /werkvoorraad — while GET /beoordeling/{id}, the detail view of the
same data, already masked it. The detail screen showed ******782 and the list
one click earlier showed the whole thing.
Masked in the mapper rather than at each endpoint, so a third cross-owner
list cannot be added that forgets to.
MaskTail moves out of Program.cs into Domain/People/Pii.cs: it now has
callers in Contracts, Program.cs and (once RB-04 lands) Data, and a second
hand-rolled copy is how one of them drifts into leaking. Documented as
idempotent, which is what lets /beoordeling/{id} keep its own call —
IZaakSource has a second implementation whose Owner is mapped from the
OpenZaak zaak identificatie, so that endpoint should not depend on which
source answered.
No frontend change: all three consumers display the value, and the parse
boundaries only require a non-empty string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.7 KiB
C#
100 lines
3.7 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using BigRegister.Api.Contracts;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
|
|
namespace BigRegister.Tests;
|
|
|
|
/// WP-64: the behandelportal's queue of aanvragen needing treatment, gated by the
|
|
/// medewerker capability `CanBeoordelen` (WP-62) — not the admin role.
|
|
public class WerkvoorraadTests(TestWebApplicationFactory factory) : IClassFixture<TestWebApplicationFactory>
|
|
{
|
|
private readonly HttpClient _client = factory.CreateClient();
|
|
|
|
private HttpRequestMessage AsBehandelaar(string path)
|
|
{
|
|
var req = new HttpRequestMessage(HttpMethod.Get, path);
|
|
req.Headers.Add("X-Medewerker", "medewerker-1");
|
|
return req;
|
|
}
|
|
|
|
private async Task<ApplicationDetailDto> CreateAndSubmitHerregistratie()
|
|
{
|
|
var created = await _client.PostAsJsonAsync("/api/v1/applications", new { type = "herregistratie" });
|
|
var a = (await created.Content.ReadFromJsonAsync<ApplicationDetailDto>())!;
|
|
(await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { uren = 200 }))
|
|
.EnsureSuccessStatusCode();
|
|
return a;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Behandelaar_sees_submitted_cases_in_the_queue()
|
|
{
|
|
var a = await CreateAndSubmitHerregistratie();
|
|
try
|
|
{
|
|
var res = await _client.SendAsync(AsBehandelaar("/api/v1/werkvoorraad"));
|
|
res.EnsureSuccessStatusCode();
|
|
var queue = (await res.Content.ReadFromJsonAsync<List<ApplicationSummaryDto>>())!;
|
|
var mine = queue.Single(x => x.Id == a.Id);
|
|
Assert.Equal("InBehandeling", mine.Status.Tag);
|
|
// RB-03/BIO-003: masked, like /admin/cases — both inherit ToAdminSummaryDto.
|
|
Assert.Equal("******782", mine.Owner);
|
|
}
|
|
finally
|
|
{
|
|
await _client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/admin/cases/{a.Id}")
|
|
{
|
|
Headers = { { "X-Role", "admin" } },
|
|
});
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Queue_excludes_concepts()
|
|
{
|
|
var created = await _client.PostAsJsonAsync("/api/v1/applications", new { type = "herregistratie" });
|
|
var a = (await created.Content.ReadFromJsonAsync<ApplicationDetailDto>())!;
|
|
try
|
|
{
|
|
var res = await _client.SendAsync(AsBehandelaar("/api/v1/werkvoorraad"));
|
|
res.EnsureSuccessStatusCode();
|
|
var queue = (await res.Content.ReadFromJsonAsync<List<ApplicationSummaryDto>>())!;
|
|
Assert.DoesNotContain(queue, x => x.Id == a.Id);
|
|
}
|
|
finally
|
|
{
|
|
await _client.DeleteAsync($"/api/v1/applications/{a.Id}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Zorgverlener_is_forbidden_even_with_admin_role()
|
|
{
|
|
var req = new HttpRequestMessage(HttpMethod.Get, "/api/v1/werkvoorraad");
|
|
req.Headers.Add("X-Role", "admin"); // admin role, but no X-Medewerker — still a zorgverlener
|
|
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Medewerker_without_behandelaar_rol_is_forbidden()
|
|
{
|
|
var req = new HttpRequestMessage(HttpMethod.Get, "/api/v1/werkvoorraad");
|
|
req.Headers.Add("X-Medewerker", "medewerker-2");
|
|
req.Headers.Add("X-Rollen", "geen");
|
|
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Me_reports_the_capability_only_for_a_behandelaar()
|
|
{
|
|
var behandelaar = new HttpRequestMessage(HttpMethod.Get, "/api/v1/me");
|
|
behandelaar.Headers.Add("X-Medewerker", "medewerker-1");
|
|
var caps = (await (await _client.SendAsync(behandelaar)).Content.ReadFromJsonAsync<MeDto>())!;
|
|
Assert.Contains("aanvraag:beoordelen", caps.Capabilities);
|
|
|
|
var zorgverlener = (await (await _client.GetAsync("/api/v1/me")).Content.ReadFromJsonAsync<MeDto>())!;
|
|
Assert.DoesNotContain("aanvraag:beoordelen", zorgverlener.Capabilities);
|
|
}
|
|
}
|