From 007122a6590e0abceb7bb8831cdc0d12bd5315a4 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 11:22:17 -0500 Subject: [PATCH] test(batch7): implement t2 route leaf and proxy mapped tests --- .../LeafNodeHandlerTests.Batch7T2.cs | 139 +++++++++++++ .../ImplBacklog/LeafNodeProxyTests.cs | 83 ++++++++ .../ImplBacklog/RouteHandlerTests.Batch7T2.cs | 183 ++++++++++++++++++ porting.db | Bin 6500352 -> 6504448 bytes 4 files changed, 405 insertions(+) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch7T2.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeProxyTests.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch7T2.cs 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 3c8775ed4e84fe0687142ae9a4ae9fc30bb39e8f..7fbe62b4dee0fd55a39c5b5a9758c73ed03da7f4 100644 GIT binary patch delta 2840 zcmai$3vg3q7RT=+$<4htNpHDnYG2_tLBOuscUs!g2b3y{%Hvilbk`;+#TJ!;Casl4 zDeUaT*(tSIphvoB3piAFb)+zI)rFCcyR!=n*u_EKQmVK+*p@atoDs!6y$NZYX=XF? z%lyy%{yE?IzVCc@(mL2ix3+GjJCd|+g3#sor4rKJ=PXSp|MC0X>Av0Rq_R7ms_oHx zj50A{0K)+F4P}&-zyRek=5<3$s@8Pw>Tj25;ldypB7{b#kw9IqZ+@J8T#GD!Yz#vlVPUYf|K9TOI+@6a3;z-pkjWq&6LQHmSbj`!XxKx9N$EHy#F6AF*mqo*ZRP8U{8e?U>lz#D zRxwQ&rG+CeUk}5_g^knrKM~2{r)w}WDH~1*f1E*ntZ2ssCn*~Ec4`jaz?bk0cbWT~ z>*o$a$G8v=-Q$8@{Zdf%u~^O0yTVM0-vuw<73`Y~iNw15iMcx%xx>+Ij4H#?EsQF{ z(M^oPMRNg+!bNlA7=?@F#xOF4e|N)3r%>T&)DT-9VI47)It*RzeR#kaXnN_t06{o5O-#tDSnWo(1Mf2Y^l$-ldu?&dg zq=xMys)0W(R>4OX#8)6oG^K#^d(i~Wi{dhn+LE-|tjvOJhqC$$agk0#5TvH}!}B5u zedonZ5a<={u;jcr6JEX`=0@5NUlbQ;?AZS%iX}`f#=VA5`O6f`1=#uejm#f(er=ED zOZt17R#Nxtu95K3ccKT%NOK9S85WDxOsqQb=5_6_f!t^dMTUCb#W{(7cDcTq*`qtG-Ku$=-Umw)$(``KvzD!}{+MkJl%2CI zo-8nLbIp!sNWEf7fU2)7kHeNzv38IyS}btxD@);I5qXeu#Zm`>{YfbxoV3Nmp(~cs zh+(CF!19Ss={jfW(RQ}-I-hRPg2W>aAPGn!l7u89DTr0k4Ngz)pfnATITrseoLncp z5ZuF-^-{#tj@~2dC6b~$;nPOxES@3p1$23&&!N*JZ3TCZUEQ^0aC@ailM8cfe){z| z$lM^Al|4;T1{KVl@{$w-Qj3%}S-Qhl*&-pShz+qLGmr<7naC_;Hu4aXhWrY77JP^RJw~<7 zIiAo@BS;Dsz9Cn^mMhtI5Z;nSh<`)Qf{ed0dd5+p8X==wekR!JXh;R;5lb*noH}gs z5dWRXQ)tXH7E)v!@Llp^h`GQd1iP-iOP;G9U3_MKK~^5xha>It)8Xz;xhA4b!#@aJ z$K^Xb@bgYN0c!m-=E3P!8#d2~#SEAHGYTSl0zUY&ydpS^mmwC4cFDgOZh2F_NA|)? zZ^?TnHuJgQn@7DExQ}HKnYKT6z;~cWMkyAg@(i*)Y7h(*RSET1onOM( z8K;5beu5vqb)~^OLoONEuU#f`*f%=lL|n?~klQksP95= ze;$zLT9IO!fIR3fq;sW8xVqP!%sC3w18ep>LI&?I4bwvgk3W|sQmK@Maf`E?);4%+ zymcP0hgk#ty3Cn?|3Eac9+pzXIsnX?nUb;WSWP+Tj5;XkZ1d}CZt0gKfTWzZ}yjm40oa#=7$WoqZ#If47a8<i)P3V&cHyQ!}TZ#r`)FSH27jtJM(|HiN2Wt delta 2086 zcmY+_eNYs27{Kv;x!b$ly*(Cwuz=j*a(5}w3kTd%QV_p&uuT$G4q-EKKt9*+uIke zrDYA?ky0JcNSX??UL zVWBNF?>K{4WI|@dAq%o19tlW9v3(_E*XJEi*cGLhfqB2Y$loA7EY2727H<)~qDxE` zIpJI3OJTnd7M>9%@PF{z`HlQ)zKO5pEBR^sjeHJow_eoO@0Vp--`gu^Sy+m4z~JZd z?X(@#1G1mq3#A9-8=%_3dedXIL1~4%P!0A=Kmj9_$_YwHE#MPZu&WHx)?WIh?_>^rt@*rIVo-&fu?*W zL!bJGY)NRgwNdZZ)i0^!N3eDpbr!3`s9~(IQKzw18+8h6rBP9=Mx#z*J#N$xR<%*z zVa+${1XhVr$FZgvbqs3~mcNo8#LPD~euI^5)Yn*$a9NF5;= z&>kTY?73FS35%Sl_Z%ZdCO312vQrBvdn1z~y=dzayM;b#fwjz1&DENl1=iHT?uMCT z=qci=Yuc6)ie`#eSzzZd@#w)HNh=Mnj*ufJ-bH-~Yke*PV@{E|kW`%H2JiR80okWW z5?8LZ*0wbn>pHIY;EOm+wy}x$7M}d)hy_wHL#IIJFv$cwZWnxYnjD0m&yb%$h>{@8 z`Xt2-?Z4s`+7~4SP?DS?!ik?qB~)(zcVXpO;`@7H|5QPu&udV@iy*dphEm4c0kLMr~JWx@9%Rn}-?hV1id#ZoShyY--JlV0rlm>@)eXJr=^l_bcC!Yu{VNHev&?Kk@E%u*4fX0`#D|8Pm-K@O=qkFOxDA=IQ zg#(+lnXz$vxUje@)YYM(`_ODuisqoXXrA8HF@IJw`#Vz$7PahA6Z@^X4Vu)lOwQ8C zhM9=IT`hZ%R)h({I3Z0Cj9)(ozWl}?6#fu+BDBI)hVDlXpa)U8zQPp<^_b2x=O`%b zWG3lFLxD^h_GOg`u<~Re0^Uxp6GjIDw$P<$09ELhqLspA{0*Fr=TTJ