From 8d00012ba85ab51ef4b086a11b657a99a5fccfe1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 20:28:13 -0500 Subject: [PATCH] test(batch19): port accounts core mapped tests --- .../ImplBacklog/AccountTests.cs | 56 ++++++++++++++++++ .../ImplBacklog/GatewayHandlerTests.cs | 48 +++++++++++++++ .../LeafNodeHandlerTests.Impltests.cs | 51 ++++++++++++++++ .../ImplBacklog/MessageTracerTests.cs | 35 +++++++++++ porting.db | Bin 6692864 -> 6696960 bytes 5 files changed, 190 insertions(+) diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs index f993d79..831f0f7 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs @@ -246,6 +246,62 @@ public sealed class AccountTests system.CheckServiceExportApproved(global, "$SYS.REQ.INFO", null).ShouldBeTrue(); } + [Fact] // T:81 + public void MultipleStreamImportsWithSameSubjectDifferentPrefix_ShouldSucceed() + { + var fooAccount = Account.NewAccount("foo"); + var barAccount = Account.NewAccount("bar"); + var importAccount = Account.NewAccount("import"); + + fooAccount.AddStreamExport("test", null).ShouldBeNull(); + barAccount.AddStreamExport("test", null).ShouldBeNull(); + + importAccount.AddStreamImport(fooAccount, "test", "foo").ShouldBeNull(); + importAccount.AddStreamImport(barAccount, "test", "bar").ShouldBeNull(); + + importAccount.Imports.Streams.ShouldNotBeNull(); + importAccount.Imports.Streams!.Count.ShouldBe(2); + importAccount.Imports.Streams.Select(si => si.To).OrderBy(static t => t).ToArray() + .ShouldBe(["bar.test", "foo.test"]); + } + + [Fact] // T:82 + public void MultipleStreamImportsWithSameSubject_ShouldSucceed() + { + var fooAccount = Account.NewAccount("foo"); + var barAccount = Account.NewAccount("bar"); + var importAccount = Account.NewAccount("import"); + + fooAccount.AddStreamExport("test", null).ShouldBeNull(); + barAccount.AddStreamExport("test", null).ShouldBeNull(); + + importAccount.AddStreamImport(fooAccount, "test", string.Empty).ShouldBeNull(); + importAccount.AddStreamImport(fooAccount, "test", string.Empty).ShouldBe(ServerErrors.ErrStreamImportDuplicate); + importAccount.AddStreamImport(barAccount, "test", string.Empty).ShouldBeNull(); + + importAccount.Imports.Streams.ShouldNotBeNull(); + importAccount.Imports.Streams!.Count.ShouldBe(2); + importAccount.Imports.Streams.Select(si => si.Account?.Name).OrderBy(static n => n).ToArray() + .ShouldBe(["bar", "foo"]); + } + + [Fact] // T:95 + public void BenchmarkNewRouteReply() + { + var globalAccount = Account.NewAccount("$G"); + + var first = globalAccount.NewServiceReply(tracking: false); + var second = globalAccount.NewServiceReply(tracking: false); + var tracked = globalAccount.NewServiceReply(tracking: true); + + first.Length.ShouldBeGreaterThan(20); + second.Length.ShouldBeGreaterThan(20); + first.SequenceEqual(second).ShouldBeFalse(); + + var trackedText = Encoding.ASCII.GetString(tracked); + trackedText.EndsWith(".T", StringComparison.Ordinal).ShouldBeTrue(); + } + [Fact] // T:98 public void ImportSubscriptionPartialOverlapWithPrefix_ShouldSucceed() { diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs index 0da9237..7a46e03 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Linq; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; @@ -122,6 +123,53 @@ public sealed partial class GatewayHandlerTests NatsServer.ValidateCluster(conflict).ShouldBe(ServerErrors.ErrClusterNameConfigConflict); } + [Fact] // T:658 + public void GatewaySendReplyAcrossGatewaysServiceImport_ShouldSucceed() + { + var fooAccount = Account.NewAccount("$foo"); + var barAccount = Account.NewAccount("$bar"); + + fooAccount.AddServiceExport("foo.request", null).ShouldBeNull(); + barAccount.AddServiceImport(fooAccount, "bar.request", "foo.request").ShouldBeNull(); + + var serviceImport = barAccount.Imports.Services!["bar.request"].Single(); + var responseImport = barAccount.AddRespServiceImport(fooAccount, "reply", serviceImport, tracking: false, header: null); + + responseImport.From.ShouldNotBe("reply"); + responseImport.To.ShouldBe("reply"); + barAccount.Exports.Responses.ShouldNotBeNull(); + barAccount.Exports.Responses!.ShouldContainKey(responseImport.From); + + fooAccount.Imports.ReverseResponseMap.ShouldNotBeNull(); + fooAccount.Imports.ReverseResponseMap!.ShouldContainKey("reply"); + fooAccount.Imports.ReverseResponseMap["reply"].Any(e => e.MappedSubject == responseImport.From).ShouldBeTrue(); + + barAccount.ProcessServiceImportResponse(responseImport.From, "ok"u8.ToArray()); + responseImport.DidDeliver.ShouldBeTrue(); + } + + [Fact] // T:666 + public void GatewayNoAccountUnsubWhenServiceReplyInUse_ShouldSucceed() + { + var fooAccount = Account.NewAccount("$foo"); + var barAccount = Account.NewAccount("$bar"); + + fooAccount.AddServiceExport("test.request", null).ShouldBeNull(); + barAccount.AddServiceImport(fooAccount, "foo.request", "test.request").ShouldBeNull(); + + var serviceImport = barAccount.Imports.Services!["foo.request"].Single(); + var responseImport1 = barAccount.AddRespServiceImport(fooAccount, "reply", serviceImport, tracking: false, header: null); + var responseImport2 = barAccount.AddRespServiceImport(fooAccount, "reply", serviceImport, tracking: false, header: null); + + fooAccount.Imports.ReverseResponseMap.ShouldNotBeNull(); + fooAccount.Imports.ReverseResponseMap!["reply"].Count.ShouldBe(2); + + fooAccount.CheckForReverseEntry("reply", responseImport1, checkInterest: false); + + fooAccount.Imports.ReverseResponseMap["reply"].Count.ShouldBe(1); + fooAccount.Imports.ReverseResponseMap["reply"].Single().MappedSubject.ShouldBe(responseImport2.From); + } + private static NatsServer CreateServer(ServerOptions? opts = null) { var (server, err) = NatsServer.NewServer(opts ?? new ServerOptions()); diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Impltests.cs index 7ba10ef..b08b498 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Impltests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Impltests.cs @@ -1,5 +1,6 @@ using Shouldly; using ZB.MOM.NatsNet.Server; +using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; @@ -71,4 +72,54 @@ public sealed partial class LeafNodeHandlerTests remotes[1].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(3)); remotes[1].Urls[0].Scheme.ShouldBe("ws"); } + + [Fact] // T:1935 + public void LeafNodeNoDuplicateWithinCluster_ShouldSucceed() + { + var account = Account.NewAccount("$G"); + var leaf1 = new ClientConnection(ClientKind.Leaf); + var leaf2 = new ClientConnection(ClientKind.Leaf); + + ((INatsAccount)account).AddClient(leaf1); + ((INatsAccount)account).AddClient(leaf2); + + account.RegisterLeafNodeCluster("xyz"); + account.RegisterLeafNodeCluster("xyz"); + + account.NumLocalLeafNodes().ShouldBe(2); + account.HasLeafNodeCluster("xyz").ShouldBeTrue(); + account.IsLeafNodeClusterIsolated("xyz").ShouldBeTrue(); + + account.RegisterLeafNodeCluster("abc"); + account.IsLeafNodeClusterIsolated("xyz").ShouldBeFalse(); + } + + [Fact] // T:1952 + public void LeafNodeStreamImport_ShouldSucceed() + { + var exporter = Account.NewAccount("B"); + var importer = Account.NewAccount("C"); + + exporter.AddStreamExport(">", null).ShouldBeNull(); + importer.AddStreamImport(exporter, ">", string.Empty).ShouldBeNull(); + + importer.Imports.Streams.ShouldNotBeNull(); + importer.Imports.Streams!.Count.ShouldBe(1); + importer.Imports.Streams[0].From.ShouldBe(">"); + importer.Imports.Streams[0].To.ShouldBe(">"); + exporter.CheckStreamExportApproved(importer, "a", null).ShouldBeTrue(); + } + + [Fact] // T:1955 + public void LeafNodeUnsubOnRouteDisconnect_ShouldSucceed() + { + var account = Account.NewAccount("$G"); + var leaf = new ClientConnection(ClientKind.Leaf); + + ((INatsAccount)account).AddClient(leaf); + account.NumLocalLeafNodes().ShouldBe(1); + + ((INatsAccount)account).RemoveClient(leaf); + account.NumLocalLeafNodes().ShouldBe(0); + } } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs index 4873d29..bd0a121 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs @@ -6,6 +6,41 @@ namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class MessageTracerTests { + [Fact] // T:2359 + public void MsgTraceParseAccountDestWithSampling_ShouldSucceed() + { + var cases = new[] + { + (Name: "trace sampling no dest", Value: (object)new Dictionary { ["sampling"] = 50 }, Want: 0), + (Name: "trace dest only", Value: (object)new Dictionary { ["dest"] = "foo" }, Want: 100), + (Name: "trace dest with number only", Value: (object)new Dictionary { ["dest"] = "foo", ["sampling"] = 20 }, Want: 20), + (Name: "trace dest with percentage", Value: (object)new Dictionary { ["dest"] = "foo", ["sampling"] = "50%" }, Want: 50), + }; + + foreach (var testCase in cases) + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var accounts = new Dictionary + { + ["A"] = new Dictionary + { + ["msg_trace"] = testCase.Value, + }, + }; + + var parseError = ServerOptions.ParseAccounts(accounts, options, errors, warnings); + parseError.ShouldBeNull(testCase.Name); + errors.ShouldBeEmpty(testCase.Name); + options.Accounts.Count.ShouldBe(1, testCase.Name); + + var (_, sampling) = options.Accounts[0].GetTraceDestAndSampling(); + sampling.ShouldBe(testCase.Want, testCase.Name); + } + } + [Fact] // T:2331 public void MsgTraceBasic_ShouldSucceed() { diff --git a/porting.db b/porting.db index 4c7856ec802457a4aea3ed883c9236fcb41addb3..5452cfd445b596513b8519d308848f4e67d10758 100644 GIT binary patch delta 3155 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 delta 3715 zcmd6o{Zmxe8OQHA_wK#RzMONHMUXe$T~P!zytoP~0>0rpDx%RqaY4{ggAxU6qX9IT zS#69M9em7aQgk{dCbqTJc+#}B7`2HnNsXq~CTfXkwBuXxHAXYq2Ml}f{s;Ntvva=p z`@EgAXYV$)ZIv5ax5^viEn6f>t$5v&N4ESvke5asPhQ@U7Tl3W#EvvNx5e7x7$bE= z2*)X!Phi@h?nUCQY5Z+laLjp%$YRWST>(uEg2l2%Ld&iCq$U zBRkE~vCBQDoJcFH=2b0ST2(18=Sh;I>j}DdyVe~pt%1lUmO<)ZbQ7CS%Ei_uwxGo= zkC2{~BM95hnpg=dWPaymVCj4(Y|7x#(2~KeKaCGXypzG-APsV}RM53y@_{m6N(iZ! z!?s(Bks*(kba-=@RM$LTRa<56XO6_0pGv8rD)nJrD|#&C{l(c}I&ZQP+t-B57sC=i zR=lRfBVpcQ%*zht9YSTauM68!ZCKthrwn<1UX-f*)0A-%RcNE+1S(_T)EiHr^65Jb2V8y5Ze5yu+SRI z8B_c77GY}f6)K}eby(g7%rm@2Vcumm*z-Wvzw-7mE;CaW<*{O+ru^Jp2brewAZB(ogK}Iv>QGSHKgt^FH-xJ(U7{r zJYDc>uS^N$zj=j&||P+B<~(+ea}><7b*rPpWsnjZ<%^~P#NbCgUaX~ zjVjce>mdRtuHpm&x;~K~two>WyuH&IO=5hpV z3#F7UkC-hw(SFvLf=&qY#$j50R~j9QDs=H^0ji3SH>Mb6=;qPUs6uaBItrEXb&N!1 z9LWe&#*yTr3LOa@4yV5-sn6K%o3f{%GO`O%8QGIjF*x;@;Q!B1K9kvVOvR>wGK^;{ z_E{M6cqEUGlEA*vlw54bbAa|vXLHyo%OK=2C*`=tA!Ar!L z9KM*4JFtH^&r~|@EnFSW>lLPPB+b*uVI zHJ}!$tJKBnF7>*45jF()E6p|ZC~VH>LoG$<6~W@Ra<4csf^Sw}n4dFB>%r~kDX@1S zk0%uJ2l8x4yhj*}?f6(RkUPOPnf4HwWBGPj>DVBgp1{9?;3&#q-w^&PBoD!q!iVH2 z+|9z(TUngL@<-$)m_C@l4NC^`1F-!8Nrcov*rvT>75_b!-s9)-Tka88kWA%*Ox5Ld z^rdLL+?LE~V< zC;At#b|G~@K>}rB)iHf-Gzo&GO;4j*itxAT?+|hEYdzf}XGsadbwzKts@99rVaq1a z+VvfgJc?`VA-loa*ctXW_D8lyw6^OCfxWkMRUEmcuaPG$tF5b`g_e4$Z|7<ueojz#j0C1*pok}KO|9CE|76NRjjA3I%miAdP14xEOG{%i=C?+ zGaVl|-gRDdo^pQV-0$4w+$ub}>pZc_=bzp`p+>qc5vt7=SKY1?6b>z{ii5vLyPQ-j z5f`IfwJOw5SA4KK(S<}JZX^onhD0MVqB^nrw%hUSnA7i=VlTH9L=;#HG{5S%^i^#1 zF?lRCXT+CMNfMLYvHKiiwI0g|>>TRr1ue5I4mjwFoe1gM6$UYpu`NK$A`Bx5Z0^{> z(00ztg2WSx#3AuW0+NXIKza(|@kCA~s)(xbtdSLmLplT1wVrjNSFPt873?cLD}mj1 zuY>$eF^Vi%B7?3~9^qQ$U786+J0r2*qPRF{{jGPxmVVJ(#Qx6voCYuK^yb2f553WF z@_nxpuD|E;3#;%(|AMl|!jtYhqCn;$S1;J>jPgOsAm0GYWpY_lPx$(p+bh-%@&zk7LDc!_d4!Wl#J3(7P6XY%QG-cXCtyEsC6>9nPh?c1($uEk9o|3hME?ocx zJ#>#yeI;cwvvrr)@~Wi^<}IkI)K)|N?#ei^zIVxbMR7cqQXwwJl?07Ts+cHDE!juO z0azU4Itbfm>50J$14@um(5`3_$=f$^}nQFn;aw!i}dH~$e}B5)~? zWg)4s^-3TKN>fW%@c5MgQjSz0bCF7M{7Ti(zH&FY0W^JHzh<9pyh!&DMHUV2d8-}& E2a__0Gynhq