// 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; namespace NATS.Server.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 = GetFreePort(); _monitorPort = 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"); } private static int GetFreePort() { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Bind(new IPEndPoint(IPAddress.Loopback, 0)); return ((IPEndPoint)sock.LocalEndPoint!).Port; } }