feat: Wave 6 batch 2 — accounts/auth, gateways, routes, JetStream API, JetStream cluster tests
Add comprehensive Go-parity test coverage across 5 subsystems: - Accounts/Auth: isolation, import/export, auth mechanisms, permissions (82 tests) - Gateways: connection, forwarding, interest mode, config (106 tests) - Routes: connection, subscription, forwarding, config validation (78 tests) - JetStream API: stream/consumer CRUD, pub/sub, features, admin (234 tests) - JetStream Cluster: streams, consumers, failover, meta (108 tests) Total: ~608 new test annotations across 22 files (+13,844 lines) All tests pass individually; suite total: 2,283 passing, 3 skipped
This commit is contained in:
576
tests/NATS.Server.Tests/Gateways/GatewayInterestModeTests.cs
Normal file
576
tests/NATS.Server.Tests/Gateways/GatewayInterestModeTests.cs
Normal file
@@ -0,0 +1,576 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NATS.Client.Core;
|
||||
using NATS.Server.Auth;
|
||||
using NATS.Server.Configuration;
|
||||
using NATS.Server.Gateways;
|
||||
using NATS.Server.Subscriptions;
|
||||
|
||||
namespace NATS.Server.Tests.Gateways;
|
||||
|
||||
/// <summary>
|
||||
/// Gateway interest-only mode, account interest, subject interest propagation,
|
||||
/// and subscription lifecycle tests.
|
||||
/// Ported from golang/nats-server/server/gateway_test.go.
|
||||
/// </summary>
|
||||
public class GatewayInterestModeTests
|
||||
{
|
||||
// ── Remote Interest Tracking via SubList ─────────────────────────────
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void Remote_interest_tracked_for_literal_subject()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.created", null, "gw1", "$G"));
|
||||
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("$G", "orders.updated").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void Remote_interest_tracked_for_wildcard_subject()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.*", null, "gw1", "$G"));
|
||||
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("$G", "orders.updated").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("$G", "orders.deep.nested").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void Remote_interest_tracked_for_fwc_subject()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("events.>", null, "gw1", "$G"));
|
||||
|
||||
subList.HasRemoteInterest("$G", "events.one").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("$G", "events.one.two.three").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("$G", "other").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void Remote_interest_scoped_to_account()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.>", null, "gw1", "ACCT_A"));
|
||||
|
||||
subList.HasRemoteInterest("ACCT_A", "orders.created").ShouldBeTrue();
|
||||
subList.HasRemoteInterest("ACCT_B", "orders.created").ShouldBeFalse();
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public void Remote_interest_removed_on_aminus()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.>", null, "gw1", "$G"));
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeTrue();
|
||||
|
||||
subList.ApplyRemoteSub(RemoteSubscription.Removal("orders.>", null, "gw1", "$G"));
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void Multiple_remote_interests_from_different_routes()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.*", null, "gw1", "$G"));
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.*", null, "gw2", "$G"));
|
||||
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeTrue();
|
||||
subList.MatchRemote("$G", "orders.created").Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public void Removing_one_route_interest_keeps_other()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.*", null, "gw1", "$G"));
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("orders.*", null, "gw2", "$G"));
|
||||
|
||||
subList.ApplyRemoteSub(RemoteSubscription.Removal("orders.*", null, "gw1", "$G"));
|
||||
subList.HasRemoteInterest("$G", "orders.created").ShouldBeTrue();
|
||||
subList.MatchRemote("$G", "orders.created").Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
// ── Interest Change Events ──────────────────────────────────────────
|
||||
|
||||
// Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934
|
||||
[Fact]
|
||||
public void Interest_change_event_fired_on_remote_add()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
var changes = new List<InterestChange>();
|
||||
subList.InterestChanged += change => changes.Add(change);
|
||||
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("test.>", null, "gw1", "$G"));
|
||||
|
||||
changes.Count.ShouldBe(1);
|
||||
changes[0].Kind.ShouldBe(InterestChangeKind.RemoteAdded);
|
||||
changes[0].Subject.ShouldBe("test.>");
|
||||
changes[0].Account.ShouldBe("$G");
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public void Interest_change_event_fired_on_remote_remove()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
var changes = new List<InterestChange>();
|
||||
subList.InterestChanged += change => changes.Add(change);
|
||||
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("test.>", null, "gw1", "$G"));
|
||||
subList.ApplyRemoteSub(RemoteSubscription.Removal("test.>", null, "gw1", "$G"));
|
||||
|
||||
changes.Count.ShouldBe(2);
|
||||
changes[1].Kind.ShouldBe(InterestChangeKind.RemoteRemoved);
|
||||
}
|
||||
|
||||
// Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934
|
||||
[Fact]
|
||||
public void Duplicate_remote_add_does_not_fire_extra_event()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
var addCount = 0;
|
||||
subList.InterestChanged += change =>
|
||||
{
|
||||
if (change.Kind == InterestChangeKind.RemoteAdded)
|
||||
addCount++;
|
||||
};
|
||||
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("test.>", null, "gw1", "$G"));
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("test.>", null, "gw1", "$G"));
|
||||
|
||||
addCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public void Remove_nonexistent_subscription_does_not_fire_event()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
var removeCount = 0;
|
||||
subList.InterestChanged += change =>
|
||||
{
|
||||
if (change.Kind == InterestChangeKind.RemoteRemoved)
|
||||
removeCount++;
|
||||
};
|
||||
|
||||
subList.ApplyRemoteSub(RemoteSubscription.Removal("nonexistent", null, "gw1", "$G"));
|
||||
|
||||
removeCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
// ── Queue Weight in MatchRemote ─────────────────────────────────────
|
||||
|
||||
// Go: TestGatewayTotalQSubs server/gateway_test.go:2484
|
||||
[Fact]
|
||||
public void Match_remote_expands_queue_weight()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("foo", "bar", "gw1", "$G", QueueWeight: 3));
|
||||
|
||||
var matches = subList.MatchRemote("$G", "foo");
|
||||
matches.Count.ShouldBe(3);
|
||||
}
|
||||
|
||||
// Go: TestGatewayTotalQSubs server/gateway_test.go:2484
|
||||
[Fact]
|
||||
public void Match_remote_default_weight_is_one()
|
||||
{
|
||||
using var subList = new SubList();
|
||||
subList.ApplyRemoteSub(new RemoteSubscription("foo", "bar", "gw1", "$G"));
|
||||
|
||||
var matches = subList.MatchRemote("$G", "foo");
|
||||
matches.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
// ── End-to-End Interest Propagation via Gateway ─────────────────────
|
||||
|
||||
// Go: TestGatewayDontSendSubInterest server/gateway_test.go:1755
|
||||
[Fact]
|
||||
public async Task Local_subscription_propagated_to_remote_via_gateway()
|
||||
{
|
||||
await using var fixture = await InterestModeFixture.StartAsync();
|
||||
|
||||
await using var localConn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://127.0.0.1:{fixture.Local.Port}",
|
||||
});
|
||||
await localConn.ConnectAsync();
|
||||
|
||||
await using var sub = await localConn.SubscribeCoreAsync<string>("prop.test");
|
||||
await localConn.PingAsync();
|
||||
|
||||
// The remote server should see the interest
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Remote.HasRemoteInterest("prop.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Remote.HasRemoteInterest("prop.test").ShouldBeTrue();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public async Task Unsubscribe_propagated_to_remote_via_gateway()
|
||||
{
|
||||
await using var fixture = await InterestModeFixture.StartAsync();
|
||||
|
||||
await using var localConn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://127.0.0.1:{fixture.Local.Port}",
|
||||
});
|
||||
await localConn.ConnectAsync();
|
||||
|
||||
var sub = await localConn.SubscribeCoreAsync<string>("unsub.test");
|
||||
await localConn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Remote.HasRemoteInterest("unsub.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Remote.HasRemoteInterest("unsub.test").ShouldBeTrue();
|
||||
|
||||
// Unsubscribe
|
||||
await sub.DisposeAsync();
|
||||
await localConn.PingAsync();
|
||||
|
||||
// Wait for interest to be removed
|
||||
using var unsubTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!unsubTimeout.IsCancellationRequested && fixture.Remote.HasRemoteInterest("unsub.test"))
|
||||
await Task.Delay(50, unsubTimeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Remote.HasRemoteInterest("unsub.test").ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestGatewaySubjectInterest server/gateway_test.go:1972
|
||||
[Fact]
|
||||
public async Task Remote_wildcard_subscription_establishes_interest()
|
||||
{
|
||||
await using var fixture = await InterestModeFixture.StartAsync();
|
||||
|
||||
await using var remoteConn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://127.0.0.1:{fixture.Remote.Port}",
|
||||
});
|
||||
await remoteConn.ConnectAsync();
|
||||
|
||||
await using var sub = await remoteConn.SubscribeCoreAsync<string>("interest.>");
|
||||
await remoteConn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("interest.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Local.HasRemoteInterest("interest.test").ShouldBeTrue();
|
||||
fixture.Local.HasRemoteInterest("interest.deep.nested").ShouldBeTrue();
|
||||
}
|
||||
|
||||
// Go: TestGatewayDontSendSubInterest server/gateway_test.go:1755
|
||||
[Fact]
|
||||
public async Task Multiple_subscribers_same_subject_produces_single_interest()
|
||||
{
|
||||
await using var fixture = await InterestModeFixture.StartAsync();
|
||||
|
||||
await using var conn1 = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://127.0.0.1:{fixture.Remote.Port}",
|
||||
});
|
||||
await conn1.ConnectAsync();
|
||||
|
||||
await using var conn2 = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://127.0.0.1:{fixture.Remote.Port}",
|
||||
});
|
||||
await conn2.ConnectAsync();
|
||||
|
||||
await using var sub1 = await conn1.SubscribeCoreAsync<string>("multi.interest");
|
||||
await using var sub2 = await conn2.SubscribeCoreAsync<string>("multi.interest");
|
||||
await conn1.PingAsync();
|
||||
await conn2.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("multi.interest"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Local.HasRemoteInterest("multi.interest").ShouldBeTrue();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public async Task Account_scoped_interest_propagated_via_gateway()
|
||||
{
|
||||
var users = new User[]
|
||||
{
|
||||
new() { Username = "acct_user", Password = "pass", Account = "MYACCT" },
|
||||
};
|
||||
|
||||
await using var fixture = await InterestModeFixture.StartWithUsersAsync(users);
|
||||
|
||||
await using var conn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
Url = $"nats://acct_user:pass@127.0.0.1:{fixture.Remote.Port}",
|
||||
});
|
||||
await conn.ConnectAsync();
|
||||
|
||||
await using var sub = await conn.SubscribeCoreAsync<string>("acct.interest");
|
||||
await conn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("MYACCT", "acct.interest"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
fixture.Local.HasRemoteInterest("MYACCT", "acct.interest").ShouldBeTrue();
|
||||
}
|
||||
|
||||
// ── RemoteSubscription Record Tests ─────────────────────────────────
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void RemoteSubscription_record_equality()
|
||||
{
|
||||
var a = new RemoteSubscription("foo", null, "gw1", "$G");
|
||||
var b = new RemoteSubscription("foo", null, "gw1", "$G");
|
||||
a.ShouldBe(b);
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void RemoteSubscription_removal_factory()
|
||||
{
|
||||
var removal = RemoteSubscription.Removal("foo", "bar", "gw1", "$G");
|
||||
removal.IsRemoval.ShouldBeTrue();
|
||||
removal.Subject.ShouldBe("foo");
|
||||
removal.Queue.ShouldBe("bar");
|
||||
removal.RouteId.ShouldBe("gw1");
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountInterest server/gateway_test.go:1794
|
||||
[Fact]
|
||||
public void RemoteSubscription_default_account_is_global()
|
||||
{
|
||||
var sub = new RemoteSubscription("foo", null, "gw1");
|
||||
sub.Account.ShouldBe("$G");
|
||||
}
|
||||
|
||||
// Go: TestGatewayTotalQSubs server/gateway_test.go:2484
|
||||
[Fact]
|
||||
public void RemoteSubscription_default_queue_weight_is_one()
|
||||
{
|
||||
var sub = new RemoteSubscription("foo", "bar", "gw1");
|
||||
sub.QueueWeight.ShouldBe(1);
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public void RemoteSubscription_default_is_not_removal()
|
||||
{
|
||||
var sub = new RemoteSubscription("foo", null, "gw1");
|
||||
sub.IsRemoval.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ── Subscription Propagation by GatewayManager ──────────────────────
|
||||
|
||||
// Go: TestGatewayDontSendSubInterest server/gateway_test.go:1755
|
||||
[Fact]
|
||||
public async Task Gateway_manager_propagate_subscription_sends_aplus()
|
||||
{
|
||||
using var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
|
||||
var options = new GatewayOptions
|
||||
{
|
||||
Name = "LOCAL",
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Remotes = [$"127.0.0.1:{port}"],
|
||||
};
|
||||
var manager = new GatewayManager(
|
||||
options,
|
||||
new ServerStats(),
|
||||
"SERVER1",
|
||||
_ => { },
|
||||
_ => { },
|
||||
NullLogger<GatewayManager>.Instance);
|
||||
|
||||
await manager.StartAsync(CancellationToken.None);
|
||||
|
||||
// Accept the connection from gateway manager
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
using var gwSocket = await listener.AcceptSocketAsync(cts.Token);
|
||||
|
||||
// Exchange handshakes
|
||||
var line = await ReadLineAsync(gwSocket, cts.Token);
|
||||
line.ShouldStartWith("GATEWAY ");
|
||||
await WriteLineAsync(gwSocket, "GATEWAY REMOTE1", cts.Token);
|
||||
|
||||
// Wait for connection to be registered
|
||||
await Task.Delay(200);
|
||||
|
||||
// Propagate a subscription
|
||||
manager.PropagateLocalSubscription("$G", "orders.>", null);
|
||||
|
||||
// Read the A+ message
|
||||
await Task.Delay(100);
|
||||
var aplusLine = await ReadLineAsync(gwSocket, cts.Token);
|
||||
aplusLine.ShouldBe("A+ $G orders.>");
|
||||
|
||||
await manager.DisposeAsync();
|
||||
}
|
||||
|
||||
// Go: TestGatewayAccountUnsub server/gateway_test.go:1912
|
||||
[Fact]
|
||||
public async Task Gateway_manager_propagate_unsubscription_sends_aminus()
|
||||
{
|
||||
using var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
|
||||
var options = new GatewayOptions
|
||||
{
|
||||
Name = "LOCAL",
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Remotes = [$"127.0.0.1:{port}"],
|
||||
};
|
||||
var manager = new GatewayManager(
|
||||
options,
|
||||
new ServerStats(),
|
||||
"SERVER1",
|
||||
_ => { },
|
||||
_ => { },
|
||||
NullLogger<GatewayManager>.Instance);
|
||||
|
||||
await manager.StartAsync(CancellationToken.None);
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
using var gwSocket = await listener.AcceptSocketAsync(cts.Token);
|
||||
|
||||
var line = await ReadLineAsync(gwSocket, cts.Token);
|
||||
line.ShouldStartWith("GATEWAY ");
|
||||
await WriteLineAsync(gwSocket, "GATEWAY REMOTE1", cts.Token);
|
||||
|
||||
await Task.Delay(200);
|
||||
|
||||
manager.PropagateLocalUnsubscription("$G", "orders.>", null);
|
||||
|
||||
await Task.Delay(100);
|
||||
var aminusLine = await ReadLineAsync(gwSocket, cts.Token);
|
||||
aminusLine.ShouldBe("A- $G orders.>");
|
||||
|
||||
await manager.DisposeAsync();
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
private static async Task<string> ReadLineAsync(Socket socket, CancellationToken ct)
|
||||
{
|
||||
var bytes = new List<byte>(64);
|
||||
var single = new byte[1];
|
||||
while (true)
|
||||
{
|
||||
var read = await socket.ReceiveAsync(single, SocketFlags.None, ct);
|
||||
if (read == 0)
|
||||
break;
|
||||
if (single[0] == (byte)'\n')
|
||||
break;
|
||||
if (single[0] != (byte)'\r')
|
||||
bytes.Add(single[0]);
|
||||
}
|
||||
|
||||
return Encoding.ASCII.GetString([.. bytes]);
|
||||
}
|
||||
|
||||
private static Task WriteLineAsync(Socket socket, string line, CancellationToken ct)
|
||||
=> socket.SendAsync(Encoding.ASCII.GetBytes($"{line}\r\n"), SocketFlags.None, ct).AsTask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared fixture for interest mode tests.
|
||||
/// </summary>
|
||||
internal sealed class InterestModeFixture : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _localCts;
|
||||
private readonly CancellationTokenSource _remoteCts;
|
||||
|
||||
private InterestModeFixture(NatsServer local, NatsServer remote, CancellationTokenSource localCts, CancellationTokenSource remoteCts)
|
||||
{
|
||||
Local = local;
|
||||
Remote = remote;
|
||||
_localCts = localCts;
|
||||
_remoteCts = remoteCts;
|
||||
}
|
||||
|
||||
public NatsServer Local { get; }
|
||||
public NatsServer Remote { get; }
|
||||
|
||||
public static Task<InterestModeFixture> StartAsync()
|
||||
=> StartWithUsersAsync(null);
|
||||
|
||||
public static async Task<InterestModeFixture> StartWithUsersAsync(IReadOnlyList<User>? users)
|
||||
{
|
||||
var localOptions = new NatsOptions
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Users = users,
|
||||
Gateway = new GatewayOptions
|
||||
{
|
||||
Name = "LOCAL",
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
},
|
||||
};
|
||||
|
||||
var local = new NatsServer(localOptions, NullLoggerFactory.Instance);
|
||||
var localCts = new CancellationTokenSource();
|
||||
_ = local.StartAsync(localCts.Token);
|
||||
await local.WaitForReadyAsync();
|
||||
|
||||
var remoteOptions = new NatsOptions
|
||||
{
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Users = users,
|
||||
Gateway = new GatewayOptions
|
||||
{
|
||||
Name = "REMOTE",
|
||||
Host = "127.0.0.1",
|
||||
Port = 0,
|
||||
Remotes = [local.GatewayListen!],
|
||||
},
|
||||
};
|
||||
|
||||
var remote = new NatsServer(remoteOptions, NullLoggerFactory.Instance);
|
||||
var remoteCts = new CancellationTokenSource();
|
||||
_ = remote.StartAsync(remoteCts.Token);
|
||||
await remote.WaitForReadyAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && (local.Stats.Gateways == 0 || remote.Stats.Gateways == 0))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
|
||||
return new InterestModeFixture(local, remote, localCts, remoteCts);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _localCts.CancelAsync();
|
||||
await _remoteCts.CancelAsync();
|
||||
Local.Dispose();
|
||||
Remote.Dispose();
|
||||
_localCts.Dispose();
|
||||
_remoteCts.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user