// 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; /// /// Standalone unit tests for helpers. /// Tests that require a running server (listener, TLS, cluster) are marked n/a /// and will be ported in sessions 10–23. /// 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 2976–2982 // ========================================================================= [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( () => 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(); }