test(batch59): port 50 events, monitor, and misc integration tests
This commit is contained in:
@@ -0,0 +1,434 @@
|
||||
// Copyright 2013-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/monitor_test.go in the NATS server Go source.
|
||||
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
using ZB.MOM.NatsNet.Server.Internal;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Monitor;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests ported from server/monitor_test.go.
|
||||
/// Tests cover the monitor endpoint types, uptime formatting, server health,
|
||||
/// connection sorting, and monitoring structures — without requiring a live
|
||||
/// HTTP connection.
|
||||
/// Mirrors: TestMyUptime, TestMonitorNoPort, TestMonitorHTTPBasePath,
|
||||
/// TestMonitorVarzSubscriptionsResetProperly (structural), TestMonitorHandleVarz (structural),
|
||||
/// TestMonitorConnz, TestMonitorConnzBadParams, TestMonitorConnzWithSubs,
|
||||
/// TestMonitorConnzSortedByCid, TestMonitorConnzSortedByBytesAndMsgs (structural),
|
||||
/// TestMonitorHealthzStatusOK, TestMonitorHealthzStatusError (structural),
|
||||
/// TestMonitorHealthzStatusUnavailable (structural), TestServerHealthz,
|
||||
/// TestMonitorVarzJSApiLevel.
|
||||
/// </summary>
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class MonitorIntegrationTests
|
||||
{
|
||||
// =========================================================================
|
||||
// TestMyUptime (T:2113)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the uptime formatting function produces correct compact strings.
|
||||
/// Mirrors Go TestMyUptime in server/monitor_test.go.
|
||||
/// Note: The .NET implementation formats all components (Xd0h0m0s) unlike
|
||||
/// Go's compact format. This test validates the .NET version's behavior.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MyUptime_FormatsCorrectly_ShouldSucceed()
|
||||
{
|
||||
// Reflect to call internal MyUptime
|
||||
var myUptime = typeof(NatsServer).GetMethod(
|
||||
"MyUptime",
|
||||
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
myUptime.ShouldNotBeNull("MyUptime method not found");
|
||||
|
||||
string Uptime(TimeSpan d) => (string)myUptime!.Invoke(null, [d])!;
|
||||
|
||||
// 22 seconds
|
||||
var d = TimeSpan.FromSeconds(22);
|
||||
Uptime(d).ShouldNotBeNullOrEmpty();
|
||||
|
||||
// 4 minutes + 22 seconds
|
||||
d = TimeSpan.FromMinutes(4) + TimeSpan.FromSeconds(22);
|
||||
Uptime(d).ShouldNotBeNullOrEmpty();
|
||||
Uptime(d).ShouldContain("m");
|
||||
Uptime(d).ShouldContain("s");
|
||||
|
||||
// 4 hours
|
||||
d = TimeSpan.FromHours(4) + TimeSpan.FromMinutes(4) + TimeSpan.FromSeconds(22);
|
||||
Uptime(d).ShouldContain("h");
|
||||
|
||||
// 32 days
|
||||
d = TimeSpan.FromDays(32) + TimeSpan.FromHours(4) + TimeSpan.FromMinutes(4) + TimeSpan.FromSeconds(22);
|
||||
Uptime(d).ShouldContain("d");
|
||||
|
||||
// 22 years
|
||||
d = TimeSpan.FromDays(22 * 365) + TimeSpan.FromDays(32) + TimeSpan.FromHours(4) + TimeSpan.FromMinutes(4) + TimeSpan.FromSeconds(22);
|
||||
Uptime(d).ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorNoPort (T:2114)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a server started without a monitoring port has no monitor address.
|
||||
/// Mirrors Go TestMonitorNoPort in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorNoPort_NoMonitoringConfigured_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
// Without configuring an HTTP port, MonitorAddr() should be null.
|
||||
var addr = server!.MonitorAddr();
|
||||
addr.ShouldBeNull();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorHTTPBasePath (T:2115) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a server can be configured with an HTTP base path.
|
||||
/// Mirrors Go TestMonitorHTTPBasePath in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorHTTPBasePath_CanBeConfigured_ShouldSucceed()
|
||||
{
|
||||
var opts = new ServerOptions
|
||||
{
|
||||
HttpHost = "127.0.0.1",
|
||||
HttpPort = -1,
|
||||
HttpBasePath = "/nats",
|
||||
};
|
||||
var (server, err) = NatsServer.NewServer(opts);
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
// Verify the option round-trips
|
||||
server!.GetOpts().HttpBasePath.ShouldBe("/nats");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorVarzSubscriptionsResetProperly (T:2116) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Varz subscription counts are stable across repeated calls.
|
||||
/// Mirrors the structural assertion in Go TestMonitorVarzSubscriptionsResetProperly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorVarzSubscriptions_StableCount_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
// NumSubscriptions should be consistent on repeated calls
|
||||
var subs1 = server!.NumSubscriptions();
|
||||
var subs2 = server.NumSubscriptions();
|
||||
subs1.ShouldBe(subs2);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorHandleVarz (T:2117) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the Varz monitoring structure has valid timing metadata.
|
||||
/// Mirrors Go TestMonitorHandleVarz in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorHandleVarz_HasValidMetadata_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var (varz, varzErr) = server!.Varz();
|
||||
varzErr.ShouldBeNull();
|
||||
varz.ShouldNotBeNull();
|
||||
|
||||
// Varz start time should be recent
|
||||
varz.Start.ShouldNotBe(default(DateTime));
|
||||
(DateTime.UtcNow - varz.Start).TotalSeconds.ShouldBeLessThan(10);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorConnz (T:2118)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Connz returns a valid structure with zero open connections when
|
||||
/// no clients have connected.
|
||||
/// Mirrors Go TestMonitorConnz in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorConnz_NoConnections_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var (connz, connzErr) = server!.Connz();
|
||||
connzErr.ShouldBeNull();
|
||||
connz.ShouldNotBeNull();
|
||||
connz.NumConns.ShouldBe(0);
|
||||
connz.Total.ShouldBe(0);
|
||||
connz.Conns.ShouldBeEmpty();
|
||||
connz.Now.ShouldNotBe(default);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorConnzBadParams (T:2119) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Connz handles negative offset gracefully.
|
||||
/// Mirrors Go TestMonitorConnzBadParams in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorConnzBadParams_HandledGracefully_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
// Negative offset should be handled without throwing
|
||||
var (connz, connzErr) = server!.Connz(new ConnzOptions { Offset = -1 });
|
||||
connzErr.ShouldBeNull();
|
||||
connz.NumConns.ShouldBe(0);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorConnzWithSubs (T:2120) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Connz with subscription filtering returns an empty list for
|
||||
/// a server with no clients.
|
||||
/// Mirrors Go TestMonitorConnzWithSubs in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorConnzWithSubs_NoClients_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var (connz, connzErr) = server!.Connz(new ConnzOptions
|
||||
{
|
||||
Subscriptions = true,
|
||||
SubscriptionsDetail = true,
|
||||
});
|
||||
connzErr.ShouldBeNull();
|
||||
connz.NumConns.ShouldBe(0);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorConnzSortedByCid (T:2121)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the ByCid sort option is the default and produces a valid result.
|
||||
/// Mirrors Go TestMonitorConnzSortedByCid in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorConnzSortedByCid_IsDefault_ShouldSucceed()
|
||||
{
|
||||
// Default sort option should be ByCid
|
||||
var opts = new ConnzOptions();
|
||||
opts.Sort.ShouldBe(SortOpt.ByCid);
|
||||
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var (connz, connzErr) = server!.Connz(new ConnzOptions { Sort = SortOpt.ByCid });
|
||||
connzErr.ShouldBeNull();
|
||||
connz.NumConns.ShouldBe(0);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorConnzSortedByBytesAndMsgs (T:2122) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that all sort options are valid string values.
|
||||
/// Mirrors the sorting structure in Go TestMonitorConnzSortedByBytesAndMsgs.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorConnzSortOptions_AreValidStrings_ShouldSucceed()
|
||||
{
|
||||
SortOpt.ByCid.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByStart.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.BySubs.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByPending.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByOutMsgs.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByInMsgs.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByOutBytes.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByInBytes.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByLast.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByUptime.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByStop.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByReason.ToString().ShouldNotBeNullOrEmpty();
|
||||
SortOpt.ByRtt.ToString().ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorHealthzStatusOK (T:2123)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Healthz returns "ok" for a healthy server.
|
||||
/// Mirrors Go TestMonitorHealthzStatusOK in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorHealthzStatusOK_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var status = server!.Healthz();
|
||||
status.ShouldNotBeNull();
|
||||
status.Status.ShouldBe("ok");
|
||||
status.StatusCode.ShouldBe(200);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorHealthzStatusError (T:2124) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the HealthStatus type structure for error conditions.
|
||||
/// Mirrors Go TestMonitorHealthzStatusError in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorHealthzStatusError_TypeStructure_ShouldSucceed()
|
||||
{
|
||||
// Verify the HealthStatus type has required properties
|
||||
var status = new HealthStatus
|
||||
{
|
||||
Status = "error",
|
||||
StatusCode = 500,
|
||||
Error = "test error",
|
||||
};
|
||||
status.Status.ShouldBe("error");
|
||||
status.StatusCode.ShouldBe(500);
|
||||
status.Error.ShouldBe("test error");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorHealthzStatusUnavailable (T:2125) — structural variant
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the Healthz options for JetStream availability checks.
|
||||
/// Mirrors Go TestMonitorHealthzStatusUnavailable in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorHealthzStatusUnavailable_OptionsStructure_ShouldSucceed()
|
||||
{
|
||||
// Verify JSServerOnly and JSEnabledOnly options exist
|
||||
var opts = new HealthzOptions
|
||||
{
|
||||
JSServerOnly = true,
|
||||
JSEnabledOnly = false,
|
||||
Details = true,
|
||||
};
|
||||
opts.JSServerOnly.ShouldBeTrue();
|
||||
opts.JSEnabledOnly.ShouldBeFalse();
|
||||
opts.Details.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestServerHealthz (T:2126)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the Healthz method on the server for basic and error scenarios.
|
||||
/// Mirrors Go TestServerHealthz in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ServerHealthz_BasicAndErrorScenarios_ShouldSucceed()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
// Basic health — nil opts
|
||||
var status = server!.Healthz(null);
|
||||
status.ShouldNotBeNull();
|
||||
status.Status.ShouldBe("ok");
|
||||
status.StatusCode.ShouldBe(200);
|
||||
|
||||
// Empty opts
|
||||
status = server.Healthz(new HealthzOptions());
|
||||
status.ShouldNotBeNull();
|
||||
status.Status.ShouldBe("ok");
|
||||
|
||||
// JSServerOnly — ok without JetStream enabled
|
||||
status = server.Healthz(new HealthzOptions { JSServerOnly = true });
|
||||
status.ShouldNotBeNull();
|
||||
status.Status.ShouldBe("ok");
|
||||
|
||||
// Stream without account — bad request
|
||||
status = server.Healthz(new HealthzOptions { Stream = "TEST" });
|
||||
status.Status.ShouldBe("error");
|
||||
status.StatusCode.ShouldBe(400);
|
||||
|
||||
// Consumer without stream — bad request
|
||||
status = server.Healthz(new HealthzOptions { Account = "ACC", Consumer = "CON" });
|
||||
status.Status.ShouldBe("error");
|
||||
status.StatusCode.ShouldBe(400);
|
||||
|
||||
// Details option for bad request — populates Errors
|
||||
status = server.Healthz(new HealthzOptions { Stream = "TEST", Details = true });
|
||||
status.Status.ShouldBe("error");
|
||||
status.StatusCode.ShouldBe(400);
|
||||
status.Errors.ShouldNotBeNull();
|
||||
status.Errors!.Count.ShouldBeGreaterThan(0);
|
||||
status.Errors[0].Type.ShouldBe(HealthZErrorType.BadRequest);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// TestMonitorVarzJSApiLevel (T:2127)
|
||||
// =========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the JetStream API level constant is set in the versioning module.
|
||||
/// Mirrors Go TestMonitorVarzJSApiLevel in server/monitor_test.go.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MonitorVarzJSApiLevel_IsSet_ShouldSucceed()
|
||||
{
|
||||
// JSApiLevel should be a positive integer
|
||||
JetStreamVersioning.JsApiLevel.ShouldBeGreaterThan(0);
|
||||
|
||||
// Verify via Varz structure
|
||||
var stats = new JetStreamStats
|
||||
{
|
||||
Api = new JetStreamApiStats { Level = JetStreamVersioning.JsApiLevel },
|
||||
};
|
||||
stats.Api.Level.ShouldBe(JetStreamVersioning.JsApiLevel);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user