370 lines
14 KiB
C#
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");
|
|
}
|
|
}
|