using System.Net; using System.Text.Json; using Campaign_Tracker.Server.Authentication; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace Campaign_Tracker.Server.Tests; public sealed class KeycloakTokenClientTests { [Fact] public async Task ExchangeAuthorizationCodeAsync_PostsClientSecretToKeycloakTokenEndpoint() { using var handler = new CapturingMessageHandler(); using var httpClient = new HttpClient(handler); var client = new KeycloakTokenClient( httpClient, Options.Create(new KeycloakOptions { Authority = "http://localhost:8180/realms/KCI", PublicAuthority = "http://localhost:8180/realms/KCI", ClientId = "canopy-web", ClientSecret = "secret-from-env", }), NullLogger.Instance); var tokens = await client.ExchangeAuthorizationCodeAsync( "auth-code", "http://kci-app01.ntp.kentcommunications.com/auth/callback", CancellationToken.None); Assert.Equal("http://localhost:8180/realms/KCI/protocol/openid-connect/token", handler.RequestUri); Assert.Equal("access-token", tokens.AccessToken); Assert.Contains("client_id=canopy-web", handler.FormBody); Assert.Contains("client_secret=secret-from-env", handler.FormBody); Assert.Contains("code=auth-code", handler.FormBody); Assert.Contains("grant_type=authorization_code", handler.FormBody); } private sealed class CapturingMessageHandler : HttpMessageHandler, IDisposable { public string RequestUri { get; private set; } = string.Empty; public string FormBody { get; private set; } = string.Empty; protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { RequestUri = request.RequestUri?.ToString() ?? string.Empty; FormBody = await request.Content!.ReadAsStringAsync(cancellationToken); return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(new { access_token = "access-token", refresh_token = "refresh-token", expires_in = 300, })), }; } } }