- 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
99 lines
3.7 KiB
C#
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) { }
|
|
}
|
|
}
|