Files
atomic-design-poc/backend/tests/BigRegister.Tests/UploadAccessTests.cs
T
ehoandClaude Sonnet 5 8560746d15 refactor: strip WP-/RB- ticket refs from backend (RD-19)
The backend half of the sweep RD-18 did for the front end. git blame
holds the provenance and stays correct when the code moves; the
comment names a closed ticket and tells the reader nothing the
sentence around it does not.

public/letter.css and LetterHtml.golden.html change together, because
the renderer inlines the CSS and the golden file snapshots the
result.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 21:48:08 +02:00

100 lines
3.8 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using BigRegister.Api.Contracts;
using BigRegister.Api.Data;
using Microsoft.AspNetCore.Mvc.Testing;
namespace BigRegister.Tests;
/// Who may see what about an upload. BIO-004: GET /uploads/{id}/content and
/// /uploads/status used to take no HttpContext at all — a diploma or identity scan was
/// protected by GUID unguessability alone, while DELETE on the same resource was
/// owner-scoped. BIO-005: the document audit trail recorded the raw owner BSN as
/// its Actor, on a store whose own doc comment says it holds no PII.
public class UploadAccessTests(TestWebApplicationFactory factory) : IClassFixture<TestWebApplicationFactory>
{
private readonly HttpClient _client = factory.CreateClient();
private const string OtherCitizen = "999999990";
private async Task<string> UploadAsOwner()
{
var form = new MultipartFormDataContent();
var file = new ByteArrayContent(new byte[] { 1, 2, 3 });
file.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(file, "file", "diploma.pdf");
form.Add(new StringContent("diploma"), "categoryId");
form.Add(new StringContent("local-rb01"), "localId");
form.Add(new StringContent("registratie"), "wizardId");
var res = await _client.PostAsync("/api/v1/uploads", form);
Assert.Equal(HttpStatusCode.Created, res.StatusCode);
return (await res.Content.ReadFromJsonAsync<UploadResponse>())!.DocumentId;
}
private Task<HttpResponseMessage> Get(string path, params (string Name, string Value)[] headers)
{
var req = new HttpRequestMessage(HttpMethod.Get, path);
foreach (var (name, value) in headers) req.Headers.Add(name, value);
return _client.SendAsync(req);
}
[Fact]
public async Task The_owner_can_read_the_bytes()
{
var id = await UploadAsOwner();
Assert.Equal(HttpStatusCode.OK, (await Get($"/api/v1/uploads/{id}/content")).StatusCode);
}
[Fact]
public async Task Another_citizen_gets_404_not_403()
{
var id = await UploadAsOwner();
// 404, not 403: a foreign id must not be distinguishable from one that never existed.
Assert.Equal(HttpStatusCode.NotFound,
(await Get($"/api/v1/uploads/{id}/content", ("X-Subject", OtherCitizen))).StatusCode);
}
[Fact]
public async Task A_behandelaar_can_read_a_linked_document()
{
var id = await UploadAsOwner();
Assert.Equal(HttpStatusCode.OK,
(await Get($"/api/v1/uploads/{id}/content", ("X-Medewerker", "medewerker-1"))).StatusCode);
}
[Fact]
public async Task A_medewerker_without_the_behandelaar_rol_does_not()
{
var id = await UploadAsOwner();
Assert.Equal(HttpStatusCode.NotFound,
(await Get($"/api/v1/uploads/{id}/content",
("X-Medewerker", "medewerker-1"), ("X-Rollen", "geen"))).StatusCode);
}
[Fact]
public async Task The_document_audit_trail_records_a_masked_actor()
{
var id = await UploadAsOwner();
(await _client.DeleteAsync($"/api/v1/uploads/{id}")).EnsureSuccessStatusCode();
var rows = DocumentStore.AuditLog.Where(e => e.DocumentId == id).ToList();
Assert.Equal(new[] { "upload", "delete-user" }, rows.Select(e => e.Action));
Assert.All(rows, e => Assert.Equal("******782", e.Actor));
// The unmasked BSN stays where it is load-bearing — the ownership key, not the trail.
Assert.All(rows, e => Assert.DoesNotContain(DocumentStore.DemoOwner, e.Actor));
}
[Fact]
public async Task Status_reports_another_citizens_localId_as_unknown()
{
await UploadAsOwner();
var res = await Get("/api/v1/uploads/status?localIds=local-rb01", ("X-Subject", OtherCitizen));
res.EnsureSuccessStatusCode();
var status = (await res.Content.ReadFromJsonAsync<UploadStatusDto>())!;
var item = Assert.Single(status.Results);
Assert.Equal("unknown", item.Status);
Assert.Null(item.DocumentId);
}
}