Subscriptions and message routing now go through account-specific SubLists instead of a single global SubList. Clients in different accounts cannot see each other's messages. When no account is specified (or auth is not configured), all clients share the global $G account.
107 lines
3.3 KiB
C#
107 lines
3.3 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Client.Core;
|
|
using NATS.Server.Auth;
|
|
|
|
namespace NATS.Server.Tests;
|
|
|
|
public class AccountIsolationTests : IAsyncLifetime
|
|
{
|
|
private NatsServer _server = null!;
|
|
private int _port;
|
|
private readonly CancellationTokenSource _cts = new();
|
|
private Task _serverTask = null!;
|
|
|
|
private static int GetFreePort()
|
|
{
|
|
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
sock.Bind(new IPEndPoint(IPAddress.Loopback, 0));
|
|
return ((IPEndPoint)sock.LocalEndPoint!).Port;
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
_port = 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<string>("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<string>("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 (timeout)
|
|
}
|
|
}
|
|
}
|