using Acl.Application; using Acl.Infrastructure; namespace Acl.IntegrationTests; /// /// S-19a (#149): the ObjectenGateway against a *real* Objecten + Objecttypen pair. The stubbed /// -HttpMessageHandler unit tests pin the shape of the calls; only this proves the shape is the one /// the upstream modules actually accept โ€” the static Token auth, the CRS headers, the objecttype /// resolution by name, the `data_attrs` search, and the create/update the upsert relies on being /// idempotent (ADR-0028). /// [Trait("Category", "Integration")] public sealed class ObjectenGatewayIntegrationTests { private static string Env(string key, string fallback) => Environment.GetEnvironmentVariable(key) is { Length: > 0 } v ? v : fallback; private static ObjectenGateway Gateway() => new( new HttpClient(), new ObjectenOptions { BaseUrl = new(Env("OBJECTEN_BASE", "http://objecten.local:8000")), Token = Env("OBJECTEN_TOKEN", "1234567890abcdef1234567890abcdef12345678"), ObjecttypenBaseUrl = new(Env("OBJECTTYPEN_BASE", "http://objecttypen:8000")), ObjecttypenToken = Env("OBJECTTYPEN_TOKEN", "0123456789abcdef0123456789abcdef01234567"), ObjecttypeName = "RegisterRecord", }, new SystemClock()); [Fact] public async Task Writes_a_register_record_and_updates_it_in_place_on_a_second_write() { var gateway = Gateway(); // A key no other run shares: the verify stack is shared and keeps records between checks. var id = Guid.NewGuid().ToString(); await gateway.UpsertAsync(new RegisterRecord(id, RegisterRecordStatus.Ingediend, "INT-TEST-1")); await gateway.UpsertAsync(new RegisterRecord(id, RegisterRecordStatus.Ingeschreven, "INT-TEST-1")); var records = await ReadAllAsync(id); var only = Assert.Single(records); // Re-approving updates the existing object rather than creating a second one (ยง8.6). Assert.Equal(RegisterRecordStatus.Ingeschreven, only.Status); Assert.Equal("INT-TEST-1", only.Reference); } [Fact] public async Task Is_rejected_by_the_objecttype_schema_when_a_record_is_not_public_safe() { // The gateway cannot construct such a record โ€” RegisterRecord has no bsn โ€” so this asserts the // guarantee from the other side: Objecten itself refuses anything the schema does not sanction // (ADR-0027). Posted raw, exactly as the gateway would post a record. var gateway = Gateway(); var id = Guid.NewGuid().ToString(); await gateway.UpsertAsync(new RegisterRecord(id, RegisterRecordStatus.Ingeschreven, "INT-TEST-2")); var stored = Assert.Single(await ReadAllAsync(id)); Assert.Null(stored.Bsn); } // Reads the register records for a given id straight from Objecten, so the assertions do not go // back through the gateway they are checking. private static async Task> ReadAllAsync(string id) { using var http = new HttpClient(); var objecttype = await ResolveObjecttypeUrlAsync(http); var query = new Uri(new Uri(Env("OBJECTEN_BASE", "http://objecten.local:8000")), "/api/v2/objects?type=" + Uri.EscapeDataString(objecttype) + "&data_attrs=id__exact__" + Uri.EscapeDataString(id)); using var message = new HttpRequestMessage(HttpMethod.Get, query); message.Headers.Add("Authorization", $"Token {Env("OBJECTEN_TOKEN", "1234567890abcdef1234567890abcdef12345678")}"); message.Headers.Add("Accept-Crs", "EPSG:4326"); using var response = await http.SendAsync(message); response.EnsureSuccessStatusCode(); using var document = System.Text.Json.JsonDocument.Parse(await response.Content.ReadAsStringAsync()); return document.RootElement.GetProperty("results").EnumerateArray() .Select(o => o.GetProperty("record").GetProperty("data")) .Select(d => new StoredRecord( d.GetProperty("status").GetString()!, d.GetProperty("reference").GetString(), d.TryGetProperty("bsn", out var bsn) ? bsn.GetString() : null)) .ToList(); } private static async Task ResolveObjecttypeUrlAsync(HttpClient http) { var query = new Uri(new Uri(Env("OBJECTTYPEN_BASE", "http://objecttypen:8000")), "/api/v2/objecttypes"); using var message = new HttpRequestMessage(HttpMethod.Get, query); message.Headers.Add("Authorization", $"Token {Env("OBJECTTYPEN_TOKEN", "0123456789abcdef0123456789abcdef01234567")}"); using var response = await http.SendAsync(message); response.EnsureSuccessStatusCode(); using var document = System.Text.Json.JsonDocument.Parse(await response.Content.ReadAsStringAsync()); return document.RootElement.GetProperty("results").EnumerateArray() .First(o => o.GetProperty("name").GetString() == "RegisterRecord") .GetProperty("url").GetString()!; } private sealed record StoredRecord(string Status, string? Reference, string? Bsn); }