// Go: TestMonitorHandleVarz server/monitor_test.go:275 // Go: TestMyUptime server/monitor_test.go:135 // Go: TestMonitorVarzSubscriptionsResetProperly server/monitor_test.go:257 // Go: TestMonitorNoPort server/monitor_test.go:168 // Go: TestMonitorHTTPBasePath server/monitor_test.go:220 // Go: TestMonitorHandleRoot server/monitor_test.go:1819 // Go: TestMonitorServerIDs server/monitor_test.go:2410 // Go: TestMonitorHttpStatsNoUpdatedWhenUsingServerFuncs server/monitor_test.go:2435 // Go: TestMonitorVarzRaces server/monitor_test.go:2641 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 /varz endpoint behavior, ported from the Go server's monitor_test.go. /// public class MonitorVarzTests : IAsyncLifetime { private readonly NatsServer _server; private readonly int _natsPort; private readonly int _monitorPort; private readonly CancellationTokenSource _cts = new(); private readonly HttpClient _http = new(); public MonitorVarzTests() { _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: TestMonitorHandleVarz (line 275), mode=0. /// Verifies /varz returns valid JSON with server identity fields including /// server_id, version, start time within 10s, host, port, max_payload. /// [Fact] public async Task Varz_returns_server_identity_and_start_within_10_seconds() { var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/varz"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var varz = await response.Content.ReadFromJsonAsync(); varz.ShouldNotBeNull(); varz.Id.ShouldNotBeNullOrEmpty(); varz.Version.ShouldNotBeNullOrEmpty(); // Go: if time.Since(v.Start) > 10*time.Second { t.Fatal(...) } (DateTime.UtcNow - varz.Start).ShouldBeLessThan(TimeSpan.FromSeconds(10)); } /// /// Go: TestMonitorHandleVarz (line 275), after connecting client. /// Verifies /varz tracks connections, in_msgs, out_msgs, in_bytes, out_bytes /// after a client connects, subscribes, and publishes. /// [Fact] public async Task Varz_tracks_connection_stats_after_client_pubsub() { 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); // Subscribe, publish 5-byte payload "hello", then flush await sock.SendAsync("CONNECT {}\r\nSUB foo 1\r\nPUB foo 5\r\nhello\r\n"u8.ToArray(), SocketFlags.None); await Task.Delay(200); var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); // Go: v.Connections != 1 varz.Connections.ShouldBeGreaterThanOrEqualTo(1); // Go: v.TotalConnections < 1 varz.TotalConnections.ShouldBeGreaterThanOrEqualTo(1UL); // Go: v.InMsgs != 1 varz.InMsgs.ShouldBeGreaterThanOrEqualTo(1L); // Go: v.InBytes != 5 varz.InBytes.ShouldBeGreaterThanOrEqualTo(5L); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies that /varz reports subscriptions count after a client subscribes. /// [Fact] public async Task Varz_reports_subscription_count() { 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\nSUB test2 2\r\n"u8.ToArray(), SocketFlags.None); await Task.Delay(200); var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Subscriptions.ShouldBeGreaterThanOrEqualTo(2u); } /// /// Go: TestMonitorVarzSubscriptionsResetProperly (line 257). /// Verifies /varz subscriptions count remains stable across multiple calls, /// and does not double on each request. /// [Fact] public async Task Varz_subscriptions_do_not_double_across_repeated_calls() { 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 varz1 = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); var subs1 = varz1!.Subscriptions; var varz2 = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); var subs2 = varz2!.Subscriptions; // Go: check that we get same number back (not doubled) subs2.ShouldBe(subs1); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz exposes JetStream config and stats sections. /// [Fact] public async Task Varz_includes_jetstream_section() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.JetStream.ShouldNotBeNull(); varz.JetStream.Config.ShouldNotBeNull(); varz.JetStream.Stats.ShouldNotBeNull(); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes runtime metrics: mem > 0, cores > 0. /// [Fact] public async Task Varz_includes_runtime_metrics() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Mem.ShouldBeGreaterThan(0L); varz.Cores.ShouldBeGreaterThan(0); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz uptime string is non-empty and matches expected format (e.g. "0s", "1m2s"). /// [Fact] public async Task Varz_uptime_is_formatted_string() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Uptime.ShouldNotBeNullOrEmpty(); // Uptime should end with 's' (seconds), matching Go format like "0s", "1m0s" varz.Uptime.ShouldEndWith("s"); } /// /// Go: TestMyUptime (line 135). /// Verifies the uptime formatting logic produces correct duration strings. /// Tests: 22s, 4m22s, 4h4m22s, 32d4h4m22s. /// [Theory] [InlineData(22, "22s")] [InlineData(22 + 4 * 60, "4m22s")] [InlineData(22 + 4 * 60 + 4 * 3600, "4h4m22s")] [InlineData(22 + 4 * 60 + 4 * 3600 + 32 * 86400, "32d4h4m22s")] public void Uptime_format_matches_go_myUptime(int totalSeconds, string expected) { var ts = TimeSpan.FromSeconds(totalSeconds); var result = FormatUptime(ts); result.ShouldBe(expected); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz serializes with correct Go JSON field names. /// [Fact] public async Task Varz_json_uses_go_field_names() { var response = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/varz"); response.ShouldContain("\"server_id\""); response.ShouldContain("\"server_name\""); response.ShouldContain("\"in_msgs\""); response.ShouldContain("\"out_msgs\""); response.ShouldContain("\"in_bytes\""); response.ShouldContain("\"out_bytes\""); response.ShouldContain("\"max_payload\""); response.ShouldContain("\"total_connections\""); response.ShouldContain("\"slow_consumers\""); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes nested configuration sections for cluster, gateway, leaf. /// [Fact] public async Task Varz_includes_cluster_gateway_leaf_sections() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Cluster.ShouldNotBeNull(); varz.Gateway.ShouldNotBeNull(); varz.Leaf.ShouldNotBeNull(); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz max_payload defaults to 1MB. /// [Fact] public async Task Varz_max_payload_defaults_to_1MB() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.MaxPayload.ShouldBe(1024 * 1024); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz host and port match the configured values. /// [Fact] public async Task Varz_host_and_port_match_configuration() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Port.ShouldBe(_natsPort); varz.Host.ShouldNotBeNullOrEmpty(); } /// /// Go: TestMonitorServerIDs (line 2410). /// Verifies /varz and /connz both expose the same server_id. /// [Fact] public async Task Varz_and_connz_report_matching_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"); varz.ShouldNotBeNull(); connz.ShouldNotBeNull(); varz.Id.ShouldNotBeNullOrEmpty(); connz.Id.ShouldBe(varz.Id); } /// /// Go: TestMonitorHttpStatsNoUpdatedWhenUsingServerFuncs (line 2435). /// Verifies /varz http_req_stats tracks endpoint hit counts and increments on each call. /// [Fact] public async Task Varz_http_req_stats_increment_on_each_request() { // First request establishes baseline 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("/varz"); var count = varz.HttpReqStats["/varz"]; count.ShouldBeGreaterThanOrEqualTo(2UL); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes slow_consumer_stats section with breakdown fields. /// [Fact] public async Task Varz_includes_slow_consumer_stats_breakdown() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.SlowConsumerStats.ShouldNotBeNull(); varz.SlowConsumerStats.Clients.ShouldBeGreaterThanOrEqualTo(0UL); varz.SlowConsumerStats.Routes.ShouldBeGreaterThanOrEqualTo(0UL); varz.SlowConsumerStats.Gateways.ShouldBeGreaterThanOrEqualTo(0UL); varz.SlowConsumerStats.Leafs.ShouldBeGreaterThanOrEqualTo(0UL); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes proto version field. /// [Fact] public async Task Varz_includes_proto_version() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Proto.ShouldBeGreaterThanOrEqualTo(0); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz config_load_time is set. /// [Fact] public async Task Varz_config_load_time_is_set() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.ConfigLoadTime.ShouldBeGreaterThan(DateTime.MinValue); } /// /// Go: TestMonitorVarzRaces (line 2641). /// Verifies concurrent /varz requests do not cause errors or data corruption. /// [Fact] public async Task Varz_handles_concurrent_requests_without_errors() { var tasks = Enumerable.Range(0, 10).Select(async _ => { var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/varz"); response.StatusCode.ShouldBe(HttpStatusCode.OK); var v = await response.Content.ReadFromJsonAsync(); v.ShouldNotBeNull(); v.Id.ShouldNotBeNullOrEmpty(); }); await Task.WhenAll(tasks); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz out_msgs increments when messages are delivered to subscribers. /// [Fact] public async Task Varz_out_msgs_increments_on_delivery() { 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); // Subscribe then publish to matched subject await sock.SendAsync("CONNECT {}\r\nSUB foo 1\r\nPUB foo 5\r\nhello\r\n"u8.ToArray(), SocketFlags.None); await Task.Delay(200); var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); // Message was published and delivered to the subscriber, so out_msgs >= 1 varz.OutMsgs.ShouldBeGreaterThanOrEqualTo(1L); varz.OutBytes.ShouldBeGreaterThanOrEqualTo(5L); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes MQTT section in response. /// [Fact] public async Task Varz_includes_mqtt_section() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Mqtt.ShouldNotBeNull(); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz includes websocket section. /// [Fact] public async Task Varz_includes_websocket_section() { var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); varz.Websocket.ShouldNotBeNull(); } /// /// Go: TestMonitorHandleRoot (line 1819). /// Verifies GET / returns a listing of available monitoring endpoints. /// [Fact] public async Task Root_endpoint_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("healthz"); } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz total_connections tracks cumulative connections, not just active. /// [Fact] public async Task Varz_total_connections_tracks_cumulative_count() { // Connect and disconnect a client 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(100); sock.Shutdown(SocketShutdown.Both); sock.Dispose(); await Task.Delay(300); // Connect a second client (still active) using var sock2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock2.ConnectAsync(new IPEndPoint(IPAddress.Loopback, _natsPort)); buf = new byte[4096]; _ = await sock2.ReceiveAsync(buf, SocketFlags.None); await sock2.SendAsync("CONNECT {}\r\n"u8.ToArray(), SocketFlags.None); await Task.Delay(200); var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); varz.ShouldNotBeNull(); // Total should be >= 2 (both connections counted), active should be 1 varz.TotalConnections.ShouldBeGreaterThanOrEqualTo(2UL); varz.Connections.ShouldBeGreaterThanOrEqualTo(1); } /// /// Go: TestMonitorNoPort (line 168). /// Verifies that when no monitor port is configured, monitoring endpoints are not accessible. /// This is a standalone test since it uses a different server configuration. /// [Fact] public async Task Monitor_not_accessible_when_port_not_configured() { var natsPort = GetFreePort(); var server = new NatsServer( new NatsOptions { Port = natsPort, MonitorPort = 0 }, NullLoggerFactory.Instance); var cts = new CancellationTokenSource(); _ = server.StartAsync(cts.Token); await server.WaitForReadyAsync(); try { using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) }; // Try a random port where no monitor should be running var act = async () => await http.GetAsync("http://127.0.0.1:11245/varz"); await act.ShouldThrowAsync(); } finally { await cts.CancelAsync(); server.Dispose(); } } /// /// Go: TestMonitorHandleVarz (line 275). /// Verifies /varz now field returns a plausible UTC timestamp. /// [Fact] public async Task Varz_now_is_plausible_utc_timestamp() { var before = DateTime.UtcNow; var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz"); var after = DateTime.UtcNow; varz.ShouldNotBeNull(); varz.Now.ShouldBeGreaterThanOrEqualTo(before.AddSeconds(-1)); varz.Now.ShouldBeLessThanOrEqualTo(after.AddSeconds(1)); } // Helper: matches Go server myUptime() format private static string FormatUptime(TimeSpan ts) { if (ts.TotalDays >= 1) return $"{(int)ts.TotalDays}d{ts.Hours}h{ts.Minutes}m{ts.Seconds}s"; if (ts.TotalHours >= 1) return $"{(int)ts.TotalHours}h{ts.Minutes}m{ts.Seconds}s"; if (ts.TotalMinutes >= 1) return $"{(int)ts.TotalMinutes}m{ts.Seconds}s"; return $"{(int)ts.TotalSeconds}s"; } 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; } }