using System.Net.Http.Json; using System.Text.Json.Serialization; namespace Azaion.Missions.E2E.Fixtures; /// /// Triggers POST {jwks-mock}/rotate-key and waits up to /// RotationTimeout for the missions service to refresh its JWKS cache, /// observable via successful authentication with the new kid. /// public sealed class JwksRotateFixture { public TimeSpan RotationTimeout { get; init; } = TimeSpan.FromSeconds(45); public async Task RotateAndWaitAsync( Func> isNewKeyAccepted, CancellationToken ct = default) { var rotateUrl = new Uri(new Uri(TestEnvironment.JwksMockBaseUrl), "/rotate-key"); using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; using var resp = await http.PostAsync(rotateUrl, content: null, ct).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); var rotated = await resp.Content.ReadFromJsonAsync(cancellationToken: ct).ConfigureAwait(false); if (rotated is null) throw new InvalidOperationException("jwks-mock /rotate-key returned an empty body"); var deadline = DateTime.UtcNow + RotationTimeout; while (DateTime.UtcNow < deadline) { if (await isNewKeyAccepted().ConfigureAwait(false)) return new RotationResult(rotated.Kid, Accepted: true); await Task.Delay(TimeSpan.FromMilliseconds(500), ct).ConfigureAwait(false); } return new RotationResult(rotated.Kid, Accepted: false); } public sealed record RotationResult(string NewKid, bool Accepted); private sealed record RotateResponse( [property: JsonPropertyName("kid")] string Kid); }