Files
natsdotnet/tests/NATS.E2E.Tests/AccountIsolationTests.cs
Joseph Doherty c30e67a69d Fix E2E test gaps and add comprehensive E2E + parity test suites
- Fix pull consumer fetch: send original stream subject in HMSG (not inbox)
  so NATS client distinguishes data messages from control messages
- Fix MaxAge expiry: add background timer in StreamManager for periodic pruning
- Fix JetStream wire format: Go-compatible anonymous objects with string enums,
  proper offset-based pagination for stream/consumer list APIs
- Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream)
- Add ~1000 parity tests across all subsystems (gaps closure)
- Update gap inventory docs to reflect implementation status
2026-03-12 14:09:23 -04:00

99 lines
3.7 KiB
C#

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<string>("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<string>("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(1000));
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<string>("shared.topic");
await using var subscriptionB = await subB.SubscribeCoreAsync<string>("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(1000));
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(1000));
completedA2.ShouldNotBe(readATask2);
await ctsANoMsg.CancelAsync();
try { await readATask2; } catch (OperationCanceledException) { }
}
}