using System.Net; using System.Net.Http.Json; using System.Text.RegularExpressions; using BigRegister.Api.Contracts; using BigRegister.Api.Data; using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; /// WP-41: the persisted authz/PII-reveal audit trail is queryable, data-minimised (no PII). public class AuthzAuditTests(TestWebApplicationFactory factory) : IClassFixture { private readonly HttpClient _client = factory.CreateClient(); private HttpRequestMessage Admin(HttpMethod method, string path) { var req = new HttpRequestMessage(method, path); req.Headers.Add("X-Role", "admin"); return req; } private async Task> AuditLog() { var res = await _client.SendAsync(Admin(HttpMethod.Get, "/api/v1/admin/audit")); res.EnsureSuccessStatusCode(); return (await res.Content.ReadFromJsonAsync>())!; } [Fact] public async Task A_denied_admin_action_is_recorded() { // No X-Role → drafter → 403 on an admin endpoint → a deny entry. Assert.Equal(HttpStatusCode.Forbidden, (await _client.GetAsync("/api/v1/admin/cases")).StatusCode); Assert.Contains(await AuditLog(), e => e.Action == "cases:manage" && e.Decision == "deny"); } [Fact] public async Task A_reveal_attempt_is_recorded() { // Drafter (capable role) without X-Step-Up → reveal denied → recorded. var res = await _client.PostAsync("/api/v1/brief/reveal-bignummer", null); Assert.Equal(HttpStatusCode.Forbidden, res.StatusCode); Assert.Contains(await AuditLog(), e => e.Action == "brief:reveal-bignummer"); } [Fact] public void The_audit_schema_carries_no_pii() { var names = typeof(AuthzAuditEntry).GetProperties().Select(p => p.Name).ToArray(); Assert.Equal( new[] { "At", "Action", "Resource", "Decision", "Role", "CorrelationId", "Id" }.OrderBy(x => x), names.OrderBy(x => x)); Assert.DoesNotContain(names, n => Regex.IsMatch(n, "naam|name|bsn|value|waarde", RegexOptions.IgnoreCase)); } }