using System.Net; using System.Text; using System.Text.Json; using Xunit; namespace SGU.CredentialProvider.Tests; public sealed class BrokerClientTests { [Fact] public void DefaultClientTimeoutLeavesMarginForThePortalAndBroker() { Assert.Equal(20, new ProviderSettings().TimeoutSeconds); } [Fact] public async Task SendsTheOriginalPasswordWithoutDerivation() { const string original = "Exacta-Árbol-🔐-27!"; CapturingHandler handler = new(HttpStatusCode.OK, """ {"domain":"LCI","username":"DO123456","upn":"DO123456@lci.lasalle.mx","created":true,"moved":false} """); using BrokerClient client = new(CreateSettings(), handler); BrokerDecision decision = await client.AuthenticateAsync( "DO123456", original, TestContext.Current.CancellationToken); Assert.Equal(BrokerDecisionKind.Authorized, decision.Kind); using JsonDocument requestJson = JsonDocument.Parse(handler.RequestBody); Assert.Equal(original, requestJson.RootElement.GetProperty("password").GetString()); Assert.DoesNotContain("derived", handler.RequestBody, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task ExplicitUnauthorizedResponseStopsTheLogin() { using BrokerClient client = new( CreateSettings(), new CapturingHandler(HttpStatusCode.Unauthorized, "{\"code\":\"INVALID_INSTITUTIONAL_CREDENTIALS\"}")); BrokerDecision decision = await client.AuthenticateAsync( "AL123456", "Wrong", TestContext.Current.CancellationToken); Assert.Equal(BrokerDecisionKind.InvalidCredentials, decision.Kind); } [Fact] public async Task BrokerOutageRequestsWindowsCachedCredentialFallback() { using BrokerClient client = new( CreateSettings(), new CapturingHandler(HttpStatusCode.ServiceUnavailable, "{\"code\":\"NTLM_UPSTREAM_ERROR\"}")); BrokerDecision decision = await client.AuthenticateAsync( "AD123456", "LastKnown", TestContext.Current.CancellationToken); Assert.Equal(BrokerDecisionKind.Unavailable, decision.Kind); } private static ProviderSettings CreateSettings() => new() { BrokerEndpoint = new Uri("https://broker.example.test/v1/authenticate"), DomainNetbios = "LCI", TimeoutSeconds = 5, ClientCertificateThumbprint = new string('A', 40), ServerCertificateThumbprint = new string('B', 40) }; private sealed class CapturingHandler(HttpStatusCode statusCode, string responseBody) : HttpMessageHandler { public string RequestBody { get; private set; } = string.Empty; protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { RequestBody = request.Content is null ? string.Empty : await request.Content.ReadAsStringAsync(cancellationToken); return new HttpResponseMessage(statusCode) { Content = new StringContent(responseBody, Encoding.UTF8, "application/json") }; } } }