Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerTests.cs
Joseph Doherty 0df93c23b0 feat: port session 09 — Server Core Init & Config
Port server/server.go account management and initialization (~1950 LOC):

- NatsServer.cs: full server struct fields (atomic counters, locks, maps,
  stubs for gateway/websocket/mqtt/ocsp/leafnode)
- NatsServer.Init.cs: factory methods (New/NewServer/NewServerFromConfig),
  compression helpers (ValidateAndNormalizeCompressionOption,
  SelectCompressionMode, SelectS2AutoModeBasedOnRtt, CompressOptsEqual),
  cluster-name management, validation (ValidateCluster, ValidatePinnedCerts,
  ValidateOptions), trusted-key processing, CLI helpers, running-state checks,
  and Start() stub
- NatsServer.Accounts.cs: account management (ConfigureAccounts,
  LookupOrRegisterAccount, RegisterAccount, SetSystemAccount,
  SetDefaultSystemAccount, SetSystemAccountInternal, CreateInternalClient*,
  ShouldTrackSubscriptions, RegisterAccountNoLock, SetAccountSublist,
  SetRouteInfo, LookupAccount, LookupOrFetchAccount, UpdateAccount,
  UpdateAccountWithClaimJwt, FetchRawAccountClaims, FetchAccountClaims,
  VerifyAccountClaims, FetchAccountFromResolver, GlobalAccountOnly,
  StandAloneMode, ConfiguredRoutes, ActivePeers, ComputeRoutePoolIdx)
- NatsServerTypes.cs: ServerInfo, ServerStats, NodeInfo, ServerProtocol,
  CompressionMode constants, AccountClaims stub, InternalState stub, and
  cross-session stubs for JetStream/gateway/websocket/mqtt/ocsp
- AuthTypes.cs: extend Account stub with Issuer, ClaimJwt, RoutePoolIdx,
  Incomplete, Updated, Sublist, Server fields, and IsExpired()
- ServerOptions.cs: add Accounts property (List<Account>)
- ServerTests.cs: 38 standalone tests (IDs 2866, 2882, plus compression
  and validation helpers); server-dependent tests marked n/a

Features: 77 complete (IDs 2974–3050)
Tests: 2 complete (2866, 2882); 18 n/a (server-dependent)
All tests: 545 unit + 1 integration pass
2026-02-26 14:18:18 -05:00

