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 4c7856e..5452cfd 100644 Binary files a/porting.db and b/porting.db differ