using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class ServerOptionsTests { [Fact] public void DeepCopyURLs_WithEntries_ReturnsIndependentCopy() { var source = new List { new("nats://127.0.0.1:4222"), new("nats://127.0.0.1:4223"), }; var copy = ServerOptions.DeepCopyURLs(source); copy.ShouldNotBeNull(); copy.Count.ShouldBe(2); ReferenceEquals(copy, source).ShouldBeFalse(); copy[0].ToString().ShouldBe(source[0].ToString()); copy[1].ToString().ShouldBe(source[1].ToString()); } [Fact] public void ProcessConfigFile_WithValidFile_ReturnsParsedOptions() { var tempFile = Path.GetTempFileName(); try { File.WriteAllText(tempFile, """ { "host": "127.0.0.1", "port": 4444, "system_account": "$SYS" } """); var options = ServerOptions.ProcessConfigFile(tempFile); options.Host.ShouldBe("127.0.0.1"); options.Port.ShouldBe(4444); options.SystemAccount.ShouldBe("$SYS"); } finally { File.Delete(tempFile); } } [Fact] public void ConfigureSystemAccount_WithSystemAccountString_SetsValue() { var options = new ServerOptions(); var config = new Dictionary { ["system_account"] = "$SYSX", }; var error = ServerOptions.ConfigureSystemAccount(options, config); error.ShouldBeNull(); options.SystemAccount.ShouldBe("$SYSX"); } [Fact] public void ConfigureSystemAccount_WithNonString_ReturnsError() { var options = new ServerOptions(); var config = new Dictionary { ["system"] = 123L, }; var error = ServerOptions.ConfigureSystemAccount(options, config); error.ShouldNotBeNull(); error.Message.ShouldContain("must be a string"); } [Fact] public void SetupUsersAndNKeysDuplicateCheckMap_WithUsersAndNkeys_IncludesAllIdentities() { var options = new ServerOptions { Users = [ new User { Username = "alice" }, new User { Username = "bob" }, ], Nkeys = [ new NkeyUser { Nkey = "UAA" }, new NkeyUser { Nkey = "UBB" }, ], }; var map = ServerOptions.SetupUsersAndNKeysDuplicateCheckMap(options); map.Count.ShouldBe(4); map.ShouldContain("alice"); map.ShouldContain("bob"); map.ShouldContain("UAA"); map.ShouldContain("UBB"); } [Fact] public void ParseDuration_WithDurationString_ReturnsExpectedDuration() { var errors = new List(); var warnings = new List(); var parsed = ServerOptions.ParseDuration("write_deadline", "5s", errors, warnings); parsed.ShouldBe(TimeSpan.FromSeconds(5)); errors.ShouldBeEmpty(); warnings.ShouldBeEmpty(); } [Fact] public void ParseDuration_WithLegacySeconds_AddsWarning() { var errors = new List(); var warnings = new List(); var parsed = ServerOptions.ParseDuration("auth_timeout", 3L, errors, warnings); parsed.ShouldBe(TimeSpan.FromSeconds(3)); errors.ShouldBeEmpty(); warnings.Count.ShouldBe(1); } [Fact] public void ParseWriteDeadlinePolicy_WithInvalidValue_ReturnsDefaultAndError() { var errors = new List(); var policy = ServerOptions.ParseWriteDeadlinePolicy("invalid", errors); policy.ShouldBe(WriteTimeoutPolicy.Default); errors.Count.ShouldBe(1); errors[0].Message.ShouldContain("write_timeout"); } [Theory] [InlineData(8222L, "", 8222)] [InlineData("127.0.0.1:6222", "127.0.0.1", 6222)] public void ParseListen_WithValidInput_ReturnsHostPort(object input, string expectedHost, int expectedPort) { var (host, port) = ServerOptions.ParseListen(input); host.ShouldBe(expectedHost); port.ShouldBe(expectedPort); } [Fact] public void ParseCompression_WithMapValue_ParsesModeAndThresholds() { var compression = new CompressionOpts(); var error = ServerOptions.ParseCompression( compression, CompressionModes.S2Fast, "compression", new Dictionary { ["mode"] = CompressionModes.S2Best, ["rtt_thresholds"] = new List { "10ms", "25ms" }, }); error.ShouldBeNull(); compression.Mode.ShouldBe(CompressionModes.S2Best); compression.RttThresholds.Count.ShouldBe(2); compression.RttThresholds[0].ShouldBe(TimeSpan.FromMilliseconds(10)); compression.RttThresholds[1].ShouldBe(TimeSpan.FromMilliseconds(25)); } [Fact] public void ParseURLs_WithDuplicateEntries_DeduplicatesAndWarns() { var warnings = new List(); var errors = new List(); var urls = ServerOptions.ParseURLs( ["nats://127.0.0.1:4222", "nats://127.0.0.1:4222", "nats://127.0.0.1:4223"], "route", warnings, errors); errors.ShouldBeEmpty(); warnings.Count.ShouldBe(1); urls.Count.ShouldBe(2); } [Fact] public void ParseCluster_WithBasicConfig_PopulatesClusterAndRoutes() { var options = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseCluster( new Dictionary { ["name"] = "core", ["listen"] = "127.0.0.1:6222", ["connect_retries"] = 8L, ["connect_backoff"] = true, ["no_advertise"] = true, ["compression"] = "s2_fast", ["routes"] = new List { "nats://127.0.0.1:6223" }, }, options, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); options.Cluster.Name.ShouldBe("core"); options.Cluster.Host.ShouldBe("127.0.0.1"); options.Cluster.Port.ShouldBe(6222); options.Cluster.ConnectRetries.ShouldBe(8); options.Cluster.ConnectBackoff.ShouldBeTrue(); options.Cluster.NoAdvertise.ShouldBeTrue(); options.Cluster.Compression.Mode.ShouldBe("s2_fast"); options.Routes.Count.ShouldBe(1); options.Routes[0].ToString().ShouldBe("nats://127.0.0.1:6223/"); } [Fact] public void ParseGateway_WithBasicConfig_PopulatesGateway() { var options = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseGateway( new Dictionary { ["name"] = "edge", ["listen"] = "127.0.0.1:7222", ["connect_retries"] = 4L, ["connect_backoff"] = true, ["advertise"] = "gw.local:7222", ["reject_unknown"] = true, ["authorization"] = new Dictionary { ["user"] = "gwu", ["password"] = "gwp", ["auth_timeout"] = 3L, }, }, options, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); options.Gateway.Name.ShouldBe("edge"); options.Gateway.Host.ShouldBe("127.0.0.1"); options.Gateway.Port.ShouldBe(7222); options.Gateway.ConnectRetries.ShouldBe(4); options.Gateway.ConnectBackoff.ShouldBeTrue(); options.Gateway.Advertise.ShouldBe("gw.local:7222"); options.Gateway.RejectUnknown.ShouldBeTrue(); options.Gateway.Username.ShouldBe("gwu"); options.Gateway.Password.ShouldBe("gwp"); options.Gateway.AuthTimeout.ShouldBe(3); } [Fact] // T:2586 public void WriteDeadlineConfigParsing_ShouldSucceed() { var options = new ServerOptions(); var errors = new List(); var warnings = new List(); var leafError = ServerOptions.ParseLeafNodes( new Dictionary { ["write_deadline"] = "5s", }, options, errors, warnings); var gatewayError = ServerOptions.ParseGateway( new Dictionary { ["write_deadline"] = "6s", }, options, errors, warnings); var clusterError = ServerOptions.ParseCluster( new Dictionary { ["write_deadline"] = "7s", }, options, errors, warnings); options.WriteDeadline = ServerOptions.ParseDuration("write_deadline", "8s", errors, warnings); leafError.ShouldBeNull(); gatewayError.ShouldBeNull(); clusterError.ShouldBeNull(); errors.ShouldBeEmpty(); options.LeafNode.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(5)); options.Gateway.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(6)); options.Cluster.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(7)); options.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(8)); } [Fact] // T:2587 public void WriteTimeoutConfigParsing_ShouldSucceed() { var expectedPolicies = new Dictionary(StringComparer.Ordinal) { ["default"] = WriteTimeoutPolicy.Default, ["retry"] = WriteTimeoutPolicy.Retry, ["close"] = WriteTimeoutPolicy.Close, }; foreach (var (rawPolicy, expectedPolicy) in expectedPolicies) { var options = new ServerOptions(); var errors = new List(); var warnings = new List(); var leafError = ServerOptions.ParseLeafNodes( new Dictionary { ["write_timeout"] = rawPolicy, }, options, errors, warnings); var gatewayError = ServerOptions.ParseGateway( new Dictionary { ["write_timeout"] = rawPolicy, }, options, errors, warnings); var clusterError = ServerOptions.ParseCluster( new Dictionary { ["write_timeout"] = rawPolicy, }, options, errors, warnings); options.WriteTimeout = ServerOptions.ParseWriteDeadlinePolicy(rawPolicy, errors); leafError.ShouldBeNull(); gatewayError.ShouldBeNull(); clusterError.ShouldBeNull(); errors.ShouldBeEmpty(); options.LeafNode.WriteTimeout.ShouldBe(expectedPolicy); options.Gateway.WriteTimeout.ShouldBe(expectedPolicy); options.Cluster.WriteTimeout.ShouldBe(expectedPolicy); options.WriteTimeout.ShouldBe(expectedPolicy); } } }