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.
139 lines
4.7 KiB
C#
139 lines
4.7 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server.Configuration;
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Gateways.Tests;
|
|
|
|
public class GatewayProtocolTests
|
|
{
|
|
[Fact]
|
|
public async Task Gateway_link_establishes_and_forwards_interested_message()
|
|
{
|
|
await using var fx = await GatewayFixture.StartTwoClustersAsync();
|
|
await fx.SubscribeRemoteClusterAsync("g.>");
|
|
await fx.PublishLocalClusterAsync("g.test", "hello");
|
|
(await fx.ReadRemoteClusterMessageAsync()).ShouldContain("hello");
|
|
}
|
|
}
|
|
|
|
internal sealed class GatewayFixture : IAsyncDisposable
|
|
{
|
|
private readonly NatsServer _local;
|
|
private readonly NatsServer _remote;
|
|
private readonly CancellationTokenSource _localCts;
|
|
private readonly CancellationTokenSource _remoteCts;
|
|
private Socket? _remoteSubscriber;
|
|
private Socket? _localPublisher;
|
|
|
|
private GatewayFixture(NatsServer local, NatsServer remote, CancellationTokenSource localCts, CancellationTokenSource remoteCts)
|
|
{
|
|
_local = local;
|
|
_remote = remote;
|
|
_localCts = localCts;
|
|
_remoteCts = remoteCts;
|
|
}
|
|
|
|
public static async Task<GatewayFixture> StartTwoClustersAsync()
|
|
{
|
|
var localOptions = new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
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,
|
|
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 GatewayFixture(local, remote, localCts, remoteCts);
|
|
}
|
|
|
|
public async Task SubscribeRemoteClusterAsync(string subject)
|
|
{
|
|
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
await sock.ConnectAsync(IPAddress.Loopback, _remote.Port);
|
|
_remoteSubscriber = sock;
|
|
|
|
_ = await ReadLineAsync(sock); // INFO
|
|
await sock.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {{}}\r\nSUB {subject} 1\r\nPING\r\n"));
|
|
await SocketTestHelper.ReadUntilAsync(sock, "PONG");
|
|
}
|
|
|
|
public async Task PublishLocalClusterAsync(string subject, string payload)
|
|
{
|
|
var sock = _localPublisher;
|
|
if (sock == null)
|
|
{
|
|
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
await sock.ConnectAsync(IPAddress.Loopback, _local.Port);
|
|
_localPublisher = sock;
|
|
_ = await ReadLineAsync(sock); // INFO
|
|
await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n"));
|
|
await SocketTestHelper.ReadUntilAsync(sock, "PONG");
|
|
}
|
|
|
|
await sock.SendAsync(Encoding.ASCII.GetBytes($"PUB {subject} {payload.Length}\r\n{payload}\r\nPING\r\n"));
|
|
await SocketTestHelper.ReadUntilAsync(sock, "PONG");
|
|
}
|
|
|
|
public Task<string> ReadRemoteClusterMessageAsync()
|
|
{
|
|
if (_remoteSubscriber == null)
|
|
throw new InvalidOperationException("Remote subscriber was not initialized.");
|
|
|
|
return SocketTestHelper.ReadUntilAsync(_remoteSubscriber, "MSG ");
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_remoteSubscriber?.Dispose();
|
|
_localPublisher?.Dispose();
|
|
await _localCts.CancelAsync();
|
|
await _remoteCts.CancelAsync();
|
|
_local.Dispose();
|
|
_remote.Dispose();
|
|
_localCts.Dispose();
|
|
_remoteCts.Dispose();
|
|
}
|
|
|
|
private static async Task<string> ReadLineAsync(Socket sock)
|
|
{
|
|
var buf = new byte[4096];
|
|
var n = await sock.ReceiveAsync(buf, SocketFlags.None);
|
|
return Encoding.ASCII.GetString(buf, 0, n);
|
|
}
|
|
|
|
}
|