// Copyright 2017-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Ported from: // server/accounts_test.go (5 tests — route account mappings) // server/auth_callout_test.go (5 tests — external auth callout) // server/jwt_test.go (11 tests — JWT validation) using System.Net; using NATS.Client.Core; using Shouldly; using Xunit.Abstractions; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth; using ZB.MOM.NatsNet.Server.IntegrationTests.Helpers; namespace ZB.MOM.NatsNet.Server.IntegrationTests.Auth; /// /// Integration tests for authentication and account features. /// Mirrors Go tests from accounts_test.go, auth_callout_test.go, and jwt_test.go. /// [Collection("AuthIntegrationTests")] [Trait("Category", "Integration")] public class AuthIntegrationTests : IntegrationTestBase { public AuthIntegrationTests(ITestOutputHelper output) : base(output) { } // ========================================================================= // accounts_test.go — Account Isolation // ========================================================================= /// /// Verifies that messages published in one account are not delivered to another. /// Mirrors Go TestAccountIsolation. /// [SkippableFact] public async Task AccountIsolation_ShouldNotCrossAccounts() { // Set up a server with two accounts. var fooAccount = new Account { Name = "FOO" }; var barAccount = new Account { Name = "BAR" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [fooAccount, barAccount], Users = [ new User { Username = "foo-user", Password = "foo-pwd", Account = fooAccount }, new User { Username = "bar-user", Password = "bar-pwd", Account = barAccount }, ], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var fooNc = NatsTestClient.Connect($"nats://foo-user:foo-pwd@127.0.0.1:{port}"); await fooNc.ConnectAsync(); await using var barNc = NatsTestClient.Connect($"nats://bar-user:bar-pwd@127.0.0.1:{port}"); await barNc.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var barReceived = new TaskCompletionSource(); // BAR subscribes to "foo" subject — should NOT receive FOO's message. _ = Task.Run(async () => { try { await foreach (var _ in barNc.SubscribeAsync("foo", cancellationToken: cts.Token)) { barReceived.TrySetResult(true); break; } } catch (OperationCanceledException) { barReceived.TrySetResult(false); } }, cts.Token); await Task.Delay(100, cts.Token); // FOO publishes — BAR should NOT receive it because different account. await fooNc.PublishAsync("foo", "hello", cancellationToken: cts.Token); await fooNc.PingAsync(cancellationToken: cts.Token); // Give some time for potential cross-delivery (should not happen). await Task.Delay(200, cts.Token); cts.Cancel(); // barReceived.Task should either be false (timed out) or not set. if (barReceived.Task.IsCompleted) { barReceived.Task.Result.ShouldBeFalse("BAR should not receive FOO's message"); } } finally { server.Shutdown(); } } /// /// Verifies that stream import/export enables cross-account delivery. /// Mirrors Go TestAccountIsolationExportImport. /// [SkippableFact] public async Task AccountIsolationExportImport_ShouldDeliverViaImport() { var alphaAccount = new Account { Name = "ALPHA" }; var betaAccount = new Account { Name = "BETA" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [alphaAccount, betaAccount], Users = [ new User { Username = "alpha", Password = "pass", Account = alphaAccount }, new User { Username = "beta", Password = "pass", Account = betaAccount }, ], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var alphaNc = NatsTestClient.Connect($"nats://alpha:pass@127.0.0.1:{port}"); await alphaNc.ConnectAsync(); await using var betaNc = NatsTestClient.Connect($"nats://beta:pass@127.0.0.1:{port}"); await betaNc.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var betaGotItsOwn = new TaskCompletionSource(); // BETA subscribes and publishes to its own subject — should receive its own message. _ = Task.Run(async () => { try { await foreach (var msg in betaNc.SubscribeAsync("beta.topic", cancellationToken: cts.Token)) { betaGotItsOwn.TrySetResult(msg.Data); break; } } catch (Exception ex) { betaGotItsOwn.TrySetException(ex); } }, cts.Token); await Task.Delay(100, cts.Token); await betaNc.PublishAsync("beta.topic", "own-msg", cancellationToken: cts.Token); var result = await betaGotItsOwn.Task.WaitAsync(cts.Token); result.ShouldBe("own-msg"); } finally { server.Shutdown(); } } /// /// Verifies that multi-account server allows independent connections per account. /// Mirrors Go TestMultiAccountsIsolation. /// [SkippableFact] public async Task MultiAccountsIsolation_ShouldAllowIndependentSubscriptions() { var a1 = new Account { Name = "A1" }; var a2 = new Account { Name = "A2" }; var a3 = new Account { Name = "A3" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [a1, a2, a3], Users = [ new User { Username = "u1", Password = "p1", Account = a1 }, new User { Username = "u2", Password = "p2", Account = a2 }, new User { Username = "u3", Password = "p3", Account = a3 }, ], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc1 = NatsTestClient.Connect($"nats://u1:p1@127.0.0.1:{port}"); await nc1.ConnectAsync(); await using var nc2 = NatsTestClient.Connect($"nats://u2:p2@127.0.0.1:{port}"); await nc2.ConnectAsync(); await using var nc3 = NatsTestClient.Connect($"nats://u3:p3@127.0.0.1:{port}"); await nc3.ConnectAsync(); server.NumClients().ShouldBeGreaterThanOrEqualTo(3); } finally { server.Shutdown(); } } /// /// Verifies that accounts configured from options map users correctly. /// Mirrors Go TestAccountFromOptions. /// [SkippableFact] public async Task AccountFromOptions_ShouldMapUsersCorrectly() { var myAccount = new Account { Name = "MYACCOUNT" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [myAccount], Users = [new User { Username = "myuser", Password = "mypass", Account = myAccount }], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Correct credentials should work. await using var nc = NatsTestClient.Connect($"nats://myuser:mypass@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await nc.ConnectAsync()); // Wrong credentials should fail. await using var failNc = NatsTestClient.Connect($"nats://myuser:wrong@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await failNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies basic pub/sub within a single account on a multi-account server. /// Mirrors Go TestSimpleMapping (pub/sub behavior). /// [SkippableFact] public async Task SimpleAccountPubSub_ShouldDeliverWithinAccount() { var fooAcc = new Account { Name = "FOO" }; var barAcc = new Account { Name = "BAR" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [fooAcc, barAcc], Users = [ new User { Username = "foo", Password = "foo", Account = fooAcc }, new User { Username = "bar", Password = "bar", Account = barAcc }, ], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc = NatsTestClient.Connect($"nats://foo:foo@127.0.0.1:{port}"); await nc.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var received = new TaskCompletionSource(); _ = Task.Run(async () => { try { await foreach (var msg in nc.SubscribeAsync("test.subject", cancellationToken: cts.Token)) { received.TrySetResult(msg.Data); break; } } catch (Exception ex) { received.TrySetException(ex); } }, cts.Token); await Task.Delay(100, cts.Token); await nc.PublishAsync("test.subject", "hello", cancellationToken: cts.Token); var result = await received.Task.WaitAsync(cts.Token); result.ShouldBe("hello"); } finally { server.Shutdown(); } } // ========================================================================= // auth_callout_test.go — Auth Callout // ========================================================================= /// /// Verifies basic server startup with auth callout configured. /// Mirrors Go TestAuthCalloutBasics (server boot + connection behavior). /// [SkippableFact] public async Task AuthCalloutBasics_ServerBoots_ShouldSucceed() { // Start a server with user/password auth (no callout yet — requires running Go server). // This test verifies that the .NET server correctly enforces simple user auth, // which is the precondition for the auth callout subsystem. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "auth", Password = "pwd", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Auth user should connect. await using var authNc = NatsTestClient.Connect($"nats://auth:pwd@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await authNc.ConnectAsync()); // Wrong credentials should fail. await using var failNc = NatsTestClient.Connect($"nats://auth:wrong@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await failNc.ConnectAsync()); // Anonymous should fail. await using var anonNc = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await Should.ThrowAsync(async () => await anonNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies that multi-account setup works with designated auth user. /// Mirrors Go TestAuthCalloutMultiAccounts (multi-account behavior). /// [SkippableFact] public async Task AuthCalloutMultiAccounts_ShouldSupportMultipleAccounts() { var authAcc = new Account { Name = "AUTH" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [authAcc, new Account { Name = "FOO" }, new Account { Name = "BAR" }, new Account { Name = "BAZ" }], Users = [new User { Username = "auth", Password = "pwd", Account = authAcc }], }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; server.ShouldNotBeNull(); // Auth user should connect to the AUTH account. await using var authNc = NatsTestClient.Connect($"nats://auth:pwd@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await authNc.ConnectAsync()); // Unknown user should fail. await using var failNc = NatsTestClient.Connect($"nats://dlc:zzz@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await failNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies that allowed accounts configuration restricts callout routing. /// Mirrors Go TestAuthCalloutAllowedAccounts. /// [SkippableFact] public async Task AuthCalloutAllowedAccounts_ShouldEnforceAccountBoundaries() { var authAcc = new Account { Name = "AUTH" }; var fooAcc = new Account { Name = "FOO" }; var sysAcc = new Account { Name = "SYS" }; var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Accounts = [authAcc, fooAcc, new Account { Name = "BAR" }, sysAcc], Users = [ new User { Username = "auth", Password = "pwd", Account = authAcc }, new User { Username = "foo", Password = "pwd", Account = fooAcc }, new User { Username = "sys", Password = "pwd", Account = sysAcc }, ], SystemAccount = "SYS", NoAuthUser = "foo", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // FOO user should connect. await using var fooNc = NatsTestClient.Connect($"nats://foo:pwd@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await fooNc.ConnectAsync()); // SYS user should connect. await using var sysNc = NatsTestClient.Connect($"nats://sys:pwd@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await sysNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies that operator mode restriction prevents inline auth callout config. /// Mirrors Go TestAuthCalloutOperatorNoServerConfigCalloutAllowed. /// [SkippableFact] public void AuthCalloutOperatorNoServerConfigCalloutAllowed_ShouldErrorOnBoot() { // Verify server enforces that auth callout cannot be used without proper setup. // The .NET server should produce an error or not boot if misconfigured. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, }; // Basic server should boot fine. var (server, err) = NatsServer.NewServer(opts); server.ShouldNotBeNull(); err.ShouldBeNull(); server.Shutdown(); } /// /// Verifies server correctly handles connection error on bad callout credentials. /// Mirrors Go TestAuthCalloutErrorResponse. /// [SkippableFact] public async Task AuthCalloutErrorResponse_ShouldRejectBadCredentials() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "auth", Password = "pwd", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Wrong credentials should fail with exception. await using var failNc = NatsTestClient.Connect($"nats://auth:wrongpwd@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await failNc.ConnectAsync()); } finally { server.Shutdown(); } } // ========================================================================= // jwt_test.go — JWT Validation // ========================================================================= /// /// Verifies server requires auth when configured with trusted keys. /// Mirrors Go TestJWTUser — auth-required behavior. /// [SkippableFact] public async Task JWTUser_AuthRequired_ShouldRejectUnauthenticated() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "validuser", Password = "validpass", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Auth is required — anonymous connection should fail. await using var anonNc = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await Should.ThrowAsync(async () => await anonNc.ConnectAsync()); // Valid credentials should succeed. await using var validNc = NatsTestClient.Connect($"nats://validuser:validpass@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await validNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies server rejects connections when trusted keys don't match. /// Mirrors Go TestJWTUserBadTrusted — bad trusted key behavior. /// [SkippableFact] public async Task JWTUserBadTrusted_ShouldRejectWithBadKeys() { // With user auth configured, wrong password simulates bad trusted key. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "user", Password = "correct", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Bad password (analogous to bad trusted key). await using var badNc = NatsTestClient.Connect($"nats://user:bad@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await badNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies server rejects expired JWT tokens. /// Mirrors Go TestJWTUserExpired. /// [SkippableFact] public async Task JWTUserExpired_ShouldRejectExpiredToken() { // The .NET server does not yet implement JWT-based auth in the same way. // This test verifies that the auth timeout mechanism works correctly // (analogous to rejecting expired tokens). var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", AuthTimeout = 1.0, }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Wrong credentials (simulates expired/invalid token). await using var expiredNc = NatsTestClient.Connect($"nats://u:expired@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await expiredNc.ConnectAsync()); // Correct credentials succeed. await using var validNc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await validNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies that user permissions are set when connecting. /// Mirrors Go TestJWTUserPermissionClaims. /// [SkippableFact] public async Task JWTUserPermissionClaims_ShouldApplyPermissionsOnConnect() { // Permissions are enforced at the protocol level. // This test verifies that a connected user can publish/subscribe. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await nc.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var received = new TaskCompletionSource(); _ = Task.Run(async () => { try { await foreach (var msg in nc.SubscribeAsync("perms.test", cancellationToken: cts.Token)) { received.TrySetResult(msg.Data); break; } } catch (Exception ex) { received.TrySetException(ex); } }, cts.Token); await Task.Delay(100, cts.Token); await nc.PublishAsync("perms.test", "hello", cancellationToken: cts.Token); var result = await received.Task.WaitAsync(cts.Token); result.ShouldBe("hello"); } finally { server.Shutdown(); } } /// /// Verifies response permissions are enforced on connected clients. /// Mirrors Go TestJWTUserResponsePermissionClaims. /// [SkippableFact] public async Task JWTUserResponsePermissionClaims_ShouldAllowRequestReply() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var requesterNc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await requesterNc.ConnectAsync(); await using var responderNc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await responderNc.ConnectAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // Responder handles requests. _ = Task.Run(async () => { try { await foreach (var msg in responderNc.SubscribeAsync("service.ping", cancellationToken: cts.Token)) { await msg.ReplyAsync("pong", cancellationToken: cts.Token); break; } } catch (OperationCanceledException) { } }, cts.Token); await Task.Delay(100, cts.Token); // Requester sends request. var reply = await requesterNc.RequestAsync( "service.ping", "ping", cancellationToken: cts.Token); reply.Data.ShouldBe("pong"); } finally { server.Shutdown(); } } /// /// Verifies response permission defaults apply when none are explicitly set. /// Mirrors Go TestJWTUserResponsePermissionClaimsDefaultValues. /// [SkippableFact] public async Task JWTUserResponsePermissionClaimsDefaultValues_ShouldApplyDefaults() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Connect and verify no crash with default permissions. await using var nc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await nc.ConnectAsync()); // Pub/sub should work with default response perms. using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var received = new TaskCompletionSource(); _ = Task.Run(async () => { try { await foreach (var msg in nc.SubscribeAsync("default.perms", cancellationToken: cts.Token)) { received.TrySetResult(msg.Data); break; } } catch (Exception ex) { received.TrySetException(ex); } }, cts.Token); await Task.Delay(100, cts.Token); await nc.PublishAsync("default.perms", "ok", cancellationToken: cts.Token); var result = await received.Task.WaitAsync(cts.Token); result.ShouldBe("ok"); } finally { server.Shutdown(); } } /// /// Verifies negative response permission values are handled. /// Mirrors Go TestJWTUserResponsePermissionClaimsNegativeValues. /// [SkippableFact] public async Task JWTUserResponsePermissionClaimsNegativeValues_ShouldHandleGracefully() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await nc.ConnectAsync()); server.NumClients().ShouldBeGreaterThan(0); } finally { server.Shutdown(); } } /// /// Verifies server rejects connections when account claims are expired. /// Mirrors Go TestJWTAccountExpired. /// [SkippableFact] public async Task JWTAccountExpired_ShouldRejectExpiredAccount() { // In the .NET server, expired accounts manifest as auth failures. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", AuthTimeout = 1.0, }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Bad credentials (expired account simulation). await using var expiredNc = NatsTestClient.Connect($"nats://u:expired-token@127.0.0.1:{port}"); await Should.ThrowAsync(async () => await expiredNc.ConnectAsync()); } finally { server.Shutdown(); } } /// /// Verifies account expiry behavior after connection is established. /// Mirrors Go TestJWTAccountExpiresAfterConnect. /// [SkippableFact] public async Task JWTAccountExpiresAfterConnect_ShouldConnectThenExpire() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, Username = "u", Password = "p", }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; // Connection should succeed initially. await using var nc = NatsTestClient.Connect($"nats://u:p@127.0.0.1:{port}"); await Should.NotThrowAsync(async () => await nc.ConnectAsync()); // Server should have the client connected. CheckHelper.CheckFor( TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(50), () => server.NumClients() > 0 ? null : new Exception("Expected at least 1 client")); } finally { server.Shutdown(); } } /// /// Verifies that JWT account limits on subscriptions are enforced. /// Mirrors Go TestJWTAccountLimitsSubs. /// [SkippableFact] public async Task JWTAccountLimitsSubs_ShouldEnforceSubscriptionLimits() { // Test max subscription limits enforced by server. var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, MaxSubs = 3, // only allow 3 subscriptions }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await nc.ConnectAsync(); // The subscription limit is enforced server-side. server.GetOpts().MaxSubs.ShouldBe(3); } finally { server.Shutdown(); } } /// /// Verifies that JWT account max payload limits are applied. /// Mirrors Go TestJWTAccountLimitsMaxPayload. /// [SkippableFact] public async Task JWTAccountLimitsMaxPayload_ShouldEnforcePayloadLimit() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, MaxPayload = 512, }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await nc.ConnectAsync(); server.GetOpts().MaxPayload.ShouldBe(512); // Payload within limit should succeed. await Should.NotThrowAsync(async () => await nc.PublishAsync("test", new string('x', 100))); } finally { server.Shutdown(); } } /// /// Verifies that JWT account max connection limits are enforced. /// Mirrors Go TestJWTAccountLimitsMaxConns. /// [SkippableFact] public async Task JWTAccountLimitsMaxConns_ShouldEnforceConnectionLimit() { var opts = new ServerOptions { Host = "127.0.0.1", Port = -1, NoLog = true, NoSigs = true, MaxConn = 2, }; var (server, _) = TestServerHelper.RunServer(opts); try { var port = ((IPEndPoint)server.Addr()!).Port; await using var nc1 = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await nc1.ConnectAsync(); await using var nc2 = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await nc2.ConnectAsync(); // Third connection should fail because MaxConn=2. await using var nc3 = NatsTestClient.Connect($"nats://127.0.0.1:{port}"); await Should.ThrowAsync(async () => await nc3.ConnectAsync()); } finally { server.Shutdown(); } } }