- Rename tests/NATS.Server.Tests -> tests/NATS.Server.Core.Tests - Update solution file, InternalsVisibleTo, and csproj references - Remove JETSTREAM_INTEGRATION_MATRIX and NATS.NKeys from csproj (moved to JetStream.Tests and Auth.Tests) - Update all namespaces from NATS.Server.Tests.* to NATS.Server.Core.Tests.* - Replace private GetFreePort/ReadUntilAsync helpers with TestUtilities calls - Fix stale namespace in Transport.Tests/NetworkingGoParityTests.cs
270 lines
8.6 KiB
C#
270 lines
8.6 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server;
|
|
using NATS.Server.Configuration;
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Core.Tests.Server;
|
|
|
|
public class CoreServerGapParityTests
|
|
{
|
|
[Fact]
|
|
public void ClientURL_uses_advertise_when_present()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions { Host = "0.0.0.0", Port = 4222, ClientAdvertise = "demo.example.net:4333" },
|
|
NullLoggerFactory.Instance);
|
|
|
|
server.ClientURL().ShouldBe("nats://demo.example.net:4333");
|
|
}
|
|
|
|
[Fact]
|
|
public void ClientURL_uses_loopback_for_wildcard_host()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions { Host = "0.0.0.0", Port = 4222 },
|
|
NullLoggerFactory.Instance);
|
|
|
|
server.ClientURL().ShouldBe("nats://127.0.0.1:4222");
|
|
}
|
|
|
|
[Fact]
|
|
public void WebsocketURL_uses_default_host_port_when_enabled()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions
|
|
{
|
|
WebSocket = new WebSocketOptions
|
|
{
|
|
Host = "0.0.0.0",
|
|
Port = 8080,
|
|
NoTls = true,
|
|
},
|
|
},
|
|
NullLoggerFactory.Instance);
|
|
|
|
server.WebsocketURL().ShouldBe("ws://127.0.0.1:8080");
|
|
}
|
|
|
|
[Fact]
|
|
public void WebsocketURL_returns_null_when_disabled()
|
|
{
|
|
using var server = new NatsServer(new NatsOptions(), NullLoggerFactory.Instance);
|
|
|
|
server.WebsocketURL().ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Account_count_methods_reflect_loaded_and_active_accounts()
|
|
{
|
|
using var server = new NatsServer(new NatsOptions(), NullLoggerFactory.Instance);
|
|
|
|
server.NumLoadedAccounts().ShouldBe(2); // $G + $SYS
|
|
server.NumActiveAccounts().ShouldBe(0);
|
|
|
|
var app = server.GetOrCreateAccount("APP");
|
|
server.NumLoadedAccounts().ShouldBe(3);
|
|
|
|
app.AddClient(42);
|
|
server.NumActiveAccounts().ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void Address_and_counter_methods_are_derived_from_options_and_stats()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 4222,
|
|
MonitorHost = "127.0.0.1",
|
|
MonitorPort = 8222,
|
|
ProfPort = 6060,
|
|
},
|
|
NullLoggerFactory.Instance);
|
|
|
|
server.Stats.Routes = 2;
|
|
server.Stats.Gateways = 1;
|
|
server.Stats.Leafs = 3;
|
|
|
|
server.Addr().ShouldBe("127.0.0.1:4222");
|
|
server.MonitorAddr().ShouldBe("127.0.0.1:8222");
|
|
server.ProfilerAddr().ShouldBe("127.0.0.1:6060");
|
|
server.NumRoutes().ShouldBe(2);
|
|
server.NumLeafNodes().ShouldBe(3);
|
|
server.NumRemotes().ShouldBe(6);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToString_includes_identity_and_address()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions { ServerName = "test-node", Host = "127.0.0.1", Port = 4222 },
|
|
NullLoggerFactory.Instance);
|
|
|
|
var value = server.ToString();
|
|
|
|
value.ShouldContain("NatsServer(");
|
|
value.ShouldContain("Name=test-node");
|
|
value.ShouldContain("Addr=127.0.0.1:4222");
|
|
}
|
|
|
|
[Fact]
|
|
public void PortsInfo_returns_configured_listen_endpoints()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 4222,
|
|
MonitorHost = "127.0.0.1",
|
|
MonitorPort = 8222,
|
|
ProfPort = 6060,
|
|
WebSocket = new WebSocketOptions { Host = "127.0.0.1", Port = 8443 },
|
|
Cluster = new ClusterOptions { Host = "127.0.0.1", Port = 6222 },
|
|
LeafNode = new LeafNodeOptions { Host = "127.0.0.1", Port = 7422 },
|
|
},
|
|
NullLoggerFactory.Instance);
|
|
|
|
var ports = server.PortsInfo();
|
|
|
|
ports.Nats.ShouldContain("127.0.0.1:4222");
|
|
ports.Monitoring.ShouldContain("127.0.0.1:8222");
|
|
ports.Profile.ShouldContain("127.0.0.1:6060");
|
|
ports.WebSocket.ShouldContain("127.0.0.1:8443");
|
|
ports.Cluster.ShouldContain("127.0.0.1:6222");
|
|
ports.LeafNodes.ShouldContain("127.0.0.1:7422");
|
|
}
|
|
|
|
[Fact]
|
|
public void Profiler_and_peer_accessors_have_parity_surface()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions { Port = 4222, ProfPort = 6060 },
|
|
NullLoggerFactory.Instance);
|
|
|
|
server.StartProfiler().ShouldBeTrue();
|
|
server.ActivePeers().ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Connect_urls_helpers_include_non_wildcard_and_cache_refresh()
|
|
{
|
|
using var server = new NatsServer(
|
|
new NatsOptions { Host = "127.0.0.1", Port = 4222 },
|
|
NullLoggerFactory.Instance);
|
|
|
|
var urls = server.GetConnectURLs();
|
|
urls.ShouldContain("nats://127.0.0.1:4222");
|
|
|
|
server.UpdateServerINFOAndSendINFOToClients();
|
|
var info = Encoding.ASCII.GetString(server.CachedInfoLine);
|
|
info.ShouldContain("\"connect_urls\":[\"nats://127.0.0.1:4222\"]");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisconnectClientByID_closes_connected_client()
|
|
{
|
|
var port = TestPortAllocator.GetFreePort();
|
|
using var server = new NatsServer(new NatsOptions { Host = "127.0.0.1", Port = port }, NullLoggerFactory.Instance);
|
|
using var cts = new CancellationTokenSource();
|
|
_ = server.StartAsync(cts.Token);
|
|
await server.WaitForReadyAsync();
|
|
|
|
using var socket = await ConnectAndHandshakeAsync(port);
|
|
await WaitUntilAsync(() => server.ClientCount == 1);
|
|
var clientId = server.GetClients().Single().Id;
|
|
|
|
server.DisconnectClientByID(clientId).ShouldBeTrue();
|
|
await WaitUntilAsync(() => server.ClientCount == 0);
|
|
|
|
await server.ShutdownAsync();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LDMClientByID_closes_connected_client()
|
|
{
|
|
var port = TestPortAllocator.GetFreePort();
|
|
using var server = new NatsServer(new NatsOptions { Host = "127.0.0.1", Port = port }, NullLoggerFactory.Instance);
|
|
using var cts = new CancellationTokenSource();
|
|
_ = server.StartAsync(cts.Token);
|
|
await server.WaitForReadyAsync();
|
|
|
|
using var socket = await ConnectAndHandshakeAsync(port);
|
|
await WaitUntilAsync(() => server.ClientCount == 1);
|
|
var clientId = server.GetClients().Single().Id;
|
|
|
|
server.LDMClientByID(clientId).ShouldBeTrue();
|
|
await WaitUntilAsync(() => server.ClientCount == 0);
|
|
|
|
await server.ShutdownAsync();
|
|
}
|
|
|
|
private static async Task<Socket> ConnectAndHandshakeAsync(int port)
|
|
{
|
|
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
await socket.ConnectAsync(IPAddress.Loopback, port);
|
|
|
|
_ = await ReadLineAsync(socket, CancellationToken.None); // INFO
|
|
await socket.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n"), SocketFlags.None);
|
|
var pong = await ReadUntilContainsAsync(socket, "PONG", CancellationToken.None);
|
|
pong.ShouldContain("PONG");
|
|
|
|
return socket;
|
|
}
|
|
|
|
private static async Task<string> ReadLineAsync(Socket socket, CancellationToken ct)
|
|
{
|
|
var buffer = new List<byte>(256);
|
|
var single = new byte[1];
|
|
while (true)
|
|
{
|
|
var n = await socket.ReceiveAsync(single.AsMemory(0, 1), SocketFlags.None, ct);
|
|
if (n == 0)
|
|
break;
|
|
|
|
if (single[0] == '\n')
|
|
break;
|
|
|
|
if (single[0] != '\r')
|
|
buffer.Add(single[0]);
|
|
}
|
|
|
|
return Encoding.ASCII.GetString([.. buffer]);
|
|
}
|
|
|
|
private static async Task<string> ReadUntilContainsAsync(Socket socket, string token, CancellationToken ct)
|
|
{
|
|
var end = DateTime.UtcNow.AddSeconds(3);
|
|
var builder = new StringBuilder();
|
|
while (DateTime.UtcNow < end)
|
|
{
|
|
var line = await ReadLineAsync(socket, ct);
|
|
if (line.Length == 0)
|
|
continue;
|
|
|
|
builder.AppendLine(line);
|
|
if (builder.ToString().Contains(token, StringComparison.Ordinal))
|
|
return builder.ToString();
|
|
}
|
|
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static async Task WaitUntilAsync(Func<bool> predicate)
|
|
{
|
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
|
while (!cts.IsCancellationRequested)
|
|
{
|
|
if (predicate())
|
|
return;
|
|
|
|
await Task.Yield();
|
|
}
|
|
|
|
throw new TimeoutException("Condition was not met in time.");
|
|
}
|
|
}
|