using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth; using System.Linq; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class ServerOptionsTests { private static Dictionary Map(params (string Key, object? Value)[] entries) { var map = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var (key, value) in entries) map[key] = value; return map; } private static List Arr(params object?[] entries) => [.. entries]; private static string CreateJsonConfig(string json) { var path = Path.GetTempFileName(); File.WriteAllText(path, json); return path; } private static T ReadProperty(object target, string propertyName) { var property = target.GetType().GetProperty(propertyName); property.ShouldNotBeNull(); var value = property.GetValue(target); value.ShouldNotBeNull(); return (T)value; } [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:2514 public void ConfigFile_ShouldSucceed() { var path = CreateJsonConfig(""" { "Host": "127.0.0.1", "Port": 4242, "SystemAccount": "$SYS" } """); try { var opts = ServerOptions.ProcessConfigFile(path); opts.Host.ShouldBe("127.0.0.1"); opts.Port.ShouldBe(4242); opts.SystemAccount.ShouldBe("$SYS"); } finally { File.Delete(path); } } [Fact] // T:2517 public void RouteFlagOverride_ShouldSucceed() { var merged = ServerOptions.MergeOptions( new ServerOptions(), new ServerOptions { RoutesStr = "nats-route://ruser:top_secret@127.0.0.1:8246" }); merged.RoutesStr.ShouldBe("nats-route://ruser:top_secret@127.0.0.1:8246"); merged.Routes.Count.ShouldBe(1); merged.Routes[0].ToString().ShouldBe("nats-route://ruser:top_secret@127.0.0.1:8246/"); } [Fact] // T:2519 public void RouteFlagOverrideWithMultiple_ShouldSucceed() { var routes = "nats-route://ruser:top_secret@127.0.0.1:8246, nats-route://ruser:top_secret@127.0.0.1:8266"; var merged = ServerOptions.MergeOptions(new ServerOptions(), new ServerOptions { RoutesStr = routes }); merged.RoutesStr.ShouldBe(routes); merged.Routes.Count.ShouldBe(2); } [Fact] // T:2520 public void DynamicPortOnListen_ShouldSucceed() { var (host, port) = ServerOptions.ParseListen("127.0.0.1:-1"); host.ShouldBe("127.0.0.1"); port.ShouldBe(-1); } [Fact] // T:2521 public void ListenConfig_ShouldSucceed() { var opts = new ServerOptions(); var error = opts.ProcessConfigString(""" { "listen": "10.0.1.22:4422", "cluster": { "listen": "127.0.0.1:4244" } } """); error.ShouldBeNull(); opts.SetBaselineOptions(); opts.Host.ShouldBe("10.0.1.22"); opts.Port.ShouldBe(4422); opts.Cluster.Host.ShouldBe("127.0.0.1"); opts.Cluster.Port.ShouldBe(4244); } [Fact] // T:2522 public void ListenPortOnlyConfig_ShouldSucceed() { var opts = new ServerOptions(); var error = opts.ProcessConfigString(""" { "listen": 8922 } """); error.ShouldBeNull(); opts.SetBaselineOptions(); opts.Host.ShouldBe(ServerConstants.DefaultHost); opts.Port.ShouldBe(8922); } [Fact] // T:2523 public void ListenPortWithColonConfig_ShouldSucceed() { var (host, port) = ServerOptions.ParseListen("127.0.0.1:8922"); host.ShouldBe("127.0.0.1"); port.ShouldBe(8922); } [Fact] // T:2525 public void MultipleUsersConfig_ShouldSucceed() { var (nkeys, users, error) = ServerOptions.ParseUsers( Arr( Map(("user", "alice"), ("password", "foo")), Map(("user", "bob"), ("password", "bar")))); error.ShouldBeNull(); users.Count.ShouldBe(2); nkeys.ShouldBeEmpty(); } [Fact] // T:2526 public void AuthorizationConfig_ShouldSucceed() { var (auth, error) = ServerOptions.ParseAuthorization( Map(("users", Arr( Map( ("user", "alice"), ("password", "pwd"), ("permissions", Map(("publish", "*"), ("subscribe", ">")))), Map(("user", "bob"), ("password", "pwd")))))); error.ShouldBeNull(); auth.ShouldNotBeNull(); auth.Users.Count.ShouldBe(2); var alice = auth.Users.Single(u => u.Username == "alice"); alice.Permissions.ShouldNotBeNull(); alice.Permissions.Publish.ShouldNotBeNull(); alice.Permissions.Publish.Allow.ShouldContain("*"); alice.Permissions.Subscribe.ShouldNotBeNull(); alice.Permissions.Subscribe.Allow.ShouldContain(">"); } [Fact] // T:2527 public void NewStyleAuthorizationConfig_ShouldSucceed() { var (auth, error) = ServerOptions.ParseAuthorization( Map(("users", Arr( Map( ("user", "alice"), ("password", "pwd"), ("permissions", Map( ("publish", Map(("allow", Arr("foo", "bar", "baz")))), ("subscribe", Map(("deny", Arr("$SYS.>"))))))))))); error.ShouldBeNull(); auth.ShouldNotBeNull(); var alice = auth.Users.Single(); alice.Permissions.ShouldNotBeNull(); alice.Permissions.Publish.Allow.Count.ShouldBe(3); alice.Permissions.Subscribe.Deny.ShouldContain("$SYS.>"); } [Fact] // T:2528 public void NkeyUsersConfig_ShouldSucceed() { var (nkeys, users, error) = ServerOptions.ParseUsers( Arr( Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV")), Map(("nkey", "UA3C5TBZYK5GJQJRWPMU6NFY5JNAEVQB2V2TUZFZDHFJFUYVKTTUOFKZ")))); error.ShouldBeNull(); nkeys.Count.ShouldBe(2); users.ShouldBeEmpty(); } [Fact] // T:2529 public void TlsPinnedCertificates_ShouldSucceed() { var (tlsOptions, error) = ServerOptions.ParseTLS( Map(("pinned_certs", Arr( "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069", "a8f407340dcc719864214b85ed96f98d16cbffa8f509d9fa4ca237b7bb3f9c32"))), isClientCtx: false); error.ShouldBeNull(); tlsOptions.ShouldNotBeNull(); tlsOptions.PinnedCerts.ShouldNotBeNull(); tlsOptions.PinnedCerts.Count.ShouldBe(2); } [Fact] // T:2530 public void NkeyUsersDefaultPermissionsConfig_ShouldSucceed() { var (auth, error) = ServerOptions.ParseAuthorization( Map( ("default_permissions", Map(("publish", "foo"))), ("users", Arr( Map(("user", "user"), ("password", "pwd")), Map(("user", "other"), ("password", "pwd"), ("permissions", Map(("subscribe", "bar")))), Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV")), Map(("nkey", "UA3C5TBZYK5GJQJRWPMU6NFY5JNAEVQB2V2TUZFZDHFJFUYVKTTUOFKZ"), ("permissions", Map(("subscribe", "bar")))))))); error.ShouldBeNull(); auth.ShouldNotBeNull(); var defaultUser = auth.Users.Single(u => u.Username == "user"); defaultUser.Permissions.ShouldNotBeNull(); defaultUser.Permissions.Publish.ShouldNotBeNull(); defaultUser.Permissions.Publish.Allow.ShouldContain("foo"); var defaultNkey = auth.Nkeys.Single(n => n.Nkey.StartsWith("UDK", StringComparison.Ordinal)); defaultNkey.Permissions.ShouldNotBeNull(); defaultNkey.Permissions.Publish.ShouldNotBeNull(); defaultNkey.Permissions.Publish.Allow.ShouldContain("foo"); } [Fact] // T:2531 public void NkeyUsersWithPermsConfig_ShouldSucceed() { var (nkeys, users, error) = ServerOptions.ParseUsers( Arr(Map( ("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"), ("permissions", Map( ("publish", "$SYS.>"), ("subscribe", Map(("deny", Arr("foo", "bar", "baz"))))))))); error.ShouldBeNull(); users.ShouldBeEmpty(); nkeys.Count.ShouldBe(1); nkeys[0].Permissions.ShouldNotBeNull(); nkeys[0].Permissions.Publish.Allow.ShouldContain("$SYS.>"); nkeys[0].Permissions.Subscribe.Deny.Count.ShouldBe(3); } [Fact] // T:2532 public void BadNkeyConfig_ShouldSucceed() { var (_, _, error) = ServerOptions.ParseUsers(Arr(Map(("nkey", "Ufoo")))); error.ShouldNotBeNull(); error.Message.ShouldContain("Not a valid public nkey"); } [Fact] // T:2533 public void NkeyWithPassConfig_ShouldSucceed() { var (_, _, error) = ServerOptions.ParseUsers( Arr(Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"), ("pass", "foo")))); error.ShouldNotBeNull(); error.Message.ShouldContain("Nkey users do not take usernames or passwords"); } [Fact] // T:2540 public void EmptyConfig_ShouldSucceed() { var opts = new ServerOptions(); var error = opts.ProcessConfigFileOverload2510(string.Empty); error.ShouldBeNull(); opts.ConfigFile.ShouldBe(string.Empty); } [Fact] // T:2541 public void MalformedListenAddress_ShouldSucceed() { Should.Throw(() => ServerOptions.ParseListen("bad::address")); } [Fact] // T:2542 public void MalformedClusterAddress_ShouldSucceed() { var opts = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseCluster(Map(("listen", "bad::address")), opts, errors, warnings); parseError.ShouldBeNull(); errors.Count.ShouldBeGreaterThan(0); } [Fact] // T:2545 public void PingIntervalOld_ShouldSucceed() { var errors = new List(); var warnings = new List(); var parsed = ServerOptions.ParseDuration("ping_interval", 5L, errors, warnings); parsed.ShouldBe(TimeSpan.FromSeconds(5)); errors.ShouldBeEmpty(); warnings.Count.ShouldBe(1); } [Fact] // T:2546 public void PingIntervalNew_ShouldSucceed() { var errors = new List(); var warnings = new List(); var parsed = ServerOptions.ParseDuration("ping_interval", "5m", errors, warnings); parsed.ShouldBe(TimeSpan.FromMinutes(5)); errors.ShouldBeEmpty(); warnings.ShouldBeEmpty(); } [Fact] // T:2547 public void OptionsProcessConfigFile_ShouldSucceed() { var path = CreateJsonConfig(""" { "debug": false, "trace": true } """); try { var opts = new ServerOptions { Debug = true, Trace = false, LogFile = "test.log", }; var error = opts.ProcessConfigFileOverload2510(path); error.ShouldBeNull(); opts.ConfigFile.ShouldBe(path); opts.Debug.ShouldBeFalse(); opts.Trace.ShouldBeTrue(); opts.LogFile.ShouldBe("test.log"); } finally { File.Delete(path); } } [Fact] // T:2549 public void ClusterPermissionsConfig_ShouldSucceed() { var cluster = new ClusterOpts(); var permissions = new Permissions { Publish = new SubjectPermission { Allow = ["foo"] }, Subscribe = new SubjectPermission { Allow = ["bar"] }, }; ServerOptions.SetClusterPermissions(cluster, permissions); cluster.Permissions.ShouldNotBeNull(); cluster.Permissions.Import.ShouldNotBeNull(); cluster.Permissions.Import.Allow.ShouldContain("foo"); cluster.Permissions.Export.ShouldNotBeNull(); cluster.Permissions.Export.Allow.ShouldContain("bar"); } [Fact] // T:2550 public void ParseServiceLatency_ShouldSucceed() { var (latency, error) = ServerOptions.ParseServiceLatency( "latency", Map(("sampling", "33%"), ("subject", "latency.tracking.add"))); error.ShouldBeNull(); latency.ShouldNotBeNull(); ReadProperty(latency, "Sampling").ShouldBe(33); ReadProperty(latency, "Subject").ShouldBe("latency.tracking.add"); } [Fact] // T:2553 public void ParsingGateways_ShouldSucceed() { var opts = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseGateway( Map( ("name", "A"), ("listen", "127.0.0.1:4444"), ("authorization", Map(("user", "ivan"), ("password", "pwd"), ("timeout", 2L))), ("advertise", "me:1"), ("connect_retries", 10L), ("connect_backoff", true), ("reject_unknown_cluster", true)), opts, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); opts.Gateway.Name.ShouldBe("A"); opts.Gateway.Host.ShouldBe("127.0.0.1"); opts.Gateway.Port.ShouldBe(4444); opts.Gateway.Username.ShouldBe("ivan"); opts.Gateway.Password.ShouldBe("pwd"); opts.Gateway.AuthTimeout.ShouldBe(2); opts.Gateway.ConnectRetries.ShouldBe(10); opts.Gateway.ConnectBackoff.ShouldBeTrue(); opts.Gateway.RejectUnknown.ShouldBeTrue(); } [Fact] // T:2555 public void ParsingLeafNodesListener_ShouldSucceed() { var opts = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseLeafNodes( Map( ("listen", "127.0.0.1:3333"), ("authorization", Map(("user", "derek"), ("password", "s3cr3t!"), ("timeout", 2.2))), ("advertise", "me:22")), opts, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); opts.LeafNode.Host.ShouldBe("127.0.0.1"); opts.LeafNode.Port.ShouldBe(3333); opts.LeafNode.Username.ShouldBe("derek"); opts.LeafNode.Password.ShouldBe("s3cr3t!"); opts.LeafNode.AuthTimeout.ShouldBe(2.2); opts.LeafNode.Advertise.ShouldBe("me:22"); } [Fact] // T:2556 public void ParsingLeafNodeRemotes_ShouldSucceed() { var remotes = ServerOptions.ParseRemoteLeafNodes( Arr( Map( ("url", "nats-leaf://127.0.0.1:2222"), ("account", "foobar"), ("credentials", "./my.creds")))); remotes.Count.ShouldBe(1); remotes[0].Urls.Count.ShouldBe(1); remotes[0].Urls[0].ToString().ShouldBe("nats-leaf://127.0.0.1:2222/"); remotes[0].LocalAccount.ShouldBe("foobar"); remotes[0].Credentials.ShouldContain("my.creds"); } [Fact] // T:2560 public void SublistNoCacheConfig_ShouldSucceed() { var opts = new ServerOptions(); var error = opts.ProcessConfigString(""" { "disable_sublist_cache": true } """); error.ShouldBeNull(); opts.NoSublistCache.ShouldBeTrue(); } [Fact] // T:2583 public void OptionsProxyRequired_ShouldSucceed() { var (authSingle, singleError) = ServerOptions.ParseAuthorization( Map( ("user", "user"), ("password", "pwd"), ("proxy_required", true))); singleError.ShouldBeNull(); authSingle.ShouldNotBeNull(); authSingle.ProxyRequired.ShouldBeTrue(); var (authUsers, usersError) = ServerOptions.ParseAuthorization( Map( ("users", Arr( Map(("user", "user1"), ("password", "pwd1")), Map(("user", "user2"), ("password", "pwd2"), ("proxy_required", true)), Map(("nkey", "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"), ("proxy_required", true)), Map(("nkey", "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"), ("proxy_required", false)))))); usersError.ShouldBeNull(); authUsers.ShouldNotBeNull(); authUsers.Users.Single(u => u.Username == "user2").ProxyRequired.ShouldBeTrue(); authUsers.Users.Single(u => u.Username == "user1").ProxyRequired.ShouldBeFalse(); authUsers.Nkeys.Single(n => n.Nkey.StartsWith("UCAR", StringComparison.Ordinal)).ProxyRequired.ShouldBeTrue(); } [Fact] // T:2588 public void WebsocketPingIntervalConfig_ShouldSucceed() { var opts = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseWebsocket( Map(("port", 8080L), ("ping_interval", "30s")), opts, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); opts.Websocket.PingInterval.ShouldBe(TimeSpan.FromSeconds(30)); parseError = ServerOptions.ParseWebsocket( Map(("port", 8080L), ("ping_interval", 45L)), opts, errors, warnings); parseError.ShouldBeNull(); opts.Websocket.PingInterval.ShouldBe(TimeSpan.FromSeconds(45)); } [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); } } }