using NATS.Client.Core; using NATS.E2E.Tests.Infrastructure; namespace NATS.E2E.Tests; [Collection("E2E-Accounts")] public class AccountIsolationTests(AccountServerFixture fixture) { [Fact] public async Task Accounts_SameAccount_MessageDelivered() { await using var pub = fixture.CreateClientA(); await using var sub = fixture.CreateClientA(); await pub.ConnectAsync(); await sub.ConnectAsync(); await using var subscription = await sub.SubscribeCoreAsync("acct.test"); await sub.PingAsync(); await pub.PublishAsync("acct.test", "intra-account"); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var msg = await subscription.Msgs.ReadAsync(cts.Token); msg.Data.ShouldBe("intra-account"); } [Fact] public async Task Accounts_CrossAccount_MessageNotDelivered() { await using var pub = fixture.CreateClientA(); await using var sub = fixture.CreateClientB(); await pub.ConnectAsync(); await sub.ConnectAsync(); await using var subscription = await sub.SubscribeCoreAsync("cross.test"); await sub.PingAsync(); await pub.PublishAsync("cross.test", "cross-account"); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var readTask = subscription.Msgs.ReadAsync(cts.Token).AsTask(); var completed = await Task.WhenAny(readTask, Task.Delay(3000)); completed.ShouldNotBe(readTask); } [Fact] public async Task Accounts_EachAccountHasOwnNamespace() { await using var pubA = fixture.CreateClientA(); await using var subA = fixture.CreateClientA(); await using var pubB = fixture.CreateClientB(); await using var subB = fixture.CreateClientB(); await pubA.ConnectAsync(); await subA.ConnectAsync(); await pubB.ConnectAsync(); await subB.ConnectAsync(); await using var subscriptionA = await subA.SubscribeCoreAsync("shared.topic"); await using var subscriptionB = await subB.SubscribeCoreAsync("shared.topic"); await subA.PingAsync(); await subB.PingAsync(); // Publish from ACCT_A — only ACCT_A subscriber should receive await pubA.PublishAsync("shared.topic", "from-a"); using var ctA = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var msgA = await subscriptionA.Msgs.ReadAsync(ctA.Token); msgA.Data.ShouldBe("from-a"); using var ctsBNoMsg = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var readBTask = subscriptionB.Msgs.ReadAsync(ctsBNoMsg.Token).AsTask(); var completedB = await Task.WhenAny(readBTask, Task.Delay(3000)); completedB.ShouldNotBe(readBTask); // Cancel the abandoned read so it doesn't consume the next message await ctsBNoMsg.CancelAsync(); try { await readBTask; } catch (OperationCanceledException) { } // Publish from ACCT_B — only ACCT_B subscriber should receive await pubB.PublishAsync("shared.topic", "from-b"); using var ctB = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var msgB = await subscriptionB.Msgs.ReadAsync(ctB.Token); msgB.Data.ShouldBe("from-b"); using var ctsANoMsg = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var readATask2 = subscriptionA.Msgs.ReadAsync(ctsANoMsg.Token).AsTask(); var completedA2 = await Task.WhenAny(readATask2, Task.Delay(3000)); completedA2.ShouldNotBe(readATask2); await ctsANoMsg.CancelAsync(); try { await readATask2; } catch (OperationCanceledException) { } } }