From 7fbffffd051d7a1baf4ce56fe72d7e4932bdd966 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 12 Mar 2026 16:14:02 -0400 Subject: [PATCH] refactor: rename remaining tests to NATS.Server.Core.Tests - 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 --- NatsDotNet.slnx | 2 +- src/NATS.Server/NATS.Server.csproj | 2 +- .../ClientClosedReasonTests.cs | 2 +- .../ClientFlagsTests.cs | 2 +- .../ClientHeaderTests.cs | 36 +-- .../ClientKindCommandMatrixTests.cs | 2 +- .../ClientKindProtocolRoutingTests.cs | 2 +- .../ClientKindTests.cs | 2 +- .../ClientLifecycleTests.cs | 34 +-- .../ClientProtocolParityTests.cs | 221 ++++++++---------- .../ClientPubSubTests.cs | 36 +-- .../ClientServerGoParityTests.cs | 190 +++++++-------- .../ClientSlowConsumerTests.cs | 28 +-- .../ClientTests.cs | 2 +- .../ClientTraceModeTests.cs | 2 +- .../ClientTraceTests.cs | 2 +- .../ClientUnsubTests.cs | 36 +-- .../Concurrency/.gitkeep | 0 .../ConcurrencyStressTests.cs | 2 +- .../ConfigIntegrationTests.cs | 2 +- .../ConfigProcessorTests.cs | 2 +- .../ConfigReloadTests.cs | 2 +- .../ConfigRuntimeParityTests.cs | 2 +- .../AuthChangePropagationTests.cs | 2 +- .../Configuration/AuthReloadTests.cs | 44 +--- .../Configuration/ClusterConfigReloadTests.cs | 2 +- .../ConfigPedanticParityBatch1Tests.cs | 2 +- .../ConfigReloadAdvancedTests.cs | 30 +-- .../ConfigReloadExtendedParityTests.cs | 58 ++--- .../Configuration/ConfigReloadParityTests.cs | 42 +--- .../ConfigWarningsParityBatch1Tests.cs | 2 +- .../JetStreamConfigReloadTests.cs | 2 +- .../Configuration/LoggingReloadTests.cs | 2 +- .../Configuration/OptsGoParityTests.cs | 2 +- .../Configuration/ReloadGoParityTests.cs | 64 ++--- .../Configuration/SignalReloadTests.cs | 34 +-- .../Configuration/TlsReloadTests.cs | 2 +- .../DifferencesParityClosureTests.cs | 2 +- .../FlushCoalescingTests.cs | 2 +- .../GoParityRunnerTests.cs | 2 +- .../InfrastructureGoParityTests.cs | 36 +-- .../IntegrationTests.cs | 11 +- .../Internal/Avl/.gitkeep | 0 .../Internal/Avl/SequenceSetTests.cs | 2 +- .../Internal/Gsl/.gitkeep | 0 .../Internal/Gsl/GenericSubjectListTests.cs | 2 +- .../Internal/InternalDsParityBatch2Tests.cs | 2 +- .../InternalDsPeriodicSamplerParityTests.cs | 2 +- .../Internal/MessageTraceContextTests.cs | 2 +- .../Internal/SubjectTree/.gitkeep | 0 .../Internal/SubjectTree/SubjectTreeTests.cs | 2 +- .../Internal/TimeHashWheel/.gitkeep | 0 .../Internal/TimeHashWheel/HashWheelTests.cs | 2 +- .../Internal/TraceContextPropagationTests.cs | 2 +- .../InternalClientTests.cs | 2 +- .../LoggingTests.cs | 2 +- .../MessageTraceTests.cs | 50 ++-- .../MsgTraceGoParityTests.cs | 85 +++---- .../NATS.Server.Core.Tests.csproj} | 4 +- .../NatsConfLexerTests.cs | 2 +- .../NatsConfParserTests.cs | 2 +- .../NatsHeaderParserTests.cs | 2 +- .../NatsOptionsTests.cs | 2 +- .../NoRespondersTests.cs | 36 +-- .../Parity/JetStreamParityTruthMatrixTests.cs | 2 +- .../NatsStrictCapabilityInventoryTests.cs | 2 +- .../ParserTests.cs | 2 +- .../Protocol/ClientProtocolGoParityTests.cs | 73 +++--- .../Protocol/InterServerOpcodeRoutingTests.cs | 2 +- .../MessageTraceInitializationTests.cs | 2 +- .../Protocol/ProtoWireParityTests.cs | 2 +- .../ProtocolDefaultConstantsGapParityTests.cs | 2 +- .../ProtocolParserSnippetGapParityTests.cs | 2 +- .../Protocol/ProxyProtocolTests.cs | 2 +- .../ResponseRoutingTests.cs | 2 +- .../ResponseTrackerTests.cs | 2 +- .../RttTests.cs | 13 +- .../Server/AcceptLoopErrorCallbackTests.cs | 2 +- .../Server/AcceptLoopReloadLockTests.cs | 2 +- ...eServerClientAccessorsParityBatch2Tests.cs | 2 +- .../Server/CoreServerGapParityTests.cs | 21 +- .../CoreServerOptionsParityBatch3Tests.cs | 2 +- ...tilitiesAndRateCounterParityBatch1Tests.cs | 2 +- ...tilitiesErrorConstantsParityBatch2Tests.cs | 2 +- .../ServerConfigTests.cs | 32 +-- .../ServerStatsTests.cs | 11 +- .../ServerTests.cs | 142 +++-------- .../SignalHandlerTests.cs | 2 +- .../SlopwatchSuppressAttribute.cs | 2 +- .../SlowConsumerStallGateTests.cs | 2 +- .../StallGateTests.cs | 2 +- .../Stress/ClusterStressTests.cs | 2 +- .../Stress/ConcurrentPubSubStressTests.cs | 2 +- .../Stress/SlowConsumerStressTests.cs | 122 ++++------ .../SubList/SubListAsyncCacheSweepTests.cs | 2 +- .../SubListHighFanoutOptimizationTests.cs | 2 +- .../SubList/SubListMatchBytesTests.cs | 2 +- .../SubList/SubListNotificationTests.cs | 2 +- .../SubList/SubListQueueWeightTests.cs | 2 +- .../SubList/SubListRemoteFilterTests.cs | 2 +- .../SubListTests.cs | 2 +- .../SubjectMatchTests.cs | 2 +- .../SubjectTransformIntegrationTests.cs | 2 +- .../SubjectTransformTests.cs | 2 +- .../Subscriptions/RouteResultCacheTests.cs | 2 +- .../SubListCtorAndNotificationParityTests.cs | 2 +- .../Subscriptions/SubListGoParityTests.cs | 2 +- .../Subscriptions/SubListParityBatch2Tests.cs | 2 +- .../SubjectSubsetMatchParityBatch1Tests.cs | 2 +- .../SubjectTransformParityBatch3Tests.cs | 2 +- .../VerboseModeTests.cs | 44 ++-- .../WriteLoopTests.cs | 2 +- .../WriteTimeoutTests.cs | 2 +- .../Networking/NetworkingGoParityTests.cs | 2 +- 114 files changed, 576 insertions(+), 1121 deletions(-) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientClosedReasonTests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientFlagsTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientHeaderTests.cs (84%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientKindCommandMatrixTests.cs (91%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientKindProtocolRoutingTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientKindTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientLifecycleTests.cs (84%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientProtocolParityTests.cs (89%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientPubSubTests.cs (82%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientServerGoParityTests.cs (91%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientSlowConsumerTests.cs (84%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientTraceModeTests.cs (92%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientTraceTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ClientUnsubTests.cs (85%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Concurrency/.gitkeep (100%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ConcurrencyStressTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ConfigIntegrationTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ConfigProcessorTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ConfigReloadTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ConfigRuntimeParityTests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/AuthChangePropagationTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/AuthReloadTests.cs (91%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ClusterConfigReloadTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ConfigPedanticParityBatch1Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ConfigReloadAdvancedTests.cs (95%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ConfigReloadExtendedParityTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ConfigReloadParityTests.cs (90%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ConfigWarningsParityBatch1Tests.cs (95%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/JetStreamConfigReloadTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/LoggingReloadTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/OptsGoParityTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/ReloadGoParityTests.cs (95%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/SignalReloadTests.cs (92%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Configuration/TlsReloadTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/DifferencesParityClosureTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/FlushCoalescingTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/GoParityRunnerTests.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/InfrastructureGoParityTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/IntegrationTests.cs (92%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/Avl/.gitkeep (100%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/Avl/SequenceSetTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/Gsl/.gitkeep (100%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/Gsl/GenericSubjectListTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/InternalDsParityBatch2Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/InternalDsPeriodicSamplerParityTests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/MessageTraceContextTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/SubjectTree/.gitkeep (100%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/SubjectTree/SubjectTreeTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/TimeHashWheel/.gitkeep (100%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/TimeHashWheel/HashWheelTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Internal/TraceContextPropagationTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/InternalClientTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/LoggingTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/MessageTraceTests.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/MsgTraceGoParityTests.cs (92%) rename tests/{NATS.Server.Tests/NATS.Server.Tests.csproj => NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj} (85%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/NatsConfLexerTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/NatsConfParserTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/NatsHeaderParserTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/NatsOptionsTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/NoRespondersTests.cs (69%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Parity/JetStreamParityTruthMatrixTests.cs (94%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Parity/NatsStrictCapabilityInventoryTests.cs (90%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ParserTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/ClientProtocolGoParityTests.cs (94%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/InterServerOpcodeRoutingTests.cs (91%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/MessageTraceInitializationTests.cs (94%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/ProtoWireParityTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/ProtocolDefaultConstantsGapParityTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/ProtocolParserSnippetGapParityTests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Protocol/ProxyProtocolTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ResponseRoutingTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ResponseTrackerTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/RttTests.cs (91%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/AcceptLoopErrorCallbackTests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/AcceptLoopReloadLockTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/CoreServerClientAccessorsParityBatch2Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/CoreServerGapParityTests.cs (95%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/CoreServerOptionsParityBatch3Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/UtilitiesAndRateCounterParityBatch1Tests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Server/UtilitiesErrorConstantsParityBatch2Tests.cs (97%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ServerConfigTests.cs (82%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ServerStatsTests.cs (90%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/ServerTests.cs (84%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SignalHandlerTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SlopwatchSuppressAttribute.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SlowConsumerStallGateTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/StallGateTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Stress/ClusterStressTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Stress/ConcurrentPubSubStressTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Stress/SlowConsumerStressTests.cs (88%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListAsyncCacheSweepTests.cs (94%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListHighFanoutOptimizationTests.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListMatchBytesTests.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListNotificationTests.cs (95%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListQueueWeightTests.cs (92%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubList/SubListRemoteFilterTests.cs (93%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubListTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubjectMatchTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubjectTransformIntegrationTests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/SubjectTransformTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/RouteResultCacheTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/SubListCtorAndNotificationParityTests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/SubListGoParityTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/SubListParityBatch2Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs (96%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/Subscriptions/SubjectTransformParityBatch3Tests.cs (98%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/VerboseModeTests.cs (67%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/WriteLoopTests.cs (99%) rename tests/{NATS.Server.Tests => NATS.Server.Core.Tests}/WriteTimeoutTests.cs (98%) diff --git a/NatsDotNet.slnx b/NatsDotNet.slnx index 21823e5..a404f80 100644 --- a/NatsDotNet.slnx +++ b/NatsDotNet.slnx @@ -5,7 +5,7 @@ - + diff --git a/src/NATS.Server/NATS.Server.csproj b/src/NATS.Server/NATS.Server.csproj index 961c56e..395f186 100644 --- a/src/NATS.Server/NATS.Server.csproj +++ b/src/NATS.Server/NATS.Server.csproj @@ -1,6 +1,6 @@ - + diff --git a/tests/NATS.Server.Tests/ClientClosedReasonTests.cs b/tests/NATS.Server.Core.Tests/ClientClosedReasonTests.cs similarity index 96% rename from tests/NATS.Server.Tests/ClientClosedReasonTests.cs rename to tests/NATS.Server.Core.Tests/ClientClosedReasonTests.cs index b2b23c9..80ba993 100644 --- a/tests/NATS.Server.Tests/ClientClosedReasonTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientClosedReasonTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientClosedReasonTests { diff --git a/tests/NATS.Server.Tests/ClientFlagsTests.cs b/tests/NATS.Server.Core.Tests/ClientFlagsTests.cs similarity index 97% rename from tests/NATS.Server.Tests/ClientFlagsTests.cs rename to tests/NATS.Server.Core.Tests/ClientFlagsTests.cs index 9958c52..ecb7530 100644 --- a/tests/NATS.Server.Tests/ClientFlagsTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientFlagsTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientFlagsTests { diff --git a/tests/NATS.Server.Tests/ClientHeaderTests.cs b/tests/NATS.Server.Core.Tests/ClientHeaderTests.cs similarity index 84% rename from tests/NATS.Server.Tests/ClientHeaderTests.cs rename to tests/NATS.Server.Core.Tests/ClientHeaderTests.cs index db95b38..d8deecd 100644 --- a/tests/NATS.Server.Tests/ClientHeaderTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientHeaderTests.cs @@ -6,8 +6,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for HPUB/HMSG header support, mirroring the Go reference tests: @@ -23,7 +24,7 @@ public class ClientHeaderTests : IAsyncLifetime public ClientHeaderTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -39,30 +40,11 @@ public class ClientHeaderTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } /// /// Reads from the socket accumulating data until the accumulated string contains /// , or the timeout elapses. /// - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } /// /// Connect a raw TCP socket, read the INFO line, and send a CONNECT with @@ -72,7 +54,7 @@ public class ClientHeaderTests : IAsyncLifetime { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, _port); - await ReadUntilAsync(sock, "\r\n"); // discard INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // discard INFO await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"headers\":true,\"no_responders\":true}\r\n")); return sock; @@ -104,7 +86,7 @@ public class ClientHeaderTests : IAsyncLifetime await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\n")); // Flush via PING/PONG to ensure the subscription is registered before publishing await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Match Go reference test exactly: // Header block: "Name:Derek\r\n" = 12 bytes @@ -119,7 +101,7 @@ public class ClientHeaderTests : IAsyncLifetime // Read the full HMSG on the subscriber socket (control line + header + payload + trailing CRLF) // The complete wire message ends with the payload followed by \r\n - var received = await ReadUntilAsync(sub, payload + "\r\n", timeoutMs: 5000); + var received = await SocketTestHelper.ReadUntilAsync(sub, payload + "\r\n", timeoutMs: 5000); // Verify HMSG control line: HMSG foo 1 received.ShouldContain($"HMSG foo 1 {hdrLen} {totalLen}\r\n"); @@ -141,7 +123,7 @@ public class ClientHeaderTests : IAsyncLifetime await sock.ConnectAsync(IPAddress.Loopback, _port); // Read the INFO line - var infoLine = await ReadUntilAsync(sock, "\r\n"); + var infoLine = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO must start with "INFO " infoLine.ShouldStartWith("INFO "); @@ -181,13 +163,13 @@ public class ClientHeaderTests : IAsyncLifetime await sock.SendAsync(Encoding.ASCII.GetBytes("SUB reply.inbox 1\r\n")); // Flush via PING/PONG to ensure SUB is registered await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // Publish to a subject with no subscribers, using reply.inbox as reply-to await sock.SendAsync(Encoding.ASCII.GetBytes("PUB no.listeners reply.inbox 0\r\n\r\n")); // The server should send back an HMSG on reply.inbox with status 503 - var received = await ReadUntilAsync(sock, "NATS/1.0 503", timeoutMs: 5000); + var received = await SocketTestHelper.ReadUntilAsync(sock, "NATS/1.0 503", timeoutMs: 5000); // Must be an HMSG (header message) on the reply subject received.ShouldContain("HMSG reply.inbox"); diff --git a/tests/NATS.Server.Tests/ClientKindCommandMatrixTests.cs b/tests/NATS.Server.Core.Tests/ClientKindCommandMatrixTests.cs similarity index 91% rename from tests/NATS.Server.Tests/ClientKindCommandMatrixTests.cs rename to tests/NATS.Server.Core.Tests/ClientKindCommandMatrixTests.cs index c47ea6d..b746577 100644 --- a/tests/NATS.Server.Tests/ClientKindCommandMatrixTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientKindCommandMatrixTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientKindCommandMatrixTests { diff --git a/tests/NATS.Server.Tests/ClientKindProtocolRoutingTests.cs b/tests/NATS.Server.Core.Tests/ClientKindProtocolRoutingTests.cs similarity index 97% rename from tests/NATS.Server.Tests/ClientKindProtocolRoutingTests.cs rename to tests/NATS.Server.Core.Tests/ClientKindProtocolRoutingTests.cs index c41420e..5847ac5 100644 --- a/tests/NATS.Server.Tests/ClientKindProtocolRoutingTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientKindProtocolRoutingTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientKindProtocolRoutingTests { diff --git a/tests/NATS.Server.Tests/ClientKindTests.cs b/tests/NATS.Server.Core.Tests/ClientKindTests.cs similarity index 97% rename from tests/NATS.Server.Tests/ClientKindTests.cs rename to tests/NATS.Server.Core.Tests/ClientKindTests.cs index bc006cc..45fd383 100644 --- a/tests/NATS.Server.Tests/ClientKindTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientKindTests.cs @@ -4,7 +4,7 @@ using NATS.Server; using Shouldly; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientKindTests { diff --git a/tests/NATS.Server.Tests/ClientLifecycleTests.cs b/tests/NATS.Server.Core.Tests/ClientLifecycleTests.cs similarity index 84% rename from tests/NATS.Server.Tests/ClientLifecycleTests.cs rename to tests/NATS.Server.Core.Tests/ClientLifecycleTests.cs index 51285a0..19ac244 100644 --- a/tests/NATS.Server.Tests/ClientLifecycleTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientLifecycleTests.cs @@ -6,8 +6,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for client lifecycle: connection handshake, CONNECT proto parsing, @@ -16,26 +17,7 @@ namespace NATS.Server.Tests; /// public class ClientLifecycleTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } /// /// TestClientConnectProto: Sends CONNECT with verbose:false, pedantic:false, name:"test-client" @@ -45,7 +27,7 @@ public class ClientLifecycleTests [Fact] public async Task Connect_proto_accepted() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(cts.Token); @@ -67,7 +49,7 @@ public class ClientLifecycleTests await client.SendAsync(Encoding.ASCII.GetBytes(connectMsg)); // Should receive PONG confirming connection is accepted - var response = await ReadUntilAsync(client, "PONG"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG"); response.ShouldContain("PONG\r\n"); } finally @@ -87,7 +69,7 @@ public class ClientLifecycleTests public async Task Max_subscriptions_enforced() { const int maxSubs = 10; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port, MaxSubs = maxSubs }, @@ -119,7 +101,7 @@ public class ClientLifecycleTests await client.SendAsync(Encoding.ASCII.GetBytes(subsBuilder.ToString())); // Server should send -ERR 'Maximum Subscriptions Exceeded' and close - var response = await ReadUntilAsync(client, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(client, "-ERR", timeoutMs: 5000); response.ShouldContain("-ERR 'Maximum Subscriptions Exceeded'"); // Connection should be closed after the error @@ -144,7 +126,7 @@ public class ClientLifecycleTests [Fact] public async Task Auth_timeout_closes_connection_if_no_connect() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions @@ -170,7 +152,7 @@ public class ClientLifecycleTests // Do NOT send CONNECT — wait for auth timeout to fire // AuthTimeout is 500ms; wait up to 3x that for the error - var response = await ReadUntilAsync(client, "Authentication Timeout", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(client, "Authentication Timeout", timeoutMs: 3000); response.ShouldContain("-ERR 'Authentication Timeout'"); // Connection should be closed after the auth timeout error diff --git a/tests/NATS.Server.Tests/ClientProtocolParityTests.cs b/tests/NATS.Server.Core.Tests/ClientProtocolParityTests.cs similarity index 89% rename from tests/NATS.Server.Tests/ClientProtocolParityTests.cs rename to tests/NATS.Server.Core.Tests/ClientProtocolParityTests.cs index 48a59bf..9c8d7a9 100644 --- a/tests/NATS.Server.Tests/ClientProtocolParityTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientProtocolParityTests.cs @@ -10,8 +10,9 @@ using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; using NATS.Server.Auth; using NATS.Server.Protocol; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Protocol-level parity tests ported from Go client_test.go. @@ -24,28 +25,6 @@ public class ClientProtocolParityTests // Helpers (self-contained, duplicated per task spec) // --------------------------------------------------------------------------- - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[8192]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ReadAllAvailableAsync(Socket sock, int timeoutMs = 1000) { using var cts = new CancellationTokenSource(timeoutMs); @@ -87,7 +66,7 @@ public class ClientProtocolParityTests private static async Task<(NatsServer Server, int Port, CancellationTokenSource Cts)> StartServerAsync(NatsOptions? options = null) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); options ??= new NatsOptions(); options.Port = port; var cts = new CancellationTokenSource(); @@ -104,7 +83,7 @@ public class ClientProtocolParityTests { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // drain INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // drain INFO await sock.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {connectJson}\r\n")); return sock; } @@ -116,7 +95,7 @@ public class ClientProtocolParityTests { var sock = await ConnectAndHandshakeAsync(port, connectJson); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); return sock; } @@ -134,7 +113,7 @@ public class ClientProtocolParityTests using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldStartWith("INFO "); var jsonStart = info.IndexOf('{'); @@ -166,7 +145,7 @@ public class ClientProtocolParityTests using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); var jsonStr = info[(info.IndexOf('{'))..(info.LastIndexOf('}') + 1)]; var serverInfo = JsonSerializer.Deserialize(jsonStr); serverInfo!.MaxPayload.ShouldBe(maxPayload); @@ -188,7 +167,7 @@ public class ClientProtocolParityTests using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldContain("\"auth_required\":true"); } finally @@ -208,7 +187,7 @@ public class ClientProtocolParityTests using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // auth_required should not be present (or should be false/omitted) info.ShouldNotContain("\"auth_required\":true"); } @@ -234,7 +213,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); // With verbose:true, the CONNECT itself triggers +OK, then PING triggers PONG + +OK - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("+OK\r\n"); response.ShouldContain("PONG\r\n"); } @@ -256,7 +235,7 @@ public class ClientProtocolParityTests // PUB should not trigger +OK when verbose is false await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldNotContain("+OK"); } @@ -276,10 +255,10 @@ public class ClientProtocolParityTests { using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":true}"); // Drain the +OK from CONNECT - await ReadUntilAsync(sock, "+OK\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "+OK\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); // SUB should trigger +OK in verbose mode response.ShouldContain("+OK\r\n"); @@ -299,10 +278,10 @@ public class ClientProtocolParityTests try { using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":true}"); - await ReadUntilAsync(sock, "+OK\r\n"); // drain CONNECT +OK + await SocketTestHelper.ReadUntilAsync(sock, "+OK\r\n"); // drain CONNECT +OK await sock.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nUNSUB 1\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); // Should get two +OK (SUB + UNSUB) plus PONG CountOccurrences(response, "+OK\r\n").ShouldBeGreaterThanOrEqualTo(2); @@ -322,10 +301,10 @@ public class ClientProtocolParityTests try { using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":true}"); - await ReadUntilAsync(sock, "+OK\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "+OK\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); // PUB should trigger +OK in verbose mode response.ShouldContain("+OK\r\n"); @@ -350,7 +329,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"user\":\"derek\",\"pass\":\"foo\"}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -373,7 +352,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"auth_token\":\"YZZ222\"}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -393,7 +372,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"name\":\"my-test-client\"}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -417,7 +396,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":false,\"pedantic\":false,\"protocol\":0}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -437,7 +416,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":false,\"pedantic\":false,\"protocol\":1}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -460,7 +439,7 @@ public class ClientProtocolParityTests { using var sock = await ConnectAndHandshakeAsync(port); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -535,7 +514,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( $"SUB foo 1\r\nPUB foo {payload.Length}\r\n{payload}\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo 1"); response.ShouldContain("PONG\r\n"); } @@ -593,7 +572,7 @@ public class ClientProtocolParityTests // Publish to an invalid subject (contains space) await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo.*.bar 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n", 5000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n", 5000); response.ShouldContain("-ERR 'Invalid Publish Subject'"); } finally @@ -615,7 +594,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo.bar 1\r\nPUB foo.bar 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo.bar 1 5\r\nhello\r\n"); response.ShouldNotContain("-ERR"); } @@ -637,7 +616,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo.> 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n", 5000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n", 5000); response.ShouldContain("-ERR 'Invalid Publish Subject'"); } finally @@ -663,7 +642,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo 1 5\r\nhello\r\n"); } finally @@ -685,7 +664,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldNotContain("MSG"); } finally @@ -709,9 +688,9 @@ public class ClientProtocolParityTests // Both subscribe to same queue group await pub.SendAsync(Encoding.ASCII.GetBytes("SUB foo bar 1\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo bar 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Publish 100 messages from the echo:false client var sb = new StringBuilder(); @@ -719,11 +698,11 @@ public class ClientProtocolParityTests sb.Append("PUB foo 5\r\nhello\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); // Send PING on sub to flush deliveries await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // The subscriber should receive all 100 messages since the publisher // has echo:false (all queue messages go to the other member) @@ -753,12 +732,12 @@ public class ClientProtocolParityTests // Publish first (no subscribers), then subscribe to "foo", then publish "foo.bar" await sock.SendAsync(Encoding.ASCII.GetBytes( "PUB foo.bar 5\r\nhello\r\nSUB foo 1\r\nPING\r\n")); - var response1 = await ReadUntilAsync(sock, "PONG\r\n"); + var response1 = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response1.ShouldStartWith("PONG\r\n"); // Now publish foo.bar again -- should NOT match "foo" subscription await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo.bar 5\r\nhello\r\nPING\r\n")); - var response2 = await ReadUntilAsync(sock, "PONG\r\n"); + var response2 = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response2.ShouldStartWith("PONG\r\n"); response2.ShouldNotContain("MSG"); } @@ -785,7 +764,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"auth_token\":\"wrong_token\"}\r\n")); @@ -812,7 +791,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"user\":\"admin\",\"pass\":\"wrongpass\"}\r\n")); @@ -840,7 +819,7 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"user\":\"admin\",\"pass\":\"secret\"}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally @@ -863,10 +842,10 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO // Do NOT send CONNECT - var response = await ReadUntilAsync(sock, "Authentication Timeout", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "Authentication Timeout", timeoutMs: 5000); response.ShouldContain("-ERR 'Authentication Timeout'"); } finally @@ -906,7 +885,7 @@ public class ClientProtocolParityTests // Subscribe to a denied subject await sock.SendAsync(Encoding.ASCII.GetBytes("SUB denied.topic 1\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("-ERR 'Permissions Violation for Subscription'"); } finally @@ -941,7 +920,7 @@ public class ClientProtocolParityTests "{\"user\":\"limited\",\"pass\":\"pass\",\"verbose\":false}"); await sock.SendAsync(Encoding.ASCII.GetBytes("PUB denied.topic 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("-ERR 'Permissions Violation for Publish'"); } finally @@ -978,7 +957,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB allowed.topic 1\r\nPUB allowed.topic 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG allowed.topic 1 5\r\nhello\r\n"); response.ShouldNotContain("-ERR"); } @@ -1019,12 +998,12 @@ public class ClientProtocolParityTests // Allowed await sock.SendAsync(Encoding.ASCII.GetBytes("PUB public.topic 5\r\nhello\r\nPING\r\n")); - var r1 = await ReadUntilAsync(sock, "PONG\r\n"); + var r1 = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); r1.ShouldNotContain("-ERR"); // Denied await sock.SendAsync(Encoding.ASCII.GetBytes("PUB secret.data 5\r\nhello\r\nPING\r\n")); - var r2 = await ReadUntilAsync(sock, "PONG\r\n"); + var r2 = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); r2.ShouldContain("-ERR 'Permissions Violation for Publish'"); } finally @@ -1047,7 +1026,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO // no_responders without headers should fail await sock.SendAsync(Encoding.ASCII.GetBytes( @@ -1075,12 +1054,12 @@ public class ClientProtocolParityTests // Subscribe on the reply inbox await sock.SendAsync(Encoding.ASCII.GetBytes("SUB reply.inbox 1\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); // Publish to a subject with no subscribers, with a reply subject await sock.SendAsync(Encoding.ASCII.GetBytes("PUB no.listeners reply.inbox 0\r\n\r\n")); - var response = await ReadUntilAsync(sock, "NATS/1.0 503", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "NATS/1.0 503", timeoutMs: 5000); response.ShouldContain("HMSG reply.inbox"); response.ShouldContain("NATS/1.0 503"); } @@ -1104,7 +1083,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldContain("\"headers\":true"); } finally @@ -1125,7 +1104,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldContain("\"headers\":true"); } finally @@ -1146,12 +1125,12 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // HPUB foo 12 14\r\nName:Derek\r\nOK await pub.SendAsync(Encoding.ASCII.GetBytes("HPUB foo 12 14\r\nName:Derek\r\nOK\r\n")); - var response = await ReadUntilAsync(sub, "OK\r\n", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(sub, "OK\r\n", timeoutMs: 5000); response.ShouldContain("HMSG foo 1 12 14\r\n"); response.ShouldContain("Name:Derek"); } @@ -1210,7 +1189,7 @@ public class ClientProtocolParityTests sb.Append("PING\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldNotContain("-ERR"); response.ShouldContain("PONG\r\n"); @@ -1235,7 +1214,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); var jsonStr = info[(info.IndexOf('{'))..(info.LastIndexOf('}') + 1)]; var serverInfo = JsonSerializer.Deserialize(jsonStr); @@ -1285,7 +1264,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nSUB bar 2\r\nSUB baz 3\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); server.SubList.Count.ShouldBe(3u); @@ -1395,7 +1374,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo reply.to 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo 1 reply.to 5\r\nhello\r\n"); } @@ -1417,7 +1396,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo reply.to 0\r\n\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo 1 reply.to 0\r\n"); } @@ -1444,13 +1423,13 @@ public class ClientProtocolParityTests await sub.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nSUB foo 2\r\nUNSUB 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("MSG foo 2 5"); response.ShouldNotContain("MSG foo 1 5"); @@ -1474,7 +1453,7 @@ public class ClientProtocolParityTests await sub.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nUNSUB 1 5\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Publish 10 messages var sb = new StringBuilder(); @@ -1482,7 +1461,7 @@ public class ClientProtocolParityTests sb.Append("PUB foo 1\r\nx\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); // Collect messages on subscriber await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); @@ -1509,10 +1488,10 @@ public class ClientProtocolParityTests await sub.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nUNSUB 1 100\r\nUNSUB 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); var response = await ReadAllAvailableAsync(sub, 1000); @@ -1542,7 +1521,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo g1 1\r\nSUB foo g1 2\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); var sb = new StringBuilder(); for (int i = 0; i < count; i++) @@ -1550,7 +1529,7 @@ public class ClientProtocolParityTests sb.Append("PING\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); var n1 = CountOccurrences(response, "MSG foo 1 5"); var n2 = CountOccurrences(response, "MSG foo 2 5"); @@ -1579,7 +1558,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "PUB foo 5\r\nhello\r\nPUB foo 5\r\nhello\r\nPUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); Interlocked.Read(ref server.Stats.InMsgs).ShouldBeGreaterThanOrEqualTo(3); } @@ -1600,7 +1579,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "PUB foo 10\r\n0123456789\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); Interlocked.Read(ref server.Stats.InBytes).ShouldBeGreaterThanOrEqualTo(10); } @@ -1621,7 +1600,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); Interlocked.Read(ref server.Stats.OutMsgs).ShouldBeGreaterThanOrEqualTo(1); } @@ -1647,7 +1626,7 @@ public class ClientProtocolParityTests using var slowSub = await ConnectAndPingAsync(port, "{\"verbose\":false}"); await slowSub.SendAsync(Encoding.ASCII.GetBytes("SUB flood 1\r\nPING\r\n")); - await ReadUntilAsync(slowSub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(slowSub, "PONG\r\n"); using var pub = await ConnectAndPingAsync(port, "{\"verbose\":false}"); @@ -1658,7 +1637,7 @@ public class ClientProtocolParityTests sb.Append($"PUB flood {payload.Length}\r\n{payload}\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await Task.Delay(500); @@ -1683,7 +1662,7 @@ public class ClientProtocolParityTests try { using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":true}"); - await ReadUntilAsync(sock, "+OK\r\n"); // drain CONNECT +OK + await SocketTestHelper.ReadUntilAsync(sock, "+OK\r\n"); // drain CONNECT +OK await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); var response = await ReadAllAvailableAsync(sock, 2000); @@ -1713,13 +1692,13 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("MSG foo 1 5\r\nhello\r\n"); } finally @@ -1739,14 +1718,14 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo.* 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes( "PUB foo.bar 5\r\nhello\r\nPUB foo.baz 5\r\nworld\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("MSG foo.bar 1 5\r\nhello\r\n"); response.ShouldContain("MSG foo.baz 1 5\r\nworld\r\n"); } @@ -1767,14 +1746,14 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo.> 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes( "PUB foo.bar.baz 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("MSG foo.bar.baz 1 5\r\nhello\r\n"); } finally @@ -1800,14 +1779,14 @@ public class ClientProtocolParityTests await sub.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nUNSUB 1 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); var sb = new StringBuilder(); for (int i = 0; i < 5; i++) sb.Append("PUB foo 2\r\nok\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); var response = await ReadAllAvailableAsync(sub, 2000); @@ -1833,7 +1812,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"no_responders\":true,\"headers\":false}\r\n")); @@ -1864,13 +1843,13 @@ public class ClientProtocolParityTests using var sub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // HPUB with valid header block await pub.SendAsync(Encoding.ASCII.GetBytes( "HPUB foo 12 14\r\nName:Derek\r\nOK\r\n")); - var response = await ReadUntilAsync(sub, "OK\r\n", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(sub, "OK\r\n", timeoutMs: 5000); response.ShouldContain("HMSG foo 1 12 14\r\n"); } finally @@ -1894,7 +1873,7 @@ public class ClientProtocolParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo 0\r\n\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("MSG foo 1 0\r\n"); } @@ -1948,7 +1927,7 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Start publishing concurrently var pubTask = Task.Run(async () => @@ -1990,13 +1969,13 @@ public class ClientProtocolParityTests using var sock = await ConnectAndHandshakeAsync(port, "{\"verbose\":true}"); // Drain +OK from CONNECT - await ReadUntilAsync(sock, "+OK\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "+OK\r\n"); // SUB -> +OK, PUB -> +OK, UNSUB -> +OK, PING -> PONG + +OK await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB foo 1\r\nPUB foo 5\r\nhello\r\nUNSUB 1\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); // At least 3 +OK (SUB, PUB, UNSUB) plus the one for PING CountOccurrences(response, "+OK\r\n").ShouldBeGreaterThanOrEqualTo(3); @@ -2024,19 +2003,19 @@ public class ClientProtocolParityTests using var pub = await ConnectAndPingAsync(port); await sub1.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub1, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub1, "PONG\r\n"); await sub2.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub2, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub2, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub1.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var r1 = await ReadUntilAsync(sub1, "PONG\r\n"); + var r1 = await SocketTestHelper.ReadUntilAsync(sub1, "PONG\r\n"); r1.ShouldContain("MSG foo 1 5\r\nhello\r\n"); await sub2.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var r2 = await ReadUntilAsync(sub2, "PONG\r\n"); + var r2 = await SocketTestHelper.ReadUntilAsync(sub2, "PONG\r\n"); r2.ShouldContain("MSG foo 1 5\r\nhello\r\n"); } finally @@ -2058,7 +2037,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); var jsonStr = info[(info.IndexOf('{'))..(info.LastIndexOf('}') + 1)]; var si = JsonSerializer.Deserialize(jsonStr); @@ -2084,7 +2063,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldContain("\"proto\":"); } finally @@ -2106,7 +2085,7 @@ public class ClientProtocolParityTests { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // host field should be present info.ShouldContain("\"host\":"); @@ -2133,9 +2112,9 @@ public class ClientProtocolParityTests """.Trim(); using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // INFO await sock.SendAsync(Encoding.ASCII.GetBytes(connect + "\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } finally diff --git a/tests/NATS.Server.Tests/ClientPubSubTests.cs b/tests/NATS.Server.Core.Tests/ClientPubSubTests.cs similarity index 82% rename from tests/NATS.Server.Tests/ClientPubSubTests.cs rename to tests/NATS.Server.Core.Tests/ClientPubSubTests.cs index 3c49944..e3506be 100644 --- a/tests/NATS.Server.Tests/ClientPubSubTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientPubSubTests.cs @@ -9,8 +9,9 @@ using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientPubSubTests : IAsyncLifetime { @@ -20,7 +21,7 @@ public class ClientPubSubTests : IAsyncLifetime public ClientPubSubTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -36,12 +37,6 @@ public class ClientPubSubTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private async Task ConnectClientAsync() { @@ -53,19 +48,6 @@ public class ClientPubSubTests : IAsyncLifetime /// /// Reads from a socket until the accumulated data contains the expected substring. /// - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } // Go reference: TestClientSimplePubSub (client_test.go line 666) // SUB foo 1, PUB foo 5\r\nhello — subscriber receives MSG foo 1 5\r\nhello @@ -83,7 +65,7 @@ public class ClientPubSubTests : IAsyncLifetime "CONNECT {}\r\nSUB foo 1\r\nPUB foo 5\r\nhello\r\nPING\r\n")); // Read until we see the message payload (delivered before PONG) - var response = await ReadUntilAsync(client, "hello\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "hello\r\n"); // MSG line: MSG foo 1 5\r\nhello\r\n response.ShouldContain("MSG foo 1 5\r\nhello\r\n"); @@ -106,7 +88,7 @@ public class ClientPubSubTests : IAsyncLifetime // With echo=false the server must not deliver the message back to the publisher. // The first line we receive should be PONG, not MSG. - var response = await ReadUntilAsync(client, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); response.ShouldStartWith("PONG\r\n"); response.ShouldNotContain("MSG"); @@ -127,7 +109,7 @@ public class ClientPubSubTests : IAsyncLifetime await client.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {}\r\nSUB foo 1\r\nPUB foo bar 5\r\nhello\r\nPING\r\n")); - var response = await ReadUntilAsync(client, "hello\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "hello\r\n"); // MSG line must include the reply subject: MSG <#bytes> response.ShouldContain("MSG foo 1 bar 5\r\nhello\r\n"); @@ -149,7 +131,7 @@ public class ClientPubSubTests : IAsyncLifetime "CONNECT {}\r\nSUB foo 1\r\nPUB foo bar 0\r\n\r\nPING\r\n")); // Read until PONG — MSG should arrive before PONG - var response = await ReadUntilAsync(client, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); // MSG line: MSG foo 1 bar 0\r\n\r\n (empty body, still CRLF terminated) response.ShouldContain("MSG foo 1 bar 0\r\n"); @@ -172,7 +154,7 @@ public class ClientPubSubTests : IAsyncLifetime // CONNECT, two queue subs with different sids, PING to confirm await client.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {}\r\nSUB foo g1 1\r\nSUB foo g1 2\r\nPING\r\n")); - await ReadUntilAsync(client, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); // Publish 100 messages, then PING to flush all deliveries var pubSb = new StringBuilder(); @@ -182,7 +164,7 @@ public class ClientPubSubTests : IAsyncLifetime await client.SendAsync(Encoding.ASCII.GetBytes(pubSb.ToString())); // Read until PONG — all MSGs arrive before the PONG - var response = await ReadUntilAsync(client, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); // Count deliveries per sid var n1 = Regex.Matches(response, @"MSG foo 1 5").Count; diff --git a/tests/NATS.Server.Tests/ClientServerGoParityTests.cs b/tests/NATS.Server.Core.Tests/ClientServerGoParityTests.cs similarity index 91% rename from tests/NATS.Server.Tests/ClientServerGoParityTests.cs rename to tests/NATS.Server.Core.Tests/ClientServerGoParityTests.cs index 3e909cf..fad466b 100644 --- a/tests/NATS.Server.Tests/ClientServerGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientServerGoParityTests.cs @@ -10,8 +10,9 @@ using NATS.Server; using NATS.Server.Auth; using NATS.Server.Protocol; using NATS.Server.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for client protocol handling, matching Go client_test.go. @@ -22,12 +23,6 @@ public class ClientServerGoParityTests // Helpers shared across test methods // --------------------------------------------------------------------------- - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private static async Task StartServerAsync(NatsOptions opts) { @@ -47,19 +42,6 @@ public class ClientServerGoParityTests /// /// Read until the accumulated data contains the expected substring, with timeout. /// - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ReadLineAsync(Socket sock) { @@ -80,7 +62,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientCreateAndInfo() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port }; var server = await StartServerAsync(opts); try @@ -121,7 +103,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientConnect() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -131,7 +113,7 @@ public class ClientServerGoParityTests // Basic CONNECT with verbose and pedantic flags await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":true,\"pedantic\":true}\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // verbose=true means server sends +OK for CONNECT and PING response.ShouldContain("+OK"); response.ShouldContain("PONG"); @@ -155,7 +137,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientConnectProto() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -163,14 +145,14 @@ public class ClientServerGoParityTests using var sock0 = await ConnectRawAsync(port); await ReadLineAsync(sock0); await sock0.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"protocol\":0}\r\nPING\r\n")); - var r0 = await ReadUntilAsync(sock0, "PONG"); + var r0 = await SocketTestHelper.ReadUntilAsync(sock0, "PONG"); r0.ShouldContain("PONG"); // protocol=1 (info proto) using var sock1 = await ConnectRawAsync(port); await ReadLineAsync(sock1); await sock1.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"protocol\":1}\r\nPING\r\n")); - var r1 = await ReadUntilAsync(sock1, "PONG"); + var r1 = await SocketTestHelper.ReadUntilAsync(sock1, "PONG"); r1.ShouldContain("PONG"); } finally @@ -192,14 +174,14 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientPing() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { using var sock = await ConnectRawAsync(port); await ReadLineAsync(sock); // INFO await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); response.ShouldContain("PONG\r\n"); } finally @@ -221,7 +203,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientPub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -232,11 +214,11 @@ public class ClientServerGoParityTests await ReadLineAsync(sub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB foo 5\r\nhello\r\n")); - var msg = await ReadUntilAsync(sub, "hello"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "hello"); msg.ShouldContain("MSG foo 1 5\r\nhello\r\n"); } finally @@ -258,7 +240,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientPubPermission() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port, @@ -284,7 +266,7 @@ public class ClientServerGoParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"user\":\"testuser\",\"pass\":\"testpass\"}\r\nPUB denied.subject 3\r\nfoo\r\n")); - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain("-ERR"); response.ShouldContain("Permissions Violation"); } @@ -307,7 +289,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientSub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -318,11 +300,11 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB test.sub 42\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB test.sub 4\r\ndata\r\n")); - var msg = await ReadUntilAsync(sub, "data"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "data"); msg.ShouldContain("MSG test.sub 42 4\r\ndata\r\n"); } finally @@ -344,7 +326,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientSubWithQueueGroup() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -358,15 +340,15 @@ public class ClientServerGoParityTests // Both subscribe to same queue group await sub1.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB qtest myqueue 1\r\nPING\r\n")); - await ReadUntilAsync(sub1, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub1, "PONG"); await sub2.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB qtest myqueue 2\r\nPING\r\n")); - await ReadUntilAsync(sub2, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub2, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB qtest 5\r\nhello\r\n")); // Exactly one subscriber should receive the message - var received1Task = ReadUntilAsync(sub1, "MSG", timeoutMs: 1000); - var received2Task = ReadUntilAsync(sub2, "MSG", timeoutMs: 1000); + var received1Task = SocketTestHelper.ReadUntilAsync(sub1, "MSG", timeoutMs: 1000); + var received2Task = SocketTestHelper.ReadUntilAsync(sub2, "MSG", timeoutMs: 1000); var completed = await Task.WhenAny(received1Task, received2Task); var result = await completed; @@ -391,7 +373,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientUnsub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -403,7 +385,7 @@ public class ClientServerGoParityTests // Subscribe then immediately unsubscribe await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB unsub.test 5\r\nUNSUB 5\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB unsub.test 3\r\nfoo\r\n")); @@ -444,7 +426,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientMsg() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -455,11 +437,11 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB msg.test 99\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB msg.test reply.subj 7\r\npayload\r\n")); - var msg = await ReadUntilAsync(sub, "payload"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "payload"); // MSG [reply] <#bytes> msg.ShouldContain("MSG msg.test 99 reply.subj 7\r\npayload\r\n"); } @@ -482,7 +464,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientMaxPayload() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxPayload = 16 }); try { @@ -495,7 +477,7 @@ public class ClientServerGoParityTests await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo 32\r\n01234567890123456789012345678901\r\n")); - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain(NatsProtocol.ErrMaxPayloadViolation); } finally @@ -517,7 +499,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientSlowConsumer() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Very small MaxPending to easily trigger slow consumer var server = await StartServerAsync(new NatsOptions { Port = port, MaxPending = 512 }); try @@ -529,7 +511,7 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB slow.test 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); @@ -567,11 +549,11 @@ public class ClientServerGoParityTests public async Task TestClientWriteDeadline() { // Verify WriteDeadline is a configurable option and defaults to 10 seconds - var opts = new NatsOptions { Port = GetFreePort() }; + var opts = new NatsOptions { Port = TestPortAllocator.GetFreePort() }; opts.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(10)); // Custom write deadline - var customOpts = new NatsOptions { Port = GetFreePort(), WriteDeadline = TimeSpan.FromMilliseconds(500) }; + var customOpts = new NatsOptions { Port = TestPortAllocator.GetFreePort(), WriteDeadline = TimeSpan.FromMilliseconds(500) }; customOpts.WriteDeadline.ShouldBe(TimeSpan.FromMilliseconds(500)); // Server starts with custom write deadline without error @@ -601,7 +583,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientParseConnect() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -610,7 +592,7 @@ public class ClientServerGoParityTests await ReadLineAsync(sock); await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"verbose\":true,\"name\":\"my-client\",\"lang\":\"dotnet\"}\r\nPING\r\n")); - var r = await ReadUntilAsync(sock, "PONG"); + var r = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); r.ShouldContain("+OK"); // verbose=true → +OK after CONNECT r.ShouldContain("PONG"); } @@ -633,7 +615,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientConnectVerbose() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -643,7 +625,7 @@ public class ClientServerGoParityTests await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"verbose\":true}\r\nSUB foo 1\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // +OK for CONNECT, +OK for SUB, PONG var okCount = CountOccurrences(response, "+OK"); okCount.ShouldBeGreaterThanOrEqualTo(2); @@ -667,7 +649,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientParsePub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -678,16 +660,16 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB parse.pub 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // PUB without reply await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB parse.pub 3\r\nfoo\r\n")); - var msg1 = await ReadUntilAsync(sub, "foo"); + var msg1 = await SocketTestHelper.ReadUntilAsync(sub, "foo"); msg1.ShouldContain("MSG parse.pub 1 3\r\nfoo\r\n"); // PUB with reply await pub.SendAsync(Encoding.ASCII.GetBytes("PUB parse.pub _INBOX.reply 3\r\nbar\r\n")); - var msg2 = await ReadUntilAsync(sub, "bar"); + var msg2 = await SocketTestHelper.ReadUntilAsync(sub, "bar"); msg2.ShouldContain("MSG parse.pub 1 _INBOX.reply 3\r\nbar\r\n"); } finally @@ -709,7 +691,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientParseSub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -721,11 +703,11 @@ public class ClientServerGoParityTests // SUB without queue group await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB parse.sub 7\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB parse.sub 2\r\nhi\r\n")); - var msg = await ReadUntilAsync(sub, "hi"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "hi"); msg.ShouldContain("MSG parse.sub 7 2\r\nhi\r\n"); } finally @@ -747,7 +729,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientParseUnsub() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -759,18 +741,18 @@ public class ClientServerGoParityTests // Subscribe then set max-messages=2 via UNSUB await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB autounsub 3\r\nUNSUB 3 2\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); // First message — delivered await pub.SendAsync(Encoding.ASCII.GetBytes("PUB autounsub 3\r\none\r\n")); - var m1 = await ReadUntilAsync(sub, "one"); + var m1 = await SocketTestHelper.ReadUntilAsync(sub, "one"); m1.ShouldContain("MSG autounsub"); // Second message — delivered (reaches max) await pub.SendAsync(Encoding.ASCII.GetBytes("PUB autounsub 3\r\ntwo\r\n")); - var m2 = await ReadUntilAsync(sub, "two"); + var m2 = await SocketTestHelper.ReadUntilAsync(sub, "two"); m2.ShouldContain("MSG autounsub"); // Third message — NOT delivered (auto-unsubscribed) @@ -805,7 +787,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientSplitMsg() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -816,7 +798,7 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB split.msg 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); @@ -827,7 +809,7 @@ public class ClientServerGoParityTests await Task.Delay(10); await pub.SendAsync(part2); - var msg = await ReadUntilAsync(sub, "world"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "world"); msg.ShouldContain("MSG split.msg 1 11\r\nhello world\r\n"); } finally @@ -849,7 +831,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientAuthTimeout() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, @@ -864,7 +846,7 @@ public class ClientServerGoParityTests info.ShouldContain("\"auth_required\":true"); // Do NOT send CONNECT — wait for auth timeout - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain(NatsProtocol.ErrAuthTimeout); } finally @@ -886,7 +868,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientMaxConnections() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxConnections = 1 }); try { @@ -922,7 +904,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestClientNoEcho() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -932,7 +914,7 @@ public class ClientServerGoParityTests // Connect with echo=false, sub and pub on same connection await sock.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"echo\":false}\r\nSUB noecho 1\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // Publish on same connection await sock.SendAsync(Encoding.ASCII.GetBytes("PUB noecho 5\r\nhello\r\n")); @@ -970,7 +952,7 @@ public class ClientServerGoParityTests { // NOTE: .NET profiling endpoint is not yet implemented; ProfPort>0 logs a warning // but does not fail. This test verifies the option is accepted without error. - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, ProfPort = 6060 }); try { @@ -999,7 +981,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestStartupAndShutdown() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, NoSystemAccount = true }); try { @@ -1013,7 +995,7 @@ public class ClientServerGoParityTests info.ShouldStartWith("INFO "); await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); server.ClientCount.ShouldBe(1); } @@ -1063,7 +1045,7 @@ public class ClientServerGoParityTests [Fact] public void TestNewServer() { - var server = new NatsServer(new NatsOptions { Port = GetFreePort() }, NullLoggerFactory.Instance); + var server = new NatsServer(new NatsOptions { Port = TestPortAllocator.GetFreePort() }, NullLoggerFactory.Instance); try { server.ServerId.ShouldNotBeNullOrWhiteSpace(); @@ -1124,7 +1106,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestNoDeadlockOnSlowConsumer() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxPending = 256 }); try { @@ -1135,7 +1117,7 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB nodeadlock 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); var payload = new string('X', 200); @@ -1168,13 +1150,13 @@ public class ClientServerGoParityTests [Fact] public async Task TestServerShutdownContextTimeout() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); using var client = await ConnectRawAsync(port); await ReadLineAsync(client); await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n")); - await ReadUntilAsync(client, "PONG"); + await SocketTestHelper.ReadUntilAsync(client, "PONG"); // Shutdown should complete within 15 seconds even with active clients using var shutdownCts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); @@ -1205,7 +1187,7 @@ public class ClientServerGoParityTests opts.WriteDeadline.ShouldBe(TimeSpan.FromMilliseconds(250)); // Verify the option is honored by the running server - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, @@ -1222,7 +1204,7 @@ public class ClientServerGoParityTests await ReadLineAsync(pub); await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB wd.test 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); // Don't drain sub socket — flood to exceed MaxPending and trigger slow consumer @@ -1254,7 +1236,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestMaxPayloadVerification() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxPayload = 100 }); try { @@ -1265,7 +1247,7 @@ public class ClientServerGoParityTests var oversized = new string('A', 200); await sock.SendAsync(Encoding.ASCII.GetBytes($"PUB bigpayload {oversized.Length}\r\n{oversized}\r\n")); - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain(NatsProtocol.ErrMaxPayloadViolation); // Connection should be closed @@ -1293,7 +1275,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestMaxSubscriptions() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxSubs = 3 }); try { @@ -1304,12 +1286,12 @@ public class ClientServerGoParityTests // Subscribe 3 times (at limit) await sock.SendAsync(Encoding.ASCII.GetBytes( "SUB sub1 1\r\nSUB sub2 2\r\nSUB sub3 3\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // 4th subscription should trigger -ERR await sock.SendAsync(Encoding.ASCII.GetBytes("SUB sub4 4\r\n")); - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain(NatsProtocol.ErrMaxSubscriptionsExceeded); } finally @@ -1331,7 +1313,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestAuthorizationTimeout() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, @@ -1346,7 +1328,7 @@ public class ClientServerGoParityTests info.ShouldContain("\"auth_required\":true"); // Do not send CONNECT — wait for auth timeout - var response = await ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "-ERR", timeoutMs: 3000); response.ShouldContain(NatsProtocol.ErrAuthTimeout); } finally @@ -1373,7 +1355,7 @@ public class ClientServerGoParityTests // (TlsRateLimiter is only created when TLS is configured) var opts = new NatsOptions { - Port = GetFreePort(), + Port = TestPortAllocator.GetFreePort(), TlsRateLimit = 100, }; opts.TlsRateLimit.ShouldBe(100L); @@ -1402,8 +1384,8 @@ public class ClientServerGoParityTests [Fact] public async Task TestMonitoringPort() { - var monPort = GetFreePort(); - var natsPort = GetFreePort(); + var monPort = TestPortAllocator.GetFreePort(); + var natsPort = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = natsPort, MonitorPort = monPort }); try { @@ -1441,7 +1423,7 @@ public class ClientServerGoParityTests try { var pidFile = Path.Combine(tempDir, "nats.pid"); - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, PidFile = pidFile }); File.Exists(pidFile).ShouldBeTrue(); @@ -1472,7 +1454,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestMaxControlLine() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Default MaxControlLine is 4096 var server = await StartServerAsync(new NatsOptions { Port = port, MaxControlLine = 64 }); try @@ -1527,7 +1509,7 @@ public class ClientServerGoParityTests { // Custom MaxPayload var customMax = 512 * 1024; // 512 KB - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port, MaxPayload = customMax }); try { @@ -1554,7 +1536,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestServerShutdownWaitForClients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); var clients = new List(); @@ -1563,7 +1545,7 @@ public class ClientServerGoParityTests var sock = await ConnectRawAsync(port); await ReadLineAsync(sock); await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); clients.Add(sock); } @@ -1592,7 +1574,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestNoRaceParallelClients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -1603,7 +1585,7 @@ public class ClientServerGoParityTests var info = await ReadLineAsync(sock); info.ShouldStartWith("INFO "); await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); sock.Shutdown(SocketShutdown.Both); }).ToArray(); @@ -1628,7 +1610,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestServerListenRetry() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = await StartServerAsync(new NatsOptions { Port = port }); try { @@ -1668,7 +1650,7 @@ public class ClientServerGoParityTests [Fact] public async Task TestServerInfoWithOperatorMode() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var serverName = "test-parity-server"; var server = await StartServerAsync(new NatsOptions { Port = port, ServerName = serverName }); try diff --git a/tests/NATS.Server.Tests/ClientSlowConsumerTests.cs b/tests/NATS.Server.Core.Tests/ClientSlowConsumerTests.cs similarity index 84% rename from tests/NATS.Server.Tests/ClientSlowConsumerTests.cs rename to tests/NATS.Server.Core.Tests/ClientSlowConsumerTests.cs index 2dcedd2..f265a98 100644 --- a/tests/NATS.Server.Tests/ClientSlowConsumerTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientSlowConsumerTests.cs @@ -6,8 +6,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for slow consumer detection and client cleanup when pending bytes exceed MaxPending. @@ -15,26 +16,7 @@ namespace NATS.Server.Tests; /// public class ClientSlowConsumerTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } /// /// Slow_consumer_detected_when_pending_exceeds_limit: Creates a server with a small @@ -58,7 +40,7 @@ public class ClientSlowConsumerTests const int payloadSize = 512; // each message payload const int floodCount = 50; // enough to exceed the 1KB limit - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions @@ -81,7 +63,7 @@ public class ClientSlowConsumerTests // Subscribe to "flood" subject and confirm with PING/PONG await slowSub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB flood 1\r\nPING\r\n")); - var pong = await ReadUntilAsync(slowSub, "PONG"); + var pong = await SocketTestHelper.ReadUntilAsync(slowSub, "PONG"); pong.ShouldContain("PONG"); // Connect the publisher @@ -101,7 +83,7 @@ public class ClientSlowConsumerTests await pub.SendAsync(Encoding.ASCII.GetBytes(pubSb.ToString())); // Wait for publisher's PONG confirming all publishes were processed - await ReadUntilAsync(pub, "PONG", timeoutMs: 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", timeoutMs: 5000); // Give the server time to detect and close the slow consumer await Task.Delay(500); diff --git a/tests/NATS.Server.Tests/ClientTests.cs b/tests/NATS.Server.Core.Tests/ClientTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ClientTests.cs rename to tests/NATS.Server.Core.Tests/ClientTests.cs index d018f1d..0be1228 100644 --- a/tests/NATS.Server.Tests/ClientTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientTests.cs @@ -9,7 +9,7 @@ using NATS.Server; using NATS.Server.Auth; using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientTests : IAsyncDisposable { diff --git a/tests/NATS.Server.Tests/ClientTraceModeTests.cs b/tests/NATS.Server.Core.Tests/ClientTraceModeTests.cs similarity index 92% rename from tests/NATS.Server.Tests/ClientTraceModeTests.cs rename to tests/NATS.Server.Core.Tests/ClientTraceModeTests.cs index 20e1660..c9f998a 100644 --- a/tests/NATS.Server.Tests/ClientTraceModeTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientTraceModeTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientTraceModeTests { diff --git a/tests/NATS.Server.Tests/ClientTraceTests.cs b/tests/NATS.Server.Core.Tests/ClientTraceTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ClientTraceTests.cs rename to tests/NATS.Server.Core.Tests/ClientTraceTests.cs index 9b4ba7c..8a397a1 100644 --- a/tests/NATS.Server.Tests/ClientTraceTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientTraceTests.cs @@ -1,7 +1,7 @@ using NATS.Server; using Shouldly; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for per-client trace delivery and echo control. diff --git a/tests/NATS.Server.Tests/ClientUnsubTests.cs b/tests/NATS.Server.Core.Tests/ClientUnsubTests.cs similarity index 85% rename from tests/NATS.Server.Tests/ClientUnsubTests.cs rename to tests/NATS.Server.Core.Tests/ClientUnsubTests.cs index 6b28a65..819463b 100644 --- a/tests/NATS.Server.Tests/ClientUnsubTests.cs +++ b/tests/NATS.Server.Core.Tests/ClientUnsubTests.cs @@ -7,8 +7,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ClientUnsubTests : IAsyncLifetime { @@ -18,7 +19,7 @@ public class ClientUnsubTests : IAsyncLifetime public ClientUnsubTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -34,12 +35,6 @@ public class ClientUnsubTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private async Task ConnectAndHandshakeAsync() { @@ -53,19 +48,6 @@ public class ClientUnsubTests : IAsyncLifetime return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } /// /// Mirrors TestClientUnSub: subscribe twice, unsubscribe one sid, publish, @@ -80,17 +62,17 @@ public class ClientUnsubTests : IAsyncLifetime // Subscribe to "foo" with sid 1 and sid 2 await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nSUB foo 2\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Unsubscribe sid 1 await sub.SendAsync(Encoding.ASCII.GetBytes("UNSUB 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Publish one message to "foo" await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nHello\r\n")); // Should receive exactly one MSG for sid 2; sid 1 is gone - var response = await ReadUntilAsync(sub, "MSG foo 2 5"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "MSG foo 2 5"); response.ShouldContain("MSG foo 2 5"); response.ShouldNotContain("MSG foo 1 5"); } @@ -111,7 +93,7 @@ public class ClientUnsubTests : IAsyncLifetime // Subscribe to "foo" with sid 1, limit to 5 messages await sub.SendAsync(Encoding.ASCII.GetBytes($"SUB foo 1\r\nUNSUB 1 {maxMessages}\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Publish 10 messages var pubData = new StringBuilder(); @@ -156,7 +138,7 @@ public class ClientUnsubTests : IAsyncLifetime // Subscribe with a large max-messages limit, then immediately UNSUB without limit await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nUNSUB 1 100\r\nUNSUB 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Publish a message — subscription should already be gone await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nHello\r\n")); @@ -194,7 +176,7 @@ public class ClientUnsubTests : IAsyncLifetime // Subscribe to 3 distinct subjects await client.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nSUB bar 2\r\nSUB baz 3\r\nPING\r\n")); - await ReadUntilAsync(client, "PONG"); + await SocketTestHelper.ReadUntilAsync(client, "PONG"); // Confirm subscriptions are registered in the server's SubList _server.SubList.Count.ShouldBe(3u); diff --git a/tests/NATS.Server.Tests/Concurrency/.gitkeep b/tests/NATS.Server.Core.Tests/Concurrency/.gitkeep similarity index 100% rename from tests/NATS.Server.Tests/Concurrency/.gitkeep rename to tests/NATS.Server.Core.Tests/Concurrency/.gitkeep diff --git a/tests/NATS.Server.Tests/ConcurrencyStressTests.cs b/tests/NATS.Server.Core.Tests/ConcurrencyStressTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ConcurrencyStressTests.cs rename to tests/NATS.Server.Core.Tests/ConcurrencyStressTests.cs index 2333ac3..55cc474 100644 --- a/tests/NATS.Server.Tests/ConcurrencyStressTests.cs +++ b/tests/NATS.Server.Core.Tests/ConcurrencyStressTests.cs @@ -15,7 +15,7 @@ using NATS.Server.JetStream.Storage; using NATS.Server.Raft; using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// NORACE concurrency stress tests ported from Go's norace_test.go. diff --git a/tests/NATS.Server.Tests/ConfigIntegrationTests.cs b/tests/NATS.Server.Core.Tests/ConfigIntegrationTests.cs similarity index 98% rename from tests/NATS.Server.Tests/ConfigIntegrationTests.cs rename to tests/NATS.Server.Core.Tests/ConfigIntegrationTests.cs index 92f25f3..af7de97 100644 --- a/tests/NATS.Server.Tests/ConfigIntegrationTests.cs +++ b/tests/NATS.Server.Core.Tests/ConfigIntegrationTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ConfigIntegrationTests { diff --git a/tests/NATS.Server.Tests/ConfigProcessorTests.cs b/tests/NATS.Server.Core.Tests/ConfigProcessorTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ConfigProcessorTests.cs rename to tests/NATS.Server.Core.Tests/ConfigProcessorTests.cs index 6f7a7e5..c8353e1 100644 --- a/tests/NATS.Server.Tests/ConfigProcessorTests.cs +++ b/tests/NATS.Server.Core.Tests/ConfigProcessorTests.cs @@ -1,7 +1,7 @@ using NATS.Server; using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ConfigProcessorTests { diff --git a/tests/NATS.Server.Tests/ConfigReloadTests.cs b/tests/NATS.Server.Core.Tests/ConfigReloadTests.cs similarity index 98% rename from tests/NATS.Server.Tests/ConfigReloadTests.cs rename to tests/NATS.Server.Core.Tests/ConfigReloadTests.cs index a1d7fcd..39bd5bf 100644 --- a/tests/NATS.Server.Tests/ConfigReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/ConfigReloadTests.cs @@ -2,7 +2,7 @@ using NATS.Server; using NATS.Server.Auth; using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ConfigReloadTests { diff --git a/tests/NATS.Server.Tests/ConfigRuntimeParityTests.cs b/tests/NATS.Server.Core.Tests/ConfigRuntimeParityTests.cs similarity index 96% rename from tests/NATS.Server.Tests/ConfigRuntimeParityTests.cs rename to tests/NATS.Server.Core.Tests/ConfigRuntimeParityTests.cs index 1fb1299..3e409ac 100644 --- a/tests/NATS.Server.Tests/ConfigRuntimeParityTests.cs +++ b/tests/NATS.Server.Core.Tests/ConfigRuntimeParityTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ConfigRuntimeParityTests { diff --git a/tests/NATS.Server.Tests/Configuration/AuthChangePropagationTests.cs b/tests/NATS.Server.Core.Tests/Configuration/AuthChangePropagationTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/AuthChangePropagationTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/AuthChangePropagationTests.cs index 6e5aba6..ffb20dd 100644 --- a/tests/NATS.Server.Tests/Configuration/AuthChangePropagationTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/AuthChangePropagationTests.cs @@ -5,7 +5,7 @@ using NATS.Server.Auth; using NATS.Server.Configuration; using Shouldly; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public sealed class AuthChangePropagationTests { diff --git a/tests/NATS.Server.Tests/Configuration/AuthReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/AuthReloadTests.cs similarity index 91% rename from tests/NATS.Server.Tests/Configuration/AuthReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/AuthReloadTests.cs index 801fae9..f8a995a 100644 --- a/tests/NATS.Server.Tests/Configuration/AuthReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/AuthReloadTests.cs @@ -9,8 +9,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Tests for auth change propagation on config reload. @@ -25,13 +26,6 @@ public class AuthReloadTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task RawConnectAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -51,22 +45,6 @@ public class AuthReloadTests await sock.SendAsync(Encoding.ASCII.GetBytes(connectJson), SocketFlags.None); } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try { n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); } - catch (OperationCanceledException) { break; } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - private static void WriteConfigAndReload(NatsServer server, string configPath, string configText) { File.WriteAllText(configPath, configText); @@ -88,7 +66,7 @@ public class AuthReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-authdc-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with no auth File.WriteAllText(configPath, $"port: {port}\ndebug: false"); @@ -107,7 +85,7 @@ public class AuthReloadTests // Send a PING to confirm the connection is established await sock.SendAsync("PING\r\n"u8.ToArray(), SocketFlags.None); - var pong = await ReadUntilAsync(sock, "PONG", timeoutMs: 3000); + var pong = await SocketTestHelper.ReadUntilAsync(sock, "PONG", timeoutMs: 3000); pong.ShouldContain("PONG"); server.ClientCount.ShouldBeGreaterThanOrEqualTo(1); @@ -146,7 +124,7 @@ public class AuthReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-credchg-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with user/password auth File.WriteAllText(configPath, @@ -167,7 +145,7 @@ public class AuthReloadTests // Verify connection works await sock.SendAsync("PING\r\n"u8.ToArray(), SocketFlags.None); - var pong = await ReadUntilAsync(sock, "PONG", timeoutMs: 3000); + var pong = await SocketTestHelper.ReadUntilAsync(sock, "PONG", timeoutMs: 3000); pong.ShouldContain("PONG"); // Change the password via reload @@ -202,7 +180,7 @@ public class AuthReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-authoff-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with auth enabled File.WriteAllText(configPath, @@ -266,7 +244,7 @@ public class AuthReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-newauth-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with no auth File.WriteAllText(configPath, $"port: {port}\ndebug: false"); @@ -326,7 +304,7 @@ public class AuthReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-noauth-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {port}\ndebug: false"); var options = new NatsOptions { ConfigFile = configPath, Port = port }; @@ -341,7 +319,7 @@ public class AuthReloadTests using var sock = await RawConnectAsync(port); await SendConnectAsync(sock); await sock.SendAsync("PING\r\n"u8.ToArray(), SocketFlags.None); - var pong = await ReadUntilAsync(sock, "PONG", timeoutMs: 3000); + var pong = await SocketTestHelper.ReadUntilAsync(sock, "PONG", timeoutMs: 3000); pong.ShouldContain("PONG"); var countBefore = server.ClientCount; @@ -357,7 +335,7 @@ public class AuthReloadTests // Client should still be responsive await sock.SendAsync("PING\r\n"u8.ToArray(), SocketFlags.None); - var pong2 = await ReadUntilAsync(sock, "PONG", timeoutMs: 3000); + var pong2 = await SocketTestHelper.ReadUntilAsync(sock, "PONG", timeoutMs: 3000); pong2.ShouldContain("PONG"); } finally diff --git a/tests/NATS.Server.Tests/Configuration/ClusterConfigReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/ClusterConfigReloadTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/ClusterConfigReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ClusterConfigReloadTests.cs index 85fac95..f6a6884 100644 --- a/tests/NATS.Server.Tests/Configuration/ClusterConfigReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ClusterConfigReloadTests.cs @@ -5,7 +5,7 @@ using NATS.Server; using NATS.Server.Configuration; using Shouldly; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public class ClusterConfigReloadTests { diff --git a/tests/NATS.Server.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs b/tests/NATS.Server.Core.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs index 2e7f4ef..c2ca8e1 100644 --- a/tests/NATS.Server.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ConfigPedanticParityBatch1Tests.cs @@ -1,7 +1,7 @@ using System.Reflection; using NATS.Server.Configuration; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public class ConfigPedanticParityBatch1Tests { diff --git a/tests/NATS.Server.Tests/Configuration/ConfigReloadAdvancedTests.cs b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadAdvancedTests.cs similarity index 95% rename from tests/NATS.Server.Tests/Configuration/ConfigReloadAdvancedTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ConfigReloadAdvancedTests.cs index 3e55c1e..11596d1 100644 --- a/tests/NATS.Server.Tests/Configuration/ConfigReloadAdvancedTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadAdvancedTests.cs @@ -12,8 +12,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Advanced configuration model and hot-reload tests ported from Go's opts_test.go @@ -25,13 +26,6 @@ public class ConfigReloadAdvancedTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task RawConnectAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -41,22 +35,6 @@ public class ConfigReloadAdvancedTests return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try { n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); } - catch (OperationCanceledException) { break; } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - private static void WriteConfigAndReload(NatsServer server, string configPath, string configText) { File.WriteAllText(configPath, configText); @@ -66,7 +44,7 @@ public class ConfigReloadAdvancedTests private static async Task<(NatsServer server, int port, CancellationTokenSource cts, string configPath)> StartServerWithConfigAsync(string configContent) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-adv-{Guid.NewGuid():N}.conf"); var finalContent = configContent.Replace("{PORT}", port.ToString()); File.WriteAllText(configPath, finalContent); @@ -545,7 +523,7 @@ public class ConfigReloadAdvancedTests [Fact] public async Task Reload_adding_cluster_block_rejected() { - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync("port: {PORT}"); try { diff --git a/tests/NATS.Server.Tests/Configuration/ConfigReloadExtendedParityTests.cs b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadExtendedParityTests.cs similarity index 97% rename from tests/NATS.Server.Tests/Configuration/ConfigReloadExtendedParityTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ConfigReloadExtendedParityTests.cs index bd34f93..9fae441 100644 --- a/tests/NATS.Server.Tests/Configuration/ConfigReloadExtendedParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadExtendedParityTests.cs @@ -12,8 +12,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Extended parity tests for config hot reload behaviour ported from Go's @@ -24,13 +25,6 @@ public class ConfigReloadExtendedParityTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task RawConnectAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -40,28 +34,6 @@ public class ConfigReloadExtendedParityTests return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try - { - n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); - } - catch (OperationCanceledException) - { - break; - } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - private static void WriteConfigAndReload(NatsServer server, string configPath, string configText) { File.WriteAllText(configPath, configText); @@ -71,7 +43,7 @@ public class ConfigReloadExtendedParityTests private static async Task<(NatsServer server, int port, CancellationTokenSource cts, string configPath)> StartServerWithConfigAsync(string configContent) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-reload-{Guid.NewGuid():N}.conf"); var finalContent = configContent.Replace("{PORT}", port.ToString()); File.WriteAllText(configPath, finalContent); @@ -112,7 +84,7 @@ public class ConfigReloadExtendedParityTests [Fact] public async Task Reload_without_config_file_throws() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var options = new NatsOptions { Port = port }; var server = new NatsServer(options, NullLoggerFactory.Instance); var cts = new CancellationTokenSource(); @@ -142,7 +114,7 @@ public class ConfigReloadExtendedParityTests var (server, port, cts, configPath) = await StartServerWithConfigAsync("port: {PORT}"); try { - var newPort = GetFreePort(); + var newPort = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {newPort}"); Should.Throw(() => server.ReloadConfigOrThrow()) .Message.ShouldContain("Port"); @@ -633,11 +605,11 @@ public class ConfigReloadExtendedParityTests await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false,\"pedantic\":false}\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n"), SocketFlags.None); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n"), SocketFlags.None); - var response = await ReadUntilAsync(sock, "PONG"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); response.ShouldContain("MSG foo"); WriteConfigAndReload(server, configPath, $"port: {port}\nmax_payload: 2"); @@ -645,7 +617,7 @@ public class ConfigReloadExtendedParityTests using var sock2 = await RawConnectAsync(port); await sock2.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false,\"pedantic\":false}\r\n"), SocketFlags.None); await sock2.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\n"), SocketFlags.None); - var errResponse = await ReadUntilAsync(sock2, "-ERR", timeoutMs: 5000); + var errResponse = await SocketTestHelper.ReadUntilAsync(sock2, "-ERR", timeoutMs: 5000); errResponse.ShouldContain("-ERR"); } finally @@ -983,7 +955,7 @@ public class ConfigReloadExtendedParityTests using var c2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await c2.ConnectAsync(IPAddress.Loopback, port); - var response = await ReadUntilAsync(c2, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(c2, "-ERR", timeoutMs: 5000); response.ShouldContain("maximum connections exceeded"); WriteConfigAndReload(server, configPath, $"port: {port}\nmax_connections: 10"); @@ -1016,7 +988,7 @@ public class ConfigReloadExtendedParityTests using var c4 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await c4.ConnectAsync(IPAddress.Loopback, port); - var response = await ReadUntilAsync(c4, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(c4, "-ERR", timeoutMs: 5000); response.ShouldContain("maximum connections exceeded"); } finally @@ -1309,12 +1281,12 @@ public class ConfigReloadExtendedParityTests [Fact] public async Task Reload_cluster_port_change_rejected() { - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ncluster {{\n host: 127.0.0.1\n port: {clusterPort}\n}}"); try { - var newClusterPort = GetFreePort(); + var newClusterPort = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {port}\ncluster {{\n host: 127.0.0.1\n port: {newClusterPort}\n}}"); Should.Throw(() => server.ReloadConfigOrThrow()) @@ -1383,7 +1355,7 @@ public class ConfigReloadExtendedParityTests [Fact] public async Task Reload_cli_overrides_preserved() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-cli-{Guid.NewGuid():N}.conf"); File.WriteAllText(configPath, $"port: {port}\ndebug: false"); @@ -1734,7 +1706,7 @@ public class ConfigReloadExtendedParityTests var (server, port, cts, configPath) = await StartServerWithConfigAsync("port: {PORT}"); try { - var monPort = GetFreePort(); + var monPort = TestPortAllocator.GetFreePort(); WriteConfigAndReload(server, configPath, $"port: {port}\nhttp_port: {monPort}"); await using var client = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{port}" }); @@ -1756,7 +1728,7 @@ public class ConfigReloadExtendedParityTests var (server, port, cts, configPath) = await StartServerWithConfigAsync("port: {PORT}"); try { - var profPort = GetFreePort(); + var profPort = TestPortAllocator.GetFreePort(); WriteConfigAndReload(server, configPath, $"port: {port}\nprof_port: {profPort}"); await using var client = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{port}" }); diff --git a/tests/NATS.Server.Tests/Configuration/ConfigReloadParityTests.cs b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadParityTests.cs similarity index 90% rename from tests/NATS.Server.Tests/Configuration/ConfigReloadParityTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ConfigReloadParityTests.cs index 6183176..3997cdf 100644 --- a/tests/NATS.Server.Tests/Configuration/ConfigReloadParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ConfigReloadParityTests.cs @@ -9,8 +9,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Parity tests for config hot reload behaviour. @@ -23,16 +24,9 @@ public class ConfigReloadParityTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task<(NatsServer server, int port, CancellationTokenSource cts)> StartServerAsync(NatsOptions options) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); options.Port = port; var server = new NatsServer(options, NullLoggerFactory.Instance); var cts = new CancellationTokenSource(); @@ -60,28 +54,6 @@ public class ConfigReloadParityTests /// Reads from until the accumulated response contains /// or the timeout elapses. /// - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try - { - n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); - } - catch (OperationCanceledException) - { - break; - } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - /// /// Writes a config file, then calls . /// Mirrors the pattern from JetStreamClusterReloadTests. @@ -115,7 +87,7 @@ public class ConfigReloadParityTests // Allocate a port first so we can embed it in the config file. // The server will bind to this port; the config file must match // to avoid a non-reloadable Port-change error on reload. - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with no connection limit. File.WriteAllText(configPath, $"port: {port}\nmax_connections: 65536"); @@ -144,7 +116,7 @@ public class ConfigReloadParityTests await c3.ConnectAsync(IPAddress.Loopback, port); // The server sends INFO then immediately -ERR and closes the socket. - var response = await ReadUntilAsync(c3, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(c3, "-ERR", timeoutMs: 5000); response.ShouldContain("maximum connections exceeded"); } finally @@ -175,7 +147,7 @@ public class ConfigReloadParityTests { // Allocate a port and embed it in every config write to prevent a // non-reloadable Port-change error when the config file is updated. - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with no authentication required. File.WriteAllText(configPath, $"port: {port}\ndebug: false"); @@ -252,7 +224,7 @@ public class ConfigReloadParityTests { // Allocate a port and embed it in every config write to prevent a // non-reloadable Port-change error when the config file is updated. - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); // Start with debug disabled. File.WriteAllText(configPath, $"port: {port}\ndebug: false"); diff --git a/tests/NATS.Server.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs b/tests/NATS.Server.Core.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs similarity index 95% rename from tests/NATS.Server.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs index 1b06069..c5f639f 100644 --- a/tests/NATS.Server.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ConfigWarningsParityBatch1Tests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public class ConfigWarningsParityBatch1Tests { diff --git a/tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/JetStreamConfigReloadTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/JetStreamConfigReloadTests.cs index 8b3d348..83d4073 100644 --- a/tests/NATS.Server.Tests/Configuration/JetStreamConfigReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/JetStreamConfigReloadTests.cs @@ -4,7 +4,7 @@ using NATS.Server.Configuration; using Shouldly; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public sealed class JetStreamConfigReloadTests { diff --git a/tests/NATS.Server.Tests/Configuration/LoggingReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/LoggingReloadTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/LoggingReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/LoggingReloadTests.cs index 6d3a270..9002b0d 100644 --- a/tests/NATS.Server.Tests/Configuration/LoggingReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/LoggingReloadTests.cs @@ -5,7 +5,7 @@ using NATS.Server; using NATS.Server.Configuration; using Shouldly; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public class LoggingReloadTests { diff --git a/tests/NATS.Server.Tests/Configuration/OptsGoParityTests.cs b/tests/NATS.Server.Core.Tests/Configuration/OptsGoParityTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/OptsGoParityTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/OptsGoParityTests.cs index e6f9a29..2ca8cfe 100644 --- a/tests/NATS.Server.Tests/Configuration/OptsGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/OptsGoParityTests.cs @@ -6,7 +6,7 @@ using System.Net.Sockets; using NATS.Server.Auth; using NATS.Server.Configuration; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Parity tests ported from Go server/opts_test.go that exercise config parsing, diff --git a/tests/NATS.Server.Tests/Configuration/ReloadGoParityTests.cs b/tests/NATS.Server.Core.Tests/Configuration/ReloadGoParityTests.cs similarity index 95% rename from tests/NATS.Server.Tests/Configuration/ReloadGoParityTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/ReloadGoParityTests.cs index d58fd2e..d72151d 100644 --- a/tests/NATS.Server.Tests/Configuration/ReloadGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/ReloadGoParityTests.cs @@ -15,8 +15,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Go-parity tests for config hot-reload behaviour ported from Go's reload_test.go. @@ -28,13 +29,6 @@ public class ReloadGoParityTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task RawConnectAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -44,28 +38,6 @@ public class ReloadGoParityTests return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try - { - n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); - } - catch (OperationCanceledException) - { - break; - } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - private static void WriteConfigAndReload(NatsServer server, string configPath, string configText) { File.WriteAllText(configPath, configText); @@ -75,7 +47,7 @@ public class ReloadGoParityTests private static async Task<(NatsServer server, int port, CancellationTokenSource cts, string configPath)> StartServerWithConfigAsync(string configContent) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-reload-goparity-{Guid.NewGuid():N}.conf"); var finalContent = configContent.Replace("{PORT}", port.ToString()); File.WriteAllText(configPath, finalContent); @@ -130,7 +102,7 @@ public class ReloadGoParityTests // A third connection should be rejected. using var c3 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await c3.ConnectAsync(IPAddress.Loopback, port); - var response = await ReadUntilAsync(c3, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(c3, "-ERR", timeoutMs: 5000); response.ShouldContain("maximum connections exceeded"); } finally @@ -167,7 +139,7 @@ public class ReloadGoParityTests // A third connection should be rejected (limit=2, count=2). using var c3reject = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await c3reject.ConnectAsync(IPAddress.Loopback, port); - var r1 = await ReadUntilAsync(c3reject, "-ERR", timeoutMs: 5000); + var r1 = await SocketTestHelper.ReadUntilAsync(c3reject, "-ERR", timeoutMs: 5000); r1.ShouldContain("maximum connections exceeded"); // Increase limit to 10. @@ -203,11 +175,11 @@ public class ReloadGoParityTests await sock.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false,\"pedantic\":false}\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n"), SocketFlags.None); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); await sock.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\n"), SocketFlags.None); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n"), SocketFlags.None); - var beforeResponse = await ReadUntilAsync(sock, "PONG"); + var beforeResponse = await SocketTestHelper.ReadUntilAsync(sock, "PONG"); beforeResponse.ShouldContain("MSG foo"); // Reduce max_payload to 2 bytes. @@ -217,7 +189,7 @@ public class ReloadGoParityTests using var sock2 = await RawConnectAsync(port); await sock2.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false,\"pedantic\":false}\r\n"), SocketFlags.None); await sock2.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nhello\r\n"), SocketFlags.None); - var errResponse = await ReadUntilAsync(sock2, "-ERR", timeoutMs: 5000); + var errResponse = await SocketTestHelper.ReadUntilAsync(sock2, "-ERR", timeoutMs: 5000); errResponse.ShouldContain("-ERR"); } finally @@ -379,12 +351,12 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_ClusterAuthorization_ChangedClusterIsRejected() { // Go: TestConfigReloadEnableClusterAuthorization (reload_test.go:1411) - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ncluster {{\n name: mycluster\n host: 127.0.0.1\n port: {clusterPort}\n}}"); try { - var newClusterPort = GetFreePort(); + var newClusterPort = TestPortAllocator.GetFreePort(); // Changing cluster host/port must be rejected. File.WriteAllText(configPath, $"port: {port}\ncluster {{\n name: mycluster\n host: 127.0.0.1\n port: {newClusterPort}\n}}"); @@ -413,12 +385,12 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_ClusterRoutes_ChangeIsRejected() { // Go: TestConfigReloadClusterRoutes (reload_test.go:1586) - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ncluster {{\n name: testcluster\n host: 127.0.0.1\n port: {clusterPort}\n}}"); try { - var otherPort = GetFreePort(); + var otherPort = TestPortAllocator.GetFreePort(); // Adding routes also changes the cluster block. File.WriteAllText(configPath, $"port: {port}\ncluster {{\n name: testcluster\n host: 127.0.0.1\n port: {clusterPort}\n routes: [\"nats://127.0.0.1:{otherPort}\"]\n}}"); @@ -583,7 +555,7 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_RoutePool_ClusterBlockIsImmutable() { // Go: TestConfigReloadRoutePoolAndPerAccount (reload_test.go:5148) - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ncluster {{\n name: local\n host: 127.0.0.1\n port: {clusterPort}\n pool_size: 3\n}}"); try @@ -612,7 +584,7 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_PerAccountRoutes_ClusterBlockIsImmutable() { // Go: TestConfigReloadRoutePoolAndPerAccount (reload_test.go:5148) - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\naccounts {{\n A {{ users: [{{user: u1, password: pwd}}] }}\n}}\ncluster {{\n name: local\n host: 127.0.0.1\n port: {clusterPort}\n}}"); try @@ -640,7 +612,7 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_RouteCompression_ClusterBlockIsImmutable() { // Go: TestConfigReloadRouteCompression (reload_test.go:5877) - var clusterPort = GetFreePort(); + var clusterPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ncluster {{\n name: local\n host: 127.0.0.1\n port: {clusterPort}\n}}"); try @@ -782,7 +754,7 @@ public class ReloadGoParityTests { // Go: TestConfigReloadNoPanicOnShutdown (reload_test.go:6358) var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-shutdown-{Guid.NewGuid():N}.conf"); - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); try { @@ -984,7 +956,7 @@ public class ReloadGoParityTests // Go: TestConfigReloadNotPreventedByGateways (reload_test.go:3445) // Note: server_name is non-reloadable, so we do not include it to avoid // conflicts between the parsed config value and the Options default. - var gatewayPort = GetFreePort(); + var gatewayPort = TestPortAllocator.GetFreePort(); var (server, port, cts, configPath) = await StartServerWithConfigAsync( $"port: {{PORT}}\ngateway {{\n name: local\n port: {gatewayPort}\n}}"); try @@ -1013,7 +985,7 @@ public class ReloadGoParityTests public async Task ReloadGoParityTests_BoolFlags_CliOverridePreserved() { // Go: TestConfigReloadBoolFlags (reload_test.go:3480) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-boolflags-{Guid.NewGuid():N}.conf"); File.WriteAllText(configPath, $"port: {port}\ndebug: false"); diff --git a/tests/NATS.Server.Tests/Configuration/SignalReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/SignalReloadTests.cs similarity index 92% rename from tests/NATS.Server.Tests/Configuration/SignalReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/SignalReloadTests.cs index b1aff41..1313ca1 100644 --- a/tests/NATS.Server.Tests/Configuration/SignalReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/SignalReloadTests.cs @@ -8,8 +8,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; /// /// Tests for SIGHUP-triggered config reload and the ConfigReloader async API. @@ -24,13 +25,6 @@ public class SignalReloadTests { // ─── Helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task RawConnectAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -40,22 +34,6 @@ public class SignalReloadTests return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected, StringComparison.Ordinal)) - { - int n; - try { n = await sock.ReceiveAsync(buf, SocketFlags.None, cts.Token); } - catch (OperationCanceledException) { break; } - if (n == 0) break; - sb.Append(Encoding.ASCII.GetString(buf, 0, n)); - } - return sb.ToString(); - } - private static void WriteConfigAndReload(NatsServer server, string configPath, string configText) { File.WriteAllText(configPath, configText); @@ -77,7 +55,7 @@ public class SignalReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-sighup-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {port}\ndebug: false"); var options = new NatsOptions { ConfigFile = configPath, Port = port }; @@ -291,7 +269,7 @@ public class SignalReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-e2e-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {port}\nmax_connections: 65536"); var options = new NatsOptions { ConfigFile = configPath, Port = port }; @@ -312,7 +290,7 @@ public class SignalReloadTests // New connection should be rejected using var c2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await c2.ConnectAsync(IPAddress.Loopback, port); - var response = await ReadUntilAsync(c2, "-ERR", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(c2, "-ERR", timeoutMs: 5000); response.ShouldContain("maximum connections exceeded"); } finally @@ -336,7 +314,7 @@ public class SignalReloadTests var configPath = Path.Combine(Path.GetTempPath(), $"natsdotnet-throw-{Guid.NewGuid():N}.conf"); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); File.WriteAllText(configPath, $"port: {port}\nserver_name: original"); var options = new NatsOptions { ConfigFile = configPath, Port = port, ServerName = "original" }; diff --git a/tests/NATS.Server.Tests/Configuration/TlsReloadTests.cs b/tests/NATS.Server.Core.Tests/Configuration/TlsReloadTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Configuration/TlsReloadTests.cs rename to tests/NATS.Server.Core.Tests/Configuration/TlsReloadTests.cs index 920b32a..5087fa7 100644 --- a/tests/NATS.Server.Tests/Configuration/TlsReloadTests.cs +++ b/tests/NATS.Server.Core.Tests/Configuration/TlsReloadTests.cs @@ -8,7 +8,7 @@ using System.Security.Cryptography.X509Certificates; using NATS.Server.Configuration; using NATS.Server.Tls; -namespace NATS.Server.Tests.Configuration; +namespace NATS.Server.Core.Tests.Configuration; public class TlsReloadTests { diff --git a/tests/NATS.Server.Tests/DifferencesParityClosureTests.cs b/tests/NATS.Server.Core.Tests/DifferencesParityClosureTests.cs similarity index 98% rename from tests/NATS.Server.Tests/DifferencesParityClosureTests.cs rename to tests/NATS.Server.Core.Tests/DifferencesParityClosureTests.cs index bb54da8..7cf89c9 100644 --- a/tests/NATS.Server.Tests/DifferencesParityClosureTests.cs +++ b/tests/NATS.Server.Core.Tests/DifferencesParityClosureTests.cs @@ -1,6 +1,6 @@ using NATS.Server.TestUtilities.Parity; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class DifferencesParityClosureTests { diff --git a/tests/NATS.Server.Tests/FlushCoalescingTests.cs b/tests/NATS.Server.Core.Tests/FlushCoalescingTests.cs similarity index 97% rename from tests/NATS.Server.Tests/FlushCoalescingTests.cs rename to tests/NATS.Server.Core.Tests/FlushCoalescingTests.cs index cad0176..8a38d2c 100644 --- a/tests/NATS.Server.Tests/FlushCoalescingTests.cs +++ b/tests/NATS.Server.Core.Tests/FlushCoalescingTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; // Go reference: server/client.go (maxFlushPending, pcd, flush signal coalescing) diff --git a/tests/NATS.Server.Tests/GoParityRunnerTests.cs b/tests/NATS.Server.Core.Tests/GoParityRunnerTests.cs similarity index 93% rename from tests/NATS.Server.Tests/GoParityRunnerTests.cs rename to tests/NATS.Server.Core.Tests/GoParityRunnerTests.cs index 534130f..06e6406 100644 --- a/tests/NATS.Server.Tests/GoParityRunnerTests.cs +++ b/tests/NATS.Server.Core.Tests/GoParityRunnerTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class GoParityRunnerTests { diff --git a/tests/NATS.Server.Tests/InfrastructureGoParityTests.cs b/tests/NATS.Server.Core.Tests/InfrastructureGoParityTests.cs similarity index 97% rename from tests/NATS.Server.Tests/InfrastructureGoParityTests.cs rename to tests/NATS.Server.Core.Tests/InfrastructureGoParityTests.cs index 851c693..ad88596 100644 --- a/tests/NATS.Server.Tests/InfrastructureGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/InfrastructureGoParityTests.cs @@ -33,8 +33,9 @@ using NATS.Server.Configuration; using NATS.Server.Protocol; using NATS.Server.Subscriptions; using Serilog; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Infrastructure parity tests covering parser utilities, logging, error wrapping, @@ -44,26 +45,7 @@ public class InfrastructureGoParityTests { // ─── helpers ───────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task> ParseCommandsAsync(string input) { @@ -416,7 +398,7 @@ public class InfrastructureGoParityTests public void Log_server_reopen_log_file_callback_is_invocable() { // Go: TestReOpenLogFile (log_test.go:84) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); @@ -818,7 +800,7 @@ public class InfrastructureGoParityTests public async Task NKey_info_json_contains_nonce_when_nkeys_configured() { // Go: TestServerInfoNonceAlwaysEnabled (nkey_test.go:58) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var kp = KeyPair.CreatePair(PrefixByte.User); @@ -835,7 +817,7 @@ public class InfrastructureGoParityTests using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); sock.Dispose(); await cts.CancelAsync(); @@ -852,7 +834,7 @@ public class InfrastructureGoParityTests public async Task Ping_server_sends_ping_at_interval() { // Go: TestPing (ping_test.go:34) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer(new NatsOptions { @@ -866,14 +848,14 @@ public class InfrastructureGoParityTests var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await conn.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn, "\r\n"); // consume INFO + await SocketTestHelper.ReadUntilAsync(conn, "\r\n"); // consume INFO // Establish the connection await conn.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(conn, "PONG"); // initial PONG + await SocketTestHelper.ReadUntilAsync(conn, "PONG"); // initial PONG // Wait for the server to send a PING - var received = await ReadUntilAsync(conn, "PING", 2000); + var received = await SocketTestHelper.ReadUntilAsync(conn, "PING", 2000); received.ShouldContain("PING"); // Reply with PONG to keep alive diff --git a/tests/NATS.Server.Tests/IntegrationTests.cs b/tests/NATS.Server.Core.Tests/IntegrationTests.cs similarity index 92% rename from tests/NATS.Server.Tests/IntegrationTests.cs rename to tests/NATS.Server.Core.Tests/IntegrationTests.cs index 0117ac9..f7e0a39 100644 --- a/tests/NATS.Server.Tests/IntegrationTests.cs +++ b/tests/NATS.Server.Core.Tests/IntegrationTests.cs @@ -4,8 +4,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Client.Core; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class IntegrationTests : IAsyncLifetime { @@ -16,7 +17,7 @@ public class IntegrationTests : IAsyncLifetime public IntegrationTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -32,12 +33,6 @@ public class IntegrationTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private NatsConnection CreateClient() { diff --git a/tests/NATS.Server.Tests/Internal/Avl/.gitkeep b/tests/NATS.Server.Core.Tests/Internal/Avl/.gitkeep similarity index 100% rename from tests/NATS.Server.Tests/Internal/Avl/.gitkeep rename to tests/NATS.Server.Core.Tests/Internal/Avl/.gitkeep diff --git a/tests/NATS.Server.Tests/Internal/Avl/SequenceSetTests.cs b/tests/NATS.Server.Core.Tests/Internal/Avl/SequenceSetTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/Avl/SequenceSetTests.cs rename to tests/NATS.Server.Core.Tests/Internal/Avl/SequenceSetTests.cs index 3e82efe..6d63b68 100644 --- a/tests/NATS.Server.Tests/Internal/Avl/SequenceSetTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/Avl/SequenceSetTests.cs @@ -14,7 +14,7 @@ using System.Diagnostics; using NATS.Server.Internal.Avl; -namespace NATS.Server.Tests.Internal.Avl; +namespace NATS.Server.Core.Tests.Internal.Avl; /// /// Tests for the AVL-backed SequenceSet, ported from Go server/avl/seqset_test.go diff --git a/tests/NATS.Server.Tests/Internal/Gsl/.gitkeep b/tests/NATS.Server.Core.Tests/Internal/Gsl/.gitkeep similarity index 100% rename from tests/NATS.Server.Tests/Internal/Gsl/.gitkeep rename to tests/NATS.Server.Core.Tests/Internal/Gsl/.gitkeep diff --git a/tests/NATS.Server.Tests/Internal/Gsl/GenericSubjectListTests.cs b/tests/NATS.Server.Core.Tests/Internal/Gsl/GenericSubjectListTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/Gsl/GenericSubjectListTests.cs rename to tests/NATS.Server.Core.Tests/Internal/Gsl/GenericSubjectListTests.cs index ac15c99..110dcc1 100644 --- a/tests/NATS.Server.Tests/Internal/Gsl/GenericSubjectListTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/Gsl/GenericSubjectListTests.cs @@ -3,7 +3,7 @@ using NATS.Server.Internal.Gsl; -namespace NATS.Server.Tests.Internal.Gsl; +namespace NATS.Server.Core.Tests.Internal.Gsl; public class GenericSubjectListTests { diff --git a/tests/NATS.Server.Tests/Internal/InternalDsParityBatch2Tests.cs b/tests/NATS.Server.Core.Tests/Internal/InternalDsParityBatch2Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Internal/InternalDsParityBatch2Tests.cs rename to tests/NATS.Server.Core.Tests/Internal/InternalDsParityBatch2Tests.cs index 08503fb..878e977 100644 --- a/tests/NATS.Server.Tests/Internal/InternalDsParityBatch2Tests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/InternalDsParityBatch2Tests.cs @@ -5,7 +5,7 @@ using NATS.Server.Internal.SubjectTree; using NATS.Server.Internal.SysMem; using NATS.Server.Internal.TimeHashWheel; -namespace NATS.Server.Tests.Internal; +namespace NATS.Server.Core.Tests.Internal; public class InternalDsParityBatch2Tests { diff --git a/tests/NATS.Server.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs b/tests/NATS.Server.Core.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs similarity index 96% rename from tests/NATS.Server.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs rename to tests/NATS.Server.Core.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs index 8206b43..3e60343 100644 --- a/tests/NATS.Server.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/InternalDsPeriodicSamplerParityTests.cs @@ -2,7 +2,7 @@ using System.Reflection; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Monitoring; -namespace NATS.Server.Tests.Internal; +namespace NATS.Server.Core.Tests.Internal; public class InternalDsPeriodicSamplerParityTests { diff --git a/tests/NATS.Server.Tests/Internal/MessageTraceContextTests.cs b/tests/NATS.Server.Core.Tests/Internal/MessageTraceContextTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/MessageTraceContextTests.cs rename to tests/NATS.Server.Core.Tests/Internal/MessageTraceContextTests.cs index aa6ec17..50cd7d4 100644 --- a/tests/NATS.Server.Tests/Internal/MessageTraceContextTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/MessageTraceContextTests.cs @@ -3,7 +3,7 @@ using System.Text.Json; using NATS.Server.Events; using NATS.Server.Internal; -namespace NATS.Server.Tests.Internal; +namespace NATS.Server.Core.Tests.Internal; /// /// Tests for MsgTraceContext: header parsing, event collection, trace propagation, diff --git a/tests/NATS.Server.Tests/Internal/SubjectTree/.gitkeep b/tests/NATS.Server.Core.Tests/Internal/SubjectTree/.gitkeep similarity index 100% rename from tests/NATS.Server.Tests/Internal/SubjectTree/.gitkeep rename to tests/NATS.Server.Core.Tests/Internal/SubjectTree/.gitkeep diff --git a/tests/NATS.Server.Tests/Internal/SubjectTree/SubjectTreeTests.cs b/tests/NATS.Server.Core.Tests/Internal/SubjectTree/SubjectTreeTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/SubjectTree/SubjectTreeTests.cs rename to tests/NATS.Server.Core.Tests/Internal/SubjectTree/SubjectTreeTests.cs index 7060da8..00c6542 100644 --- a/tests/NATS.Server.Tests/Internal/SubjectTree/SubjectTreeTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/SubjectTree/SubjectTreeTests.cs @@ -3,7 +3,7 @@ using System.Security.Cryptography; using System.Text; using NATS.Server.Internal.SubjectTree; -namespace NATS.Server.Tests.Internal.SubjectTree; +namespace NATS.Server.Core.Tests.Internal.SubjectTree; /// /// Tests for the Adaptive Radix Tree (ART) based SubjectTree. diff --git a/tests/NATS.Server.Tests/Internal/TimeHashWheel/.gitkeep b/tests/NATS.Server.Core.Tests/Internal/TimeHashWheel/.gitkeep similarity index 100% rename from tests/NATS.Server.Tests/Internal/TimeHashWheel/.gitkeep rename to tests/NATS.Server.Core.Tests/Internal/TimeHashWheel/.gitkeep diff --git a/tests/NATS.Server.Tests/Internal/TimeHashWheel/HashWheelTests.cs b/tests/NATS.Server.Core.Tests/Internal/TimeHashWheel/HashWheelTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/TimeHashWheel/HashWheelTests.cs rename to tests/NATS.Server.Core.Tests/Internal/TimeHashWheel/HashWheelTests.cs index 3eb2b64..5984590 100644 --- a/tests/NATS.Server.Tests/Internal/TimeHashWheel/HashWheelTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/TimeHashWheel/HashWheelTests.cs @@ -2,7 +2,7 @@ using NATS.Server.Internal.TimeHashWheel; -namespace NATS.Server.Tests.Internal.TimeHashWheel; +namespace NATS.Server.Core.Tests.Internal.TimeHashWheel; public class HashWheelTests { diff --git a/tests/NATS.Server.Tests/Internal/TraceContextPropagationTests.cs b/tests/NATS.Server.Core.Tests/Internal/TraceContextPropagationTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Internal/TraceContextPropagationTests.cs rename to tests/NATS.Server.Core.Tests/Internal/TraceContextPropagationTests.cs index 7db8ba9..898df88 100644 --- a/tests/NATS.Server.Tests/Internal/TraceContextPropagationTests.cs +++ b/tests/NATS.Server.Core.Tests/Internal/TraceContextPropagationTests.cs @@ -1,7 +1,7 @@ using System.Text; using NATS.Server.Internal; -namespace NATS.Server.Tests.Internal; +namespace NATS.Server.Core.Tests.Internal; /// /// Tests for TraceContextPropagator: trace creation, header injection/extraction, diff --git a/tests/NATS.Server.Tests/InternalClientTests.cs b/tests/NATS.Server.Core.Tests/InternalClientTests.cs similarity index 98% rename from tests/NATS.Server.Tests/InternalClientTests.cs rename to tests/NATS.Server.Core.Tests/InternalClientTests.cs index 6a068c4..08385d3 100644 --- a/tests/NATS.Server.Tests/InternalClientTests.cs +++ b/tests/NATS.Server.Core.Tests/InternalClientTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Auth; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class InternalClientTests { diff --git a/tests/NATS.Server.Tests/LoggingTests.cs b/tests/NATS.Server.Core.Tests/LoggingTests.cs similarity index 97% rename from tests/NATS.Server.Tests/LoggingTests.cs rename to tests/NATS.Server.Core.Tests/LoggingTests.cs index d1daf6d..a817e86 100644 --- a/tests/NATS.Server.Tests/LoggingTests.cs +++ b/tests/NATS.Server.Core.Tests/LoggingTests.cs @@ -1,6 +1,6 @@ using Serilog; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class LoggingTests : IDisposable { diff --git a/tests/NATS.Server.Tests/MessageTraceTests.cs b/tests/NATS.Server.Core.Tests/MessageTraceTests.cs similarity index 93% rename from tests/NATS.Server.Tests/MessageTraceTests.cs rename to tests/NATS.Server.Core.Tests/MessageTraceTests.cs index a6fdacc..1924b1e 100644 --- a/tests/NATS.Server.Tests/MessageTraceTests.cs +++ b/tests/NATS.Server.Core.Tests/MessageTraceTests.cs @@ -15,8 +15,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; using NATS.Server.Protocol; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for message trace infrastructure: MessageTraceContext population, @@ -33,7 +34,7 @@ public class MessageTraceTests : IAsyncLifetime public MessageTraceTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -49,32 +50,13 @@ public class MessageTraceTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ConnectWithHeadersAsync(string? clientName = null, string? lang = null, string? version = null) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, _port); - await ReadUntilAsync(sock, "\r\n"); // discard INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // discard INFO var connectJson = BuildConnectJson(headers: true, name: clientName, lang: lang, version: version); await sock.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {connectJson}\r\n")); @@ -300,7 +282,7 @@ public class MessageTraceTests : IAsyncLifetime await sub.SendAsync(Encoding.ASCII.GetBytes("SUB trace.test 1\r\n")); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Build HPUB with Nats-Trace-Dest header // Header block: "NATS/1.0\r\nNats-Trace-Dest: trace.inbox\r\n\r\n" @@ -312,7 +294,7 @@ public class MessageTraceTests : IAsyncLifetime var hpub = $"HPUB trace.test {hdrLen} {totalLen}\r\n{headerBlock}{payload}\r\n"; await pub.SendAsync(Encoding.ASCII.GetBytes(hpub)); - var received = await ReadUntilAsync(sub, "Nats-Trace-Dest"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "Nats-Trace-Dest"); received.ShouldContain("HMSG trace.test"); received.ShouldContain("Nats-Trace-Dest: trace.inbox"); @@ -333,7 +315,7 @@ public class MessageTraceTests : IAsyncLifetime // Subscribe to wildcard await sub.SendAsync(Encoding.ASCII.GetBytes("SUB trace.* 1\r\n")); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); const string headerBlock = "NATS/1.0\r\nNats-Trace-Dest: t.inbox.1\r\n\r\n"; const string payload = "wildcard-msg"; @@ -343,7 +325,7 @@ public class MessageTraceTests : IAsyncLifetime var hpub = $"HPUB trace.subject {hdrLen} {totalLen}\r\n{headerBlock}{payload}\r\n"; await pub.SendAsync(Encoding.ASCII.GetBytes(hpub)); - var received = await ReadUntilAsync(sub, "Nats-Trace-Dest"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "Nats-Trace-Dest"); received.ShouldContain("HMSG trace.subject"); received.ShouldContain("Nats-Trace-Dest: t.inbox.1"); @@ -364,7 +346,7 @@ public class MessageTraceTests : IAsyncLifetime // Queue group subscription await qsub.SendAsync(Encoding.ASCII.GetBytes("SUB trace.q workers 1\r\n")); await qsub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(qsub, "PONG"); + await SocketTestHelper.ReadUntilAsync(qsub, "PONG"); const string headerBlock = "NATS/1.0\r\nNats-Trace-Dest: qg.trace\r\n\r\n"; const string payload = "queued"; @@ -374,7 +356,7 @@ public class MessageTraceTests : IAsyncLifetime var hpub = $"HPUB trace.q {hdrLen} {totalLen}\r\n{headerBlock}{payload}\r\n"; await pub.SendAsync(Encoding.ASCII.GetBytes(hpub)); - var received = await ReadUntilAsync(qsub, "Nats-Trace-Dest"); + var received = await SocketTestHelper.ReadUntilAsync(qsub, "Nats-Trace-Dest"); received.ShouldContain("Nats-Trace-Dest: qg.trace"); received.ShouldContain("queued"); @@ -393,7 +375,7 @@ public class MessageTraceTests : IAsyncLifetime await sub.SendAsync(Encoding.ASCII.GetBytes("SUB multi.hdr 1\r\n")); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); const string headerBlock = "NATS/1.0\r\n" + @@ -408,7 +390,7 @@ public class MessageTraceTests : IAsyncLifetime var hpub = $"HPUB multi.hdr {hdrLen} {totalLen}\r\n{headerBlock}{payload}\r\n"; await pub.SendAsync(Encoding.ASCII.GetBytes(hpub)); - var received = await ReadUntilAsync(sub, "X-Priority"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "X-Priority"); received.ShouldContain("X-Request-Id: req-99"); received.ShouldContain("Nats-Trace-Dest: t.multi"); @@ -429,7 +411,7 @@ public class MessageTraceTests : IAsyncLifetime await sub.SendAsync(Encoding.ASCII.GetBytes("SUB trace.long 1\r\n")); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); var longId = new string('a', 256); var headerBlock = $"NATS/1.0\r\nNats-Trace-Dest: {longId}\r\n\r\n"; @@ -440,7 +422,7 @@ public class MessageTraceTests : IAsyncLifetime var hpub = $"HPUB trace.long {hdrLen} {totalLen}\r\n{headerBlock}{payload}\r\n"; await pub.SendAsync(Encoding.ASCII.GetBytes(hpub)); - var received = await ReadUntilAsync(sub, longId); + var received = await SocketTestHelper.ReadUntilAsync(sub, longId); received.ShouldContain(longId); } @@ -494,7 +476,7 @@ public class MessageTraceTests : IAsyncLifetime [Fact] public async Task Server_with_trace_enabled_starts_and_accepts_connections() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer(new NatsOptions { Port = port, Trace = true }, NullLoggerFactory.Instance); _ = server.StartAsync(cts.Token); @@ -502,7 +484,7 @@ public class MessageTraceTests : IAsyncLifetime using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldStartWith("INFO "); diff --git a/tests/NATS.Server.Tests/MsgTraceGoParityTests.cs b/tests/NATS.Server.Core.Tests/MsgTraceGoParityTests.cs similarity index 92% rename from tests/NATS.Server.Tests/MsgTraceGoParityTests.cs rename to tests/NATS.Server.Core.Tests/MsgTraceGoParityTests.cs index 00aa7e7..8307a50 100644 --- a/tests/NATS.Server.Tests/MsgTraceGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/MsgTraceGoParityTests.cs @@ -17,7 +17,7 @@ using NATS.Server.Monitoring; using NATS.Server.Protocol; using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Go parity tests for message trace header infrastructure and closed-connection @@ -32,7 +32,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task InitializeAsync() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); _ = _server.StartAsync(_cts.Token); await _server.WaitForReadyAsync(); @@ -46,32 +46,13 @@ public class MsgTraceGoParityTests : IAsyncLifetime // ─── helpers ──────────────────────────────────────────────────────────── - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ConnectClientAsync(bool headers = true) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, _port); - await ReadUntilAsync(sock, "\r\n"); // consume INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // consume INFO var connectJson = headers ? "{\"verbose\":false,\"headers\":true}" : "{\"verbose\":false}"; @@ -260,7 +241,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime using var pub = await ConnectClientAsync(); await sub.SendAsync("SUB trace.test 1\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); const string hdrBlock = "NATS/1.0\r\nNats-Trace-Dest: trace.inbox\r\n\r\n"; const string payload = "hello"; @@ -270,7 +251,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB trace.test {hdrLen} {totalLen}\r\n{hdrBlock}{payload}\r\n")); - var received = await ReadUntilAsync(sub, "Nats-Trace-Dest"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "Nats-Trace-Dest"); received.ShouldContain("HMSG trace.test"); received.ShouldContain("Nats-Trace-Dest: trace.inbox"); @@ -289,7 +270,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime using var pub = await ConnectClientAsync(); await sub.SendAsync("SUB trace.* 1\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); const string hdrBlock = "NATS/1.0\r\nNats-Trace-Dest: t.inbox.1\r\n\r\n"; const string payload = "wildcard-msg"; @@ -299,7 +280,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB trace.subject {hdrLen} {totalLen}\r\n{hdrBlock}{payload}\r\n")); - var received = await ReadUntilAsync(sub, "Nats-Trace-Dest"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "Nats-Trace-Dest"); received.ShouldContain("HMSG trace.subject"); received.ShouldContain("Nats-Trace-Dest: t.inbox.1"); @@ -319,7 +300,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime // Subscribe via a queue group await qsub.SendAsync("SUB trace.q workers 1\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(qsub, "PONG"); + await SocketTestHelper.ReadUntilAsync(qsub, "PONG"); const string hdrBlock = "NATS/1.0\r\nNats-Trace-Dest: qg.trace\r\n\r\n"; const string payload = "queued"; @@ -330,7 +311,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB trace.q {hdrLen} {totalLen}\r\n{hdrBlock}{payload}\r\n")); - var received = await ReadUntilAsync(qsub, "Nats-Trace-Dest", 3000); + var received = await SocketTestHelper.ReadUntilAsync(qsub, "Nats-Trace-Dest", 3000); received.ShouldContain("Nats-Trace-Dest: qg.trace"); received.ShouldContain("queued"); @@ -348,7 +329,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime using var pub = await ConnectClientAsync(); await sub.SendAsync("SUB multi.hdr 1\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); const string hdrBlock = "NATS/1.0\r\n" + @@ -363,7 +344,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB multi.hdr {hdrLen} {totalLen}\r\n{hdrBlock}{payload}\r\n")); - var received = await ReadUntilAsync(sub, "X-Priority"); + var received = await SocketTestHelper.ReadUntilAsync(sub, "X-Priority"); received.ShouldContain("X-Request-Id: req-99"); received.ShouldContain("Nats-Trace-Dest: t.multi"); @@ -414,7 +395,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task MsgTrace_server_with_trace_enabled_starts_and_accepts_connections() { // Go: TestMsgTraceBasic (msgtrace_test.go:172) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions { Port = port, Trace = true }, NullLoggerFactory.Instance); @@ -423,7 +404,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var info = await ReadUntilAsync(sock, "\r\n"); + var info = await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); info.ShouldStartWith("INFO "); await cts.CancelAsync(); @@ -494,7 +475,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime // Do a full handshake so the client is accepted await sock.SendAsync("PING\r\n"u8.ToArray()); - await ReadUntilAsync(sock, "PONG"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG"); // Close the connection sock.Close(); @@ -520,7 +501,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime { // Go: TestClosedConnsAccounting (closed_conns_test.go:46) // Build a server with a tiny ring buffer - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions { Port = port, MaxClosedClients = 5 }, @@ -533,9 +514,9 @@ public class MsgTraceGoParityTests : IAsyncLifetime { using var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await s.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(s, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(s, "\r\n"); // INFO await s.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nPING\r\n")); - await ReadUntilAsync(s, "PONG"); + await SocketTestHelper.ReadUntilAsync(s, "PONG"); s.Close(); // brief pause to let server process await Task.Delay(5); @@ -587,7 +568,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task ClosedConns_max_payload_close_reason_tracked() { // Go: TestClosedMaxPayload (closed_conns_test.go:219) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions { Port = port, MaxPayload = 100 }, @@ -597,11 +578,11 @@ public class MsgTraceGoParityTests : IAsyncLifetime var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await conn.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn, "\r\n"); // INFO // Establish connection first await conn.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(conn, "PONG"); + await SocketTestHelper.ReadUntilAsync(conn, "PONG"); // Send a PUB with payload > MaxPayload (200 bytes > 100 byte limit) // Must include the full payload so the parser yields the command to NatsClient @@ -638,7 +619,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task ClosedConns_auth_timeout_close_reason_tracked() { // Go: TestClosedAuthorizationTimeout (closed_conns_test.go:143) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions @@ -654,7 +635,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime // Just connect without sending CONNECT — auth timeout fires var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await conn.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn, "\r\n"); // INFO // Don't send CONNECT — wait for auth timeout var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); @@ -682,7 +663,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task ClosedConns_auth_violation_close_reason_tracked() { // Go: TestClosedAuthorizationViolation (closed_conns_test.go:164) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions { Port = port, Authorization = "correct_token" }, @@ -693,13 +674,13 @@ public class MsgTraceGoParityTests : IAsyncLifetime // Connect with wrong token var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await conn.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn, "\r\n"); // INFO await conn.SendAsync( "CONNECT {\"verbose\":false,\"auth_token\":\"wrong_token\"}\r\nPING\r\n"u8.ToArray()); // Wait for close and error response - await ReadUntilAsync(conn, "-ERR", 2000); + await SocketTestHelper.ReadUntilAsync(conn, "-ERR", 2000); conn.Dispose(); var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); @@ -727,7 +708,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime public async Task ClosedConns_up_auth_violation_close_reason_tracked() { // Go: TestClosedUPAuthorizationViolation (closed_conns_test.go:187) - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions @@ -746,19 +727,19 @@ public class MsgTraceGoParityTests : IAsyncLifetime using (var conn1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { await conn1.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn1, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn1, "\r\n"); // INFO await conn1.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(conn1, "-ERR", 2000); + await SocketTestHelper.ReadUntilAsync(conn1, "-ERR", 2000); } // Wrong password using (var conn2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { await conn2.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn2, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn2, "\r\n"); // INFO await conn2.SendAsync( "CONNECT {\"verbose\":false,\"user\":\"my_user\",\"pass\":\"wrong_pass\"}\r\nPING\r\n"u8.ToArray()); - await ReadUntilAsync(conn2, "-ERR", 2000); + await SocketTestHelper.ReadUntilAsync(conn2, "-ERR", 2000); } var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); @@ -788,7 +769,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime var (certPath, keyPath) = TestCertHelper.GenerateTestCertFiles(); try { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); using var server = new NatsServer( new NatsOptions @@ -807,9 +788,9 @@ public class MsgTraceGoParityTests : IAsyncLifetime using (var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { await conn.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(conn, "\r\n"); // INFO + await SocketTestHelper.ReadUntilAsync(conn, "\r\n"); // INFO await conn.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray()); - _ = await ReadUntilAsync(conn, "-ERR", 1000); + _ = await SocketTestHelper.ReadUntilAsync(conn, "-ERR", 1000); } var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); diff --git a/tests/NATS.Server.Tests/NATS.Server.Tests.csproj b/tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj similarity index 85% rename from tests/NATS.Server.Tests/NATS.Server.Tests.csproj rename to tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj index f773018..c52ff2a 100644 --- a/tests/NATS.Server.Tests/NATS.Server.Tests.csproj +++ b/tests/NATS.Server.Core.Tests/NATS.Server.Core.Tests.csproj @@ -2,7 +2,6 @@ false - $(DefineConstants);JETSTREAM_INTEGRATION_MATRIX @@ -13,7 +12,6 @@ - @@ -22,7 +20,7 @@ - + diff --git a/tests/NATS.Server.Tests/NatsConfLexerTests.cs b/tests/NATS.Server.Core.Tests/NatsConfLexerTests.cs similarity index 99% rename from tests/NATS.Server.Tests/NatsConfLexerTests.cs rename to tests/NATS.Server.Core.Tests/NatsConfLexerTests.cs index e67378c..8421d2a 100644 --- a/tests/NATS.Server.Tests/NatsConfLexerTests.cs +++ b/tests/NATS.Server.Core.Tests/NatsConfLexerTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class NatsConfLexerTests { diff --git a/tests/NATS.Server.Tests/NatsConfParserTests.cs b/tests/NATS.Server.Core.Tests/NatsConfParserTests.cs similarity index 99% rename from tests/NATS.Server.Tests/NatsConfParserTests.cs rename to tests/NATS.Server.Core.Tests/NatsConfParserTests.cs index bb7bb5d..9a0dfe7 100644 --- a/tests/NATS.Server.Tests/NatsConfParserTests.cs +++ b/tests/NATS.Server.Core.Tests/NatsConfParserTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class NatsConfParserTests { diff --git a/tests/NATS.Server.Tests/NatsHeaderParserTests.cs b/tests/NATS.Server.Core.Tests/NatsHeaderParserTests.cs similarity index 97% rename from tests/NATS.Server.Tests/NatsHeaderParserTests.cs rename to tests/NATS.Server.Core.Tests/NatsHeaderParserTests.cs index 6e6cb1f..e669c77 100644 --- a/tests/NATS.Server.Tests/NatsHeaderParserTests.cs +++ b/tests/NATS.Server.Core.Tests/NatsHeaderParserTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class NatsHeaderParserTests { diff --git a/tests/NATS.Server.Tests/NatsOptionsTests.cs b/tests/NATS.Server.Core.Tests/NatsOptionsTests.cs similarity index 97% rename from tests/NATS.Server.Tests/NatsOptionsTests.cs rename to tests/NATS.Server.Core.Tests/NatsOptionsTests.cs index 91d79c0..9f63e26 100644 --- a/tests/NATS.Server.Tests/NatsOptionsTests.cs +++ b/tests/NATS.Server.Core.Tests/NatsOptionsTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class NatsOptionsTests { diff --git a/tests/NATS.Server.Tests/NoRespondersTests.cs b/tests/NATS.Server.Core.Tests/NoRespondersTests.cs similarity index 69% rename from tests/NATS.Server.Tests/NoRespondersTests.cs rename to tests/NATS.Server.Core.Tests/NoRespondersTests.cs index 356ebcb..961f319 100644 --- a/tests/NATS.Server.Tests/NoRespondersTests.cs +++ b/tests/NATS.Server.Core.Tests/NoRespondersTests.cs @@ -3,8 +3,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class NoRespondersTests : IAsyncLifetime { @@ -14,7 +15,7 @@ public class NoRespondersTests : IAsyncLifetime public NoRespondersTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -30,12 +31,6 @@ public class NoRespondersTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private async Task ConnectClientAsync() { @@ -44,19 +39,6 @@ public class NoRespondersTests : IAsyncLifetime return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } [Fact] public async Task NoResponders_without_headers_closes_connection() @@ -64,14 +46,14 @@ public class NoRespondersTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT with no_responders:true but headers:false await client.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"no_responders\":true,\"headers\":false}\r\n")); // Should receive -ERR and connection should close - var response = await ReadUntilAsync(client, "-ERR"); + var response = await SocketTestHelper.ReadUntilAsync(client, "-ERR"); response.ShouldContain("-ERR"); response.ShouldContain("No Responders Requires Headers Support"); } @@ -82,14 +64,14 @@ public class NoRespondersTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT with both no_responders and headers true, then PING await client.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"no_responders\":true,\"headers\":true}\r\nPING\r\n")); // Should receive PONG (connection stays alive) - var response = await ReadUntilAsync(client, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); response.ShouldContain("PONG\r\n"); } @@ -99,7 +81,7 @@ public class NoRespondersTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // CONNECT with no_responders and headers enabled // SUB to the reply inbox so we can receive the 503 @@ -110,7 +92,7 @@ public class NoRespondersTests : IAsyncLifetime "PUB no.subscribers _INBOX.reply 5\r\nHello\r\n")); // Should receive HMSG with 503 status on the reply subject - var response = await ReadUntilAsync(client, "HMSG"); + var response = await SocketTestHelper.ReadUntilAsync(client, "HMSG"); response.ShouldContain("HMSG _INBOX.reply 1"); response.ShouldContain("503"); } diff --git a/tests/NATS.Server.Tests/Parity/JetStreamParityTruthMatrixTests.cs b/tests/NATS.Server.Core.Tests/Parity/JetStreamParityTruthMatrixTests.cs similarity index 94% rename from tests/NATS.Server.Tests/Parity/JetStreamParityTruthMatrixTests.cs rename to tests/NATS.Server.Core.Tests/Parity/JetStreamParityTruthMatrixTests.cs index 60ac342..df93623 100644 --- a/tests/NATS.Server.Tests/Parity/JetStreamParityTruthMatrixTests.cs +++ b/tests/NATS.Server.Core.Tests/Parity/JetStreamParityTruthMatrixTests.cs @@ -1,6 +1,6 @@ using NATS.Server.TestUtilities.Parity; -namespace NATS.Server.Tests.Parity; +namespace NATS.Server.Core.Tests.Parity; public class JetStreamParityTruthMatrixTests { diff --git a/tests/NATS.Server.Tests/Parity/NatsStrictCapabilityInventoryTests.cs b/tests/NATS.Server.Core.Tests/Parity/NatsStrictCapabilityInventoryTests.cs similarity index 90% rename from tests/NATS.Server.Tests/Parity/NatsStrictCapabilityInventoryTests.cs rename to tests/NATS.Server.Core.Tests/Parity/NatsStrictCapabilityInventoryTests.cs index 445ad59..2bad8f6 100644 --- a/tests/NATS.Server.Tests/Parity/NatsStrictCapabilityInventoryTests.cs +++ b/tests/NATS.Server.Core.Tests/Parity/NatsStrictCapabilityInventoryTests.cs @@ -1,6 +1,6 @@ using NATS.Server.TestUtilities.Parity; -namespace NATS.Server.Tests.Parity; +namespace NATS.Server.Core.Tests.Parity; public class NatsStrictCapabilityInventoryTests { diff --git a/tests/NATS.Server.Tests/ParserTests.cs b/tests/NATS.Server.Core.Tests/ParserTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ParserTests.cs rename to tests/NATS.Server.Core.Tests/ParserTests.cs index ad8716a..b9accb9 100644 --- a/tests/NATS.Server.Tests/ParserTests.cs +++ b/tests/NATS.Server.Core.Tests/ParserTests.cs @@ -3,7 +3,7 @@ using System.IO.Pipelines; using System.Text; using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ParserTests { diff --git a/tests/NATS.Server.Tests/Protocol/ClientProtocolGoParityTests.cs b/tests/NATS.Server.Core.Tests/Protocol/ClientProtocolGoParityTests.cs similarity index 94% rename from tests/NATS.Server.Tests/Protocol/ClientProtocolGoParityTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/ClientProtocolGoParityTests.cs index 6857077..8e0691e 100644 --- a/tests/NATS.Server.Tests/Protocol/ClientProtocolGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/ClientProtocolGoParityTests.cs @@ -12,8 +12,9 @@ using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; using NATS.Server.Protocol; using NATS.Server.Subscriptions; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Go parity tests ported from client_test.go for protocol-level behaviors @@ -26,28 +27,6 @@ public class ClientProtocolGoParityTests // Helpers (self-contained per project conventions) // --------------------------------------------------------------------------- - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[8192]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ReadAllAvailableAsync(Socket sock, int timeoutMs = 1000) { using var cts = new CancellationTokenSource(timeoutMs); @@ -73,7 +52,7 @@ public class ClientProtocolGoParityTests private static async Task<(NatsServer Server, int Port, CancellationTokenSource Cts)> StartServerAsync(NatsOptions? options = null) { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); options ??= new NatsOptions(); options.Port = port; var cts = new CancellationTokenSource(); @@ -87,7 +66,7 @@ public class ClientProtocolGoParityTests { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - await ReadUntilAsync(sock, "\r\n"); // drain INFO + await SocketTestHelper.ReadUntilAsync(sock, "\r\n"); // drain INFO await sock.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {connectJson}\r\n")); return sock; } @@ -96,7 +75,7 @@ public class ClientProtocolGoParityTests { var sock = await ConnectAndHandshakeAsync(port, connectJson); await sock.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); return sock; } @@ -119,17 +98,17 @@ public class ClientProtocolGoParityTests using var pub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // HPUB foo 12 14\r\nName:Derek\r\nOK\r\n // Header block: "Name:Derek\r\n" = 12 bytes // Payload: "OK" = 2 bytes -> total = 14 await pub.SendAsync(Encoding.ASCII.GetBytes("HPUB foo 12 14\r\nName:Derek\r\nOK\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Non-header subscriber should get a plain MSG with only the payload (2 bytes: "OK") response.ShouldContain("MSG foo 1 2\r\n"); @@ -162,14 +141,14 @@ public class ClientProtocolGoParityTests // Queue subscription: SUB foo bar 1 await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo bar 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("HPUB foo 12 14\r\nName:Derek\r\nOK\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Queue subscriber without headers should get MSG with only payload response.ShouldContain("MSG foo 1 2\r\n"); @@ -241,7 +220,7 @@ public class ClientProtocolGoParityTests // (they're embedded in a comma-delimited token), so they are literal var subj = "foo.bar,*,>,baz"; await sock.SendAsync(Encoding.ASCII.GetBytes($"SUB {subj} 1\r\nPUB {subj} 3\r\nmsg\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain($"MSG {subj} 1 3\r\n"); response.ShouldContain("msg\r\n"); @@ -343,7 +322,7 @@ public class ClientProtocolGoParityTests using var pub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Publish a message with headers var hdr = "NATS/1.0\r\nA: 1\r\nB: 2\r\n\r\n"; @@ -352,10 +331,10 @@ public class ClientProtocolGoParityTests await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB foo {hdr.Length} {totalLen}\r\n{hdr}{payload}\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("HMSG foo 1"); response.ShouldContain("Hello Traced"); @@ -383,7 +362,7 @@ public class ClientProtocolGoParityTests using var pub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); var hdr = "NATS/1.0\r\nFoo: bar\r\nBaz: qux\r\n\r\n"; var payload = "data"; @@ -391,10 +370,10 @@ public class ClientProtocolGoParityTests await pub.SendAsync(Encoding.ASCII.GetBytes( $"HPUB foo {hdr.Length} {totalLen}\r\n{hdr}{payload}\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); response.ShouldContain("HMSG foo 1"); response.ShouldContain("NATS/1.0"); @@ -452,7 +431,7 @@ public class ClientProtocolGoParityTests // First sub should succeed await sock.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - var r1 = await ReadUntilAsync(sock, "PONG\r\n"); + var r1 = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); r1.ShouldNotContain("-ERR"); // Second sub should exceed the limit @@ -771,7 +750,7 @@ public class ClientProtocolGoParityTests // Attempt to publish to an NRG subject await sock.SendAsync(Encoding.ASCII.GetBytes("PUB $NRG.foo 0\r\n\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n", timeoutMs: 5000); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n", timeoutMs: 5000); // The server should reject this with a permissions violation // (In Go, non-system clients get a publish permission error for $NRG.*) @@ -800,14 +779,14 @@ public class ClientProtocolGoParityTests using var pub = await ConnectAndPingAsync(port, "{\"headers\":true}"); await sub.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes("HPUB foo 12 14\r\nName:Derek\r\nOK\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - await ReadUntilAsync(pub, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(pub, "PONG\r\n"); await sub.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var response = await ReadUntilAsync(sub, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sub, "PONG\r\n"); // Header-aware subscriber should get HMSG with full headers response.ShouldContain("HMSG foo 1 12 14\r\n"); @@ -841,14 +820,14 @@ public class ClientProtocolGoParityTests for (int i = 0; i < 2; i++) { await sock.SendAsync(Encoding.ASCII.GetBytes($"SUB {subj} {i + 1}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes($"PUB {subj} 3\r\nmsg\r\nPING\r\n")); - var response = await ReadUntilAsync(sock, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); response.ShouldContain($"MSG {subj} {i + 1} 3\r\n"); await sock.SendAsync(Encoding.ASCII.GetBytes($"UNSUB {i + 1}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG\r\n"); + await SocketTestHelper.ReadUntilAsync(sock, "PONG\r\n"); } } finally diff --git a/tests/NATS.Server.Tests/Protocol/InterServerOpcodeRoutingTests.cs b/tests/NATS.Server.Core.Tests/Protocol/InterServerOpcodeRoutingTests.cs similarity index 91% rename from tests/NATS.Server.Tests/Protocol/InterServerOpcodeRoutingTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/InterServerOpcodeRoutingTests.cs index 1e1b2c0..eb1d3de 100644 --- a/tests/NATS.Server.Tests/Protocol/InterServerOpcodeRoutingTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/InterServerOpcodeRoutingTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class InterServerOpcodeRoutingTests { diff --git a/tests/NATS.Server.Tests/Protocol/MessageTraceInitializationTests.cs b/tests/NATS.Server.Core.Tests/Protocol/MessageTraceInitializationTests.cs similarity index 94% rename from tests/NATS.Server.Tests/Protocol/MessageTraceInitializationTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/MessageTraceInitializationTests.cs index cf94b1d..f981bb2 100644 --- a/tests/NATS.Server.Tests/Protocol/MessageTraceInitializationTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/MessageTraceInitializationTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class MessageTraceInitializationTests { diff --git a/tests/NATS.Server.Tests/Protocol/ProtoWireParityTests.cs b/tests/NATS.Server.Core.Tests/Protocol/ProtoWireParityTests.cs similarity index 98% rename from tests/NATS.Server.Tests/Protocol/ProtoWireParityTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/ProtoWireParityTests.cs index 44e1ca6..2795634 100644 --- a/tests/NATS.Server.Tests/Protocol/ProtoWireParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/ProtoWireParityTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Protocol; -namespace NATS.Server.Tests.ProtocolParity; +namespace NATS.Server.Core.Tests.ProtocolParity; public class ProtoWireParityTests { diff --git a/tests/NATS.Server.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs b/tests/NATS.Server.Core.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs similarity index 98% rename from tests/NATS.Server.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs index a21c89b..11ae8a4 100644 --- a/tests/NATS.Server.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/ProtocolDefaultConstantsGapParityTests.cs @@ -1,7 +1,7 @@ using NATS.Server; using NATS.Server.Protocol; -namespace NATS.Server.Tests.ProtocolParity; +namespace NATS.Server.Core.Tests.ProtocolParity; public class ProtocolDefaultConstantsGapParityTests { diff --git a/tests/NATS.Server.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs b/tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs similarity index 96% rename from tests/NATS.Server.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs index 63283ae..f2831f4 100644 --- a/tests/NATS.Server.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/ProtocolParserSnippetGapParityTests.cs @@ -2,7 +2,7 @@ using System.Buffers; using System.Text; using NATS.Server.Protocol; -namespace NATS.Server.Tests.ProtocolParity; +namespace NATS.Server.Core.Tests.ProtocolParity; public class ProtocolParserSnippetGapParityTests { diff --git a/tests/NATS.Server.Tests/Protocol/ProxyProtocolTests.cs b/tests/NATS.Server.Core.Tests/Protocol/ProxyProtocolTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Protocol/ProxyProtocolTests.cs rename to tests/NATS.Server.Core.Tests/Protocol/ProxyProtocolTests.cs index d2a89ee..78f234d 100644 --- a/tests/NATS.Server.Tests/Protocol/ProxyProtocolTests.cs +++ b/tests/NATS.Server.Core.Tests/Protocol/ProxyProtocolTests.cs @@ -8,7 +8,7 @@ using System.Net; using System.Text; using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// PROXY protocol v1/v2 parser tests. diff --git a/tests/NATS.Server.Tests/ResponseRoutingTests.cs b/tests/NATS.Server.Core.Tests/ResponseRoutingTests.cs similarity index 99% rename from tests/NATS.Server.Tests/ResponseRoutingTests.cs rename to tests/NATS.Server.Core.Tests/ResponseRoutingTests.cs index 33badf8..73e70ef 100644 --- a/tests/NATS.Server.Tests/ResponseRoutingTests.cs +++ b/tests/NATS.Server.Core.Tests/ResponseRoutingTests.cs @@ -1,7 +1,7 @@ using NATS.Server.Auth; using NATS.Server.Imports; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ResponseRoutingTests { diff --git a/tests/NATS.Server.Tests/ResponseTrackerTests.cs b/tests/NATS.Server.Core.Tests/ResponseTrackerTests.cs similarity index 97% rename from tests/NATS.Server.Tests/ResponseTrackerTests.cs rename to tests/NATS.Server.Core.Tests/ResponseTrackerTests.cs index b5758dc..1eccff5 100644 --- a/tests/NATS.Server.Tests/ResponseTrackerTests.cs +++ b/tests/NATS.Server.Core.Tests/ResponseTrackerTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Auth; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ResponseTrackerTests { diff --git a/tests/NATS.Server.Tests/RttTests.cs b/tests/NATS.Server.Core.Tests/RttTests.cs similarity index 91% rename from tests/NATS.Server.Tests/RttTests.cs rename to tests/NATS.Server.Core.Tests/RttTests.cs index 9632cf5..210119e 100644 --- a/tests/NATS.Server.Tests/RttTests.cs +++ b/tests/NATS.Server.Core.Tests/RttTests.cs @@ -3,8 +3,9 @@ using System.Net.Http.Json; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Monitoring; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class RttTests : IAsyncLifetime { @@ -16,8 +17,8 @@ public class RttTests : IAsyncLifetime public RttTests() { - _natsPort = GetFreePort(); - _monitorPort = GetFreePort(); + _natsPort = TestPortAllocator.GetFreePort(); + _monitorPort = TestPortAllocator.GetFreePort(); _server = new NatsServer( new NatsOptions { @@ -114,10 +115,4 @@ public class RttTests : IAsyncLifetime response.StatusCode.ShouldBe(System.Net.HttpStatusCode.OK); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } } diff --git a/tests/NATS.Server.Tests/Server/AcceptLoopErrorCallbackTests.cs b/tests/NATS.Server.Core.Tests/Server/AcceptLoopErrorCallbackTests.cs similarity index 97% rename from tests/NATS.Server.Tests/Server/AcceptLoopErrorCallbackTests.cs rename to tests/NATS.Server.Core.Tests/Server/AcceptLoopErrorCallbackTests.cs index 3c72b60..9a6b84e 100644 --- a/tests/NATS.Server.Tests/Server/AcceptLoopErrorCallbackTests.cs +++ b/tests/NATS.Server.Core.Tests/Server/AcceptLoopErrorCallbackTests.cs @@ -3,7 +3,7 @@ using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Server; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class AcceptLoopErrorCallbackTests { diff --git a/tests/NATS.Server.Tests/Server/AcceptLoopReloadLockTests.cs b/tests/NATS.Server.Core.Tests/Server/AcceptLoopReloadLockTests.cs similarity index 98% rename from tests/NATS.Server.Tests/Server/AcceptLoopReloadLockTests.cs rename to tests/NATS.Server.Core.Tests/Server/AcceptLoopReloadLockTests.cs index 63c9e00..03e6562 100644 --- a/tests/NATS.Server.Tests/Server/AcceptLoopReloadLockTests.cs +++ b/tests/NATS.Server.Core.Tests/Server/AcceptLoopReloadLockTests.cs @@ -2,7 +2,7 @@ using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class AcceptLoopReloadLockTests { diff --git a/tests/NATS.Server.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs b/tests/NATS.Server.Core.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs rename to tests/NATS.Server.Core.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs index fad0bf4..72bdd57 100644 --- a/tests/NATS.Server.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs +++ b/tests/NATS.Server.Core.Tests/Server/CoreServerClientAccessorsParityBatch2Tests.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Auth; using NATS.Server.Protocol; -namespace NATS.Server.Tests.Server; +namespace NATS.Server.Core.Tests.Server; public class CoreServerClientAccessorsParityBatch2Tests { diff --git a/tests/NATS.Server.Tests/Server/CoreServerGapParityTests.cs b/tests/NATS.Server.Core.Tests/Server/CoreServerGapParityTests.cs similarity index 95% rename from tests/NATS.Server.Tests/Server/CoreServerGapParityTests.cs rename to tests/NATS.Server.Core.Tests/Server/CoreServerGapParityTests.cs index 8aaa0c9..bb4c6fe 100644 --- a/tests/NATS.Server.Tests/Server/CoreServerGapParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Server/CoreServerGapParityTests.cs @@ -4,8 +4,9 @@ using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; using NATS.Server.Configuration; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Server; +namespace NATS.Server.Core.Tests.Server; public class CoreServerGapParityTests { @@ -166,7 +167,7 @@ public class CoreServerGapParityTests [Fact] public async Task DisconnectClientByID_closes_connected_client() { - 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); @@ -185,7 +186,7 @@ public class CoreServerGapParityTests [Fact] public async Task LDMClientByID_closes_connected_client() { - 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); @@ -252,20 +253,6 @@ public class CoreServerGapParityTests return builder.ToString(); } - private static int GetFreePort() - { - var listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - try - { - return ((IPEndPoint)listener.LocalEndpoint).Port; - } - finally - { - listener.Stop(); - } - } - private static async Task WaitUntilAsync(Func predicate) { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); diff --git a/tests/NATS.Server.Tests/Server/CoreServerOptionsParityBatch3Tests.cs b/tests/NATS.Server.Core.Tests/Server/CoreServerOptionsParityBatch3Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Server/CoreServerOptionsParityBatch3Tests.cs rename to tests/NATS.Server.Core.Tests/Server/CoreServerOptionsParityBatch3Tests.cs index e94b390..90622dc 100644 --- a/tests/NATS.Server.Tests/Server/CoreServerOptionsParityBatch3Tests.cs +++ b/tests/NATS.Server.Core.Tests/Server/CoreServerOptionsParityBatch3Tests.cs @@ -1,6 +1,6 @@ using NATS.Server.Server; -namespace NATS.Server.Tests.Server; +namespace NATS.Server.Core.Tests.Server; public class CoreServerOptionsParityBatch3Tests { diff --git a/tests/NATS.Server.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs b/tests/NATS.Server.Core.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs similarity index 97% rename from tests/NATS.Server.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs rename to tests/NATS.Server.Core.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs index 1def72a..f53a8a5 100644 --- a/tests/NATS.Server.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs +++ b/tests/NATS.Server.Core.Tests/Server/UtilitiesAndRateCounterParityBatch1Tests.cs @@ -2,7 +2,7 @@ using System.Net.Sockets; using NATS.Server.Routes; using NATS.Server.Server; -namespace NATS.Server.Tests.Server; +namespace NATS.Server.Core.Tests.Server; public class UtilitiesAndRateCounterParityBatch1Tests { diff --git a/tests/NATS.Server.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs b/tests/NATS.Server.Core.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs similarity index 97% rename from tests/NATS.Server.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs rename to tests/NATS.Server.Core.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs index 793d643..fc7ee53 100644 --- a/tests/NATS.Server.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs +++ b/tests/NATS.Server.Core.Tests/Server/UtilitiesErrorConstantsParityBatch2Tests.cs @@ -1,6 +1,6 @@ using NATS.Server.Server; -namespace NATS.Server.Tests.Server; +namespace NATS.Server.Core.Tests.Server; public class UtilitiesErrorConstantsParityBatch2Tests { diff --git a/tests/NATS.Server.Tests/ServerConfigTests.cs b/tests/NATS.Server.Core.Tests/ServerConfigTests.cs similarity index 82% rename from tests/NATS.Server.Tests/ServerConfigTests.cs rename to tests/NATS.Server.Core.Tests/ServerConfigTests.cs index ab0ef52..943d5d8 100644 --- a/tests/NATS.Server.Tests/ServerConfigTests.cs +++ b/tests/NATS.Server.Core.Tests/ServerConfigTests.cs @@ -3,34 +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; +namespace NATS.Server.Core.Tests; // Tests ported from Go server_test.go: // TestRandomPorts, TestInfoServerNameDefaultsToPK, TestInfoServerNameIsSettable, // TestLameDuckModeInfo (simplified — no cluster, just ldm property/state) public class ServerConfigTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } // Ref: golang/nats-server/server/server_test.go TestRandomPorts // The Go test uses Port=-1 (their sentinel for "random"), we use Port=0 (.NET/BSD standard). @@ -63,7 +45,7 @@ public class ServerConfigTests public async Task Server_info_contains_server_name() { const string name = "my-test-server"; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port, ServerName = name }; using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); @@ -79,7 +61,7 @@ public class ServerConfigTests // Wire check — INFO line sent on connect using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var infoLine = await ReadUntilAsync(sock, "INFO"); + var infoLine = await SocketTestHelper.ReadUntilAsync(sock, "INFO"); infoLine.ShouldContain("\"server_name\":\"my-test-server\""); } finally @@ -95,7 +77,7 @@ public class ServerConfigTests [Fact] public async Task Server_info_defaults_name_when_not_configured() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port }; // no ServerName set using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); @@ -112,7 +94,7 @@ public class ServerConfigTests // Wire check — INFO line includes both fields using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); - var infoLine = await ReadUntilAsync(sock, "INFO"); + var infoLine = await SocketTestHelper.ReadUntilAsync(sock, "INFO"); infoLine.ShouldContain("\"server_id\":"); infoLine.ShouldContain("\"server_name\":"); } @@ -131,7 +113,7 @@ public class ServerConfigTests [Fact] public async Task Lame_duck_mode_sets_is_lame_duck_mode_and_shuts_down() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port, diff --git a/tests/NATS.Server.Tests/ServerStatsTests.cs b/tests/NATS.Server.Core.Tests/ServerStatsTests.cs similarity index 90% rename from tests/NATS.Server.Tests/ServerStatsTests.cs rename to tests/NATS.Server.Core.Tests/ServerStatsTests.cs index 7baf061..287c3c7 100644 --- a/tests/NATS.Server.Tests/ServerStatsTests.cs +++ b/tests/NATS.Server.Core.Tests/ServerStatsTests.cs @@ -2,8 +2,9 @@ using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ServerStatsTests : IAsyncLifetime { @@ -13,7 +14,7 @@ public class ServerStatsTests : IAsyncLifetime public ServerStatsTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -96,10 +97,4 @@ public class ServerStatsTests : IAsyncLifetime stats.StaleConnectionGateways.ShouldBe(0); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } } diff --git a/tests/NATS.Server.Tests/ServerTests.cs b/tests/NATS.Server.Core.Tests/ServerTests.cs similarity index 84% rename from tests/NATS.Server.Tests/ServerTests.cs rename to tests/NATS.Server.Core.Tests/ServerTests.cs index 86b5989..a2c608b 100644 --- a/tests/NATS.Server.Tests/ServerTests.cs +++ b/tests/NATS.Server.Core.Tests/ServerTests.cs @@ -3,8 +3,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class ServerTests : IAsyncLifetime { @@ -15,7 +16,7 @@ public class ServerTests : IAsyncLifetime public ServerTests() { // Use random port - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -31,12 +32,6 @@ public class ServerTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private async Task ConnectClientAsync() { @@ -55,19 +50,6 @@ public class ServerTests : IAsyncLifetime /// /// Reads from a socket until the accumulated data contains the expected substring. /// - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } [Fact] public async Task Server_accepts_connection_and_sends_INFO() @@ -97,7 +79,7 @@ public class ServerTests : IAsyncLifetime await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB foo 5\r\nHello\r\n")); // Read MSG from subscriber (may arrive across multiple TCP segments) - var msg = await ReadUntilAsync(sub, "Hello\r\n"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "Hello\r\n"); msg.ShouldContain("MSG foo 1 5\r\nHello\r\n"); } @@ -137,17 +119,17 @@ public class ServerTests : IAsyncLifetime // Connect with pedantic mode ON await pub.SendAsync(Encoding.ASCII.GetBytes( "CONNECT {\"pedantic\":true}\r\nPING\r\n")); - var pong = await ReadUntilAsync(pub, "PONG"); + var pong = await SocketTestHelper.ReadUntilAsync(pub, "PONG"); // Subscribe on sub await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo.* 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // PUB with wildcard subject (invalid for publish) await pub.SendAsync(Encoding.ASCII.GetBytes("PUB foo.* 5\r\nHello\r\n")); // Publisher should get -ERR - var errResponse = await ReadUntilAsync(pub, "-ERR", timeoutMs: 3000); + var errResponse = await SocketTestHelper.ReadUntilAsync(pub, "-ERR", timeoutMs: 3000); errResponse.ShouldContain("-ERR 'Invalid Publish Subject'"); } @@ -162,12 +144,12 @@ public class ServerTests : IAsyncLifetime // Connect without pedantic mode (default) await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo.* 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nPUB foo.* 5\r\nHello\r\n")); // Sub should still receive the message (no validation in non-pedantic mode) - var msg = await ReadUntilAsync(sub, "Hello\r\n"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "Hello\r\n"); msg.ShouldContain("MSG foo.* 1 5\r\nHello\r\n"); } @@ -175,7 +157,7 @@ public class ServerTests : IAsyncLifetime public async Task Server_rejects_max_payload_violation() { // Create server with tiny max payload - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer(new NatsOptions { Port = port, MaxPayload = 10 }, NullLoggerFactory.Instance); _ = server.StartAsync(cts.Token); @@ -254,7 +236,7 @@ public class MaxConnectionsTests : IAsyncLifetime public MaxConnectionsTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port, MaxConnections = 2 }, NullLoggerFactory.Instance); } @@ -270,12 +252,6 @@ public class MaxConnectionsTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } [Fact] public async Task Server_rejects_connection_when_max_reached() @@ -320,7 +296,7 @@ public class PingKeepaliveTests : IAsyncLifetime public PingKeepaliveTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); // Short intervals for testing: 500ms ping interval, 2 max pings out _server = new NatsServer( new NatsOptions @@ -344,26 +320,7 @@ public class PingKeepaliveTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } [Fact] public async Task Server_sends_PING_after_inactivity() @@ -379,7 +336,7 @@ public class PingKeepaliveTests : IAsyncLifetime await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); // Wait for server to send PING (should come within ~500ms) - var response = await ReadUntilAsync(client, "PING", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(client, "PING", timeoutMs: 3000); response.ShouldContain("PING"); client.Dispose(); @@ -397,14 +354,14 @@ public class PingKeepaliveTests : IAsyncLifetime await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\n")); // Wait for first PING - var response = await ReadUntilAsync(client, "PING", timeoutMs: 3000); + var response = await SocketTestHelper.ReadUntilAsync(client, "PING", timeoutMs: 3000); response.ShouldContain("PING"); // Respond with PONG — this resets the counter await client.SendAsync(Encoding.ASCII.GetBytes("PONG\r\n")); // Wait for next PING (counter reset, so we should get another one) - response = await ReadUntilAsync(client, "PING", timeoutMs: 3000); + response = await SocketTestHelper.ReadUntilAsync(client, "PING", timeoutMs: 3000); response.ShouldContain("PING"); // Respond again to keep alive @@ -412,7 +369,7 @@ public class PingKeepaliveTests : IAsyncLifetime // Client should still be alive — send a PING and expect PONG back await client.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - response = await ReadUntilAsync(client, "PONG", timeoutMs: 3000); + response = await SocketTestHelper.ReadUntilAsync(client, "PONG", timeoutMs: 3000); response.ShouldContain("PONG"); client.Dispose(); @@ -465,7 +422,7 @@ public class CloseReasonTests : IAsyncLifetime public CloseReasonTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -481,12 +438,6 @@ public class CloseReasonTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } [Fact] public async Task Client_close_reason_set_on_normal_disconnect() @@ -550,31 +501,12 @@ public class ServerIdentityTests public class FlushBeforeCloseTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } [Fact] public async Task Shutdown_flushes_pending_data_to_clients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -591,7 +523,7 @@ public class FlushBeforeCloseTests // Subscribe to "foo" await sub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo 1\r\nPING\r\n")); - var pong = await ReadUntilAsync(sub, "PONG"); + var pong = await SocketTestHelper.ReadUntilAsync(sub, "PONG"); pong.ShouldContain("PONG"); // Connect a publisher @@ -606,7 +538,7 @@ public class FlushBeforeCloseTests await Task.Delay(200); // Read from subscriber to verify MSG was received - var msg = await ReadUntilAsync(sub, "Hello\r\n"); + var msg = await SocketTestHelper.ReadUntilAsync(sub, "Hello\r\n"); msg.ShouldContain("MSG foo 1 5\r\nHello\r\n"); } finally @@ -619,17 +551,11 @@ public class FlushBeforeCloseTests public class GracefulShutdownTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } [Fact] public async Task ShutdownAsync_disconnects_all_clients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -664,7 +590,7 @@ public class GracefulShutdownTests [Fact] public async Task WaitForShutdown_blocks_until_shutdown() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -689,7 +615,7 @@ public class GracefulShutdownTests [Fact] public async Task ShutdownAsync_is_idempotent() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -706,7 +632,7 @@ public class GracefulShutdownTests [Fact] public async Task Accept_loop_waits_for_active_clients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -731,17 +657,11 @@ public class GracefulShutdownTests public class LameDuckTests { - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } [Fact] public async Task LameDuckShutdown_stops_accepting_new_connections() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer( new NatsOptions { @@ -795,7 +715,7 @@ public class LameDuckTests [Fact] public async Task LameDuckShutdown_eventually_closes_all_clients() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer( new NatsOptions { @@ -855,18 +775,12 @@ public class PidFileTests : IDisposable Directory.Delete(_tempDir, recursive: true); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } [Fact] public async Task Server_writes_pid_file_on_startup() { var pidFile = Path.Combine(_tempDir, "nats.pid"); - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port, PidFile = pidFile }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); @@ -884,7 +798,7 @@ public class PidFileTests : IDisposable [Fact] public async Task Server_writes_ports_file_on_startup() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); var server = new NatsServer(new NatsOptions { Port = port, PortsFileDir = _tempDir }, NullLoggerFactory.Instance); _ = server.StartAsync(CancellationToken.None); await server.WaitForReadyAsync(); diff --git a/tests/NATS.Server.Tests/SignalHandlerTests.cs b/tests/NATS.Server.Core.Tests/SignalHandlerTests.cs similarity index 98% rename from tests/NATS.Server.Tests/SignalHandlerTests.cs rename to tests/NATS.Server.Core.Tests/SignalHandlerTests.cs index 87aede0..b65c925 100644 --- a/tests/NATS.Server.Tests/SignalHandlerTests.cs +++ b/tests/NATS.Server.Core.Tests/SignalHandlerTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Configuration; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; // Go reference: server/signal_unix.go (handleSignals), server/reload.go (Reload) diff --git a/tests/NATS.Server.Tests/SlopwatchSuppressAttribute.cs b/tests/NATS.Server.Core.Tests/SlopwatchSuppressAttribute.cs similarity index 93% rename from tests/NATS.Server.Tests/SlopwatchSuppressAttribute.cs rename to tests/NATS.Server.Core.Tests/SlopwatchSuppressAttribute.cs index 0695aee..8316a54 100644 --- a/tests/NATS.Server.Tests/SlopwatchSuppressAttribute.cs +++ b/tests/NATS.Server.Core.Tests/SlopwatchSuppressAttribute.cs @@ -2,7 +2,7 @@ // Apply to a test method to suppress a specific slopwatch rule violation. // The justification must be 20+ characters explaining why the suppression is intentional. -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class SlopwatchSuppressAttribute(string ruleId, string justification) : Attribute diff --git a/tests/NATS.Server.Tests/SlowConsumerStallGateTests.cs b/tests/NATS.Server.Core.Tests/SlowConsumerStallGateTests.cs similarity index 99% rename from tests/NATS.Server.Tests/SlowConsumerStallGateTests.cs rename to tests/NATS.Server.Core.Tests/SlowConsumerStallGateTests.cs index 0dc966c..0be8edd 100644 --- a/tests/NATS.Server.Tests/SlowConsumerStallGateTests.cs +++ b/tests/NATS.Server.Core.Tests/SlowConsumerStallGateTests.cs @@ -2,7 +2,7 @@ using NATS.Server; using NATS.Server.Auth; using Shouldly; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Tests for and slow-consumer counters. diff --git a/tests/NATS.Server.Tests/StallGateTests.cs b/tests/NATS.Server.Core.Tests/StallGateTests.cs similarity index 98% rename from tests/NATS.Server.Tests/StallGateTests.cs rename to tests/NATS.Server.Core.Tests/StallGateTests.cs index 81648ea..5ae03d3 100644 --- a/tests/NATS.Server.Tests/StallGateTests.cs +++ b/tests/NATS.Server.Core.Tests/StallGateTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; // Go reference: server/client.go (stc channel, stall gate backpressure) diff --git a/tests/NATS.Server.Tests/Stress/ClusterStressTests.cs b/tests/NATS.Server.Core.Tests/Stress/ClusterStressTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Stress/ClusterStressTests.cs rename to tests/NATS.Server.Core.Tests/Stress/ClusterStressTests.cs index c2b9b6a..9847e6c 100644 --- a/tests/NATS.Server.Tests/Stress/ClusterStressTests.cs +++ b/tests/NATS.Server.Core.Tests/Stress/ClusterStressTests.cs @@ -15,7 +15,7 @@ using NATS.Server.JetStream.Publish; using ClusterFixture = NATS.Server.TestUtilities.JetStreamClusterFixture; using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Stress; +namespace NATS.Server.Core.Tests.Stress; /// /// Stress tests for clustered JetStream operations under concurrency. diff --git a/tests/NATS.Server.Tests/Stress/ConcurrentPubSubStressTests.cs b/tests/NATS.Server.Core.Tests/Stress/ConcurrentPubSubStressTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Stress/ConcurrentPubSubStressTests.cs rename to tests/NATS.Server.Core.Tests/Stress/ConcurrentPubSubStressTests.cs index aa716d3..cc52af2 100644 --- a/tests/NATS.Server.Tests/Stress/ConcurrentPubSubStressTests.cs +++ b/tests/NATS.Server.Core.Tests/Stress/ConcurrentPubSubStressTests.cs @@ -6,7 +6,7 @@ using System.Collections.Concurrent; using NATS.Server.Subscriptions; -namespace NATS.Server.Tests.Stress; +namespace NATS.Server.Core.Tests.Stress; /// /// Stress tests for concurrent pub/sub operations on the in-process SubList and SubjectMatch diff --git a/tests/NATS.Server.Tests/Stress/SlowConsumerStressTests.cs b/tests/NATS.Server.Core.Tests/Stress/SlowConsumerStressTests.cs similarity index 88% rename from tests/NATS.Server.Tests/Stress/SlowConsumerStressTests.cs rename to tests/NATS.Server.Core.Tests/Stress/SlowConsumerStressTests.cs index f91a0de..c9875d7 100644 --- a/tests/NATS.Server.Tests/Stress/SlowConsumerStressTests.cs +++ b/tests/NATS.Server.Core.Tests/Stress/SlowConsumerStressTests.cs @@ -8,8 +8,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Stress; +namespace NATS.Server.Core.Tests.Stress; /// /// Stress tests for slow consumer behaviour and connection lifecycle using real NatsServer @@ -24,27 +25,6 @@ public class SlowConsumerStressTests // Helpers // --------------------------------------------------------------- - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } - - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[8192]; - while (!sb.ToString().Contains(expected)) - { - 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 async Task ConnectRawAsync(int port) { var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -68,7 +48,7 @@ public class SlowConsumerStressTests const int payloadSize = 256; const int floodCount = 30; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port, MaxPending = maxPending }, @@ -81,7 +61,7 @@ public class SlowConsumerStressTests using var slowSub = await ConnectRawAsync(port); await slowSub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB sc.stat 1\r\nPING\r\n")); - await ReadUntilAsync(slowSub, "PONG"); + await SocketTestHelper.ReadUntilAsync(slowSub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -92,7 +72,7 @@ public class SlowConsumerStressTests sb.Append($"PUB sc.stat {payloadSize}\r\n{payload}\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); await Task.Delay(500); @@ -118,7 +98,7 @@ public class SlowConsumerStressTests const int payloadSize = 128; const int floodCount = 20; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port, MaxPending = maxPending }, @@ -134,11 +114,11 @@ public class SlowConsumerStressTests await slow1.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB multi.slow 1\r\nPING\r\n")); - await ReadUntilAsync(slow1, "PONG"); + await SocketTestHelper.ReadUntilAsync(slow1, "PONG"); await slow2.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB multi.slow 2\r\nPING\r\n")); - await ReadUntilAsync(slow2, "PONG"); + await SocketTestHelper.ReadUntilAsync(slow2, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -149,7 +129,7 @@ public class SlowConsumerStressTests sb.Append($"PUB multi.slow {payloadSize}\r\n{payload}\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); await Task.Delay(600); @@ -173,7 +153,7 @@ public class SlowConsumerStressTests { const long maxPending = 512; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port, MaxPending = maxPending }, @@ -186,7 +166,7 @@ public class SlowConsumerStressTests using var sub = await ConnectRawAsync(port); await sub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB bp.test 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -197,7 +177,7 @@ public class SlowConsumerStressTests sb.Append($"PUB bp.test 400\r\n{payload}\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); await Task.Delay(400); var stats = server.Stats; @@ -220,7 +200,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Subscriber_receives_messages_after_100_rapid_publishes() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -233,7 +213,7 @@ public class SlowConsumerStressTests using var sub = await ConnectRawAsync(port); await sub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB rapid 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -243,9 +223,9 @@ public class SlowConsumerStressTests sb.Append("PUB rapid 4\r\nping\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); - var received = await ReadUntilAsync(sub, "MSG rapid", 5000); + var received = await SocketTestHelper.ReadUntilAsync(sub, "MSG rapid", 5000); received.ShouldContain("MSG rapid"); } finally @@ -263,7 +243,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Concurrent_publish_and_subscribe_startup_does_not_crash_server() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -278,7 +258,7 @@ public class SlowConsumerStressTests using var sock = await ConnectRawAsync(port); await sock.SendAsync( Encoding.ASCII.GetBytes($"CONNECT {{\"verbose\":false}}\r\nSUB conc.start.{i} {i + 1}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG", 3000); + await SocketTestHelper.ReadUntilAsync(sock, "PONG", 3000); }); await Task.WhenAll(tasks); @@ -302,7 +282,7 @@ public class SlowConsumerStressTests { // Use 8KB payload — large enough to span multiple TCP segments but small // enough to stay well within the default MaxPending limit in CI. - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -320,20 +300,20 @@ public class SlowConsumerStressTests await sub1.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB large.msg 1\r\nPING\r\n")); - await ReadUntilAsync(sub1, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub1, "PONG"); await sub2.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB large.msg 2\r\nPING\r\n")); - await ReadUntilAsync(sub2, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub2, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes($"PUB large.msg {payloadSize}\r\n{payload}\r\nPING\r\n")); // Use a longer timeout for large message delivery - await ReadUntilAsync(pub, "PONG", 10000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 10000); - var r1 = await ReadUntilAsync(sub1, "MSG large.msg", 10000); - var r2 = await ReadUntilAsync(sub2, "MSG large.msg", 10000); + var r1 = await SocketTestHelper.ReadUntilAsync(sub1, "MSG large.msg", 10000); + var r2 = await SocketTestHelper.ReadUntilAsync(sub2, "MSG large.msg", 10000); r1.ShouldContain("MSG large.msg"); r2.ShouldContain("MSG large.msg"); @@ -353,7 +333,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Subscribe_unsubscribe_resubscribe_cycle_100_times_without_error() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -373,7 +353,7 @@ public class SlowConsumerStressTests } await client.SendAsync(Encoding.ASCII.GetBytes("PING\r\n")); - var resp = await ReadUntilAsync(client, "PONG", 5000); + var resp = await SocketTestHelper.ReadUntilAsync(client, "PONG", 5000); resp.ShouldContain("PONG"); } finally @@ -391,7 +371,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Subscriber_receives_messages_correctly_after_brief_pause() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -404,7 +384,7 @@ public class SlowConsumerStressTests using var sub = await ConnectRawAsync(port); await sub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB pause.sub 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); // Brief pause simulating a subscriber that drifts slightly await Task.Delay(100); @@ -412,9 +392,9 @@ public class SlowConsumerStressTests using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB pause.sub 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); - var received = await ReadUntilAsync(sub, "hello", 5000); + var received = await SocketTestHelper.ReadUntilAsync(sub, "hello", 5000); received.ShouldContain("hello"); } finally @@ -432,7 +412,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Multiple_client_connections_and_disconnections_leave_server_stable() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -448,7 +428,7 @@ public class SlowConsumerStressTests using var sock = await ConnectRawAsync(port); await sock.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nPING\r\n")); - await ReadUntilAsync(sock, "PONG", 3000); + await SocketTestHelper.ReadUntilAsync(sock, "PONG", 3000); sock.Close(); } @@ -458,7 +438,7 @@ public class SlowConsumerStressTests // Server should still accept new connections using var final = await ConnectRawAsync(port); await final.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nPING\r\n")); - var resp = await ReadUntilAsync(final, "PONG", 3000); + var resp = await SocketTestHelper.ReadUntilAsync(final, "PONG", 3000); resp.ShouldContain("PONG"); } finally @@ -476,7 +456,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Stats_in_and_out_bytes_increment_correctly_under_load() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -489,7 +469,7 @@ public class SlowConsumerStressTests using var sub = await ConnectRawAsync(port); await sub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB stats.load 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -499,7 +479,7 @@ public class SlowConsumerStressTests sb.Append("PUB stats.load 10\r\n0123456789\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); await Task.Delay(200); @@ -522,7 +502,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Rapid_connect_disconnect_cycles_do_not_corrupt_server_state() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -550,7 +530,7 @@ public class SlowConsumerStressTests // Server should still respond using var healthy = await ConnectRawAsync(port); await healthy.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nPING\r\n")); - var resp = await ReadUntilAsync(healthy, "PONG", 3000); + var resp = await SocketTestHelper.ReadUntilAsync(healthy, "PONG", 3000); resp.ShouldContain("PONG"); } finally @@ -568,7 +548,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Server_accepts_connection_after_cancelled_client_task() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var serverCts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -599,7 +579,7 @@ public class SlowConsumerStressTests // Server should still function using var good = await ConnectRawAsync(port); await good.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nPING\r\n")); - var resp = await ReadUntilAsync(good, "PONG", 3000); + var resp = await SocketTestHelper.ReadUntilAsync(good, "PONG", 3000); resp.ShouldContain("PONG"); } finally @@ -621,7 +601,7 @@ public class SlowConsumerStressTests const int payloadSize = 256; const int floodCount = 20; - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port, MaxPending = maxPending }, @@ -634,7 +614,7 @@ public class SlowConsumerStressTests using var slowSub = await ConnectRawAsync(port); await slowSub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB drop.test 1\r\nPING\r\n")); - await ReadUntilAsync(slowSub, "PONG"); + await SocketTestHelper.ReadUntilAsync(slowSub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -645,7 +625,7 @@ public class SlowConsumerStressTests sb.Append($"PUB drop.test {payloadSize}\r\n{payload}\r\n"); sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); await Task.Delay(600); @@ -667,7 +647,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Server_delivers_to_correct_subscriber_when_multiple_subjects_active() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -682,18 +662,18 @@ public class SlowConsumerStressTests await sub1.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB target.A 1\r\nPING\r\n")); - await ReadUntilAsync(sub1, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub1, "PONG"); await sub2.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB target.B 1\r\nPING\r\n")); - await ReadUntilAsync(sub2, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub2, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); await pub.SendAsync(Encoding.ASCII.GetBytes("PUB target.A 5\r\nhello\r\nPING\r\n")); - await ReadUntilAsync(pub, "PONG", 5000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 5000); - var r1 = await ReadUntilAsync(sub1, "hello", 3000); + var r1 = await SocketTestHelper.ReadUntilAsync(sub1, "hello", 3000); r1.ShouldContain("MSG target.A"); // sub2 should NOT have received the target.A message @@ -719,7 +699,7 @@ public class SlowConsumerStressTests [Trait("Category", "Stress")] public async Task Server_remains_stable_after_processing_many_medium_sized_messages() { - var port = GetFreePort(); + var port = TestPortAllocator.GetFreePort(); using var cts = new CancellationTokenSource(); var server = new NatsServer( new NatsOptions { Port = port }, @@ -732,7 +712,7 @@ public class SlowConsumerStressTests using var sub = await ConnectRawAsync(port); await sub.SendAsync( Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\nSUB medium.msgs 1\r\nPING\r\n")); - await ReadUntilAsync(sub, "PONG"); + await SocketTestHelper.ReadUntilAsync(sub, "PONG"); using var pub = await ConnectRawAsync(port); await pub.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":false}\r\n")); @@ -744,7 +724,7 @@ public class SlowConsumerStressTests sb.Append("PING\r\n"); await pub.SendAsync(Encoding.ASCII.GetBytes(sb.ToString())); - await ReadUntilAsync(pub, "PONG", 10000); + await SocketTestHelper.ReadUntilAsync(pub, "PONG", 10000); var stats = server.Stats; Interlocked.Read(ref stats.InMsgs).ShouldBeGreaterThanOrEqualTo(200); diff --git a/tests/NATS.Server.Tests/SubList/SubListAsyncCacheSweepTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListAsyncCacheSweepTests.cs similarity index 94% rename from tests/NATS.Server.Tests/SubList/SubListAsyncCacheSweepTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListAsyncCacheSweepTests.cs index 337d999..e3f27e3 100644 --- a/tests/NATS.Server.Tests/SubList/SubListAsyncCacheSweepTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListAsyncCacheSweepTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListAsyncCacheSweepTests { diff --git a/tests/NATS.Server.Tests/SubList/SubListHighFanoutOptimizationTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListHighFanoutOptimizationTests.cs similarity index 93% rename from tests/NATS.Server.Tests/SubList/SubListHighFanoutOptimizationTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListHighFanoutOptimizationTests.cs index 6a5c513..1ea7653 100644 --- a/tests/NATS.Server.Tests/SubList/SubListHighFanoutOptimizationTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListHighFanoutOptimizationTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListHighFanoutOptimizationTests { diff --git a/tests/NATS.Server.Tests/SubList/SubListMatchBytesTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListMatchBytesTests.cs similarity index 93% rename from tests/NATS.Server.Tests/SubList/SubListMatchBytesTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListMatchBytesTests.cs index 3c9acd0..467814a 100644 --- a/tests/NATS.Server.Tests/SubList/SubListMatchBytesTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListMatchBytesTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListMatchBytesTests { diff --git a/tests/NATS.Server.Tests/SubList/SubListNotificationTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListNotificationTests.cs similarity index 95% rename from tests/NATS.Server.Tests/SubList/SubListNotificationTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListNotificationTests.cs index dc50ff9..b9b4b78 100644 --- a/tests/NATS.Server.Tests/SubList/SubListNotificationTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListNotificationTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListNotificationTests { diff --git a/tests/NATS.Server.Tests/SubList/SubListQueueWeightTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListQueueWeightTests.cs similarity index 92% rename from tests/NATS.Server.Tests/SubList/SubListQueueWeightTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListQueueWeightTests.cs index 53a8672..693ef84 100644 --- a/tests/NATS.Server.Tests/SubList/SubListQueueWeightTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListQueueWeightTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListQueueWeightTests { diff --git a/tests/NATS.Server.Tests/SubList/SubListRemoteFilterTests.cs b/tests/NATS.Server.Core.Tests/SubList/SubListRemoteFilterTests.cs similarity index 93% rename from tests/NATS.Server.Tests/SubList/SubListRemoteFilterTests.cs rename to tests/NATS.Server.Core.Tests/SubList/SubListRemoteFilterTests.cs index 5ef2556..fb0f703 100644 --- a/tests/NATS.Server.Tests/SubList/SubListRemoteFilterTests.cs +++ b/tests/NATS.Server.Core.Tests/SubList/SubListRemoteFilterTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListRemoteFilterTests { diff --git a/tests/NATS.Server.Tests/SubListTests.cs b/tests/NATS.Server.Core.Tests/SubListTests.cs similarity index 99% rename from tests/NATS.Server.Tests/SubListTests.cs rename to tests/NATS.Server.Core.Tests/SubListTests.cs index 4909bfe..028b215 100644 --- a/tests/NATS.Server.Tests/SubListTests.cs +++ b/tests/NATS.Server.Core.Tests/SubListTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubListTests { diff --git a/tests/NATS.Server.Tests/SubjectMatchTests.cs b/tests/NATS.Server.Core.Tests/SubjectMatchTests.cs similarity index 98% rename from tests/NATS.Server.Tests/SubjectMatchTests.cs rename to tests/NATS.Server.Core.Tests/SubjectMatchTests.cs index 3c87352..216588f 100644 --- a/tests/NATS.Server.Tests/SubjectMatchTests.cs +++ b/tests/NATS.Server.Core.Tests/SubjectMatchTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubjectMatchTests { diff --git a/tests/NATS.Server.Tests/SubjectTransformIntegrationTests.cs b/tests/NATS.Server.Core.Tests/SubjectTransformIntegrationTests.cs similarity index 98% rename from tests/NATS.Server.Tests/SubjectTransformIntegrationTests.cs rename to tests/NATS.Server.Core.Tests/SubjectTransformIntegrationTests.cs index c193c5a..ebe6c45 100644 --- a/tests/NATS.Server.Tests/SubjectTransformIntegrationTests.cs +++ b/tests/NATS.Server.Core.Tests/SubjectTransformIntegrationTests.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubjectTransformIntegrationTests { diff --git a/tests/NATS.Server.Tests/SubjectTransformTests.cs b/tests/NATS.Server.Core.Tests/SubjectTransformTests.cs similarity index 99% rename from tests/NATS.Server.Tests/SubjectTransformTests.cs rename to tests/NATS.Server.Core.Tests/SubjectTransformTests.cs index 77f3fa1..19fb0da 100644 --- a/tests/NATS.Server.Tests/SubjectTransformTests.cs +++ b/tests/NATS.Server.Core.Tests/SubjectTransformTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class SubjectTransformTests { diff --git a/tests/NATS.Server.Tests/Subscriptions/RouteResultCacheTests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/RouteResultCacheTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Subscriptions/RouteResultCacheTests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/RouteResultCacheTests.cs index e49d718..1b91422 100644 --- a/tests/NATS.Server.Tests/Subscriptions/RouteResultCacheTests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/RouteResultCacheTests.cs @@ -3,7 +3,7 @@ using NATS.Server.Subscriptions; using Shouldly; -namespace NATS.Server.Tests.Subscriptions; +namespace NATS.Server.Core.Tests.Subscriptions; /// /// Unit tests for RouteResultCache — the per-account LRU cache that stores diff --git a/tests/NATS.Server.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs similarity index 96% rename from tests/NATS.Server.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs index 142b8e8..2332e13 100644 --- a/tests/NATS.Server.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/SubListCtorAndNotificationParityTests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests.Subscriptions; +namespace NATS.Server.Core.Tests.Subscriptions; public class SubListCtorAndNotificationParityTests { diff --git a/tests/NATS.Server.Tests/Subscriptions/SubListGoParityTests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/SubListGoParityTests.cs similarity index 99% rename from tests/NATS.Server.Tests/Subscriptions/SubListGoParityTests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/SubListGoParityTests.cs index b8ee5e9..ff8ccdf 100644 --- a/tests/NATS.Server.Tests/Subscriptions/SubListGoParityTests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/SubListGoParityTests.cs @@ -3,7 +3,7 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; /// /// Go parity tests for SubList ported from sublist_test.go. diff --git a/tests/NATS.Server.Tests/Subscriptions/SubListParityBatch2Tests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/SubListParityBatch2Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Subscriptions/SubListParityBatch2Tests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/SubListParityBatch2Tests.cs index bfe0d6e..99f4d8a 100644 --- a/tests/NATS.Server.Tests/Subscriptions/SubListParityBatch2Tests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/SubListParityBatch2Tests.cs @@ -3,7 +3,7 @@ using NATS.Server.Auth; using NATS.Server.Protocol; using NATS.Server.Subscriptions; -namespace NATS.Server.Tests.Subscriptions; +namespace NATS.Server.Core.Tests.Subscriptions; public class SubListParityBatch2Tests { diff --git a/tests/NATS.Server.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs similarity index 96% rename from tests/NATS.Server.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs index c0904ff..f638d41 100644 --- a/tests/NATS.Server.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/SubjectSubsetMatchParityBatch1Tests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests.Subscriptions; +namespace NATS.Server.Core.Tests.Subscriptions; public class SubjectSubsetMatchParityBatch1Tests { diff --git a/tests/NATS.Server.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs b/tests/NATS.Server.Core.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs similarity index 98% rename from tests/NATS.Server.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs rename to tests/NATS.Server.Core.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs index 662f18a..8a78af5 100644 --- a/tests/NATS.Server.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs +++ b/tests/NATS.Server.Core.Tests/Subscriptions/SubjectTransformParityBatch3Tests.cs @@ -1,6 +1,6 @@ using NATS.Server.Subscriptions; -namespace NATS.Server.Tests.Subscriptions; +namespace NATS.Server.Core.Tests.Subscriptions; public class SubjectTransformParityBatch3Tests { diff --git a/tests/NATS.Server.Tests/VerboseModeTests.cs b/tests/NATS.Server.Core.Tests/VerboseModeTests.cs similarity index 67% rename from tests/NATS.Server.Tests/VerboseModeTests.cs rename to tests/NATS.Server.Core.Tests/VerboseModeTests.cs index c5c1fd0..274cd71 100644 --- a/tests/NATS.Server.Tests/VerboseModeTests.cs +++ b/tests/NATS.Server.Core.Tests/VerboseModeTests.cs @@ -3,8 +3,9 @@ using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; +using NATS.Server.TestUtilities; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class VerboseModeTests : IAsyncLifetime { @@ -14,7 +15,7 @@ public class VerboseModeTests : IAsyncLifetime public VerboseModeTests() { - _port = GetFreePort(); + _port = TestPortAllocator.GetFreePort(); _server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance); } @@ -30,12 +31,6 @@ public class VerboseModeTests : IAsyncLifetime _server.Dispose(); } - private static int GetFreePort() - { - using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - return ((IPEndPoint)sock.LocalEndPoint!).Port; - } private async Task ConnectClientAsync() { @@ -44,19 +39,6 @@ public class VerboseModeTests : IAsyncLifetime return sock; } - private static async Task ReadUntilAsync(Socket sock, string expected, int timeoutMs = 5000) - { - using var cts = new CancellationTokenSource(timeoutMs); - var sb = new StringBuilder(); - var buf = new byte[4096]; - while (!sb.ToString().Contains(expected)) - { - 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(); - } [Fact] public async Task Verbose_mode_sends_OK_after_CONNECT() @@ -64,13 +46,13 @@ public class VerboseModeTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT with verbose:true await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":true}\r\n")); // Should receive +OK after CONNECT - var response = await ReadUntilAsync(client, "+OK\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "+OK\r\n"); response.ShouldContain("+OK\r\n"); } @@ -80,19 +62,19 @@ public class VerboseModeTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT with verbose:true, then SUB await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":true}\r\n")); // Read +OK from CONNECT - await ReadUntilAsync(client, "+OK\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "+OK\r\n"); // Send SUB await client.SendAsync(Encoding.ASCII.GetBytes("SUB foo 1\r\n")); // Should receive +OK after SUB - var response = await ReadUntilAsync(client, "+OK\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "+OK\r\n"); response.ShouldContain("+OK\r\n"); } @@ -102,19 +84,19 @@ public class VerboseModeTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT with verbose:true await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {\"verbose\":true}\r\n")); // Read +OK from CONNECT - await ReadUntilAsync(client, "+OK\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "+OK\r\n"); // Send PUB await client.SendAsync(Encoding.ASCII.GetBytes("PUB foo 5\r\nHello\r\n")); // Should receive +OK after PUB - var response = await ReadUntilAsync(client, "+OK\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "+OK\r\n"); response.ShouldContain("+OK\r\n"); } @@ -124,13 +106,13 @@ public class VerboseModeTests : IAsyncLifetime using var client = await ConnectClientAsync(); // Read INFO - await ReadUntilAsync(client, "\r\n"); + await SocketTestHelper.ReadUntilAsync(client, "\r\n"); // Send CONNECT without verbose (default false), then SUB, then PING await client.SendAsync(Encoding.ASCII.GetBytes("CONNECT {}\r\nSUB foo 1\r\nPING\r\n")); // Should receive PONG but NOT +OK - var response = await ReadUntilAsync(client, "PONG\r\n"); + var response = await SocketTestHelper.ReadUntilAsync(client, "PONG\r\n"); response.ShouldContain("PONG\r\n"); response.ShouldNotContain("+OK"); } diff --git a/tests/NATS.Server.Tests/WriteLoopTests.cs b/tests/NATS.Server.Core.Tests/WriteLoopTests.cs similarity index 99% rename from tests/NATS.Server.Tests/WriteLoopTests.cs rename to tests/NATS.Server.Core.Tests/WriteLoopTests.cs index c27b1e4..1dba612 100644 --- a/tests/NATS.Server.Tests/WriteLoopTests.cs +++ b/tests/NATS.Server.Core.Tests/WriteLoopTests.cs @@ -6,7 +6,7 @@ using NATS.Server; using NATS.Server.Auth; using NATS.Server.Protocol; -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; public class WriteLoopTests : IAsyncDisposable { diff --git a/tests/NATS.Server.Tests/WriteTimeoutTests.cs b/tests/NATS.Server.Core.Tests/WriteTimeoutTests.cs similarity index 98% rename from tests/NATS.Server.Tests/WriteTimeoutTests.cs rename to tests/NATS.Server.Core.Tests/WriteTimeoutTests.cs index 277c9bd..d8b3a51 100644 --- a/tests/NATS.Server.Tests/WriteTimeoutTests.cs +++ b/tests/NATS.Server.Core.Tests/WriteTimeoutTests.cs @@ -1,4 +1,4 @@ -namespace NATS.Server.Tests; +namespace NATS.Server.Core.Tests; // Go reference: server/client.go (write timeout handling, per-kind policies) diff --git a/tests/NATS.Server.Transport.Tests/Networking/NetworkingGoParityTests.cs b/tests/NATS.Server.Transport.Tests/Networking/NetworkingGoParityTests.cs index cda7cff..53e503c 100644 --- a/tests/NATS.Server.Transport.Tests/Networking/NetworkingGoParityTests.cs +++ b/tests/NATS.Server.Transport.Tests/Networking/NetworkingGoParityTests.cs @@ -12,7 +12,7 @@ using NATS.Server.Routes; using NATS.Server.Subscriptions; using NATS.Server.TestUtilities; -namespace NATS.Server.Tests.Networking; +namespace NATS.Server.Transport.Tests.Networking; /// /// Ported Go networking tests for gateway interest mode, route pool accounting,