using System.Reflection; using System.Text; using System.Text.Json; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth; using ZB.MOM.NatsNet.Server.Internal; using ZB.MOM.NatsNet.Server.Internal.DataStructures; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class EventsHandlerTests { [Fact] // T:299 public void SystemAccount_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true, }); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.SetDefaultSystemAccount().ShouldBeNull(); var sys = server.SystemAccount(); var global = server.GlobalAccount(); sys.ShouldNotBeNull(); global.ShouldNotBeNull(); sys!.Name.ShouldBe(ServerConstants.DefaultSystemAccount); global!.Name.ShouldBe(ServerConstants.DefaultGlobalAccount); sys.Name.ShouldNotBe(global.Name); } [Fact] // T:300 public void SystemAccountNewConnection_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true }); err.ShouldBeNull(); server!.SetDefaultSystemAccount().ShouldBeNull(); var sys = server.SystemAccount(); sys.ShouldNotBeNull(); var c = new ClientConnection(ClientKind.Client, server) { Cid = 1001 }; c.RegisterWithAccount(sys!); sys.NumConnections().ShouldBe(1); } [Fact] // T:301 public void SystemAccountingWithLeafNodes_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true }); err.ShouldBeNull(); server!.SetDefaultSystemAccount().ShouldBeNull(); var sys = server.SystemAccount(); sys.ShouldNotBeNull(); var leaf = new ClientConnection(ClientKind.Leaf, server) { Cid = 1002 }; leaf.RegisterWithAccount(sys!); sys.NumLeafNodes().ShouldBe(1); } [Fact] // T:302 public void SystemAccountDisconnectBadLogin_ShouldSucceed() { var c = new ClientConnection(ClientKind.Client); c.AuthViolation(); c.IsClosed().ShouldBeTrue(); } [Fact] // T:306 public void SystemAccountConnectionLimits_ShouldSucceed() { var acc = Account.NewAccount("SYS"); acc.MaxConnections = 1; var c1 = new ClientConnection(ClientKind.Client) { Cid = 1 }; var c2 = new ClientConnection(ClientKind.Client) { Cid = 2 }; c1.RegisterWithAccount(acc); Should.Throw(() => c2.RegisterWithAccount(acc)); } [Fact] // T:308 public void SystemAccountSystemConnectionLimitsHonored_ShouldSucceed() { var acc = Account.NewAccount("SYS"); acc.MaxConnections = 1; var s1 = new ClientConnection(ClientKind.System) { Cid = 11 }; var s2 = new ClientConnection(ClientKind.System) { Cid = 12 }; s1.RegisterWithAccount(acc); s2.RegisterWithAccount(acc); acc.NumConnections().ShouldBe(0); } [Fact] // T:309 public void SystemAccountConnectionLimitsServersStaggered_ShouldSucceed() { var acc = Account.NewAccount("TEST"); acc.MaxConnections = 3; for (var i = 0; i < 3; i++) new ClientConnection(ClientKind.Client) { Cid = (ulong)(20 + i) }.RegisterWithAccount(acc); var overByTwo = acc.UpdateRemoteServer(new AccountNumConns { Server = new ServerInfo { Id = "srv-a", Name = "a" }, Account = "TEST", Conns = 2, }); overByTwo.Count.ShouldBe(2); var overByOne = acc.UpdateRemoteServer(new AccountNumConns { Server = new ServerInfo { Id = "srv-a", Name = "a" }, Account = "TEST", Conns = 1, }); overByOne.Count.ShouldBe(1); } [Fact] // T:310 public void SystemAccountConnectionLimitsServerShutdownGraceful_ShouldSucceed() { var acc = Account.NewAccount("TEST"); acc.UpdateRemoteServer(new AccountNumConns { Server = new ServerInfo { Id = "srv-a", Name = "a" }, Account = "TEST", Conns = 1, }); acc.ExpectedRemoteResponses().ShouldBe(1); acc.RemoveRemoteServer("srv-a"); acc.ExpectedRemoteResponses().ShouldBe(0); } [Fact] // T:311 public void SystemAccountConnectionLimitsServerShutdownForced_ShouldSucceed() { var acc = Account.NewAccount("TEST"); acc.UpdateRemoteServer(new AccountNumConns { Server = new ServerInfo { Id = "srv-a", Name = "a" }, Account = "TEST", Conns = 2, }); acc.RemoveRemoteServer("srv-missing"); acc.ExpectedRemoteResponses().ShouldBe(1); acc.RemoveRemoteServer("srv-a"); acc.ExpectedRemoteResponses().ShouldBe(0); } [Fact] // T:312 public void SystemAccountFromConfig_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { Accounts = [new Account { Name = "SYSCFG" }], SystemAccount = "SYSCFG", }); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.SystemAccount().ShouldNotBeNull(); server.SystemAccount()!.Name.ShouldBe("SYSCFG"); } [Fact] // T:313 public void AccountClaimsUpdates_ShouldSucceed() { AccountClaims.TryDecode(string.Empty).ShouldBeNull(); AccountClaims.TryDecode("not-a-real-jwt").ShouldBeNull(); } [Fact] // T:315 public void AccountReqInfo_ShouldSucceed() { var acc = Account.NewAccount("A"); var id1 = acc.NextEventId(); var id2 = acc.NextEventId(); id1.ShouldNotBeNullOrWhiteSpace(); id2.ShouldNotBeNullOrWhiteSpace(); id1.ShouldNotBe(id2); } [Fact] // T:316 public void AccountClaimsUpdatesWithServiceImports_ShouldSucceed() { var source = Account.NewAccount("src"); var original = Account.NewAccount("dst"); original.Imports.Services = new Dictionary> { ["svc"] = [new ServiceImportEntry { Account = source, From = "svc", To = "svc" }], }; var updated = Account.NewAccount("dst"); original.ShallowCopy(updated); updated.Imports.Services.ShouldNotBeNull(); updated.Imports.Services!.ShouldContainKey("svc"); updated.Imports.Services["svc"].Count.ShouldBe(1); } [Fact] // T:317 public void AccountConnsLimitExceededAfterUpdate_ShouldSucceed() { var acc = Account.NewAccount("A"); acc.MaxConnections = 2; new ClientConnection(ClientKind.Client) { Cid = 71 }.RegisterWithAccount(acc); new ClientConnection(ClientKind.Client) { Cid = 72 }.RegisterWithAccount(acc); var toDisconnect = acc.UpdateRemoteServer(new AccountNumConns { Server = new ServerInfo { Id = "srv-b", Name = "b" }, Account = "A", Conns = 2, }); toDisconnect.Count.ShouldBe(2); } [Fact] // T:320 public void SystemAccountWithGateways_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { Gateway = new GatewayOpts { Name = "G1" }, Accounts = [new Account { Name = "SYS" }], SystemAccount = "SYS", }); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.GetOpts().Gateway.Name.ShouldBe("G1"); server.SystemAccount()!.Name.ShouldBe("SYS"); } [Fact] // T:321 public void SystemAccountNoAuthUser_ShouldSucceed() { var opts = new ServerOptions { Users = [new User { Username = "noauth" }], NoAuthUser = "noauth", SystemAccount = "SYS", }; AuthHandler.ValidateNoAuthUser(opts, opts.NoAuthUser).ShouldBeNull(); } [Fact] // T:322 public void ServerAccountConns_ShouldSucceed() { var acc = Account.NewAccount("A"); var c = new ClientConnection(ClientKind.Client) { Cid = 81 }; c.RegisterWithAccount(acc); acc.NumConnections().ShouldBe(1); ((INatsAccount)acc).RemoveClient(c); acc.NumConnections().ShouldBe(0); } [Fact] // T:323 public void ServerEventsStatsZ_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server!.NumSlowConsumers().ShouldBe(0); server.NumStaleConnections().ShouldBe(0); server.NumClients().ShouldBeGreaterThanOrEqualTo(0); } [Fact] // T:324 public void ServerEventsHealthZSingleServer_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server!.NumRoutes().ShouldBe(0); server.NumRemotes().ShouldBe(0); } [Fact] // T:327 public void ServerEventsHealthZJetStreamNotEnabled_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.GetOpts().JetStream.ShouldBeFalse(); } [Fact] // T:328 public void ServerEventsPingStatsZ_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); server!.NumSlowConsumersClients().ShouldBe(0); server.NumSlowConsumersRoutes().ShouldBe(0); server.NumSlowConsumersGateways().ShouldBe(0); server.NumSlowConsumersLeafs().ShouldBe(0); } [Fact] // T:329 public void ServerEventsPingStatsZDedicatedRecvQ_ShouldSucceed() { var server = CreateServer(); var sc = GetPrivateField(server, "_scStats"); sc.Clients = 1; sc.Routes = 2; sc.Gateways = 3; sc.Leafs = 4; server.NumSlowConsumersClients().ShouldBe(1); server.NumSlowConsumersRoutes().ShouldBe(2); server.NumSlowConsumersGateways().ShouldBe(3); server.NumSlowConsumersLeafs().ShouldBe(4); } [Fact] // T:330 public void ServerEventsPingStatsZFilter_ShouldSucceed() { var server = CreateServer(new ServerOptions { Host = "127.0.0.1", ServerName = "SRV", Cluster = new ClusterOpts { Name = "CLUSTER" }, }); var info = server.CopyInfo(); MatchesServerFilter(info, cluster: "CLUSTER").ShouldBeTrue(); MatchesServerFilter(info, host: "127.0.0.1").ShouldBeTrue(); MatchesServerFilter(info, name: "SRV").ShouldBeTrue(); MatchesServerFilter(info, cluster: "OTHER").ShouldBeFalse(); MatchesServerFilter(info, host: "bad-host").ShouldBeFalse(); MatchesServerFilter(info, name: "bad-name").ShouldBeFalse(); } [Fact] // T:331 public void ServerEventsPingStatsZFailFilter_ShouldSucceed() { Should.Throw(() => JsonSerializer.Deserialize>("{MALFORMEDJSON")); var ok = JsonSerializer.Deserialize>("{\"cluster\":\"DOESNOTEXIST\"}"); ok.ShouldNotBeNull(); ok!.ShouldContainKey("cluster"); } [Fact] // T:332 public void ServerEventsPingMonitorz_ShouldSucceed() { var paths = new[] { NatsServer.MonitorPaths.Varz, NatsServer.MonitorPaths.Subsz, NatsServer.MonitorPaths.Connz, NatsServer.MonitorPaths.Routez, NatsServer.MonitorPaths.Gatewayz, NatsServer.MonitorPaths.Leafz, NatsServer.MonitorPaths.Accountz, NatsServer.MonitorPaths.Healthz, NatsServer.MonitorPaths.Expvarz, }; foreach (var path in paths) { path.ShouldStartWith("/"); path.Length.ShouldBeGreaterThan(1); } } [Fact] // T:335 public void ServerEventsReceivedByQSubs_ShouldSucceed() { var sublist = SubscriptionIndex.NewSublistWithCache(); var subject = "$SYS.SERVER.*.CLIENT.AUTH.ERR"; var queue = Encoding.UTF8.GetBytes("queue"); sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes(subject), Queue = queue }).ShouldBeNull(); sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes(subject), Queue = queue }).ShouldBeNull(); var result = sublist.Match("$SYS.SERVER.SRV.CLIENT.AUTH.ERR"); result.QSubs.Count.ShouldBe(1); result.QSubs[0].Count.ShouldBe(2); result.PSubs.Count.ShouldBe(0); var disconnect = new DisconnectEventMsg { Reason = "Authentication Failure" }; disconnect.Reason.ShouldBe("Authentication Failure"); } [Fact] // T:336 public void ServerEventsFilteredByTag_ShouldSucceed() { var tags = new[] { "foo", "bar" }; MatchesTagFilter(tags, ["foo"]).ShouldBeTrue(); MatchesTagFilter(tags, ["foo", "bar"]).ShouldBeTrue(); MatchesTagFilter(tags, ["baz"]).ShouldBeFalse(); MatchesTagFilter(tags, ["bar"]).ShouldBeTrue(); } [Fact] // T:337 public void ServerUnstableEventFilterMatch_ShouldSucceed() { var info = new ServerInfo { Name = "srv10", Cluster = "clust", Host = "127.0.0.1" }; MatchesServerFilter(info, name: "srv1", exactMatch: true).ShouldBeFalse(); MatchesServerFilter(info, name: "srv10", exactMatch: true).ShouldBeTrue(); MatchesServerFilter(info, name: "srv1", exactMatch: false).ShouldBeTrue(); } [Fact] // T:339 public void ServerEventsStatszSingleServer_ShouldSucceed() { var server = CreateServer(new ServerOptions { NoSystemAccount = true }); server.SystemAccount().ShouldBeNull(); server.SetDefaultSystemAccount().ShouldBeNull(); var sys = server.SystemAccount(); sys.ShouldNotBeNull(); sys!.NumConnections().ShouldBe(0); var c = new ClientConnection(ClientKind.Client, server) { Cid = 2001 }; c.RegisterWithAccount(sys); sys.NumConnections().ShouldBe(1); } [Fact] // T:340 public void ServerEventsReload_ShouldSucceed() { var server = CreateServer(); var resolver = new TrackingResolver(); SetPrivateField(server, "_accResolver", resolver); var before = GetPrivateField(server, "_configTime"); Thread.Sleep(5); server.Reload(); resolver.ReloadCalls.ShouldBe(1); var after = GetPrivateField(server, "_configTime"); after.ShouldBeGreaterThan(before); } [Fact] // T:341 public void ServerEventsLDMKick_ShouldSucceed() { var server = CreateServer(); var clients = GetPrivateField>(server, "_clients"); var c = new ClientConnection(ClientKind.Client, server) { Cid = 999 }; c.Opts = new ClientOptions { Protocol = ClientProtocol.Info }; c.Flags |= ClientFlags.FirstPongSent; clients[c.Cid] = c; server.LDMClientByID(c.Cid).ShouldBeNull(); server.DisconnectClientByID(c.Cid).ShouldBeNull(); c.IsClosed().ShouldBeTrue(); server.DisconnectClientByID(123456).ShouldNotBeNull(); } [Fact] // T:344 public void ServerEventsProfileZNotBlockingRecvQ_ShouldSucceed() { var recv = new IpQueue("recvq"); var priority = new IpQueue("recvqp"); recv.Push(1).error.ShouldBeNull(); priority.Push(2).error.ShouldBeNull(); var (v, ok) = priority.PopOne(); ok.ShouldBeTrue(); v.ShouldBe(2); recv.Len().ShouldBe(1); recv.Pop().ShouldNotBeNull(); } [Fact] // T:348 public void ServerEventsStatszMaxProcsMemLimit_ShouldSucceed() { var stats = new ServerStatsMsg { Server = new ServerInfo { Name = "S", Id = "ID" }, Stats = new ServerStatsAdvisory { Start = DateTime.UtcNow, MaxProcs = Environment.ProcessorCount * 2, MemLimit = 123456789, }, }; var json = JsonSerializer.Serialize(stats); json.ShouldContain("\"gomaxprocs\":"); json.ShouldContain("\"gomemlimit\":"); json.ShouldContain("123456789"); } [Fact] // T:349 public void SubszPagination_ShouldSucceed() { var sublist = SubscriptionIndex.NewSublistWithCache(); for (var i = 0; i < 100; i++) { sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes($"foo.{i}"), Sid = Encoding.UTF8.GetBytes(i.ToString()), }).ShouldBeNull(); } var all = new List(); sublist.All(all); all.Count.ShouldBe(100); all.Skip(0).Take(10).Count().ShouldBe(10); for (var i = 0; i < 10; i++) { sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes("bar.*"), Sid = Encoding.UTF8.GetBytes($"b{i}"), }).ShouldBeNull(); } var bar = sublist.Match("bar.A"); bar.PSubs.Count.ShouldBe(10); bar.PSubs.Skip(0).Take(5).Count().ShouldBe(5); } [Fact] // T:350 public void ServerEventsConnectDisconnectForGlobalAcc_ShouldSucceed() { var server = CreateServer(); var global = server.GlobalAccount(); global.ShouldNotBeNull(); global!.Name.ShouldBe(ServerConstants.DefaultGlobalAccount); var connectSubj = string.Format(SystemSubjects.ConnectEventSubj, ServerConstants.DefaultGlobalAccount); var disconnectSubj = string.Format(SystemSubjects.DisconnectEventSubj, ServerConstants.DefaultGlobalAccount); connectSubj.ShouldBe("$SYS.ACCOUNT.$G.CONNECT"); disconnectSubj.ShouldBe("$SYS.ACCOUNT.$G.DISCONNECT"); var c = new ClientConnection(ClientKind.Client, server) { Cid = 3001 }; c.RegisterWithAccount(global); global.NumConnections().ShouldBe(1); ((INatsAccount)global).RemoveClient(c); global.NumConnections().ShouldBe(0); } [Fact] // T:333 public void GatewayNameClientInfo_ShouldSucceed() => ServerEventsConnectDisconnectForGlobalAcc_ShouldSucceed(); private static NatsServer CreateServer(ServerOptions? opts = null) { var (server, err) = NatsServer.NewServer(opts ?? new ServerOptions()); err.ShouldBeNull(); server.ShouldNotBeNull(); return server!; } private static T GetPrivateField(object target, string name) { var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); field.ShouldNotBeNull(); var value = field!.GetValue(target); value.ShouldNotBeNull(); return (T)value!; } private static void SetPrivateField(object target, string name, T value) { var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); field.ShouldNotBeNull(); field!.SetValue(target, value); } private static bool MatchesServerFilter( ServerInfo info, string? cluster = null, string? host = null, string? name = null, bool exactMatch = false) { if (!string.IsNullOrEmpty(cluster) && !string.Equals(info.Cluster, cluster, StringComparison.OrdinalIgnoreCase)) return false; if (!string.IsNullOrEmpty(host) && !string.Equals(info.Host, host, StringComparison.OrdinalIgnoreCase)) return false; if (!string.IsNullOrEmpty(name)) { if (exactMatch) return string.Equals(info.Name, name, StringComparison.Ordinal); return info.Name.Contains(name, StringComparison.Ordinal); } return true; } private static bool MatchesTagFilter(IEnumerable serverTags, IEnumerable requestedTags) { var set = new HashSet(serverTags, StringComparer.OrdinalIgnoreCase); foreach (var tag in requestedTags) { if (!set.Contains(tag)) return false; } return true; } private sealed class TrackingResolver : ResolverDefaultsOps { public int ReloadCalls { get; private set; } public override Task FetchAsync(string name, CancellationToken ct = default) => Task.FromException(new InvalidOperationException($"unknown account: {name}")); public override void Reload() => ReloadCalls++; } }