252 lines
9.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2012-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/server_test.go in the NATS server Go source.
// Session 09: standalone unit tests for NatsServer helpers.
using System.Text.RegularExpressions;
using Shouldly;
using Xunit;
namespace ZB.MOM.NatsNet.Server.Tests;
/// <summary>
/// Standalone unit tests for <see cref="NatsServer"/> helpers.
/// Tests that require a running server (listener, TLS, cluster) are marked n/a
/// and will be ported in sessions 1023.
/// </summary>
public sealed class ServerTests
{
// =========================================================================
// TestSemanticVersion — Test ID 2866
// Validates that ServerConstants.Version matches semver format.
// Mirrors Go TestSemanticVersion in server/server_test.go.
// =========================================================================
[Fact]
public void Version_IsValidSemVer()
{
// SemVer regex: major.minor.patch with optional pre-release / build meta.
var semVerRe = new Regex(@"^\d+\.\d+\.\d+(-\S+)?(\+\S+)?$", RegexOptions.Compiled);
semVerRe.IsMatch(ServerConstants.Version).ShouldBeTrue(
$"Version ({ServerConstants.Version}) is not a valid SemVer string");
}
// =========================================================================
// TestProcessCommandLineArgs — Test ID 2882
// Tests the ProcessCommandLineArgs helper.
// The Go version uses flag.FlagSet; our C# port takes string[].
// Mirrors Go TestProcessCommandLineArgs in server/server_test.go.
// =========================================================================
[Fact]
public void ProcessCommandLineArgs_NoArgs_ReturnsFalse()
{
var (showVersion, showHelp, err) = NatsServer.ProcessCommandLineArgs([]);
err.ShouldBeNull();
showVersion.ShouldBeFalse();
showHelp.ShouldBeFalse();
}
[Theory]
[InlineData("version", true, false)]
[InlineData("VERSION", true, false)]
[InlineData("help", false, true)]
[InlineData("HELP", false, true)]
public void ProcessCommandLineArgs_KnownSubcommand_ReturnsCorrectFlags(
string arg, bool wantVersion, bool wantHelp)
{
var (showVersion, showHelp, err) = NatsServer.ProcessCommandLineArgs([arg]);
err.ShouldBeNull();
showVersion.ShouldBe(wantVersion);
showHelp.ShouldBe(wantHelp);
}
[Fact]
public void ProcessCommandLineArgs_UnknownSubcommand_ReturnsError()
{
var (_, _, err) = NatsServer.ProcessCommandLineArgs(["foo"]);
err.ShouldNotBeNull();
}
// =========================================================================
// CompressionMode helpers — standalone tests for features 29762982
// =========================================================================
[Theory]
[InlineData("off", CompressionMode.Off)]
[InlineData("false", CompressionMode.Off)]
[InlineData("accept", CompressionMode.Accept)]
[InlineData("s2_fast", CompressionMode.S2Fast)]
[InlineData("fast", CompressionMode.S2Fast)]
[InlineData("better", CompressionMode.S2Better)]
[InlineData("best", CompressionMode.S2Best)]
public void ValidateAndNormalizeCompressionOption_KnownModes_NormalizesCorrectly(
string input, string expected)
{
var co = new CompressionOpts { Mode = input };
NatsServer.ValidateAndNormalizeCompressionOption(co, CompressionMode.S2Fast);
co.Mode.ShouldBe(expected);
}
[Fact]
public void ValidateAndNormalizeCompressionOption_OnAlias_MapsToChosenMode()
{
var co = new CompressionOpts { Mode = "on" };
NatsServer.ValidateAndNormalizeCompressionOption(co, CompressionMode.S2Better);
co.Mode.ShouldBe(CompressionMode.S2Better);
}
[Fact]
public void ValidateAndNormalizeCompressionOption_S2Auto_UsesDefaults_WhenNoThresholds()
{
var co = new CompressionOpts { Mode = "s2_auto" };
NatsServer.ValidateAndNormalizeCompressionOption(co, CompressionMode.S2Fast);
co.Mode.ShouldBe(CompressionMode.S2Auto);
co.RttThresholds.ShouldBe(NatsServer.DefaultCompressionS2AutoRttThresholds.ToList());
}
[Fact]
public void ValidateAndNormalizeCompressionOption_UnsupportedMode_Throws()
{
var co = new CompressionOpts { Mode = "bogus" };
Should.Throw<InvalidOperationException>(
() => NatsServer.ValidateAndNormalizeCompressionOption(co, CompressionMode.S2Fast));
}
[Theory]
[InlineData(5, CompressionMode.S2Uncompressed)] // <= 10 ms threshold
[InlineData(25, CompressionMode.S2Fast)] // 10 < rtt <= 50 ms
[InlineData(75, CompressionMode.S2Better)] // 50 < rtt <= 100 ms
[InlineData(150, CompressionMode.S2Best)] // > 100 ms
public void SelectS2AutoModeBasedOnRtt_DefaultThresholds_CorrectMode(int rttMs, string expected)
{
var result = NatsServer.SelectS2AutoModeBasedOnRtt(
TimeSpan.FromMilliseconds(rttMs),
NatsServer.DefaultCompressionS2AutoRttThresholds);
result.ShouldBe(expected);
}
[Theory]
[InlineData(CompressionMode.Off, CompressionMode.S2Fast, CompressionMode.Off)]
[InlineData(CompressionMode.Accept, CompressionMode.Accept, CompressionMode.Off)]
[InlineData(CompressionMode.S2Fast, CompressionMode.Accept, CompressionMode.S2Fast)]
[InlineData(CompressionMode.Accept, CompressionMode.S2Fast, CompressionMode.S2Fast)]
[InlineData(CompressionMode.Accept, CompressionMode.S2Auto, CompressionMode.S2Fast)]
public void SelectCompressionMode_TableDriven(string local, string remote, string expected)
{
NatsServer.SelectCompressionMode(local, remote).ShouldBe(expected);
}
[Fact]
public void SelectCompressionMode_RemoteNotSupported_ReturnsNotSupported()
{
NatsServer.SelectCompressionMode(CompressionMode.S2Fast, CompressionMode.NotSupported)
.ShouldBe(CompressionMode.NotSupported);
}
[Fact]
public void CompressOptsEqual_SameMode_ReturnsTrue()
{
var c1 = new CompressionOpts { Mode = CompressionMode.S2Fast };
var c2 = new CompressionOpts { Mode = CompressionMode.S2Fast };
NatsServer.CompressOptsEqual(c1, c2).ShouldBeTrue();
}
[Fact]
public void CompressOptsEqual_DifferentModes_ReturnsFalse()
{
var c1 = new CompressionOpts { Mode = CompressionMode.S2Fast };
var c2 = new CompressionOpts { Mode = CompressionMode.S2Best };
NatsServer.CompressOptsEqual(c1, c2).ShouldBeFalse();
}
// =========================================================================
// Validation helpers
// =========================================================================
[Fact]
public void ValidateCluster_ClusterNameWithSpaces_ReturnsError()
{
var opts = new ServerOptions();
opts.Cluster.Name = "bad name";
var err = NatsServer.ValidateCluster(opts);
err.ShouldNotBeNull();
err.ShouldBeSameAs(ServerErrors.ErrClusterNameHasSpaces);
}
[Fact]
public void ValidatePinnedCerts_ValidSha256_ReturnsNull()
{
var pinned = new PinnedCertSet(
[new string('a', 64)]); // 64 hex chars
var err = NatsServer.ValidatePinnedCerts(pinned);
err.ShouldBeNull();
}
[Fact]
public void ValidatePinnedCerts_InvalidSha256_ReturnsError()
{
var pinned = new PinnedCertSet(["not_a_sha256"]);
var err = NatsServer.ValidatePinnedCerts(pinned);
err.ShouldNotBeNull();
}
// =========================================================================
// GetServerProto
// =========================================================================
[Fact]
public void GetServerProto_DefaultOpts_ReturnsMsgTraceProto()
{
var opts = new ServerOptions();
// SetBaselineOptions so OverrideProto gets default 0.
opts.SetBaselineOptions();
var (s, err) = NatsServer.NewServer(opts);
err.ShouldBeNull();
s.ShouldNotBeNull();
s!.GetServerProto().ShouldBe(ServerProtocol.MsgTraceProto);
}
// =========================================================================
// Account helpers
// =========================================================================
[Fact]
public void ComputeRoutePoolIdx_PoolSizeOne_AlwaysReturnsZero()
{
NatsServer.ComputeRoutePoolIdx(1, "any-account").ShouldBe(0);
NatsServer.ComputeRoutePoolIdx(0, "any-account").ShouldBe(0);
}
[Fact]
public void ComputeRoutePoolIdx_PoolSizeN_ReturnsIndexInRange()
{
const int poolSize = 5;
var idx = NatsServer.ComputeRoutePoolIdx(poolSize, "my-account");
idx.ShouldBeInRange(0, poolSize - 1);
}
[Fact]
public void NeedsCompression_Empty_ReturnsFalse()
=> NatsServer.NeedsCompression(string.Empty).ShouldBeFalse();
[Fact]
public void NeedsCompression_Off_ReturnsFalse()
=> NatsServer.NeedsCompression(CompressionMode.Off).ShouldBeFalse();
[Fact]
public void NeedsCompression_S2Fast_ReturnsTrue()
=> NatsServer.NeedsCompression(CompressionMode.S2Fast).ShouldBeTrue();
}