using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; using ZB.MOM.NatsNet.Server.WebSocket; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed partial class LeafNodeHandlerTests { [Fact] // T:1975 public void LeafNodeTLSHandshakeFirstVerifyNoInfoSent_ShouldSucceed() { var errors = new List(); var warnings = new List(); var remotes = ServerOptions.ParseRemoteLeafNodes( new List { new Dictionary { ["url"] = "wss://127.0.0.1:7422", ["first_info_timeout"] = "2s", ["tls"] = new Dictionary { ["verify"] = true, ["timeout"] = 1, }, }, }, errors, warnings); errors.ShouldBeEmpty(); remotes.Count.ShouldBe(1); remotes[0].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(2)); remotes[0].TlsConfig.ShouldNotBeNull(); remotes[0].TlsTimeout.ShouldBe(1d); } [Fact] // T:1986 public void LeafNodeCompressionWithWSGetNeedsData_ShouldSucceed() { var frame = new byte[] { WsConstants.FinalBit, (byte)(WsConstants.MaskBit | 5), 1, 2, 3, 4, 0x31, 0x32, 0x33, 0x34, 0x35 }; var initial = frame[..1]; using var remainder = new MemoryStream(frame[1..]); var (b1, nextPos) = WebSocketHelpers.WsGet(remainder, initial, 1, 1); b1[0].ShouldBe((byte)(WsConstants.MaskBit | 5)); nextPos.ShouldBe(1); var (mask, _) = WebSocketHelpers.WsGet(remainder, initial, 1, 4); var (payload, _) = WebSocketHelpers.WsGet(remainder, initial, 1, 5); WebSocketHelpers.WsMaskBuf(mask, payload); payload.ShouldBe(new byte[] { 0x30, 0x30, 0x30, 0x30, 0x34 }); } [Fact] // T:1984 public void LeafNodeCompressionAuto_ShouldSucceed() { var options = new ServerOptions(); var errors = new List(); var warnings = new List(); var parseError = ServerOptions.ParseLeafNodes( new Dictionary { ["remotes"] = new List { new Dictionary { ["url"] = "nats://127.0.0.1:7422", ["compression"] = new Dictionary { ["mode"] = CompressionModes.S2Auto, ["rtt_thresholds"] = new List { "10ms", "20ms", "30ms" }, }, }, }, }, options, errors, warnings); parseError.ShouldBeNull(); errors.ShouldBeEmpty(); options.LeafNode.Remotes.Count.ShouldBe(1); options.LeafNode.Remotes[0].Compression.Mode.ShouldBe(CompressionModes.S2Auto); options.LeafNode.Remotes[0].Compression.RttThresholds.Count.ShouldBe(3); options.LeafNode.Remotes[0].Compression.RttThresholds[0].ShouldBe(TimeSpan.FromMilliseconds(10)); options.LeafNode.Remotes[0].Compression.RttThresholds[1].ShouldBe(TimeSpan.FromMilliseconds(20)); options.LeafNode.Remotes[0].Compression.RttThresholds[2].ShouldBe(TimeSpan.FromMilliseconds(30)); } [Fact] // T:2001 public void LeafNodeConnectionSucceedsEvenWithDelayedFirstINFO_ShouldSucceed() { var errors = new List(); var warnings = new List(); var remotes = ServerOptions.ParseRemoteLeafNodes( new List { new Dictionary { ["url"] = "nats://127.0.0.1:7422", ["first_info_timeout"] = "3s", }, new Dictionary { ["url"] = "ws://127.0.0.1:7423", ["first_info_timeout"] = "3s", }, }, errors, warnings); errors.ShouldBeEmpty(); remotes.Count.ShouldBe(2); remotes[0].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(3)); 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); } [Fact] // T:1966 public void LeafNodeRoutedSubKeyDifferentBetweenLeafSubAndRoutedSub_ShouldSucceed() { var plain = new Subscription { Subject = "foo"u8.ToArray() }; var queue = new Subscription { Subject = "XYZ"u8.ToArray(), Queue = "foo"u8.ToArray() }; var routedPlain = LeafNodeHandler.KeyFromSubWithOrigin(plain); var routedQueue = LeafNodeHandler.KeyFromSubWithOrigin(queue); var leafPlain = LeafNodeHandler.KeyFromSubWithOrigin(plain, "XYZ"); var leafQueue = LeafNodeHandler.KeyFromSubWithOrigin(queue, "XYZ"); routedPlain.ShouldNotBe(routedQueue); routedQueue.ShouldNotBe(leafPlain); leafPlain.ShouldNotBe(leafQueue); routedPlain.ShouldNotBe(leafPlain); routedQueue.ShouldNotBe(leafQueue); } }