using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; using System.Text.Json; using Acl.Infrastructure; namespace Acl.IntegrationTests; /// /// Shared connection to the running OpenZaak compose stack (ADR-0006). Reads the /// same endpoint + JWT-client config the seed uses, and locates the published /// BIG-REGISTRATIE zaaktype the ACL opens zaken against. Defaults match /// `infra/openzaak/seed_catalogus.py`; override via OZ_BASE / OZ_CLIENT_ID / OZ_SECRET. /// public sealed class OpenZaakFixture : IDisposable { private static string Env(string key, string fallback) => Environment.GetEnvironmentVariable(key) is { Length: > 0 } v ? v : fallback; public Uri BaseUrl { get; } = new(Env("OZ_BASE", "http://localhost:8000")); public string ClientId { get; } = Env("OZ_CLIENT_ID", "big-reference-seed"); public string Secret { get; } = Env("OZ_SECRET", "insecure-dev-secret-change-me"); public HttpClient Http { get; } = new(); public OpenZaakOptions Options => new() { BaseUrl = BaseUrl, ClientId = ClientId, Secret = Secret, }; /// /// The URL of the published BIG-REGISTRATIE zaaktype, or null when none is /// published yet (a concept-only stack). `status=definitief` returns published /// zaaktypen only — a concept zaaktype is deliberately excluded. /// public async Task FindPublishedBigZaaktypeAsync(CancellationToken ct = default) { var query = new Uri(BaseUrl, "/catalogi/api/v1/zaaktypen?identificatie=BIG-REGISTRATIE&status=definitief"); using var message = new HttpRequestMessage(HttpMethod.Get, query); message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MintToken()); using var response = await Http.SendAsync(message, ct); response.EnsureSuccessStatusCode(); using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync(ct)); var results = document.RootElement.GetProperty("results"); return results.GetArrayLength() == 0 ? null : new Uri(results[0].GetProperty("url").GetString()!); } /// GETs a previously-created zaak to prove it was really persisted. public async Task GetZaakAsync(Uri zaakUrl, CancellationToken ct = default) { using var message = new HttpRequestMessage(HttpMethod.Get, zaakUrl); message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", MintToken()); message.Headers.Add("Accept-Crs", "EPSG:4326"); using var response = await Http.SendAsync(message, ct); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(ct); return JsonDocument.Parse(json).RootElement.Clone(); } // A ZGW (vng-api-common) HS256 JWT, mirroring the seed's client. Minted here // rather than reusing Acl.Infrastructure's internal minter to keep that internal. private string MintToken() { static string B64(byte[] b) => Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_'); var header = B64(JsonSerializer.SerializeToUtf8Bytes(new { alg = "HS256", typ = "JWT" })); var payload = B64(JsonSerializer.SerializeToUtf8Bytes(new { iss = ClientId, iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), client_id = ClientId, user_id = "acl-integration-test", user_representation = "acl-integration-test", })); var signingInput = $"{header}.{payload}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(Secret)); var signature = B64(hmac.ComputeHash(Encoding.UTF8.GetBytes(signingInput))); return $"{signingInput}.{signature}"; } public void Dispose() => Http.Dispose(); } [CollectionDefinition(Name)] public sealed class OpenZaakCollection : ICollectionFixture { public const string Name = "OpenZaak"; }