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.
88 lines
3.0 KiB
C#
88 lines
3.0 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using NATS.Server.Gateways;
|
|
using NATS.Server.Subscriptions;
|
|
|
|
namespace NATS.Server.Gateways.Tests.Gateways;
|
|
|
|
public class GatewayInterestIdempotencyTests
|
|
{
|
|
[Fact]
|
|
public async Task Duplicate_RSplus_or_reconnect_replay_does_not_double_count_remote_interest()
|
|
{
|
|
using var listener = new TcpListener(IPAddress.Loopback, 0);
|
|
listener.Start();
|
|
|
|
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
|
using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
await remoteSocket.ConnectAsync(IPAddress.Loopback, port);
|
|
using var gatewaySocket = await listener.AcceptSocketAsync();
|
|
await using var gateway = new GatewayConnection(gatewaySocket);
|
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
|
|
var handshakeTask = gateway.PerformOutboundHandshakeAsync("LOCAL", timeout.Token);
|
|
(await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("GATEWAY LOCAL");
|
|
await WriteLineAsync(remoteSocket, "GATEWAY REMOTE", timeout.Token);
|
|
await handshakeTask;
|
|
|
|
using var subList = new SubList();
|
|
var remoteAdded = 0;
|
|
subList.InterestChanged += change =>
|
|
{
|
|
if (change.Kind == InterestChangeKind.RemoteAdded)
|
|
remoteAdded++;
|
|
};
|
|
|
|
gateway.RemoteSubscriptionReceived = sub =>
|
|
{
|
|
subList.ApplyRemoteSub(sub);
|
|
return Task.CompletedTask;
|
|
};
|
|
gateway.StartLoop(timeout.Token);
|
|
|
|
await WriteLineAsync(remoteSocket, "A+ A orders.*", timeout.Token);
|
|
await WaitForAsync(() => subList.HasRemoteInterest("A", "orders.created"), timeout.Token);
|
|
|
|
await WriteLineAsync(remoteSocket, "A+ A orders.*", timeout.Token);
|
|
await Task.Delay(100, timeout.Token);
|
|
|
|
subList.MatchRemote("A", "orders.created").Count.ShouldBe(1);
|
|
remoteAdded.ShouldBe(1);
|
|
}
|
|
|
|
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();
|
|
|
|
private static async Task WaitForAsync(Func<bool> predicate, CancellationToken ct)
|
|
{
|
|
while (!ct.IsCancellationRequested)
|
|
{
|
|
if (predicate())
|
|
return;
|
|
|
|
await Task.Delay(20, ct);
|
|
}
|
|
|
|
throw new TimeoutException("Timed out waiting for condition.");
|
|
}
|
|
}
|