Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/Events/EventsTests.cs

370 lines
14 KiB
C#

// 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;
/// <summary>
/// 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.
/// </summary>
[Trait("Category", "Integration")]
public sealed class EventsTests
{
// =========================================================================
// TestSystemAccount (T:299)
// =========================================================================
/// <summary>
/// Verifies that a system account can be created and set on the server.
/// Mirrors Go TestSystemAccount in server/events_test.go.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that registering a connection on the system account increments
/// the connection count.
/// Mirrors Go TestSystemAccountNewConnection in server/events_test.go.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that leaf-node connections are tracked separately in the system account.
/// Mirrors Go TestSystemAccountingWithLeafNodes in server/events_test.go.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that an auth violation closes the client connection.
/// Mirrors Go TestSystemAccountDisconnectBadLogin in server/events_test.go.
/// </summary>
[Fact]
public void SystemAccountDisconnectBadLogin_ShouldSucceed()
{
var c = new ClientConnection(ClientKind.Client);
c.AuthViolation();
c.IsClosed().ShouldBeTrue();
}
// =========================================================================
// TestSysSubscribeRace (T:303) — structural variant
// =========================================================================
/// <summary>
/// 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.
/// </summary>
[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
// =========================================================================
/// <summary>
/// Verifies that internal subscription errors are reported when the system
/// account is not configured. Mirrors Go TestSystemAccountInternalSubscriptions.
/// </summary>
[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
// =========================================================================
/// <summary>
/// Verifies connection count management when all local connections disconnect.
/// Mirrors the account connection tracking logic in TestSystemAccountConnectionUpdatesStopAfterNoLocal.
/// </summary>
[Fact]
public void SystemAccountConnectionUpdates_ConnectionCountTracking_ShouldSucceed()
{
var acc = Account.NewAccount("TEST");
acc.MaxConnections = 10;
// Register 4 connections
var conns = new List<ClientConnection>();
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)
// =========================================================================
/// <summary>
/// Verifies that account connection limits are enforced.
/// Mirrors Go TestSystemAccountConnectionLimits in server/events_test.go.
/// </summary>
[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<TooManyAccountConnectionsException>(() => c2.RegisterWithAccount(acc));
}
// =========================================================================
// TestSystemAccountSystemConnectionLimitsHonored (T:308)
// =========================================================================
/// <summary>
/// Verifies that system client connections are exempt from account connection limits.
/// Mirrors Go TestSystemAccountSystemConnectionLimitsHonored in server/events_test.go.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that multi-server connection limit enforcement correctly handles
/// remote server connection counts.
/// Mirrors Go TestSystemAccountConnectionLimitsServersStaggered.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that graceful server shutdown removes the server from remote tracking.
/// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownGraceful.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that forced server shutdown removes the server from remote tracking.
/// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownForced.
/// </summary>
[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)
// =========================================================================
/// <summary>
/// Verifies that the system account can be set via server options.
/// Mirrors Go TestSystemAccountFromConfig in server/events_test.go.
/// </summary>
[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");
}
}