using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using BigRegister.Api.Contracts; using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; /// WP-65 (read side): one aanvraag's case-treatment detail, gated by the same medewerker /// capability (`CanBeoordelen`, WP-62) as the werkvoorraad list (WP-64). public class BeoordelingTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); private static HttpRequestMessage AsBehandelaar(HttpMethod method, string path) { var req = new HttpRequestMessage(method, path); req.Headers.Add("X-Medewerker", "medewerker-1"); return req; } private static MultipartFormDataContent UploadForm(string localId, string categoryId, string fileName) { var content = new MultipartFormDataContent(); var file = new ByteArrayContent(new byte[] { 1, 2, 3 }); file.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(file, "file", fileName); content.Add(new StringContent(categoryId), "categoryId"); content.Add(new StringContent(localId), "localId"); content.Add(new StringContent("registratie"), "wizardId"); return content; } /// A manual (never auto-approved) case with one linked document, so it stays /// InBehandeling/decidable regardless of test timing (the 8s auto-approval window /// would otherwise make a duo-registratie/herregistratie fixture flaky). private async Task<(ApplicationDetailDto App, string DocumentId)> CreateManualCaseWithDocument() { var created = await _client.PostAsJsonAsync("/api/v1/applications", new { type = "registratie" }); var a = (await created.Content.ReadFromJsonAsync())!; var localId = Guid.NewGuid().ToString(); var upload = await _client.PostAsync("/api/v1/uploads", UploadForm(localId, "diploma", "diploma.pdf")); upload.EnsureSuccessStatusCode(); var doc = (await upload.Content.ReadFromJsonAsync())!; var submit = await _client.PostAsJsonAsync($"/api/v1/applications/{a.Id}/submit", new { diplomaHerkomst = "handmatig", documents = new[] { new { categoryId = "diploma", channel = "digital", documentId = doc.DocumentId } }, }); submit.EnsureSuccessStatusCode(); return (a, doc.DocumentId!); } private Task DeleteAsAdmin(string id) => _client.SendAsync(new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/admin/cases/{id}") { Headers = { { "X-Role", "admin" } }, }); [Fact] public async Task Detail_shows_status_documents_and_a_masked_owner() { var (a, documentId) = await CreateManualCaseWithDocument(); try { var res = await _client.SendAsync(AsBehandelaar(HttpMethod.Get, $"/api/v1/beoordeling/{a.Id}")); res.EnsureSuccessStatusCode(); var view = (await res.Content.ReadFromJsonAsync())!; Assert.Equal("InBehandeling", view.Aanvraag.Status.Tag); Assert.Single(view.Documenten); Assert.Equal(documentId, view.Documenten[0].DocumentId); Assert.Equal("diploma", view.Documenten[0].CategoryId); Assert.True(view.Decisions.CanBesluiten); // masked: not empty, but not the full 9-digit BSN either var owner = view.Aanvraag.Owner!; Assert.NotEmpty(owner); Assert.Contains('*', owner); } finally { await DeleteAsAdmin(a.Id); } } [Fact] public async Task Concept_and_unknown_id_are_not_found() { var created = await _client.PostAsJsonAsync("/api/v1/applications", new { type = "registratie" }); var concept = (await created.Content.ReadFromJsonAsync())!; try { var conceptRes = await _client.SendAsync(AsBehandelaar(HttpMethod.Get, $"/api/v1/beoordeling/{concept.Id}")); Assert.Equal(HttpStatusCode.NotFound, conceptRes.StatusCode); var unknownRes = await _client.SendAsync(AsBehandelaar(HttpMethod.Get, "/api/v1/beoordeling/does-not-exist")); Assert.Equal(HttpStatusCode.NotFound, unknownRes.StatusCode); } finally { await _client.DeleteAsync($"/api/v1/applications/{concept.Id}"); } } [Fact] public async Task Zorgverlener_is_forbidden_even_with_admin_role() { var (a, _) = await CreateManualCaseWithDocument(); try { var req = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/beoordeling/{a.Id}"); req.Headers.Add("X-Role", "admin"); // admin role, but no X-Medewerker — still a zorgverlener Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode); } finally { await DeleteAsAdmin(a.Id); } } [Fact] public async Task Medewerker_without_behandelaar_rol_is_forbidden() { var req = new HttpRequestMessage(HttpMethod.Get, "/api/v1/beoordeling/anything"); req.Headers.Add("X-Medewerker", "medewerker-2"); req.Headers.Add("X-Rollen", "geen"); Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode); } }