Move 25 gateway-related test files from NATS.Server.Tests into a dedicated NATS.Server.Gateways.Tests project. Update namespaces, replace private ReadUntilAsync with SocketTestHelper from TestUtilities, inline TestServerFactory usage, add InternalsVisibleTo, and register the project in the solution file. All 261 tests pass.
577 lines
21 KiB
C#
577 lines
21 KiB
C#
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.Gateways.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();
|
|
}
|
|
}
|