S-04a: the deferred S-04 acceptance criterion. A gated Acl.IntegrationTests project (Category=Integration) drives the real OpenZaakGateway against the running compose stack — real ZGW JWT auth and the real POST /zaken contract a stubbed HttpMessageHandler cannot exercise. The lane is kept out of the fast checks: make unit filters Category!=Integration, Stryker is pinned to Acl.Tests, and a new make integration target brings the stack up, seeds a published zaaktype and tears down. Red: against real OpenZaak the gateway POST fails 400 — JsonContent streams the body chunked and OpenZaak's uwsgi rejects it. Fixed in the next commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
4.0 KiB
C#
98 lines
4.0 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Acl.Infrastructure;
|
|
|
|
namespace Acl.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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,
|
|
};
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public async Task<Uri?> 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()!);
|
|
}
|
|
|
|
/// <summary>GETs a previously-created zaak to prove it was really persisted.</summary>
|
|
public async Task<JsonElement> 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<OpenZaakFixture>
|
|
{
|
|
public const string Name = "OpenZaak";
|
|
}
|