feat(batch18): implement group-a server core helpers
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
using ZB.MOM.NatsNet.Server.Internal;
|
||||
@@ -10,6 +13,99 @@ namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;
|
||||
|
||||
public sealed class MonitoringHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetMonitoringTLSConfig_WithServerTlsConfig_DisablesClientCertificateRequirementOnClone()
|
||||
{
|
||||
var (certFile, keyFile, tempDir, _) = CreatePemCertificate(DateTimeOffset.UtcNow.AddMinutes(10));
|
||||
var (tlsOpts, parseErr) = ServerOptions.ParseTLS(
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["cert_file"] = certFile,
|
||||
["key_file"] = keyFile,
|
||||
["verify"] = true,
|
||||
},
|
||||
isClientCtx: false);
|
||||
parseErr.ShouldBeNull();
|
||||
tlsOpts.ShouldNotBeNull();
|
||||
|
||||
var (tlsConfig, tlsErr) = ServerOptions.GenTLSConfig(tlsOpts!);
|
||||
tlsErr.ShouldBeNull();
|
||||
tlsConfig.ShouldNotBeNull();
|
||||
|
||||
var opts = new ServerOptions { TlsConfig = tlsConfig };
|
||||
|
||||
var (server, error) = NatsServer.NewServer(opts);
|
||||
|
||||
error.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var monitoringTls = server!.GetMonitoringTLSConfig();
|
||||
monitoringTls.ShouldNotBeNull();
|
||||
monitoringTls!.ClientCertificateRequired.ShouldBeFalse();
|
||||
monitoringTls.CertificateRevocationCheckMode.ShouldBe(opts.TlsConfig!.CertificateRevocationCheckMode);
|
||||
opts.TlsConfig!.ClientCertificateRequired.ShouldBeTrue();
|
||||
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HTTPHandler_WhenMonitoringListenerStops_TransitionsToNull()
|
||||
{
|
||||
var opts = new ServerOptions
|
||||
{
|
||||
HttpHost = "127.0.0.1",
|
||||
HttpPort = -1,
|
||||
};
|
||||
var (server, error) = NatsServer.NewServer(opts);
|
||||
error.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
server!.HTTPHandler().ShouldBeNull();
|
||||
|
||||
var startError = server.StartMonitoring();
|
||||
startError.ShouldBeNull();
|
||||
server.HTTPHandler().ShouldNotBeNull();
|
||||
|
||||
var listenerField = typeof(NatsServer).GetField("_http", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
listenerField.ShouldNotBeNull();
|
||||
var listener = listenerField!.GetValue(server).ShouldBeOfType<TcpListener>();
|
||||
listener.Stop();
|
||||
|
||||
var transitioned = SpinWait.SpinUntil(() => server.HTTPHandler() == null, TimeSpan.FromSeconds(5));
|
||||
transitioned.ShouldBeTrue();
|
||||
server.HTTPHandler().ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LogRejectedTLSConns_WhenRateCounterHasBlockedConnections_EmitsWarning()
|
||||
{
|
||||
var opts = new ServerOptions { TlsRateLimit = 1 };
|
||||
var (server, error) = NatsServer.NewServer(opts);
|
||||
error.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var logger = new MonitoringCaptureLogger();
|
||||
server!.SetLogger(logger, debugFlag: false, traceFlag: false);
|
||||
|
||||
var rateCounterField = typeof(NatsServer).GetField("_connRateCounter", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
rateCounterField.ShouldNotBeNull();
|
||||
var rateCounter = rateCounterField!.GetValue(server).ShouldBeOfType<RateCounter>();
|
||||
|
||||
rateCounter.Allow();
|
||||
rateCounter.Allow();
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
var loop = server.LogRejectedTLSConns(cts.Token, TimeSpan.FromMilliseconds(10));
|
||||
|
||||
var warned = SpinWait.SpinUntil(
|
||||
() => logger.WarningEntries.Any(w => w.Contains("Rejected", StringComparison.OrdinalIgnoreCase)),
|
||||
TimeSpan.FromSeconds(2));
|
||||
cts.Cancel();
|
||||
await loop;
|
||||
|
||||
warned.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact] // T:2108
|
||||
public void MonitorConnzClosedConnsBadTLSClient_ShouldSucceed()
|
||||
{
|
||||
@@ -228,6 +324,18 @@ public sealed class MonitoringHandlerTests
|
||||
return (certFile, keyFile, tempDir, notAfter);
|
||||
}
|
||||
|
||||
private sealed class MonitoringCaptureLogger : INatsLogger
|
||||
{
|
||||
public List<string> WarningEntries { get; } = [];
|
||||
|
||||
public void Noticef(string format, params object[] args) { }
|
||||
public void Warnf(string format, params object[] args) => WarningEntries.Add(string.Format(format, args));
|
||||
public void Fatalf(string format, params object[] args) { }
|
||||
public void Errorf(string format, params object[] args) { }
|
||||
public void Debugf(string format, params object[] args) { }
|
||||
public void Tracef(string format, params object[] args) { }
|
||||
}
|
||||
|
||||
[Fact] // T:2065
|
||||
public void MonitorNoPort_ShouldSucceed()
|
||||
{
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net.Security;
|
||||
using System.Threading;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Shouldly;
|
||||
@@ -280,6 +282,27 @@ public sealed class ServerTests
|
||||
[Fact]
|
||||
public void NeedsCompression_S2Fast_ReturnsTrue()
|
||||
=> NatsServer.NeedsCompression(CompressionMode.S2Fast).ShouldBeTrue();
|
||||
|
||||
[Theory]
|
||||
[InlineData(CompressionMode.S2Uncompressed, "writer_concurrency=1", "writer_uncompressed")]
|
||||
[InlineData(CompressionMode.S2Best, "writer_concurrency=1", "writer_best_compression")]
|
||||
[InlineData(CompressionMode.S2Better, "writer_concurrency=1", "writer_better_compression")]
|
||||
public void S2WriterOptions_KnownModes_ReturnExpectedOptions(
|
||||
string mode,
|
||||
string expectedFirst,
|
||||
string expectedSecond)
|
||||
{
|
||||
var options = NatsServer.S2WriterOptions(mode);
|
||||
|
||||
options.ShouldNotBeNull();
|
||||
options!.ShouldBe([expectedFirst, expectedSecond]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void S2WriterOptions_UnsupportedMode_ReturnsNull()
|
||||
{
|
||||
NatsServer.S2WriterOptions(CompressionMode.S2Fast).ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -292,6 +315,57 @@ public sealed class ServerTests
|
||||
/// </summary>
|
||||
public sealed class ServerListenersTests
|
||||
{
|
||||
[Fact]
|
||||
public void TlsTimeout_IncompleteHandshake_ClosesConnection()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client, nc: new MemoryStream());
|
||||
using var tls = new SslStream(new MemoryStream(), leaveInnerStreamOpen: false);
|
||||
|
||||
c.IsClosed().ShouldBeFalse();
|
||||
|
||||
NatsServer.TlsTimeout(c, tls);
|
||||
|
||||
c.IsClosed().ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartGoRoutine_WithLabels_InvokesSetGoRoutineLabels()
|
||||
{
|
||||
var (s, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
s.ShouldNotBeNull();
|
||||
s!.Start();
|
||||
|
||||
var signal = new ManualResetEventSlim(false);
|
||||
IReadOnlyList<KeyValuePair<string, string>>? observed = null;
|
||||
|
||||
NatsServer.SetGoRoutineLabelsHookForTest = labels =>
|
||||
{
|
||||
observed = labels;
|
||||
signal.Set();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var started = s.StartGoRoutine(
|
||||
() => { },
|
||||
new Dictionary<string, string> { ["component"] = "server", ["loop"] = "tls" });
|
||||
|
||||
started.ShouldBeTrue();
|
||||
signal.Wait(TimeSpan.FromSeconds(2)).ShouldBeTrue();
|
||||
|
||||
observed.ShouldNotBeNull();
|
||||
observed!.ShouldContain(kv => kv.Key == "component" && kv.Value == "server");
|
||||
observed.ShouldContain(kv => kv.Key == "loop" && kv.Value == "tls");
|
||||
}
|
||||
finally
|
||||
{
|
||||
NatsServer.SetGoRoutineLabelsHookForTest = null;
|
||||
s.Shutdown();
|
||||
s.WaitForShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GenerateInfoJson (feature 3069) — Test ID 2906
|
||||
// Mirrors Go TestServerJsonMarshalNestedStructsPanic (guards against
|
||||
|
||||
Reference in New Issue
Block a user