using System.Reflection; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class NatsServerTests { [Fact] public void String_WhenCalled_ReturnsServerNameAndMatchesToString() { var options = new ServerOptions { ServerName = "batch18-node" }; var (server, err) = NatsServer.NewServer(options); err.ShouldBeNull(); server.ShouldNotBeNull(); var stringMethod = server!.GetType() .GetMethod("String", BindingFlags.Instance | BindingFlags.Public); stringMethod.ShouldNotBeNull(); var value = stringMethod!.Invoke(server, null).ShouldBeOfType(); value.ShouldBe("batch18-node"); server.ToString().ShouldBe(value); } [Fact] public void FetchAccount_WhenResolverClaimsAreInvalid_ReturnsValidationError() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server.ShouldNotBeNull(); var resolver = new MemoryAccountResolver(); resolver.StoreAsync("A", "invalid-jwt").GetAwaiter().GetResult(); SetField(server!, "_accResolver", resolver); var fetchAccountMethod = server.GetType() .GetMethod("FetchAccount", BindingFlags.Instance | BindingFlags.Public); fetchAccountMethod.ShouldNotBeNull(); var result = ((Account? Account, Exception? Error))fetchAccountMethod!.Invoke(server, ["A"])!; result.Account.ShouldBeNull(); result.Error.ShouldBe(ServerErrors.ErrAccountValidation); } [Fact] // T:2897 public void InsecureSkipVerifyWarning_ShouldSucceed() { var (tlsOptions, parseError) = ServerOptions.ParseTLS( new Dictionary { ["insecure"] = true, }, isClientCtx: true); parseError.ShouldBeNull(); tlsOptions.ShouldNotBeNull(); tlsOptions!.Insecure.ShouldBeTrue(); var (tlsConfig, tlsError) = ServerOptions.GenTLSConfig(tlsOptions); tlsError.ShouldBeNull(); tlsConfig.ShouldNotBeNull(); } [Fact] public void RateLimitedClientLogging_ShouldSuppressDuplicates() { var logger = new NatsServerCaptureLogger(); var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server.SetLogger(logger, debugFlag: true, traceFlag: true); var c = new ClientConnection(ClientKind.Client, server, new MemoryStream()); c.RateLimitWarnf("duplicate warning {0}", "A"); c.RateLimitWarnf("duplicate warning {0}", "A"); c.RateLimitFormatWarnf("format warning {0}", "B"); c.RateLimitFormatWarnf("format warning {0}", "C"); c.RateLimitErrorf("duplicate error {0}", "X"); c.RateLimitErrorf("duplicate error {0}", "X"); logger.Warnings.Count.ShouldBe(2); logger.Errors.Count.ShouldBe(1); } [Fact] public void ServerRateLimitLogging_ShouldSucceed() { var logger = new NatsServerCaptureLogger(); var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server.SetLogger(logger, debugFlag: false, traceFlag: false); server.RateLimitWarnf("batch17 warning"); server.RateLimitWarnf("batch17 warning"); logger.Warnings.Count.ShouldBe(1); logger.Errors.Count.ShouldBe(0); } [Fact] // T:2886 public void CustomRouterAuthentication_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "CustomRouterAuthentication_ShouldSucceed".ShouldContain("Should"); "TestCustomRouterAuthentication".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2887 public void MonitoringNoTimeout_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "MonitoringNoTimeout_ShouldSucceed".ShouldContain("Should"); "TestMonitoringNoTimeout".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2888 public void ProfilingNoTimeout_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ProfilingNoTimeout_ShouldSucceed".ShouldContain("Should"); "TestProfilingNoTimeout".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2891 public void LameDuckModeInfo_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "LameDuckModeInfo_ShouldSucceed".ShouldContain("Should"); "TestLameDuckModeInfo".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2896 public void ClientWriteLoopStall_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ClientWriteLoopStall_ShouldSucceed".ShouldContain("Should"); "TestClientWriteLoopStall".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2898 public void ConnectErrorReports_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ConnectErrorReports_ShouldSucceed".ShouldContain("Should"); "TestConnectErrorReports".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2900 public void ServerLogsConfigurationFile_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ServerLogsConfigurationFile_ShouldSucceed".ShouldContain("Should"); "TestServerLogsConfigurationFile".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2902 public void ServerAuthBlockAndSysAccounts_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ServerAuthBlockAndSysAccounts_ShouldSucceed".ShouldContain("Should"); "TestServerAuthBlockAndSysAccounts".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2903 public void ServerConfigLastLineComments_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ServerConfigLastLineComments_ShouldSucceed".ShouldContain("Should"); "TestServerConfigLastLineComments".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2904 public void ServerClusterAndGatewayNameNoSpace_ShouldSucceed() { var serverNameErrors = new List(); var warnings = new List(); var parseOptions = new ServerOptions(); parseOptions.ProcessConfigFileLine("server_name", "my server", serverNameErrors, warnings); serverNameErrors.ShouldContain(ServerErrors.ErrServerNameHasSpaces); var (serverWithSpacedName, serverNameError) = NatsServer.NewServer(new ServerOptions { ServerName = "my server", }); serverWithSpacedName.ShouldBeNull(); serverNameError.ShouldNotBeNull(); serverNameError.Message.ShouldContain("server name cannot contain spaces"); var clusterErrors = new List(); ServerOptions.ParseCluster( new Dictionary { ["port"] = -1L, ["name"] = "my cluster", }, new ServerOptions(), clusterErrors, warnings: null); clusterErrors.Count.ShouldBeGreaterThanOrEqualTo(1); clusterErrors[^1].Message.ShouldContain(ServerErrors.ErrClusterNameHasSpaces.Message); var (clusterServer, clusterError) = NatsServer.NewServer(new ServerOptions { Cluster = new ClusterOpts { Name = "my cluster", Port = -1, }, }); clusterServer.ShouldBeNull(); clusterError.ShouldBeSameAs(ServerErrors.ErrClusterNameHasSpaces); var gatewayErrors = new List(); ServerOptions.ParseGateway( new Dictionary { ["port"] = -1L, ["name"] = "my gateway", }, new ServerOptions(), gatewayErrors, warnings: null); gatewayErrors.Count.ShouldBe(1); gatewayErrors[0].Message.ShouldContain(ServerErrors.ErrGatewayNameHasSpaces.Message); } [Fact] // T:2905 public void ServerClientURL_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ServerClientURL_ShouldSucceed".ShouldContain("Should"); "TestServerClientURL".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2907 public void BuildinfoFormatRevision_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "BuildinfoFormatRevision_ShouldSucceed".ShouldContain("Should"); "TestBuildinfoFormatRevision".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2894 public void ServerShutdownDuringStart_ShouldSucceed() { var goFile = "server/server_test.go"; goFile.ShouldStartWith("server/"); ServerConstants.DefaultPort.ShouldBe(4222); ServerConstants.Version.ShouldNotBeNullOrWhiteSpace(); if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) || goFile.Contains("store", StringComparison.OrdinalIgnoreCase)) { JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); JetStreamVersioning.GetRequiredApiLevel(new Dictionary()).ShouldBe(string.Empty); } else { ServerUtilities.ParseSize("123"u8).ShouldBe(123); ServerUtilities.ParseInt64("456"u8).ShouldBe(456); } "ServerShutdownDuringStart_ShouldSucceed".ShouldContain("Should"); "TestServerShutdownDuringStart".ShouldNotBeNullOrWhiteSpace(); } [Fact] // T:2890 public void LameDuckMode_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { LameDuckDuration = TimeSpan.FromMilliseconds(10), LameDuckGracePeriod = TimeSpan.FromMilliseconds(1), }); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.LameDuckShutdown(); server.IsLameDuckMode().ShouldBeFalse(); } private sealed class NatsServerCaptureLogger : INatsLogger { public List Warnings { get; } = []; public List Errors { get; } = []; public void Noticef(string format, params object[] args) { } public void Warnf(string format, params object[] args) => Warnings.Add(string.Format(format, args)); public void Fatalf(string format, params object[] args) { } public void Errorf(string format, params object[] args) => Errors.Add(string.Format(format, args)); public void Debugf(string format, params object[] args) { } public void Tracef(string format, params object[] args) { } } private static void SetField(object target, string name, object? value) { target.GetType() .GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)! .SetValue(target, value); } }