diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch7T2.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch7T2.cs new file mode 100644 index 0000000..d87a407 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch7T2.cs @@ -0,0 +1,139 @@ +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed partial class LeafNodeHandlerTests +{ + [Fact] // T:1908 + public void LeafNodeTLSWithCerts_ShouldSucceed() + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var parseError = ServerOptions.ParseLeafNodes( + Map( + ("listen", "127.0.0.1:7422"), + ("tls", Map(("verify", true), ("map", true), ("timeout", 2L)))), + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.LeafNode.Port.ShouldBe(7422); + options.LeafNode.TlsConfig.ShouldNotBeNull(); + options.LeafNode.TlsTimeout.ShouldBe(2d); + options.LeafNode.TlsMap.ShouldBeTrue(); + } + + [Fact] // T:1909 + public void LeafNodeTLSRemoteWithNoCerts_ShouldSucceed() + { + var errors = new List(); + var warnings = new List(); + + var remotes = ServerOptions.ParseRemoteLeafNodes( + Arr( + Map( + ("url", "nats://localhost:7422"), + ("tls", Map(("timeout", 5L))))), + errors, + warnings); + + errors.ShouldBeEmpty(); + remotes.Count.ShouldBe(1); + remotes[0].TlsTimeout.ShouldBe(5d); + + remotes = ServerOptions.ParseRemoteLeafNodes( + Arr( + Map( + ("url", "nats://localhost:7422"), + ("tls", Map()))), + errors, + warnings); + + remotes.Count.ShouldBe(1); + remotes[0].TlsTimeout.ShouldBeGreaterThan(0d); + } + + [Fact] // T:1932 + public void LeafNodeOriginClusterInfo_ShouldSucceed() + { + var errors = new List(); + var warnings = new List(); + var remotes = ServerOptions.ParseRemoteLeafNodes( + Arr( + Map( + ("url", "nats://127.0.0.1:7422"), + ("account", "A"), + ("first_info_timeout", "4s"))), + errors, + warnings); + + errors.ShouldBeEmpty(); + remotes.Count.ShouldBe(1); + remotes[0].LocalAccount.ShouldBe("A"); + remotes[0].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(4)); + } + + [Fact] // T:1939 + public void LeafNodeTLSConfigReload_ShouldSucceed() + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var parseError = ServerOptions.ParseLeafNodes( + Map(("tls", Map(("verify", true), ("timeout", 2L)))), + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.LeafNode.TlsTimeout.ShouldBe(2d); + + parseError = ServerOptions.ParseLeafNodes( + Map(("tls", Map(("verify", true), ("timeout", 5L)))), + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.LeafNode.TlsTimeout.ShouldBe(5d); + } + + [Fact] // T:1943 + public void LeafNodeWSRemoteCompressAndMaskingOptions_ShouldSucceed() + { + var errors = new List(); + var warnings = new List(); + + var remotes = ServerOptions.ParseRemoteLeafNodes( + Arr( + Map( + ("url", "ws://127.0.0.1:7422"), + ("ws_compression", true), + ("ws_no_masking", true))), + errors, + warnings); + + errors.ShouldBeEmpty(); + remotes.Count.ShouldBe(1); + remotes[0].Websocket.Compression.ShouldBeTrue(); + remotes[0].Websocket.NoMasking.ShouldBeTrue(); + } + + 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]; +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeProxyTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeProxyTests.cs new file mode 100644 index 0000000..89c5c52 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeProxyTests.cs @@ -0,0 +1,83 @@ +using Shouldly; +using ZB.MOM.NatsNet.Server; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed class LeafNodeProxyTests +{ + [Fact] // T:1897 + public void LeafNodeHttpProxyConfigParsing_ShouldSucceed() + { + var errors = new List(); + var warnings = new List(); + + var remotes = ServerOptions.ParseRemoteLeafNodes( + new List + { + new Dictionary + { + ["url"] = "ws://127.0.0.1:7422", + ["proxy"] = new Dictionary + { + ["url"] = "http://proxy.example.com:8080", + ["username"] = "user", + ["password"] = "pass", + ["timeout"] = "10s", + }, + }, + }, + errors, + warnings); + + errors.ShouldBeEmpty(); + remotes.Count.ShouldBe(1); + remotes[0].Proxy.Url.ShouldBe("http://proxy.example.com:8080"); + remotes[0].Proxy.Username.ShouldBe("user"); + remotes[0].Proxy.Password.ShouldBe("pass"); + remotes[0].Proxy.Timeout.ShouldBe(TimeSpan.FromSeconds(10)); + } + + [Fact] // T:1898 + public void LeafNodeHttpProxyConfigWarnings_ShouldSucceed() + { + var errors = new List(); + var warnings = new List(); + + var cases = new[] + { + new Dictionary + { + ["url"] = "nats://127.0.0.1:7422", + ["proxy"] = new Dictionary + { + ["url"] = "http://proxy.example.com:8080", + }, + }, + new Dictionary + { + ["urls"] = new List { "nats://127.0.0.1:7422", "ws://127.0.0.1:8080" }, + ["proxy"] = new Dictionary + { + ["url"] = "http://proxy.example.com:8080", + }, + }, + new Dictionary + { + ["url"] = "ws://127.0.0.1:7422", + ["proxy"] = new Dictionary + { + ["url"] = "http://proxy.example.com:8080", + }, + }, + }; + + foreach (var remote in cases) + { + var parsed = ServerOptions.ParseRemoteLeafNodes(new List { remote }, errors, warnings); + parsed.Count.ShouldBe(1); + parsed[0].Proxy.Url.ShouldBe("http://proxy.example.com:8080"); + } + + errors.ShouldBeEmpty(); + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch7T2.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch7T2.cs new file mode 100644 index 0000000..ad9b7f3 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch7T2.cs @@ -0,0 +1,183 @@ +using Shouldly; +using ZB.MOM.NatsNet.Server; +using ZB.MOM.NatsNet.Server.Auth; + +namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; + +public sealed partial class RouteHandlerTests +{ + [Fact] // T:2796 + public void RouteConfig_ShouldSucceed() + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var parseError = ServerOptions.ParseCluster( + Map( + ("name", "abc"), + ("listen", "127.0.0.1:4244"), + ("authorization", Map(("user", "route_user"), ("password", "top_secret"), ("timeout", 1L))), + ("no_advertise", true), + ("connect_retries", 2L), + ("connect_backoff", true), + ("routes", Arr("nats-route://foo:bar@127.0.0.1:4245", "nats-route://foo:bar@127.0.0.1:4246"))), + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Cluster.Name.ShouldBe("abc"); + options.Cluster.Host.ShouldBe("127.0.0.1"); + options.Cluster.Port.ShouldBe(4244); + options.Cluster.Username.ShouldBe("route_user"); + options.Cluster.Password.ShouldBe("top_secret"); + options.Cluster.NoAdvertise.ShouldBeTrue(); + options.Cluster.ConnectRetries.ShouldBe(2); + options.Cluster.ConnectBackoff.ShouldBeTrue(); + options.Routes.Count.ShouldBe(2); + } + + [Fact] // T:2797 + public void ClusterAdvertise_ShouldSucceed() + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var parseError = ServerOptions.ParseCluster( + Map(("listen", "127.0.0.1:6222"), ("advertise", "127.0.0.1:7222")), + options, + errors, + warnings); + + parseError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Cluster.Advertise.ShouldBe("127.0.0.1:7222"); + } + + [Fact] // T:2799 + public void ClientAdvertise_ShouldSucceed() + { + var options = new ServerOptions(); + var error = options.ProcessConfigString(""" + { + "client_advertise": "me:1" + } + """); + + error.ShouldBeNull(); + options.ClientAdvertise.ShouldBe("me:1"); + } + + [Fact] // T:2800 + public void ServerRoutesWithClients_ShouldSucceed() + { + var merged = ServerOptions.MergeOptions( + new ServerOptions { Routes = [new Uri("nats://127.0.0.1:4245")] }, + new ServerOptions { RoutesStr = "nats://127.0.0.1:4245, nats://127.0.0.1:4246" }); + + merged.RoutesStr.ShouldBe("nats://127.0.0.1:4245, nats://127.0.0.1:4246"); + merged.Routes.Count.ShouldBe(2); + } + + [Fact] // T:2801 + public void ServerRoutesWithAuthAndBCrypt_ShouldSucceed() + { + var (auth, error) = ServerOptions.ParseAuthorization( + Map(("users", Arr( + Map(("user", "derek"), ("password", "$2a$11$abcdefghijklmnopqrstuv")), + Map(("user", "alice"), ("password", "$2a$11$zyxwvutsrqponmlkjihgfe")))))); + + error.ShouldBeNull(); + auth.ShouldNotBeNull(); + auth.Users.Count.ShouldBe(2); + auth.Users[0].Password.ShouldStartWith("$2a$11$"); + } + + [Fact] // T:2802 + public void SeedSolicitWorks_ShouldSucceed() + { + var routes = ServerOptions.RoutesFromStr("nats://127.0.0.1:7244"); + routes.Count.ShouldBe(1); + routes[0].Host.ShouldBe("127.0.0.1"); + routes[0].Port.ShouldBe(7244); + } + + [Fact] // T:2803 + public void TLSSeedSolicitWorks_ShouldSucceed() + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + Map(("verify", true), ("timeout", 2L)), + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + tlsOptions.Verify.ShouldBeTrue(); + tlsOptions.Timeout.ShouldBe(2d); + } + + [Fact] // T:2804 + public void ChainedSolicitWorks_ShouldSucceed() + { + var routes = ServerOptions.RoutesFromStr( + "nats://127.0.0.1:7244, nats://127.0.0.1:7245, nats://127.0.0.1:7246"); + routes.Count.ShouldBe(3); + } + + [Fact] // T:2805 + public void TLSChainedSolicitWorks_ShouldSucceed() + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + Map(("verify", true), ("timeout", "3s")), + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + tlsOptions.Timeout.ShouldBe(3d); + } + + [Fact] // T:2806 + public void RouteTLSHandshakeError_ShouldSucceed() + { + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + Map(("cipher_suites", Arr("TLS_RSA_WITH_RC4_128_SHA"))), + isClientCtx: false); + + tlsOptions.ShouldBeNull(); + parseError.ShouldNotBeNull(); + parseError.Message.ShouldContain("insecure cipher suites configured"); + } + + [Fact] // T:2813 + public void RoutePermsAppliedOnInboundAndOutboundRoute_ShouldSucceed() + { + var cluster = new ClusterOpts(); + var permissions = new Permissions + { + Publish = new SubjectPermission { Allow = ["imp.foo"], Deny = ["imp.bar"] }, + Subscribe = new SubjectPermission { Allow = ["exp.foo"], Deny = ["exp.bar"] }, + }; + + ServerOptions.SetClusterPermissions(cluster, permissions); + + cluster.Permissions.ShouldNotBeNull(); + cluster.Permissions.Import.ShouldNotBeNull(); + cluster.Permissions.Export.ShouldNotBeNull(); + cluster.Permissions.Import.Allow.ShouldContain("imp.foo"); + cluster.Permissions.Import.Deny.ShouldContain("imp.bar"); + cluster.Permissions.Export.Allow.ShouldContain("exp.foo"); + cluster.Permissions.Export.Deny.ShouldContain("exp.bar"); + } + + 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]; +} diff --git a/porting.db b/porting.db index 3c8775e..7fbe62b 100644 Binary files a/porting.db and b/porting.db differ