using NATS.Server.Auth; using NATS.Server.Protocol; namespace NATS.Server.Tests; public class ExternalAuthCalloutTests { [Fact] public void External_callout_authenticator_can_allow_and_deny_with_timeout_and_reason_mapping() { var authenticator = new ExternalAuthCalloutAuthenticator( new FakeExternalAuthClient(), TimeSpan.FromMilliseconds(50)); var allowed = authenticator.Authenticate(new ClientAuthContext { Opts = new ClientOptions { Username = "u", Password = "p" }, Nonce = [], }); allowed.ShouldNotBeNull(); allowed.Identity.ShouldBe("u"); var denied = authenticator.Authenticate(new ClientAuthContext { Opts = new ClientOptions { Username = "u", Password = "bad" }, Nonce = [], }); denied.ShouldBeNull(); var timeout = new ExternalAuthCalloutAuthenticator( new SlowExternalAuthClient(TimeSpan.FromMilliseconds(200)), TimeSpan.FromMilliseconds(30)); timeout.Authenticate(new ClientAuthContext { Opts = new ClientOptions { Username = "u", Password = "p" }, Nonce = [], }).ShouldBeNull(); } private sealed class FakeExternalAuthClient : IExternalAuthClient { public Task AuthorizeAsync(ExternalAuthRequest request, CancellationToken ct) { if (request is { Username: "u", Password: "p" }) return Task.FromResult(new ExternalAuthDecision(true, "u", "A")); return Task.FromResult(new ExternalAuthDecision(false, Reason: "denied")); } } private sealed class SlowExternalAuthClient(TimeSpan delay) : IExternalAuthClient { public async Task AuthorizeAsync(ExternalAuthRequest request, CancellationToken ct) { await Task.Delay(delay, ct); return new ExternalAuthDecision(true, "slow"); } } }