// Copyright 2024-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Adapted from server/events_test.go in the NATS server Go source. using System.Reflection; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.IntegrationTests.Events; /// /// Integration tests ported from server/events_test.go. /// Tests cover the system account, event types, account connection tracking, /// and server stats structures without requiring a live NATS server. /// Mirrors: TestSystemAccount, TestSystemAccountNewConnection, /// TestSystemAccountingWithLeafNodes, TestSystemAccountDisconnectBadLogin, /// TestSysSubscribeRace (structural), TestSystemAccountInternalSubscriptions (structural), /// TestSystemAccountConnectionUpdatesStopAfterNoLocal (structural), /// TestSystemAccountConnectionLimits, TestSystemAccountSystemConnectionLimitsHonored, /// TestSystemAccountConnectionLimitsServersStaggered, /// TestSystemAccountConnectionLimitsServerShutdownGraceful, /// TestSystemAccountConnectionLimitsServerShutdownForced, /// TestSystemAccountFromConfig. /// [Trait("Category", "Integration")] public sealed class EventsTests { // ========================================================================= // TestSystemAccount (T:299) // ========================================================================= /// /// Verifies that a system account can be created and set on the server. /// Mirrors Go TestSystemAccount in server/events_test.go. /// [Fact] 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); } // ========================================================================= // TestSystemAccountNewConnection (T:300) // ========================================================================= /// /// Verifies that registering a connection on the system account increments /// the connection count. /// Mirrors Go TestSystemAccountNewConnection in server/events_test.go. /// [Fact] 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); } // ========================================================================= // TestSystemAccountingWithLeafNodes (T:301) // ========================================================================= /// /// Verifies that leaf-node connections are tracked separately in the system account. /// Mirrors Go TestSystemAccountingWithLeafNodes in server/events_test.go. /// [Fact] 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); } // ========================================================================= // TestSystemAccountDisconnectBadLogin (T:302) // ========================================================================= /// /// Verifies that an auth violation closes the client connection. /// Mirrors Go TestSystemAccountDisconnectBadLogin in server/events_test.go. /// [Fact] public void SystemAccountDisconnectBadLogin_ShouldSucceed() { var c = new ClientConnection(ClientKind.Client); c.AuthViolation(); c.IsClosed().ShouldBeTrue(); } // ========================================================================= // TestSysSubscribeRace (T:303) — structural variant // ========================================================================= /// /// Verifies that the system account exists and internal subscriptions can be /// established without races (structural/structural test without live server). /// Mirrors Go TestSysSubscribeRace in server/events_test.go. /// [Fact] public void SysSubscribeRace_SystemAccountExists_ShouldSucceed() { var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true }); err.ShouldBeNull(); server!.SetDefaultSystemAccount().ShouldBeNull(); var sys = server.SystemAccount(); sys.ShouldNotBeNull(); sys!.Name.ShouldBe(ServerConstants.DefaultSystemAccount); // Verify the system account is configured for internal subscriptions var sysFromServer = server.SystemAccount(); sysFromServer.ShouldNotBeNull(); sysFromServer!.Name.ShouldBe(sys.Name); } // ========================================================================= // TestSystemAccountInternalSubscriptions (T:304) — structural variant // ========================================================================= /// /// Verifies that internal subscription errors are reported when the system /// account is not configured. Mirrors Go TestSystemAccountInternalSubscriptions. /// [Fact] public void SystemAccountInternalSubscriptions_NoSystemAccount_ShouldSucceed() { // A server without a system account should have no system account set. var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true }); err.ShouldBeNull(); server.ShouldNotBeNull(); // Before setting a system account, SystemAccount() should be null. var sysBefore = server!.SystemAccount(); sysBefore.ShouldBeNull(); // After setting, it should be non-null. server.SetDefaultSystemAccount().ShouldBeNull(); var sysAfter = server.SystemAccount(); sysAfter.ShouldNotBeNull(); } // ========================================================================= // TestSystemAccountConnectionUpdatesStopAfterNoLocal (T:305) — structural // ========================================================================= /// /// Verifies connection count management when all local connections disconnect. /// Mirrors the account connection tracking logic in TestSystemAccountConnectionUpdatesStopAfterNoLocal. /// [Fact] public void SystemAccountConnectionUpdates_ConnectionCountTracking_ShouldSucceed() { var acc = Account.NewAccount("TEST"); acc.MaxConnections = 10; // Register 4 connections var conns = new List(); for (var i = 0; i < 4; i++) { var c = new ClientConnection(ClientKind.Client) { Cid = (ulong)(100 + i) }; c.RegisterWithAccount(acc); conns.Add(c); } acc.NumConnections().ShouldBe(4); // Disconnect all — count should go to 0 foreach (var c in conns) { ((INatsAccount)acc).RemoveClient(c); } acc.NumConnections().ShouldBe(0); } // ========================================================================= // TestSystemAccountConnectionLimits (T:306) // ========================================================================= /// /// Verifies that account connection limits are enforced. /// Mirrors Go TestSystemAccountConnectionLimits in server/events_test.go. /// [Fact] 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)); } // ========================================================================= // TestSystemAccountSystemConnectionLimitsHonored (T:308) // ========================================================================= /// /// Verifies that system client connections are exempt from account connection limits. /// Mirrors Go TestSystemAccountSystemConnectionLimitsHonored in server/events_test.go. /// [Fact] 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); // System clients do not count toward connection limits. acc.NumConnections().ShouldBe(0); } // ========================================================================= // TestSystemAccountConnectionLimitsServersStaggered (T:309) // ========================================================================= /// /// Verifies that multi-server connection limit enforcement correctly handles /// remote server connection counts. /// Mirrors Go TestSystemAccountConnectionLimitsServersStaggered. /// [Fact] 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); } // ========================================================================= // TestSystemAccountConnectionLimitsServerShutdownGraceful (T:310) // ========================================================================= /// /// Verifies that graceful server shutdown removes the server from remote tracking. /// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownGraceful. /// [Fact] 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); } // ========================================================================= // TestSystemAccountConnectionLimitsServerShutdownForced (T:311) // ========================================================================= /// /// Verifies that forced server shutdown removes the server from remote tracking. /// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownForced. /// [Fact] 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, }); // Remove a server not in the map — no effect. acc.RemoveRemoteServer("srv-missing"); acc.ExpectedRemoteResponses().ShouldBe(1); // Remove the actual server. acc.RemoveRemoteServer("srv-a"); acc.ExpectedRemoteResponses().ShouldBe(0); } // ========================================================================= // TestSystemAccountFromConfig (T:312) // ========================================================================= /// /// Verifies that the system account can be set via server options. /// Mirrors Go TestSystemAccountFromConfig in server/events_test.go. /// [Fact] 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"); } }