using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Auth; using NATS.Server.TestUtilities; namespace NATS.Server.Auth.Tests; public class AccountIsolationTests : IAsyncLifetime { private NatsServer _server = null!; private int _port; private readonly CancellationTokenSource _cts = new(); private Task _serverTask = null!; public async Task InitializeAsync() { _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port, Users = [ new User { Username = "alice", Password = "pass", Account = "acct-a" }, new User { Username = "bob", Password = "pass", Account = "acct-b" }, new User { Username = "charlie", Password = "pass", Account = "acct-a" }, ], }, NullLoggerFactory.Instance); _serverTask = _server.StartAsync(_cts.Token); await _server.WaitForReadyAsync(); } public async Task DisposeAsync() { await _cts.CancelAsync(); _server.Dispose(); } [Fact] public async Task Same_account_receives_messages() { // Alice and Charlie are in acct-a await using var alice = new NatsConnection(new NatsOpts { Url = $"nats://alice:pass@127.0.0.1:{_port}", }); await using var charlie = new NatsConnection(new NatsOpts { Url = $"nats://charlie:pass@127.0.0.1:{_port}", }); await alice.ConnectAsync(); await charlie.ConnectAsync(); await using var sub = await charlie.SubscribeCoreAsync("test.subject"); await charlie.PingAsync(); await alice.PublishAsync("test.subject", "from-alice"); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var msg = await sub.Msgs.ReadAsync(timeout.Token); msg.Data.ShouldBe("from-alice"); } [Fact] public async Task Different_account_does_not_receive_messages() { // Alice is in acct-a, Bob is in acct-b await using var alice = new NatsConnection(new NatsOpts { Url = $"nats://alice:pass@127.0.0.1:{_port}", }); await using var bob = new NatsConnection(new NatsOpts { Url = $"nats://bob:pass@127.0.0.1:{_port}", }); await alice.ConnectAsync(); await bob.ConnectAsync(); await using var sub = await bob.SubscribeCoreAsync("test.subject"); await bob.PingAsync(); await alice.PublishAsync("test.subject", "from-alice"); // Bob should NOT receive this — wait briefly then verify nothing arrived using var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(500)); try { await sub.Msgs.ReadAsync(timeout.Token); throw new Exception("Bob should not have received a message from a different account"); } catch (OperationCanceledException) { // Expected — no message received means accounts are properly isolated return; } } }