refactor: extract NATS.Server.Transport.Tests project
Move TLS, OCSP, WebSocket, Networking, and IO test files from NATS.Server.Tests into a dedicated NATS.Server.Transport.Tests project. Update namespaces, replace private GetFreePort/ReadUntilAsync with shared TestUtilities helpers, extract TestCertHelper to TestUtilities, and replace Task.Delay polling loops with PollHelper.WaitUntilAsync/YieldForAsync for proper synchronization.
This commit is contained in:
@@ -18,4 +18,18 @@ public static class SocketTestHelper
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static async Task<string> ReadUntilAsync(Stream stream, 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))
|
||||
{
|
||||
var n = await stream.ReadAsync(buf, cts.Token);
|
||||
if (n == 0) break;
|
||||
sb.Append(Encoding.ASCII.GetString(buf, 0, n));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
31
tests/NATS.Server.TestUtilities/TestCertHelper.cs
Normal file
31
tests/NATS.Server.TestUtilities/TestCertHelper.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace NATS.Server.TestUtilities;
|
||||
|
||||
public static class TestCertHelper
|
||||
{
|
||||
public static (string certPath, string keyPath) GenerateTestCertFiles()
|
||||
{
|
||||
var (cert, key) = GenerateTestCert();
|
||||
var certPath = Path.GetTempFileName();
|
||||
var keyPath = Path.GetTempFileName();
|
||||
File.WriteAllText(certPath, cert.ExportCertificatePem());
|
||||
File.WriteAllText(keyPath, key.ExportPkcs8PrivateKeyPem());
|
||||
return (certPath, keyPath);
|
||||
}
|
||||
|
||||
public static (X509Certificate2 cert, RSA key) GenerateTestCert()
|
||||
{
|
||||
var key = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
req.CertificateExtensions.Add(sanBuilder.Build());
|
||||
var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
|
||||
return (cert, key);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NATS.Server.Monitoring;
|
||||
using NATS.Server.TestUtilities;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
|
||||
@@ -310,7 +311,7 @@ public class MonitorTlsTests : IAsyncLifetime
|
||||
{
|
||||
_natsPort = GetFreePort();
|
||||
_monitorPort = GetFreePort();
|
||||
(_certPath, _keyPath) = TlsHelperTests.GenerateTestCertFiles();
|
||||
(_certPath, _keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
_server = new NatsServer(
|
||||
new NatsOptions
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ using NATS.Server;
|
||||
using NATS.Server.Auth;
|
||||
using NATS.Server.Monitoring;
|
||||
using NATS.Server.Protocol;
|
||||
using NATS.Server.TestUtilities;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
|
||||
@@ -784,7 +785,7 @@ public class MsgTraceGoParityTests : IAsyncLifetime
|
||||
public async Task ClosedConns_tls_handshake_close_reason_tracked()
|
||||
{
|
||||
// Go: TestClosedTLSHandshake (closed_conns_test.go:247)
|
||||
var (certPath, keyPath) = TlsHelperTests.GenerateTestCertFiles();
|
||||
var (certPath, keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
try
|
||||
{
|
||||
var port = GetFreePort();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using NATS.Server.IO;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.IO;
|
||||
namespace NATS.Server.Transport.Tests.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the consecutive short-read counter in AdaptiveReadBuffer.
|
||||
@@ -1,6 +1,6 @@
|
||||
using NATS.Server.IO;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class AdaptiveReadBufferTests
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using Shouldly;
|
||||
|
||||
// Go reference: client.go — dynamic buffer sizing and broadcast flush coalescing for fan-out.
|
||||
|
||||
namespace NATS.Server.Tests.IO;
|
||||
namespace NATS.Server.Transport.Tests.IO;
|
||||
|
||||
public class DynamicBufferPoolTests
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using NATS.Server.IO;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class OutboundBufferPoolTests
|
||||
{
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="NATS.Client.Core" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Shouldly" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="Shouldly" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\NATS.Server\NATS.Server.csproj" />
|
||||
<ProjectReference Include="..\NATS.Server.TestUtilities\NATS.Server.TestUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -10,6 +10,7 @@ using NATS.Server.Gateways;
|
||||
using NATS.Server.LeafNodes;
|
||||
using NATS.Server.Routes;
|
||||
using NATS.Server.Subscriptions;
|
||||
using NATS.Server.TestUtilities;
|
||||
|
||||
namespace NATS.Server.Tests.Networking;
|
||||
|
||||
@@ -177,9 +178,7 @@ public class NetworkingGoParityTests
|
||||
await using var sub = await conn.SubscribeCoreAsync<string>("gw.interest.test");
|
||||
await conn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("gw.interest.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => fixture.Local.HasRemoteInterest("gw.interest.test"));
|
||||
|
||||
fixture.Local.HasRemoteInterest("gw.interest.test").ShouldBeTrue();
|
||||
}
|
||||
@@ -204,9 +203,7 @@ public class NetworkingGoParityTests
|
||||
await using var sub = await remoteConn.SubscribeCoreAsync<string>("gw.fwd.test");
|
||||
await remoteConn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("gw.fwd.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => fixture.Local.HasRemoteInterest("gw.fwd.test"));
|
||||
|
||||
await localConn.PublishAsync("gw.fwd.test", "gateway-msg");
|
||||
|
||||
@@ -229,18 +226,14 @@ public class NetworkingGoParityTests
|
||||
var sub = await conn.SubscribeCoreAsync<string>("gw.unsub.test");
|
||||
await conn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("gw.unsub.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => fixture.Local.HasRemoteInterest("gw.unsub.test"));
|
||||
|
||||
fixture.Local.HasRemoteInterest("gw.unsub.test").ShouldBeTrue();
|
||||
|
||||
await sub.DisposeAsync();
|
||||
await conn.PingAsync();
|
||||
|
||||
using var unsTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!unsTimeout.IsCancellationRequested && fixture.Local.HasRemoteInterest("gw.unsub.test"))
|
||||
await Task.Delay(50, unsTimeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !(fixture.Local.HasRemoteInterest("gw.unsub.test")));
|
||||
|
||||
fixture.Local.HasRemoteInterest("gw.unsub.test").ShouldBeFalse();
|
||||
}
|
||||
@@ -260,9 +253,7 @@ public class NetworkingGoParityTests
|
||||
await using var sub = await conn.SubscribeCoreAsync<string>("gw.wild.>");
|
||||
await conn.PingAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && !fixture.Local.HasRemoteInterest("gw.wild.test"))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => fixture.Local.HasRemoteInterest("gw.wild.test"));
|
||||
|
||||
fixture.Local.HasRemoteInterest("gw.wild.test").ShouldBeTrue();
|
||||
fixture.Local.HasRemoteInterest("gw.wild.deep.nested").ShouldBeTrue();
|
||||
@@ -410,9 +401,7 @@ public class NetworkingGoParityTests
|
||||
|
||||
try
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && serverA.Stats.Routes < 3)
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !(serverA.Stats.Routes < 3));
|
||||
|
||||
serverA.Stats.Routes.ShouldBeGreaterThanOrEqualTo(3);
|
||||
}
|
||||
@@ -488,9 +477,7 @@ public class NetworkingGoParityTests
|
||||
|
||||
try
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && serverA.Stats.Routes < 3)
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !(serverA.Stats.Routes < 3));
|
||||
|
||||
await using var conn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
@@ -501,9 +488,7 @@ public class NetworkingGoParityTests
|
||||
await using var sub = await conn.SubscribeCoreAsync<string>("route.sub.test");
|
||||
await conn.PingAsync();
|
||||
|
||||
using var interest = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!interest.IsCancellationRequested && !serverA.HasRemoteInterest("route.sub.test"))
|
||||
await Task.Delay(50, interest.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => serverA.HasRemoteInterest("route.sub.test"));
|
||||
|
||||
serverA.HasRemoteInterest("route.sub.test").ShouldBeTrue();
|
||||
}
|
||||
@@ -582,9 +567,7 @@ public class NetworkingGoParityTests
|
||||
|
||||
try
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && serverA.Stats.Routes < 3)
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !(serverA.Stats.Routes < 3));
|
||||
|
||||
await using var subConn = new NatsConnection(new NatsOpts
|
||||
{
|
||||
@@ -601,9 +584,7 @@ public class NetworkingGoParityTests
|
||||
await using var sub = await subConn.SubscribeCoreAsync<string>("route.fwd.test");
|
||||
await subConn.PingAsync();
|
||||
|
||||
using var interest = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!interest.IsCancellationRequested && !serverA.HasRemoteInterest("route.fwd.test"))
|
||||
await Task.Delay(50, interest.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => serverA.HasRemoteInterest("route.fwd.test"));
|
||||
|
||||
await pubConn.PublishAsync("route.fwd.test", "routed-msg");
|
||||
|
||||
@@ -852,7 +833,7 @@ public class NetworkingGoParityTests
|
||||
leaf.StartLoop(cts.Token);
|
||||
|
||||
await WriteLineAsync(remoteSocket, "LS+ $G events.>", cts.Token);
|
||||
await Task.Delay(100);
|
||||
await PollHelper.YieldForAsync(100);
|
||||
await WriteLineAsync(remoteSocket, "LS- $G events.>", cts.Token);
|
||||
|
||||
var result = await received.Task.WaitAsync(cts.Token);
|
||||
@@ -1132,9 +1113,7 @@ internal sealed class TwoGatewayFixture : IAsyncDisposable
|
||||
_ = remote.StartAsync(remoteCts.Token);
|
||||
await remote.WaitForReadyAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && (local.Stats.Gateways == 0 || remote.Stats.Gateways == 0))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !((local.Stats.Gateways == 0 || remote.Stats.Gateways == 0)));
|
||||
|
||||
return new TwoGatewayFixture(local, remote, localCts, remoteCts);
|
||||
}
|
||||
@@ -1205,37 +1184,23 @@ internal sealed class LeafFixture : IAsyncDisposable
|
||||
_ = spoke.StartAsync(spokeCts.Token);
|
||||
await spoke.WaitForReadyAsync();
|
||||
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested && (hub.Stats.Leafs == 0 || spoke.Stats.Leafs == 0))
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
await PollHelper.WaitUntilAsync(() => !((hub.Stats.Leafs == 0 || spoke.Stats.Leafs == 0)));
|
||||
|
||||
return new LeafFixture(hub, spoke, hubCts, spokeCts);
|
||||
}
|
||||
|
||||
public async Task WaitForRemoteInterestOnHubAsync(string subject)
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested)
|
||||
{
|
||||
if (Hub.HasRemoteInterest(subject))
|
||||
return;
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Timed out waiting for remote interest on hub for '{subject}'.");
|
||||
await PollHelper.WaitOrThrowAsync(
|
||||
() => Hub.HasRemoteInterest(subject),
|
||||
$"Timed out waiting for remote interest on hub for '{subject}'.");
|
||||
}
|
||||
|
||||
public async Task WaitForRemoteInterestOnSpokeAsync(string subject)
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
while (!timeout.IsCancellationRequested)
|
||||
{
|
||||
if (Spoke.HasRemoteInterest(subject))
|
||||
return;
|
||||
await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Timed out waiting for remote interest on spoke for '{subject}'.");
|
||||
await PollHelper.WaitOrThrowAsync(
|
||||
() => Spoke.HasRemoteInterest(subject),
|
||||
$"Timed out waiting for remote interest on spoke for '{subject}'.");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -1,6 +1,6 @@
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class OcspConfigTests
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class OcspStaplingTests
|
||||
{
|
||||
@@ -6,9 +6,10 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NATS.Server;
|
||||
using NATS.Server.Protocol;
|
||||
using NATS.Server.TestUtilities;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsConnectionWrapperTests
|
||||
{
|
||||
@@ -32,7 +33,7 @@ public class TlsConnectionWrapperTests
|
||||
[Fact]
|
||||
public async Task TlsRequired_upgrades_to_ssl()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
var (serverSocket, clientSocket) = await CreateSocketPairAsync();
|
||||
using var clientNetStream = new NetworkStream(clientSocket, ownsSocket: true);
|
||||
@@ -85,7 +86,7 @@ public class TlsConnectionWrapperTests
|
||||
[Fact]
|
||||
public async Task MixedMode_allows_plaintext_when_AllowNonTls()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
var (serverSocket, clientSocket) = await CreateSocketPairAsync();
|
||||
using var clientNetStream = new NetworkStream(clientSocket, ownsSocket: true);
|
||||
@@ -133,7 +134,7 @@ public class TlsConnectionWrapperTests
|
||||
[Fact]
|
||||
public async Task TlsRequired_rejects_plaintext()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
var (serverSocket, clientSocket) = await CreateSocketPairAsync();
|
||||
using var clientNetStream = new NetworkStream(clientSocket, ownsSocket: true);
|
||||
@@ -180,7 +181,7 @@ public class TlsConnectionWrapperTests
|
||||
[Fact]
|
||||
public async Task TlsFirst_handshakes_before_sending_info()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
var (serverSocket, clientSocket) = await CreateSocketPairAsync();
|
||||
using var clientNetStream = new NetworkStream(clientSocket, ownsSocket: true);
|
||||
@@ -2,16 +2,17 @@ using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NATS.Server;
|
||||
using NATS.Server.TestUtilities;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void LoadCertificate_loads_pem_cert_and_key()
|
||||
{
|
||||
var (certPath, keyPath) = GenerateTestCertFiles();
|
||||
var (certPath, keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
try
|
||||
{
|
||||
var cert = TlsHelper.LoadCertificate(certPath, keyPath);
|
||||
@@ -24,7 +25,7 @@ public class TlsHelperTests
|
||||
[Fact]
|
||||
public void BuildServerAuthOptions_creates_valid_options()
|
||||
{
|
||||
var (certPath, keyPath) = GenerateTestCertFiles();
|
||||
var (certPath, keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
try
|
||||
{
|
||||
var opts = new NatsOptions { TlsCert = certPath, TlsKey = keyPath };
|
||||
@@ -38,7 +39,7 @@ public class TlsHelperTests
|
||||
[Fact]
|
||||
public void LoadCaCertificates_rejects_non_certificate_pem_block()
|
||||
{
|
||||
var (_, key) = GenerateTestCert();
|
||||
var (_, key) = TestCertHelper.GenerateTestCert();
|
||||
var pemPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
@@ -123,27 +124,10 @@ public class TlsHelperTests
|
||||
await limiter.WaitAsync(cts.Token);
|
||||
}
|
||||
|
||||
// Public helper methods used by other test classes
|
||||
// Delegate to shared TestCertHelper in TestUtilities
|
||||
public static (string certPath, string keyPath) GenerateTestCertFiles()
|
||||
{
|
||||
var (cert, key) = GenerateTestCert();
|
||||
var certPath = Path.GetTempFileName();
|
||||
var keyPath = Path.GetTempFileName();
|
||||
File.WriteAllText(certPath, cert.ExportCertificatePem());
|
||||
File.WriteAllText(keyPath, key.ExportPkcs8PrivateKeyPem());
|
||||
return (certPath, keyPath);
|
||||
}
|
||||
=> TestCertHelper.GenerateTestCertFiles();
|
||||
|
||||
public static (X509Certificate2 cert, RSA key) GenerateTestCert()
|
||||
{
|
||||
var key = RSA.Create(2048);
|
||||
var req = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
sanBuilder.AddDnsName("localhost");
|
||||
req.CertificateExtensions.Add(sanBuilder.Build());
|
||||
var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
|
||||
return (cert, key);
|
||||
}
|
||||
=> TestCertHelper.GenerateTestCert();
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NATS.Server.Auth;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsMapAuthenticatorTests
|
||||
{
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using NATS.Server.Configuration;
|
||||
using NATS.Server.TestUtilities;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsOcspParityBatch1Tests
|
||||
{
|
||||
@@ -84,7 +85,7 @@ public class TlsOcspParityBatch1Tests
|
||||
[Fact]
|
||||
public void GenerateFingerprint_uses_raw_certificate_sha256()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
var expected = Convert.ToBase64String(SHA256.HashData(cert.RawData));
|
||||
TlsHelper.GenerateFingerprint(cert).ShouldBe(expected);
|
||||
@@ -104,7 +105,7 @@ public class TlsOcspParityBatch1Tests
|
||||
[Fact]
|
||||
public void Subject_and_issuer_dn_helpers_return_values_and_empty_for_null()
|
||||
{
|
||||
var (cert, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (cert, _) = TestCertHelper.GenerateTestCert();
|
||||
|
||||
TlsHelper.GetSubjectDNForm(cert).ShouldNotBeNullOrWhiteSpace();
|
||||
TlsHelper.GetIssuerDNForm(cert).ShouldNotBeNullOrWhiteSpace();
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Formats.Asn1;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using NATS.Server.TestUtilities;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsOcspParityBatch2Tests
|
||||
{
|
||||
@@ -24,7 +25,7 @@ public class TlsOcspParityBatch2Tests
|
||||
[Fact]
|
||||
public void CertOCSPEligible_returns_false_when_leaf_has_no_ocsp_servers()
|
||||
{
|
||||
var (leaf, _) = TlsHelperTests.GenerateTestCert();
|
||||
var (leaf, _) = TestCertHelper.GenerateTestCert();
|
||||
var link = new ChainLink { Leaf = leaf };
|
||||
|
||||
TlsHelper.CertOCSPEligible(link).ShouldBeFalse();
|
||||
@@ -1,6 +1,7 @@
|
||||
using NATS.Server.TestUtilities;
|
||||
using NATS.Server.Tls;
|
||||
|
||||
namespace NATS.Server.Tests;
|
||||
namespace NATS.Server.Transport.Tests;
|
||||
|
||||
public class TlsRateLimiterTests
|
||||
{
|
||||
@@ -39,8 +40,8 @@ public class TlsRateLimiterTests
|
||||
await limiter.WaitAsync(CancellationToken.None);
|
||||
await limiter.WaitAsync(CancellationToken.None);
|
||||
|
||||
// Wait for refill
|
||||
await Task.Delay(1200);
|
||||
// Wait for refill (rate limiter refills tokens after 1 second)
|
||||
await PollHelper.YieldForAsync(1200);
|
||||
|
||||
// Should have tokens again
|
||||
using var cts = new CancellationTokenSource(200);
|
||||
@@ -4,8 +4,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.Transport.Tests;
|
||||
|
||||
public class TlsServerTests : IAsyncLifetime
|
||||
{
|
||||
@@ -17,8 +18,8 @@ public class TlsServerTests : IAsyncLifetime
|
||||
|
||||
public TlsServerTests()
|
||||
{
|
||||
_port = GetFreePort();
|
||||
(_certPath, _keyPath) = TlsHelperTests.GenerateTestCertFiles();
|
||||
_port = TestPortAllocator.GetFreePort();
|
||||
(_certPath, _keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
_server = new NatsServer(
|
||||
new NatsOptions
|
||||
{
|
||||
@@ -98,25 +99,11 @@ public class TlsServerTests : IAsyncLifetime
|
||||
await ssl2.FlushAsync();
|
||||
|
||||
// Client 1 should receive MSG (may arrive across multiple TLS records)
|
||||
var msg = await ReadUntilAsync(ssl1, "hello");
|
||||
var msg = await SocketTestHelper.ReadUntilAsync(ssl1, "hello");
|
||||
msg.ShouldContain("MSG test 1 5");
|
||||
msg.ShouldContain("hello");
|
||||
}
|
||||
|
||||
private static async Task<string> ReadUntilAsync(Stream stream, 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 stream.ReadAsync(buf, cts.Token);
|
||||
if (n == 0) break;
|
||||
sb.Append(Encoding.ASCII.GetString(buf, 0, n));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static async Task<SslStream> UpgradeToTlsAsync(TcpClient tcp)
|
||||
{
|
||||
var netStream = tcp.GetStream();
|
||||
@@ -127,13 +114,6 @@ public class TlsServerTests : IAsyncLifetime
|
||||
await ssl.AuthenticateAsClientAsync("localhost");
|
||||
return ssl;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public class TlsMixedModeTests : IAsyncLifetime
|
||||
@@ -146,8 +126,8 @@ public class TlsMixedModeTests : IAsyncLifetime
|
||||
|
||||
public TlsMixedModeTests()
|
||||
{
|
||||
_port = GetFreePort();
|
||||
(_certPath, _keyPath) = TlsHelperTests.GenerateTestCertFiles();
|
||||
_port = TestPortAllocator.GetFreePort();
|
||||
(_certPath, _keyPath) = TestCertHelper.GenerateTestCertFiles();
|
||||
_server = new NatsServer(
|
||||
new NatsOptions
|
||||
{
|
||||
@@ -215,11 +195,4 @@ public class TlsMixedModeTests : IAsyncLifetime
|
||||
var pong = Encoding.ASCII.GetString(pongBuf, 0, read);
|
||||
pong.ShouldContain("PONG");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Shouldly;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WebSocketOptionsTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using NATS.Server.Auth;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WebSocketOptionsValidatorParityBatch2Tests
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="WebSocketTlsConfig"/> — WebSocket-specific TLS configuration
|
||||
@@ -6,7 +6,7 @@
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsCompressionNegotiationTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsCompressionTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Buffers.Binary;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsConnectionTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsConstantsTests
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsFrameReadTests
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsFrameWriterTests
|
||||
{
|
||||
@@ -8,7 +8,7 @@ using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Parity tests ported from Go server/websocket_test.go exercising WebSocket
|
||||
@@ -5,7 +5,7 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
@@ -67,8 +67,12 @@ public class WsIntegrationTests : IAsyncLifetime
|
||||
using var sub = await ConnectWsClient();
|
||||
using var pub = await ConnectWsClient();
|
||||
|
||||
await SendWsText(sub, "CONNECT {}\r\nSUB test.ws 1\r\n");
|
||||
await Task.Delay(200);
|
||||
await SendWsText(sub, "CONNECT {}\r\nSUB test.ws 1\r\nPING\r\n");
|
||||
|
||||
// Wait for PONG to confirm subscription is registered
|
||||
using var subCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
var pong = await ReadWsFrameAsync(sub, subCts.Token);
|
||||
Encoding.ASCII.GetString(pong).ShouldContain("PONG");
|
||||
|
||||
await SendWsText(pub, "CONNECT {}\r\nPUB test.ws 5\r\nHello\r\n");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsJwtAuthTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using NATS.Server.WebSocket;
|
||||
using Shouldly;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsOriginCheckerTests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsUpgradeHelperParityBatch1Tests
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Text;
|
||||
using NATS.Server.WebSocket;
|
||||
|
||||
namespace NATS.Server.Tests.WebSocket;
|
||||
namespace NATS.Server.Transport.Tests.WebSocket;
|
||||
|
||||
public class WsUpgradeTests
|
||||
{
|
||||
Reference in New Issue
Block a user