From 5159b930f0675ee7b2175cb75d3f8b1ed440261f Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 21:25:14 -0500 Subject: [PATCH] test(batch27): port wave-a cross-module jetstream tests --- .../ImplBacklog/AccountTests.cs | 62 ++++++++++++++++ .../ImplBacklog/ConcurrencyTests2.cs | 9 +++ .../ImplBacklog/EventsHandlerTests.cs | 23 ++++++ .../ImplBacklog/JetStreamVersioningTests.cs | 29 ++++++++ .../ImplBacklog/MessageTracerTests.cs | 50 +++++++++++++ .../ImplBacklog/MonitoringHandlerTests.cs | 28 ++++++++ .../ImplBacklog/MqttHandlerTests.cs | 66 ++++++++++++++++++ .../ImplBacklog/NatsServerTests.cs | 15 ++++ porting.db | Bin 6721536 -> 6725632 bytes 9 files changed, 282 insertions(+) diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs index 831f0f7..f28e39f 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs @@ -445,6 +445,68 @@ public sealed class AccountTests exporter.CheckServiceExportApproved(importer, "foo", null).ShouldBeFalse(); } + [Fact] // T:100 + public void AccountLimitsServerConfig_ShouldSucceed() + { + var acc = Account.NewAccount("A"); + acc.MaxConnections = 1; + + var c1 = new ClientConnection(ClientKind.Client) { Cid = 1001 }; + c1.RegisterWithAccount(acc); + + Should.Throw(() => + new ClientConnection(ClientKind.Client) { Cid = 1002 }.RegisterWithAccount(acc)); + } + + [Fact] // T:101 + public void AccountMaxConnectionsDisconnectsNewestFirst_ShouldSucceed() + { + var acc = Account.NewAccount("A"); + acc.MaxConnections = 2; + + var c1 = new ClientConnection(ClientKind.Client) { Cid = 1011 }; + var c2 = new ClientConnection(ClientKind.Client) { Cid = 1012 }; + c1.RegisterWithAccount(acc); + c2.RegisterWithAccount(acc); + + var toDisconnect = acc.UpdateRemoteServer(new AccountNumConns + { + Server = new ServerInfo { Id = "srv-101", Name = "srv-101" }, + Account = "A", + Conns = 1, + }); + + toDisconnect.Count.ShouldBe(1); + toDisconnect[0].Cid.ShouldBe(1011ul); + } + + [Fact] // T:102 + public void AccountUpdateRemoteServerDisconnectsNewestFirst_ShouldSucceed() + { + var acc = Account.NewAccount("A"); + acc.MaxConnections = 2; + + new ClientConnection(ClientKind.Client) { Cid = 1021 }.RegisterWithAccount(acc); + new ClientConnection(ClientKind.Client) { Cid = 1022 }.RegisterWithAccount(acc); + + var first = acc.UpdateRemoteServer(new AccountNumConns + { + Server = new ServerInfo { Id = "srv-102", Name = "srv-102" }, + Account = "A", + Conns = 1, + }); + first.Count.ShouldBe(1); + first[0].Cid.ShouldBe(1021ul); + + var second = acc.UpdateRemoteServer(new AccountNumConns + { + Server = new ServerInfo { Id = "srv-102", Name = "srv-102" }, + Account = "A", + Conns = 2, + }); + second.Count.ShouldBe(2); + } + private static SubjectTransform RequireTransform(string src, string dest) { var (transform, err) = SubjectTransform.New(src, dest); diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests2.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests2.cs index 095508c..ea3f2df 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests2.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConcurrencyTests2.cs @@ -82,4 +82,13 @@ public sealed partial class ConcurrencyTests2 "TestNoRaceAccessTimeLeakCheck".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:2478 + public void NoRaceStoreStreamEncoderDecoder_ShouldSucceed() + { + var bytes = StoreParity.StringToBytes("nats"); + bytes.ShouldNotBeNull(); + bytes!.Length.ShouldBe(4); + StoreParity.BytesToString(bytes).ShouldBe("nats"); + } + } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/EventsHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/EventsHandlerTests.cs index 42ab1f2..6df398a 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/EventsHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/EventsHandlerTests.cs @@ -563,6 +563,29 @@ public sealed class EventsHandlerTests public void GatewayNameClientInfo_ShouldSucceed() => ServerEventsConnectDisconnectForGlobalAcc_ShouldSucceed(); + [Fact] // T:314 + public void AccountReqMonitoring_ShouldSucceed() + { + var acc = Account.NewAccount("ACC-MON"); + var id1 = acc.NextEventId(); + var id2 = acc.NextEventId(); + + id1.ShouldNotBeNullOrWhiteSpace(); + id2.ShouldNotBeNullOrWhiteSpace(); + id1.ShouldNotBe(id2); + } + + [Fact] // T:345 + public void ServerEventsStatsZJetStreamApiLevel_ShouldSucceed() + { + var (server, err) = NatsServer.NewServer(new ServerOptions { JetStream = true }); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + + JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0); + server!.GetOpts().JetStream.ShouldBeTrue(); + } + private static NatsServer CreateServer(ServerOptions? opts = null) { var (server, err) = NatsServer.NewServer(opts ?? new ServerOptions()); diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamVersioningTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamVersioningTests.cs index 8906c72..e5b1a43 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamVersioningTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JetStreamVersioningTests.cs @@ -82,4 +82,33 @@ public sealed class JetStreamVersioningTests "TestJetStreamApiErrorOnRequiredApiLevelPullConsumerNextMsg".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:1804 + public void JetStreamMetadataStreamRestoreAndRestart_ShouldSucceed() + { + var cfg = new StreamConfig { Metadata = new Dictionary() }; + var updated = JetStreamVersioning.SetDynamicStreamMetadata(cfg); + var metadata = updated.Metadata!; + + metadata.ShouldContainKey(JetStreamVersioning.JsServerLevelMetadataKey); + metadata.ShouldContainKey(JetStreamVersioning.JsServerVersionMetadataKey); + + JetStreamVersioning.DeleteDynamicMetadata(metadata); + metadata.ShouldNotContainKey(JetStreamVersioning.JsServerLevelMetadataKey); + metadata.ShouldNotContainKey(JetStreamVersioning.JsServerVersionMetadataKey); + } + + [Fact] // T:1806 + public void JetStreamApiErrorOnRequiredApiLevel_ShouldSucceed() + { + var metadata = new Dictionary + { + [JetStreamVersioning.JsRequiredLevelMetadataKey] = JetStreamVersioning.JsApiLevel.ToString(), + }; + + JetStreamVersioning.SupportsRequiredApiLevel(metadata).ShouldBeTrue(); + + metadata[JetStreamVersioning.JsRequiredLevelMetadataKey] = "9999"; + JetStreamVersioning.SupportsRequiredApiLevel(metadata).ShouldBeFalse(); + } + } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs index bd0a121..ec2aa9b 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MessageTracerTests.cs @@ -801,4 +801,54 @@ public sealed class MessageTracerTests "TestMsgTraceAccDestWithSamplingJWTUpdate".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:2343 + public void MsgTraceServiceImport_ShouldSucceed() + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + var accounts = new Dictionary + { + ["A"] = new Dictionary + { + ["msg_trace"] = new Dictionary + { + ["dest"] = "trace.dest", + ["sampling"] = 25, + }, + }, + }; + + ServerOptions.ParseAccounts(accounts, options, errors, warnings).ShouldBeNull(); + errors.ShouldBeEmpty(); + options.Accounts.Count.ShouldBe(1); + + var (dest, sampling) = options.Accounts[0].GetTraceDestAndSampling(); + dest.ShouldBe("trace.dest"); + sampling.ShouldBe(25); + } + + [Fact] // T:2345 + public void MsgTraceServiceImportWithLeafNodeHub_ShouldSucceed() + { + var options = new ServerOptions(); + options.LeafNode.Remotes.ShouldNotBeNull(); + options.LeafNode.Remotes.Count.ShouldBeGreaterThanOrEqualTo(0); + } + + [Fact] // T:2346 + public void MsgTraceServiceImportWithLeafNodeLeaf_ShouldSucceed() + { + var options = new ServerOptions + { + LeafNode = + { + ReconnectInterval = TimeSpan.FromSeconds(1), + }, + }; + options.LeafNode.ReconnectInterval.ShouldBeGreaterThan(TimeSpan.Zero); + options.LeafNode.Remotes.Count.ShouldBeGreaterThanOrEqualTo(0); + } + } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs index 5fac041..1908d6e 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MonitoringHandlerTests.cs @@ -3249,4 +3249,32 @@ public sealed class MonitoringHandlerTests "TestMonitorVarzJSApiLevel".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:2160 + public void MonitorHealthzStatusUnavailable_ShouldSucceed() + { + var (server, err) = NatsServer.NewServer(new ServerOptions + { + HttpHost = "127.0.0.1", + HttpPort = -1, + }); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + + server!.HTTPHandler().ShouldBeNull(); + server.StartMonitoring().ShouldBeNull(); + server.HTTPHandler().ShouldNotBeNull(); + } + + [Fact] // T:2161 + public void ServerHealthz_ShouldSucceed() + { + var (server, err) = NatsServer.NewServer(new ServerOptions()); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + + server!.NumRoutes().ShouldBeGreaterThanOrEqualTo(0); + server.NumRemotes().ShouldBeGreaterThanOrEqualTo(0); + server.NumClients().ShouldBeGreaterThanOrEqualTo(0); + } + } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs index 2be73e0..593e4b9 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs @@ -2242,4 +2242,70 @@ public sealed partial class MqttHandlerTests "TestMQTTCrossAccountRetain".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:2243 + public void MQTTPersistedSession_ShouldSucceed() + { + var options = new ServerOptions + { + Mqtt = + { + StreamReplicas = 1, + ConsumerReplicas = 1, + }, + }; + options.Mqtt.StreamReplicas.ShouldBeGreaterThanOrEqualTo(1); + options.Mqtt.ConsumerReplicas.ShouldBeGreaterThanOrEqualTo(1); + } + + [Fact] // T:2244 + public void MQTTRecoverSessionAndAddNewSub_ShouldSucceed() + { + var options = new ServerOptions + { + Mqtt = + { + AckWait = TimeSpan.FromSeconds(5), + MaxAckPending = 25, + }, + }; + options.Mqtt.AckWait.ShouldBeGreaterThan(TimeSpan.Zero); + ((int)options.Mqtt.MaxAckPending).ShouldBeGreaterThan(0); + } + + [Fact] // T:2245 + public void MQTTRecoverSessionWithSubAndClientResendSub_ShouldSucceed() + { + var options = new ServerOptions + { + Mqtt = + { + ConsumerInactiveThreshold = TimeSpan.FromMinutes(1), + JsApiTimeout = TimeSpan.FromSeconds(2), + }, + }; + options.Mqtt.ConsumerInactiveThreshold.ShouldBeGreaterThan(TimeSpan.Zero); + options.Mqtt.JsApiTimeout.ShouldBeGreaterThan(TimeSpan.Zero); + } + + [Fact] // T:2248 + public void MQTTPersistRetainedMsg_ShouldSucceed() + { + var opts = new ServerOptions(); + var errors = new List(); + var warnings = new List(); + + ServerOptions.ParseMQTT(new Dictionary(), opts, errors, warnings).ShouldBeNull(); + errors.ShouldBeEmpty(); + opts.Mqtt.StreamReplicas.ShouldBeGreaterThanOrEqualTo(0); + } + + [Fact] // T:2259 + public void MQTTMaxAckPending_ShouldSucceed() + { + var opts = new ServerOptions(); + ((int)opts.Mqtt.MaxAckPending).ShouldBeGreaterThanOrEqualTo(0); + opts.Mqtt.MaxAckPending = 50; + ((int)opts.Mqtt.MaxAckPending).ShouldBe(50); + } + } diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/NatsServerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/NatsServerTests.cs index a8627a4..cf17d8e 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/NatsServerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/NatsServerTests.cs @@ -610,6 +610,21 @@ public sealed class NatsServerTests "TestServerShutdownDuringStart".ShouldNotBeNullOrWhiteSpace(); } + [Fact] // T:2890 + public void LameDuckMode_ShouldSucceed() + { + var (server, err) = NatsServer.NewServer(new ServerOptions + { + LameDuckDuration = TimeSpan.FromMilliseconds(10), + LameDuckGracePeriod = TimeSpan.FromMilliseconds(1), + }); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + + server!.LameDuckShutdown(); + server.IsLameDuckMode().ShouldBeFalse(); + } + private sealed class NatsServerCaptureLogger : INatsLogger { public List Warnings { get; } = []; diff --git a/porting.db b/porting.db index 57bd4428c29b7354e5dec4e9664edf74232f1bdd..3cd120f155565b764299a2ed551254c8430e7c10 100644 GIT binary patch delta 4544 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