diff --git a/NatsDotNet.slnx b/NatsDotNet.slnx
index 8c9e61e..0fcfe13 100644
--- a/NatsDotNet.slnx
+++ b/NatsDotNet.slnx
@@ -10,6 +10,7 @@
+
diff --git a/src/NATS.Server/NATS.Server.csproj b/src/NATS.Server/NATS.Server.csproj
index 253b7bc..6132fcd 100644
--- a/src/NATS.Server/NATS.Server.csproj
+++ b/src/NATS.Server/NATS.Server.csproj
@@ -5,6 +5,7 @@
+
diff --git a/tests/NATS.Server.Tests/ImplicitDiscoveryTests.cs b/tests/NATS.Server.Clustering.Tests/ImplicitDiscoveryTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/ImplicitDiscoveryTests.cs
rename to tests/NATS.Server.Clustering.Tests/ImplicitDiscoveryTests.cs
index 36ca02e..f3d1d9f 100644
--- a/tests/NATS.Server.Tests/ImplicitDiscoveryTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/ImplicitDiscoveryTests.cs
@@ -5,7 +5,7 @@ using NATS.Server.Gateways;
using NATS.Server.Protocol;
using NATS.Server.Routes;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
// Go reference: server/route.go processImplicitRoute, server/gateway.go processImplicitGateway
diff --git a/tests/NATS.Server.Tests/InterServerAccountProtocolTests.cs b/tests/NATS.Server.Clustering.Tests/InterServerAccountProtocolTests.cs
similarity index 98%
rename from tests/NATS.Server.Tests/InterServerAccountProtocolTests.cs
rename to tests/NATS.Server.Clustering.Tests/InterServerAccountProtocolTests.cs
index b4be42e..a59b26e 100644
--- a/tests/NATS.Server.Tests/InterServerAccountProtocolTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/InterServerAccountProtocolTests.cs
@@ -4,7 +4,7 @@ using System.Text;
using NATS.Server.Gateways;
using NATS.Server.Subscriptions;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class InterServerAccountProtocolTests
{
diff --git a/tests/NATS.Server.Clustering.Tests/NATS.Server.Clustering.Tests.csproj b/tests/NATS.Server.Clustering.Tests/NATS.Server.Clustering.Tests.csproj
new file mode 100644
index 0000000..c886361
--- /dev/null
+++ b/tests/NATS.Server.Clustering.Tests/NATS.Server.Clustering.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/NATS.Server.Tests/Route/RouteGoParityTests.cs b/tests/NATS.Server.Clustering.Tests/Route/RouteGoParityTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Route/RouteGoParityTests.cs
rename to tests/NATS.Server.Clustering.Tests/Route/RouteGoParityTests.cs
index a407ee4..d6aa7ae 100644
--- a/tests/NATS.Server.Tests/Route/RouteGoParityTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Route/RouteGoParityTests.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Route;
+namespace NATS.Server.Clustering.Tests.Route;
///
/// Go parity tests for the .NET route subsystem ported from
diff --git a/tests/NATS.Server.Clustering.Tests/RouteHandshakeTests.cs b/tests/NATS.Server.Clustering.Tests/RouteHandshakeTests.cs
new file mode 100644
index 0000000..f2a7a22
--- /dev/null
+++ b/tests/NATS.Server.Clustering.Tests/RouteHandshakeTests.cs
@@ -0,0 +1,25 @@
+using NATS.Server.TestUtilities;
+
+namespace NATS.Server.Clustering.Tests;
+
+public class RouteHandshakeTests
+{
+ [Fact]
+ public async Task Two_servers_establish_route_connection()
+ {
+ await using var a = await TestServerFactory.CreateClusterEnabledAsync();
+ await using var b = await TestServerFactory.CreateClusterEnabledAsync(seed: a.ClusterListen);
+
+ await a.WaitForReadyAsync();
+ await b.WaitForReadyAsync();
+
+ using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ while (!timeout.IsCancellationRequested && (a.Stats.Routes == 0 || b.Stats.Routes == 0))
+ {
+ await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
+ }
+
+ a.Stats.Routes.ShouldBeGreaterThan(0);
+ b.Stats.Routes.ShouldBeGreaterThan(0);
+ }
+}
diff --git a/tests/NATS.Server.Tests/RoutePoolTests.cs b/tests/NATS.Server.Clustering.Tests/RoutePoolTests.cs
similarity index 87%
rename from tests/NATS.Server.Tests/RoutePoolTests.cs
rename to tests/NATS.Server.Clustering.Tests/RoutePoolTests.cs
index d43dc7e..a84c6b4 100644
--- a/tests/NATS.Server.Tests/RoutePoolTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/RoutePoolTests.cs
@@ -1,4 +1,4 @@
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RoutePoolTests
{
diff --git a/tests/NATS.Server.Tests/RouteRmsgForwardingTests.cs b/tests/NATS.Server.Clustering.Tests/RouteRmsgForwardingTests.cs
similarity index 91%
rename from tests/NATS.Server.Tests/RouteRmsgForwardingTests.cs
rename to tests/NATS.Server.Clustering.Tests/RouteRmsgForwardingTests.cs
index ff91cf0..427e1c9 100644
--- a/tests/NATS.Server.Tests/RouteRmsgForwardingTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/RouteRmsgForwardingTests.cs
@@ -1,4 +1,4 @@
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteRmsgForwardingTests
{
diff --git a/tests/NATS.Server.Tests/RouteSubscriptionPropagationTests.cs b/tests/NATS.Server.Clustering.Tests/RouteSubscriptionPropagationTests.cs
similarity index 89%
rename from tests/NATS.Server.Tests/RouteSubscriptionPropagationTests.cs
rename to tests/NATS.Server.Clustering.Tests/RouteSubscriptionPropagationTests.cs
index 0f0173a..5f248e2 100644
--- a/tests/NATS.Server.Tests/RouteSubscriptionPropagationTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/RouteSubscriptionPropagationTests.cs
@@ -3,8 +3,9 @@ using System.Net.Sockets;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
+using NATS.Server.TestUtilities;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteSubscriptionPropagationTests
{
@@ -90,7 +91,7 @@ internal sealed class RouteFixture : IAsyncDisposable
await ReadLineAsync(sock); // INFO
await sock.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {{}}\r\nSUB {subject} 1\r\nPING\r\n"));
- await ReadUntilAsync(sock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(sock, "PONG");
}
public async Task SendRouteSubFrameAsync(string subject)
@@ -123,11 +124,11 @@ internal sealed class RouteFixture : IAsyncDisposable
_publisherOnA = sock;
_ = await ReadLineAsync(sock); // INFO
await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n"));
- await ReadUntilAsync(sock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(sock, "PONG");
}
await sock.SendAsync(Encoding.ASCII.GetBytes($"PUB {subject} {payload.Length}\r\n{payload}\r\nPING\r\n"));
- await ReadUntilAsync(sock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(sock, "PONG");
}
public async Task ReadServerBMessageAsync()
@@ -135,7 +136,7 @@ internal sealed class RouteFixture : IAsyncDisposable
if (_subscriberOnB == null)
throw new InvalidOperationException("No subscriber socket on server B.");
- return await ReadUntilAsync(_subscriberOnB, "MSG ");
+ return await SocketTestHelper.ReadUntilAsync(_subscriberOnB, "MSG ");
}
public async Task ServerAHasRemoteInterestAsync(string subject, bool expected = true)
@@ -184,22 +185,6 @@ internal sealed class RouteFixture : IAsyncDisposable
return Encoding.ASCII.GetString(buf, 0, n);
}
- private static async Task ReadUntilAsync(Socket sock, string expected)
- {
- var sb = new StringBuilder();
- var buf = new byte[4096];
- using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
-
- while (!sb.ToString().Contains(expected, StringComparison.Ordinal))
- {
- var n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token);
- if (n == 0)
- break;
- sb.Append(Encoding.ASCII.GetString(buf, 0, n));
- }
-
- return sb.ToString();
- }
private static (string Host, int Port) ParseHostPort(string endpoint)
{
diff --git a/tests/NATS.Server.Tests/RouteWireSubscriptionProtocolTests.cs b/tests/NATS.Server.Clustering.Tests/RouteWireSubscriptionProtocolTests.cs
similarity index 92%
rename from tests/NATS.Server.Tests/RouteWireSubscriptionProtocolTests.cs
rename to tests/NATS.Server.Clustering.Tests/RouteWireSubscriptionProtocolTests.cs
index 0002553..1653f45 100644
--- a/tests/NATS.Server.Tests/RouteWireSubscriptionProtocolTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/RouteWireSubscriptionProtocolTests.cs
@@ -1,4 +1,4 @@
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteWireSubscriptionProtocolTests
{
diff --git a/tests/NATS.Server.Tests/Routes/AccountRouteTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/AccountRouteTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/AccountRouteTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/AccountRouteTests.cs
index b94de27..5ba5f72 100644
--- a/tests/NATS.Server.Tests/Routes/AccountRouteTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/AccountRouteTests.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for per-account dedicated route connections (Gap 13.2).
diff --git a/tests/NATS.Server.Tests/Routes/ClusterSplitTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/ClusterSplitTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/ClusterSplitTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/ClusterSplitTests.cs
index 7ea73dc..a7f4f5a 100644
--- a/tests/NATS.Server.Tests/Routes/ClusterSplitTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/ClusterSplitTests.cs
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for ,
diff --git a/tests/NATS.Server.Tests/Routes/NoPoolFallbackTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/NoPoolFallbackTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/NoPoolFallbackTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/NoPoolFallbackTests.cs
index 24a65b5..e70a202 100644
--- a/tests/NATS.Server.Tests/Routes/NoPoolFallbackTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/NoPoolFallbackTests.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for no-pool (legacy) route fallback — Gap 13.6.
diff --git a/tests/NATS.Server.Tests/Routes/PoolSizeNegotiationTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/PoolSizeNegotiationTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/PoolSizeNegotiationTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/PoolSizeNegotiationTests.cs
index 2f65daa..dee3f0b 100644
--- a/tests/NATS.Server.Tests/Routes/PoolSizeNegotiationTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/PoolSizeNegotiationTests.cs
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route pool size negotiation (Gap 13.3).
diff --git a/tests/NATS.Server.Tests/Routes/RouteAccountScopedDeliveryTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedDeliveryTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteAccountScopedDeliveryTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedDeliveryTests.cs
index 34efb55..129939e 100644
--- a/tests/NATS.Server.Tests/Routes/RouteAccountScopedDeliveryTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedDeliveryTests.cs
@@ -3,7 +3,7 @@ using NATS.Client.Core;
using NATS.Server.Auth;
using NATS.Server.Configuration;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteAccountScopedDeliveryTests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteAccountScopedTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedTests.cs
similarity index 89%
rename from tests/NATS.Server.Tests/Routes/RouteAccountScopedTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedTests.cs
index c0d6968..cabe5ed 100644
--- a/tests/NATS.Server.Tests/Routes/RouteAccountScopedTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteAccountScopedTests.cs
@@ -1,6 +1,6 @@
using NATS.Server.Routes;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteAccountScopedTests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs
similarity index 98%
rename from tests/NATS.Server.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs
index b39c17f..6258f45 100644
--- a/tests/NATS.Server.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteBatchProtoParityBatch3Tests.cs
@@ -4,7 +4,7 @@ using System.Text;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteBatchProtoParityBatch3Tests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteCompressionTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteCompressionTests.cs
similarity index 91%
rename from tests/NATS.Server.Tests/Routes/RouteCompressionTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteCompressionTests.cs
index 676c65c..36ed96f 100644
--- a/tests/NATS.Server.Tests/Routes/RouteCompressionTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteCompressionTests.cs
@@ -1,7 +1,7 @@
using System.Text;
using NATS.Server.Routes;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteCompressionTests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteConfigTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteConfigTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteConfigTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteConfigTests.cs
index 4135a9f..87d9b43 100644
--- a/tests/NATS.Server.Tests/Routes/RouteConfigTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteConfigTests.cs
@@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Client.Core;
using NATS.Server.Configuration;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests cluster route formation and message forwarding between servers.
diff --git a/tests/NATS.Server.Tests/Routes/RouteConfigValidationTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteConfigValidationTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteConfigValidationTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteConfigValidationTests.cs
index debffaa..036588e 100644
--- a/tests/NATS.Server.Tests/Routes/RouteConfigValidationTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteConfigValidationTests.cs
@@ -7,7 +7,7 @@ using NATS.Server.Auth;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route configuration validation, compression options, topology gossip,
diff --git a/tests/NATS.Server.Tests/Routes/RouteConnectionTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteConnectionTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteConnectionTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteConnectionTests.cs
index 04e19d6..c15e29e 100644
--- a/tests/NATS.Server.Tests/Routes/RouteConnectionTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteConnectionTests.cs
@@ -7,7 +7,7 @@ using NATS.Server.Configuration;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route connection establishment, handshake, reconnection, and lifecycle.
diff --git a/tests/NATS.Server.Tests/Routes/RouteForwardingTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteForwardingTests.cs
similarity index 96%
rename from tests/NATS.Server.Tests/Routes/RouteForwardingTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteForwardingTests.cs
index 258c707..78e8de6 100644
--- a/tests/NATS.Server.Tests/Routes/RouteForwardingTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteForwardingTests.cs
@@ -7,8 +7,9 @@ using NATS.Server.Auth;
using NATS.Server.Configuration;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;
+using NATS.Server.TestUtilities;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route message forwarding (RMSG), reply propagation, payload delivery,
@@ -140,7 +141,7 @@ public class RouteForwardingTests
var buf = new byte[4096];
_ = await responderSock.ReceiveAsync(buf); // INFO
await responderSock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB service.echo 1\r\nPING\r\n"));
- await ReadUntilAsync(responderSock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(responderSock, "PONG");
await WaitForCondition(() => b.Server.HasRemoteInterest("service.echo"));
// Requester on server B: subscribe to reply inbox via raw socket
@@ -150,26 +151,26 @@ public class RouteForwardingTests
var replyInbox = $"_INBOX.{Guid.NewGuid():N}";
await requesterSock.SendAsync(Encoding.ASCII.GetBytes(
$"CONNECT {{}}\r\nSUB {replyInbox} 2\r\nPING\r\n"));
- await ReadUntilAsync(requesterSock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(requesterSock, "PONG");
await WaitForCondition(() => a.Server.HasRemoteInterest(replyInbox));
// Publish request with reply-to from B
await requesterSock.SendAsync(Encoding.ASCII.GetBytes(
$"PUB service.echo {replyInbox} 4\r\nping\r\nPING\r\n"));
- await ReadUntilAsync(requesterSock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(requesterSock, "PONG");
// Read the request on A, verify reply-to
- var requestData = await ReadUntilAsync(responderSock, "ping");
+ var requestData = await SocketTestHelper.ReadUntilAsync(responderSock, "ping");
requestData.ShouldContain($"MSG service.echo 1 {replyInbox} 4");
requestData.ShouldContain("ping");
// Publish reply from A to the reply-to subject
await responderSock.SendAsync(Encoding.ASCII.GetBytes(
$"PUB {replyInbox} 4\r\npong\r\nPING\r\n"));
- await ReadUntilAsync(responderSock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(responderSock, "PONG");
// Read the reply on B
- var replyData = await ReadUntilAsync(requesterSock, "pong");
+ var replyData = await SocketTestHelper.ReadUntilAsync(requesterSock, "pong");
replyData.ShouldContain($"MSG {replyInbox} 2 4");
replyData.ShouldContain("pong");
}
@@ -595,7 +596,7 @@ public class RouteForwardingTests
_ = await sock.ReceiveAsync(buf); // INFO
await sock.SendAsync(Encoding.ASCII.GetBytes(
"CONNECT {}\r\nPUB reply.subject.test _INBOX.reply123 5\r\nHello\r\nPING\r\n"));
- await ReadUntilAsync(sock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(sock, "PONG");
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var msg = await sub.Msgs.ReadAsync(timeout.Token);
@@ -684,7 +685,7 @@ public class RouteForwardingTests
await route.SendRmsgAsync("$G", "subject.test", "_INBOX.reply", payload, timeout.Token);
// Read the RMSG frame from the remote side, waiting until expected content arrives
- var data = await ReadUntilAsync(remote, "test-payload");
+ var data = await SocketTestHelper.ReadUntilAsync(remote, "test-payload");
data.ShouldContain("RMSG $G subject.test _INBOX.reply 12");
data.ShouldContain("test-payload");
}
@@ -803,18 +804,4 @@ public class RouteForwardingTests
return sb.ToString();
}
- private static async Task ReadUntilAsync(Socket sock, string expected)
- {
- var sb = new StringBuilder();
- var buf = new byte[4096];
- using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
- while (!sb.ToString().Contains(expected, StringComparison.Ordinal))
- {
- var n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token);
- if (n == 0) break;
- sb.Append(Encoding.ASCII.GetString(buf, 0, n));
- }
-
- return sb.ToString();
- }
}
diff --git a/tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteHashStorageTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteHashStorageTests.cs
index e61df9f..4b7a90c 100644
--- a/tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteHashStorageTests.cs
@@ -8,7 +8,7 @@ using NATS.Server;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for the FNV-1a hash-based route storage on .
diff --git a/tests/NATS.Server.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs
similarity index 86%
rename from tests/NATS.Server.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs
index e92cc24..89f9fc7 100644
--- a/tests/NATS.Server.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteInfoBroadcastParityBatch4Tests.cs
@@ -3,15 +3,16 @@ using System.Net.Sockets;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server;
+using NATS.Server.TestUtilities;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteInfoBroadcastParityBatch4Tests
{
[Fact]
public async Task UpdateServerINFOAndSendINFOToClients_broadcasts_INFO_to_connected_clients()
{
- var port = GetFreePort();
+ 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);
@@ -68,17 +69,4 @@ public class RouteInfoBroadcastParityBatch4Tests
return Encoding.ASCII.GetString([.. buffer]);
}
- private static int GetFreePort()
- {
- var listener = new TcpListener(IPAddress.Loopback, 0);
- listener.Start();
- try
- {
- return ((IPEndPoint)listener.LocalEndpoint).Port;
- }
- finally
- {
- listener.Stop();
- }
- }
}
diff --git a/tests/NATS.Server.Tests/Routes/RouteInterestIdempotencyTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteInterestIdempotencyTests.cs
similarity index 98%
rename from tests/NATS.Server.Tests/Routes/RouteInterestIdempotencyTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteInterestIdempotencyTests.cs
index c596cab..bba3307 100644
--- a/tests/NATS.Server.Tests/Routes/RouteInterestIdempotencyTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteInterestIdempotencyTests.cs
@@ -4,7 +4,7 @@ using System.Text;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteInterestIdempotencyTests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteParityHelpersBatch1Tests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteParityHelpersBatch1Tests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteParityHelpersBatch1Tests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteParityHelpersBatch1Tests.cs
index 19e4198..934aa06 100644
--- a/tests/NATS.Server.Tests/Routes/RouteParityHelpersBatch1Tests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteParityHelpersBatch1Tests.cs
@@ -5,7 +5,7 @@ using NATS.Server.Configuration;
using NATS.Server.Protocol;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteParityHelpersBatch1Tests
{
diff --git a/tests/NATS.Server.Tests/Routes/RoutePoolAccountTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RoutePoolAccountTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RoutePoolAccountTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RoutePoolAccountTests.cs
index 2135646..56909cf 100644
--- a/tests/NATS.Server.Tests/Routes/RoutePoolAccountTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RoutePoolAccountTests.cs
@@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route pool accounting per account, matching Go's
diff --git a/tests/NATS.Server.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs
index 9d7d36d..684a69c 100644
--- a/tests/NATS.Server.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteRemoteSubCleanupParityBatch2Tests.cs
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Subscriptions;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
public class RouteRemoteSubCleanupParityBatch2Tests
{
diff --git a/tests/NATS.Server.Tests/Routes/RouteS2CompressionTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteS2CompressionTests.cs
similarity index 99%
rename from tests/NATS.Server.Tests/Routes/RouteS2CompressionTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteS2CompressionTests.cs
index 235fdb4..efa35f8 100644
--- a/tests/NATS.Server.Tests/Routes/RouteS2CompressionTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteS2CompressionTests.cs
@@ -4,7 +4,7 @@
using System.Text;
using NATS.Server.Routes;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route S2/Snappy compression codec, matching Go's route compression
diff --git a/tests/NATS.Server.Tests/Routes/RouteSubscriptionTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteSubscriptionTests.cs
similarity index 97%
rename from tests/NATS.Server.Tests/Routes/RouteSubscriptionTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteSubscriptionTests.cs
index b702fe8..051de09 100644
--- a/tests/NATS.Server.Tests/Routes/RouteSubscriptionTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteSubscriptionTests.cs
@@ -7,8 +7,9 @@ using NATS.Server.Auth;
using NATS.Server.Configuration;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;
+using NATS.Server.TestUtilities;
-namespace NATS.Server.Tests.Routes;
+namespace NATS.Server.Clustering.Tests.Routes;
///
/// Tests for route subscription propagation: RS+/RS-, wildcard subs, queue subs,
@@ -357,7 +358,7 @@ public class RouteSubscriptionTests
await sock.ConnectAsync(IPAddress.Loopback, a.Server.Port);
_ = await ReadLineAsync(sock, default);
await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo queue1 1\r\nPING\r\n"));
- await ReadUntilAsync(sock, "PONG");
+ await SocketTestHelper.ReadUntilAsync(sock, "PONG");
await WaitForCondition(() => b.Server.HasRemoteInterest("foo"));
b.Server.HasRemoteInterest("foo").ShouldBeTrue();
@@ -829,20 +830,6 @@ public class RouteSubscriptionTests
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 ReadUntilAsync(Socket sock, string expected)
- {
- var sb = new StringBuilder();
- var buf = new byte[4096];
- using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
- while (!sb.ToString().Contains(expected, StringComparison.Ordinal))
- {
- var n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token);
- if (n == 0) break;
- sb.Append(Encoding.ASCII.GetString(buf, 0, n));
- }
-
- return sb.ToString();
- }
}
file static class CancellationTokenExtensions
diff --git a/tests/NATS.Server.Tests/Routes/RouteTopologyGossipTests.cs b/tests/NATS.Server.Clustering.Tests/Routes/RouteTopologyGossipTests.cs
similarity index 94%
rename from tests/NATS.Server.Tests/Routes/RouteTopologyGossipTests.cs
rename to tests/NATS.Server.Clustering.Tests/Routes/RouteTopologyGossipTests.cs
index 6cae4aa..01b318f 100644
--- a/tests/NATS.Server.Tests/Routes/RouteTopologyGossipTests.cs
+++ b/tests/NATS.Server.Clustering.Tests/Routes/RouteTopologyGossipTests.cs
@@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Routes;
-namespace NATS.Server.Tests;
+namespace NATS.Server.Clustering.Tests;
public class RouteTopologyGossipTests
{
diff --git a/tests/NATS.Server.Tests/RouteHandshakeTests.cs b/tests/NATS.Server.TestUtilities/TestServerFactory.cs
similarity index 73%
rename from tests/NATS.Server.Tests/RouteHandshakeTests.cs
rename to tests/NATS.Server.TestUtilities/TestServerFactory.cs
index 8c5a7f8..ef83a4b 100644
--- a/tests/NATS.Server.Tests/RouteHandshakeTests.cs
+++ b/tests/NATS.Server.TestUtilities/TestServerFactory.cs
@@ -1,31 +1,9 @@
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
-namespace NATS.Server.Tests;
+namespace NATS.Server.TestUtilities;
-public class RouteHandshakeTests
-{
- [Fact]
- public async Task Two_servers_establish_route_connection()
- {
- await using var a = await TestServerFactory.CreateClusterEnabledAsync();
- await using var b = await TestServerFactory.CreateClusterEnabledAsync(seed: a.ClusterListen);
-
- await a.WaitForReadyAsync();
- await b.WaitForReadyAsync();
-
- using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
- while (!timeout.IsCancellationRequested && (a.Stats.Routes == 0 || b.Stats.Routes == 0))
- {
- await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
- }
-
- a.Stats.Routes.ShouldBeGreaterThan(0);
- b.Stats.Routes.ShouldBeGreaterThan(0);
- }
-}
-
-internal static class TestServerFactory
+public static class TestServerFactory
{
public static async Task CreateClusterEnabledAsync(string? seed = null)
{
@@ -100,7 +78,7 @@ internal static class TestServerFactory
}
}
-internal sealed class ClusterTestServer(NatsServer server, CancellationTokenSource cts) : IAsyncDisposable
+public sealed class ClusterTestServer(NatsServer server, CancellationTokenSource cts) : IAsyncDisposable
{
public ServerStats Stats => server.Stats;
public string ClusterListen => server.ClusterListen!;
diff --git a/tests/NATS.Server.Tests/JetStreamStartupTests.cs b/tests/NATS.Server.Tests/JetStreamStartupTests.cs
index b0bfc7d..b7ddadb 100644
--- a/tests/NATS.Server.Tests/JetStreamStartupTests.cs
+++ b/tests/NATS.Server.Tests/JetStreamStartupTests.cs
@@ -1,3 +1,4 @@
+using NATS.Server.TestUtilities;
namespace NATS.Server.Tests;
public class JetStreamStartupTests