Move 50 auth/accounts/permissions/JWT/NKey test files from NATS.Server.Tests into a dedicated NATS.Server.Auth.Tests project. Update namespaces, replace private GetFreePort/ReadUntilAsync helpers with TestUtilities calls, replace Task.Delay with TaskCompletionSource in test doubles, and add InternalsVisibleTo. 690 tests pass.
227 lines
8.8 KiB
C#
227 lines
8.8 KiB
C#
// Port of Go server/accounts_test.go — TestSystemAccountDefaultCreation,
|
|
// TestSystemAccountSysSubjectRouting, TestNonSystemAccountCannotSubscribeToSys.
|
|
// Reference: golang/nats-server/server/accounts_test.go, server.go — initSystemAccount.
|
|
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server.Auth;
|
|
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Auth.Tests.Auth;
|
|
|
|
/// <summary>
|
|
/// Tests for the $SYS system account functionality including:
|
|
/// - Default system account creation with IsSystemAccount flag
|
|
/// - $SYS.> subject routing to the system account's SubList
|
|
/// - Non-system accounts blocked from subscribing to $SYS.> subjects
|
|
/// - System account event publishing
|
|
/// Reference: Go server/accounts.go — isSystemAccount, isReservedSubject.
|
|
/// </summary>
|
|
public class SystemAccountTests
|
|
{
|
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
|
|
private static async Task<(NatsServer server, int port, CancellationTokenSource cts)> StartServerAsync(NatsOptions options)
|
|
{
|
|
var port = TestPortAllocator.GetFreePort();
|
|
options.Port = port;
|
|
var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
var cts = new CancellationTokenSource();
|
|
_ = server.StartAsync(cts.Token);
|
|
await server.WaitForReadyAsync();
|
|
return (server, port, cts);
|
|
}
|
|
|
|
// ─── Tests ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Verifies that the server creates a $SYS system account by default with
|
|
/// IsSystemAccount set to true.
|
|
/// Reference: Go server/server.go — initSystemAccount.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Default_system_account_is_created()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
server.SystemAccount.ShouldNotBeNull();
|
|
server.SystemAccount.Name.ShouldBe(Account.SystemAccountName);
|
|
server.SystemAccount.IsSystemAccount.ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the system account constant matches "$SYS".
|
|
/// </summary>
|
|
[Fact]
|
|
public void System_account_name_constant_is_correct()
|
|
{
|
|
Account.SystemAccountName.ShouldBe("$SYS");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that a non-system account does not have IsSystemAccount set.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Regular_account_is_not_system_account()
|
|
{
|
|
var account = new Account("test-account");
|
|
account.IsSystemAccount.ShouldBeFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that IsSystemAccount can be explicitly set on an account.
|
|
/// </summary>
|
|
[Fact]
|
|
public void IsSystemAccount_can_be_set()
|
|
{
|
|
var account = new Account("custom-sys") { IsSystemAccount = true };
|
|
account.IsSystemAccount.ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that IsSystemSubject correctly identifies $SYS subjects.
|
|
/// Reference: Go server/server.go — isReservedSubject.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData("$SYS", true)]
|
|
[InlineData("$SYS.ACCOUNT.test.CONNECT", true)]
|
|
[InlineData("$SYS.SERVER.abc.STATSZ", true)]
|
|
[InlineData("$SYS.REQ.SERVER.PING.VARZ", true)]
|
|
[InlineData("foo.bar", false)]
|
|
[InlineData("$G", false)]
|
|
[InlineData("SYS.test", false)]
|
|
[InlineData("$JS.API.STREAM.LIST", false)]
|
|
[InlineData("$SYS.", true)]
|
|
public void IsSystemSubject_identifies_sys_subjects(string subject, bool expected)
|
|
{
|
|
NatsServer.IsSystemSubject(subject).ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the system account is listed among server accounts.
|
|
/// </summary>
|
|
[Fact]
|
|
public void System_account_is_in_server_accounts()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
var accounts = server.GetAccounts().ToList();
|
|
accounts.ShouldContain(a => a.Name == Account.SystemAccountName && a.IsSystemAccount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that IsSubscriptionAllowed blocks non-system accounts from $SYS.> subjects.
|
|
/// Reference: Go server/accounts.go — isReservedForSys.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Non_system_account_cannot_subscribe_to_sys_subjects()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
var regularAccount = new Account("regular");
|
|
|
|
server.IsSubscriptionAllowed(regularAccount, "$SYS.SERVER.abc.STATSZ").ShouldBeFalse();
|
|
server.IsSubscriptionAllowed(regularAccount, "$SYS.ACCOUNT.test.CONNECT").ShouldBeFalse();
|
|
server.IsSubscriptionAllowed(regularAccount, "$SYS.REQ.SERVER.PING.VARZ").ShouldBeFalse();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the system account IS allowed to subscribe to $SYS.> subjects.
|
|
/// </summary>
|
|
[Fact]
|
|
public void System_account_can_subscribe_to_sys_subjects()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
server.IsSubscriptionAllowed(server.SystemAccount, "$SYS.SERVER.abc.STATSZ").ShouldBeTrue();
|
|
server.IsSubscriptionAllowed(server.SystemAccount, "$SYS.ACCOUNT.test.CONNECT").ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that any account can subscribe to non-$SYS subjects.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Any_account_can_subscribe_to_regular_subjects()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
var regularAccount = new Account("regular");
|
|
|
|
server.IsSubscriptionAllowed(regularAccount, "foo.bar").ShouldBeTrue();
|
|
server.IsSubscriptionAllowed(regularAccount, "$JS.API.STREAM.LIST").ShouldBeTrue();
|
|
server.IsSubscriptionAllowed(server.SystemAccount, "foo.bar").ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that GetSubListForSubject routes $SYS subjects to the system account's SubList.
|
|
/// Reference: Go server/server.go — sublist routing for internal subjects.
|
|
/// </summary>
|
|
[Fact]
|
|
public void GetSubListForSubject_routes_sys_to_system_account()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
var globalAccount = server.GetOrCreateAccount(Account.GlobalAccountName);
|
|
|
|
// $SYS subjects should route to the system account's SubList
|
|
var sysList = server.GetSubListForSubject(globalAccount, "$SYS.SERVER.abc.STATSZ");
|
|
sysList.ShouldBeSameAs(server.SystemAccount.SubList);
|
|
|
|
// Regular subjects should route to the specified account's SubList
|
|
var regularList = server.GetSubListForSubject(globalAccount, "foo.bar");
|
|
regularList.ShouldBeSameAs(globalAccount.SubList);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the EventSystem publishes to the system account's SubList
|
|
/// and that internal subscriptions for monitoring are registered there.
|
|
/// The subscriptions are wired up during StartAsync via InitEventTracking.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Event_system_subscribes_in_system_account()
|
|
{
|
|
var (server, _, cts) = await StartServerAsync(new NatsOptions());
|
|
try
|
|
{
|
|
// The system account's SubList should have subscriptions registered
|
|
// by the internal event system (VARZ, HEALTHZ, etc.)
|
|
server.EventSystem.ShouldNotBeNull();
|
|
server.SystemAccount.SubList.Count.ShouldBeGreaterThan(0u);
|
|
}
|
|
finally
|
|
{
|
|
await cts.CancelAsync();
|
|
server.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the global account is separate from the system account.
|
|
/// </summary>
|
|
[Fact]
|
|
public void Global_and_system_accounts_are_separate()
|
|
{
|
|
var options = new NatsOptions { Port = 0 };
|
|
using var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
|
|
var globalAccount = server.GetOrCreateAccount(Account.GlobalAccountName);
|
|
var systemAccount = server.SystemAccount;
|
|
|
|
globalAccount.ShouldNotBeSameAs(systemAccount);
|
|
globalAccount.Name.ShouldBe(Account.GlobalAccountName);
|
|
systemAccount.Name.ShouldBe(Account.SystemAccountName);
|
|
globalAccount.IsSystemAccount.ShouldBeFalse();
|
|
systemAccount.IsSystemAccount.ShouldBeTrue();
|
|
globalAccount.SubList.ShouldNotBeSameAs(systemAccount.SubList);
|
|
}
|
|
}
|