// Ported from golang/nats-server/server/monitor_test.go // TestMonitorHandleVarz — verify /varz returns valid server identity fields and tracks message stats. using System.Net; using System.Net.Http.Json; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Monitoring; using NATS.Server.TestUtilities; namespace NATS.Server.Monitoring.Tests; public class VarzParityTests : IAsyncLifetime { private readonly NatsServer _server; private readonly int _natsPort; private readonly int _monitorPort; private readonly CancellationTokenSource _cts = new(); private readonly HttpClient _http = new(); public VarzParityTests() { _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(); } /// /// Corresponds to Go TestMonitorHandleVarz (first block, mode=0). /// Verifies the /varz endpoint returns valid JSON containing required server identity fields: /// server_id, version, now, start, host, port, max_payload, mem, cores. /// [Fact] public async Task Varz_returns_valid_json_with_server_info() { 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(); // server_id must be present and non-empty varz.Id.ShouldNotBeNullOrEmpty(); // version must be present varz.Version.ShouldNotBeNullOrEmpty(); // now must be a plausible timestamp (not default DateTime.MinValue) varz.Now.ShouldBeGreaterThan(DateTime.MinValue); // start must be within a reasonable window of now (DateTime.UtcNow - varz.Start).ShouldBeLessThan(TimeSpan.FromSeconds(30)); // host and port must reflect server configuration varz.Host.ShouldNotBeNullOrEmpty(); varz.Port.ShouldBe(_natsPort); // max_payload is 1 MB by default (Go reference: defaultMaxPayload = 1MB) varz.MaxPayload.ShouldBe(1024 * 1024); // uptime must be non-empty varz.Uptime.ShouldNotBeNullOrEmpty(); // runtime metrics must be populated varz.Mem.ShouldBeGreaterThan(0L); varz.Cores.ShouldBeGreaterThan(0); } /// /// Corresponds to Go TestMonitorHandleVarz (second block after connecting a client). /// Verifies /varz correctly tracks connections, total_connections, in_msgs, in_bytes /// after a client connects, subscribes, and publishes a message. /// [Fact] public async Task Varz_tracks_connections_and_messages() { 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); // consume INFO // CONNECT + SUB + PUB "hello" (5 bytes) to "test" var cmd = "CONNECT {}\r\nSUB test 1\r\nPUB test 5\r\nhello\r\n"u8.ToArray(); await sock.SendAsync(cmd, SocketFlags.None); await Task.Delay(200); 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(); // At least 1 active connection varz.Connections.ShouldBeGreaterThanOrEqualTo(1); // Total connections must have been counted varz.TotalConnections.ShouldBeGreaterThanOrEqualTo(1UL); // in_msgs: at least the 1 PUB we sent varz.InMsgs.ShouldBeGreaterThanOrEqualTo(1L); // in_bytes: at least 5 bytes ("hello") varz.InBytes.ShouldBeGreaterThanOrEqualTo(5L); } }