// Go: TestMonitorStacksz server/monitor_test.go:2135
// Go: TestMonitorConcurrentMonitoring server/monitor_test.go:2148
// Go: TestMonitorHandleRoot server/monitor_test.go:1819
// Go: TestMonitorHTTPBasePath server/monitor_test.go:220
// Go: TestMonitorAccountz server/monitor_test.go:4300
// Go: TestMonitorAccountStatz server/monitor_test.go:4330
using System.Net;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Text.Json;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Monitoring;
using NATS.Server.TestUtilities;
namespace NATS.Server.Monitoring.Tests.Monitoring;
///
/// Tests covering miscellaneous monitoring endpoints: root, accountz, accstatz,
/// gatewayz, leafz, and concurrent monitoring safety.
/// Ported from the Go server's monitor_test.go.
///
public class MonitorStackszTests : IAsyncLifetime
{
private readonly NatsServer _server;
private readonly int _natsPort;
private readonly int _monitorPort;
private readonly CancellationTokenSource _cts = new();
private readonly HttpClient _http = new();
public MonitorStackszTests()
{
_natsPort = TestPortAllocator.GetFreePort();
_monitorPort = TestPortAllocator.GetFreePort();
_server = new NatsServer(
new NatsOptions { Port = _natsPort, MonitorPort = _monitorPort },
NullLoggerFactory.Instance);
}
public async Task InitializeAsync()
{
_ = _server.StartAsync(_cts.Token);
await _server.WaitForReadyAsync();
for (var i = 0; i < 50; i++)
{
try
{
var probe = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/healthz");
if (probe.IsSuccessStatusCode) break;
}
catch (HttpRequestException) { }
await Task.Delay(50);
}
}
public async Task DisposeAsync()
{
_http.Dispose();
await _cts.CancelAsync();
_server.Dispose();
}
///
/// Go: TestMonitorHandleRoot (line 1819).
/// Verifies GET / returns HTTP 200 with endpoint listing.
///
[Fact]
public async Task Root_returns_endpoint_listing()
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var body = await response.Content.ReadAsStringAsync();
body.ShouldContain("varz");
body.ShouldContain("connz");
body.ShouldContain("routez");
body.ShouldContain("healthz");
}
///
/// Go: TestMonitorHandleRoot (line 1819).
/// Verifies GET / response includes subsz endpoint.
///
[Fact]
public async Task Root_includes_subz_endpoint()
{
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
body.ShouldContain("subz");
}
///
/// Go: TestMonitorAccountz (line 4300).
/// Verifies /accountz returns valid JSON with accounts list.
///
[Fact]
public async Task Accountz_returns_accounts_list()
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/accountz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var body = await response.Content.ReadAsStringAsync();
body.ShouldContain("accounts");
body.ShouldContain("num_accounts");
}
///
/// Go: TestMonitorAccountz (line 4300).
/// Verifies /accountz num_accounts is at least 1 (global account).
///
[Fact]
public async Task Accountz_num_accounts_at_least_one()
{
var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accountz"));
doc.RootElement.GetProperty("num_accounts").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
}
///
/// Go: TestMonitorAccountStatz (line 4330).
/// Verifies /accstatz returns aggregate account statistics.
///
[Fact]
public async Task Accstatz_returns_aggregate_stats()
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/accstatz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var body = await response.Content.ReadAsStringAsync();
body.ShouldContain("total_accounts");
body.ShouldContain("total_connections");
body.ShouldContain("total_subscriptions");
}
///
/// Go: TestMonitorAccountStatz (line 4330).
/// Verifies /accstatz total_accounts is at least 1.
///
[Fact]
public async Task Accstatz_total_accounts_at_least_one()
{
var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
doc.RootElement.GetProperty("total_accounts").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
}
///
/// Go: TestMonitorGateway (line 2880).
/// Verifies /gatewayz returns valid JSON.
///
[Fact]
public async Task Gatewayz_returns_valid_json()
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/gatewayz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var body = await response.Content.ReadAsStringAsync();
body.ShouldContain("gateways");
}
///
/// Go: TestMonitorLeafNode (line 3112).
/// Verifies /leafz returns valid JSON.
///
[Fact]
public async Task Leafz_returns_valid_json()
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/leafz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
var body = await response.Content.ReadAsStringAsync();
body.ShouldContain("leafs");
}
///
/// Go: TestMonitorConcurrentMonitoring (line 2148).
/// Verifies concurrent requests across multiple endpoint types do not fail.
///
[Fact]
public async Task Concurrent_requests_across_endpoints_succeed()
{
var endpoints = new[] { "varz", "varz", "connz", "connz", "subz", "subz", "routez", "routez" };
var tasks = endpoints.Select(async endpoint =>
{
for (var i = 0; i < 10; i++)
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/{endpoint}");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
});
await Task.WhenAll(tasks);
}
///
/// Go: TestMonitorConcurrentMonitoring (line 2148).
/// Verifies concurrent /healthz requests do not fail.
///
[Fact]
public async Task Concurrent_healthz_requests_succeed()
{
var tasks = Enumerable.Range(0, 20).Select(async _ =>
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/healthz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
});
await Task.WhenAll(tasks);
}
///
/// Go: TestMonitorHttpStatsNoUpdatedWhenUsingServerFuncs (line 2435).
/// Verifies /varz http_req_stats keys include all endpoints that were accessed.
///
[Fact]
public async Task Http_req_stats_tracks_accessed_endpoints()
{
// Access multiple endpoints
await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz");
await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/subz");
await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/routez");
await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/varz");
var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz");
varz.ShouldNotBeNull();
varz.HttpReqStats.ShouldContainKey("/connz");
varz.HttpReqStats.ShouldContainKey("/subz");
varz.HttpReqStats.ShouldContainKey("/routez");
varz.HttpReqStats.ShouldContainKey("/varz");
}
///
/// Go: TestMonitorHandleRoot (line 1819).
/// Verifies GET / includes jsz endpoint in listing.
///
[Fact]
public async Task Root_includes_jsz_endpoint()
{
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
body.ShouldContain("jsz");
}
///
/// Go: TestMonitorHandleRoot (line 1819).
/// Verifies GET / includes accountz endpoint in listing.
///
[Fact]
public async Task Root_includes_accountz_endpoint()
{
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
body.ShouldContain("accountz");
}
///
/// Go: TestMonitorServerIDs (line 2410).
/// Verifies multiple monitoring endpoints return the same server_id.
///
[Fact]
public async Task All_endpoints_return_consistent_server_id()
{
var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz");
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
var subsz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/subz");
varz.ShouldNotBeNull();
connz.ShouldNotBeNull();
subsz.ShouldNotBeNull();
var serverId = varz.Id;
serverId.ShouldNotBeNullOrEmpty();
connz.Id.ShouldBe(serverId);
subsz.Id.ShouldBe(serverId);
}
///
/// Go: TestMonitorAccountStatz (line 4330).
/// Verifies /accstatz total_connections updates after a client connects.
///
[Fact]
public async Task Accstatz_total_connections_updates_after_connect()
{
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await sock.ConnectAsync(new IPEndPoint(IPAddress.Loopback, _natsPort));
var buf = new byte[4096];
_ = await sock.ReceiveAsync(buf, SocketFlags.None);
await sock.SendAsync("CONNECT {}\r\n"u8.ToArray(), SocketFlags.None);
await Task.Delay(200);
var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
doc.RootElement.GetProperty("total_connections").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
}
///
/// Go: TestMonitorAccountStatz (line 4330).
/// Verifies /accstatz total_subscriptions updates after a client subscribes.
///
[Fact]
public async Task Accstatz_total_subscriptions_updates_after_subscribe()
{
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await sock.ConnectAsync(new IPEndPoint(IPAddress.Loopback, _natsPort));
var buf = new byte[4096];
_ = await sock.ReceiveAsync(buf, SocketFlags.None);
await sock.SendAsync("CONNECT {}\r\nSUB test 1\r\n"u8.ToArray(), SocketFlags.None);
await Task.Delay(200);
var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
doc.RootElement.GetProperty("total_subscriptions").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
}
///
/// Go: TestMonitorAccountz (line 4300).
/// Verifies /accountz includes per-account fields: name, connections, subscriptions.
///
[Fact]
public async Task Accountz_includes_per_account_fields()
{
using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await sock.ConnectAsync(new IPEndPoint(IPAddress.Loopback, _natsPort));
var buf = new byte[4096];
_ = await sock.ReceiveAsync(buf, SocketFlags.None);
await sock.SendAsync("CONNECT {}\r\nSUB test 1\r\n"u8.ToArray(), SocketFlags.None);
await Task.Delay(200);
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accountz");
body.ShouldContain("\"name\"");
body.ShouldContain("\"connections\"");
body.ShouldContain("\"subscriptions\"");
}
///
/// Go: TestMonitorGateway (line 2880).
/// Verifies /gatewayz includes num_gateways field.
///
[Fact]
public async Task Gatewayz_includes_num_gateways()
{
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/gatewayz");
body.ShouldContain("gateways");
}
///
/// Go: TestMonitorLeafNode (line 3112).
/// Verifies /leafz includes num_leafs field.
///
[Fact]
public async Task Leafz_includes_num_leafs()
{
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/leafz");
body.ShouldContain("leafs");
}
}