139 lines
4.8 KiB
C#
139 lines
4.8 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Client.Core;
|
|
using NATS.Server.Auth;
|
|
using NATS.Server.Configuration;
|
|
|
|
namespace NATS.Server.Tests.LeafNodes;
|
|
|
|
public class LeafAccountScopedDeliveryTests
|
|
{
|
|
[Fact]
|
|
public async Task Remote_message_delivery_uses_target_account_sublist_not_global_sublist()
|
|
{
|
|
const string subject = "orders.created";
|
|
await using var fixture = await LeafAccountDeliveryFixture.StartAsync();
|
|
|
|
await using var remoteAccountA = await fixture.ConnectAsync(fixture.Spoke, "a_sub");
|
|
await using var remoteAccountB = await fixture.ConnectAsync(fixture.Spoke, "b_sub");
|
|
await using var publisher = await fixture.ConnectAsync(fixture.Hub, "a_pub");
|
|
|
|
await using var subA = await remoteAccountA.SubscribeCoreAsync<string>(subject);
|
|
await using var subB = await remoteAccountB.SubscribeCoreAsync<string>(subject);
|
|
await remoteAccountA.PingAsync();
|
|
await remoteAccountB.PingAsync();
|
|
await fixture.WaitForRemoteInterestOnHubAsync("A", subject);
|
|
|
|
await publisher.PublishAsync(subject, "from-leaf-a");
|
|
|
|
using var receiveTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
var msgA = await subA.Msgs.ReadAsync(receiveTimeout.Token);
|
|
msgA.Data.ShouldBe("from-leaf-a");
|
|
|
|
using var leakTimeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
|
|
await Should.ThrowAsync<OperationCanceledException>(async () =>
|
|
await subB.Msgs.ReadAsync(leakTimeout.Token));
|
|
}
|
|
}
|
|
|
|
internal sealed class LeafAccountDeliveryFixture : IAsyncDisposable
|
|
{
|
|
private readonly CancellationTokenSource _hubCts;
|
|
private readonly CancellationTokenSource _spokeCts;
|
|
|
|
private LeafAccountDeliveryFixture(NatsServer hub, NatsServer spoke, CancellationTokenSource hubCts, CancellationTokenSource spokeCts)
|
|
{
|
|
Hub = hub;
|
|
Spoke = spoke;
|
|
_hubCts = hubCts;
|
|
_spokeCts = spokeCts;
|
|
}
|
|
|
|
public NatsServer Hub { get; }
|
|
public NatsServer Spoke { get; }
|
|
|
|
public static async Task<LeafAccountDeliveryFixture> StartAsync()
|
|
{
|
|
var users = new User[]
|
|
{
|
|
new() { Username = "a_pub", Password = "pass", Account = "A" },
|
|
new() { Username = "a_sub", Password = "pass", Account = "A" },
|
|
new() { Username = "b_sub", Password = "pass", Account = "B" },
|
|
};
|
|
|
|
var hubOptions = new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
Users = users,
|
|
LeafNode = new LeafNodeOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
},
|
|
};
|
|
|
|
var hub = new NatsServer(hubOptions, NullLoggerFactory.Instance);
|
|
var hubCts = new CancellationTokenSource();
|
|
_ = hub.StartAsync(hubCts.Token);
|
|
await hub.WaitForReadyAsync();
|
|
|
|
var spokeOptions = new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
Users = users,
|
|
LeafNode = new LeafNodeOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
Remotes = [hub.LeafListen!],
|
|
},
|
|
};
|
|
|
|
var spoke = new NatsServer(spokeOptions, NullLoggerFactory.Instance);
|
|
var spokeCts = new CancellationTokenSource();
|
|
_ = spoke.StartAsync(spokeCts.Token);
|
|
await spoke.WaitForReadyAsync();
|
|
|
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
while (!timeout.IsCancellationRequested && (hub.Stats.Leafs == 0 || spoke.Stats.Leafs == 0))
|
|
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
|
|
|
return new LeafAccountDeliveryFixture(hub, spoke, hubCts, spokeCts);
|
|
}
|
|
|
|
public async Task<NatsConnection> ConnectAsync(NatsServer server, string username)
|
|
{
|
|
var connection = new NatsConnection(new NatsOpts
|
|
{
|
|
Url = $"nats://{username}:pass@127.0.0.1:{server.Port}",
|
|
});
|
|
await connection.ConnectAsync();
|
|
return connection;
|
|
}
|
|
|
|
public async Task WaitForRemoteInterestOnHubAsync(string account, string subject)
|
|
{
|
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
while (!timeout.IsCancellationRequested)
|
|
{
|
|
if (Hub.HasRemoteInterest(account, subject))
|
|
return;
|
|
|
|
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
|
}
|
|
|
|
throw new TimeoutException($"Timed out waiting for remote interest {account}:{subject}.");
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await _hubCts.CancelAsync();
|
|
await _spokeCts.CancelAsync();
|
|
Hub.Dispose();
|
|
Spoke.Dispose();
|
|
_hubCts.Dispose();
|
|
_spokeCts.Dispose();
|
|
}
|
|
}
|