// Port of Go server/monitor_test.go — monitoring endpoint parity tests. // Reference: golang/nats-server/server/monitor_test.go // // Tests cover: Connz sorting, filtering, pagination, closed connections ring buffer, // Subsz structure, Varz metadata, and healthz status codes. using System.Text.Json; using NATS.Server.Monitoring; namespace NATS.Server.Tests.Monitoring; /// /// Parity tests ported from Go server/monitor_test.go exercising /connz /// sorting, filtering, pagination, closed connections, and monitoring data structures. /// public class MonitorGoParityTests { // ======================================================================== // Connz DTO serialization // Go reference: monitor_test.go TestMonitorConnzBadParams // ======================================================================== [Fact] public void Connz_JsonSerialization_MatchesGoShape() { // Go: TestMonitorConnzBadParams — verifies JSON response shape. var connz = new Connz { Id = "test-server-id", Now = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc), NumConns = 2, Total = 5, Offset = 0, Limit = 1024, Conns = [ new ConnInfo { Cid = 1, Kind = "Client", Ip = "127.0.0.1", Port = 50000, Name = "test-client", Lang = "go", Version = "1.0", InMsgs = 100, OutMsgs = 50, InBytes = 1024, OutBytes = 512, NumSubs = 3, }, ], }; var json = JsonSerializer.Serialize(connz); json.ShouldContain("\"server_id\":"); json.ShouldContain("\"num_connections\":"); json.ShouldContain("\"connections\":"); json.ShouldContain("\"cid\":"); json.ShouldContain("\"in_msgs\":"); json.ShouldContain("\"out_msgs\":"); json.ShouldContain("\"subscriptions\":"); } // ======================================================================== // ConnzOptions defaults // Go reference: monitor_test.go TestMonitorConnzBadParams // ======================================================================== [Fact] public void ConnzOptions_DefaultSort_ByCid() { // Go: TestMonitorConnzBadParams — default sort is by CID. var opts = new ConnzOptions(); opts.Sort.ShouldBe(SortOpt.ByCid); } [Fact] public void ConnzOptions_DefaultState_Open() { var opts = new ConnzOptions(); opts.State.ShouldBe(ConnState.Open); } [Fact] public void ConnzOptions_DefaultLimit_1024() { // Go: default limit is 1024. var opts = new ConnzOptions(); opts.Limit.ShouldBe(1024); } [Fact] public void ConnzOptions_DefaultOffset_Zero() { var opts = new ConnzOptions(); opts.Offset.ShouldBe(0); } // ======================================================================== // SortOpt enumeration // Go reference: monitor_test.go TestMonitorConnzSortedByUptimeClosedConn // ======================================================================== [Fact] public void SortOpt_AllValues_Defined() { // Go: TestMonitorConnzSortedByUptimeClosedConn — all sort options. var values = Enum.GetValues(); values.ShouldContain(SortOpt.ByCid); values.ShouldContain(SortOpt.ByStart); values.ShouldContain(SortOpt.BySubs); values.ShouldContain(SortOpt.ByPending); values.ShouldContain(SortOpt.ByMsgsTo); values.ShouldContain(SortOpt.ByMsgsFrom); values.ShouldContain(SortOpt.ByBytesTo); values.ShouldContain(SortOpt.ByBytesFrom); values.ShouldContain(SortOpt.ByLast); values.ShouldContain(SortOpt.ByIdle); values.ShouldContain(SortOpt.ByUptime); values.ShouldContain(SortOpt.ByRtt); values.ShouldContain(SortOpt.ByStop); values.ShouldContain(SortOpt.ByReason); } // ======================================================================== // ConnInfo sorting — in-memory // Go reference: monitor_test.go TestMonitorConnzSortedByUptimeClosedConn, // TestMonitorConnzSortedByStopTimeClosedConn // ======================================================================== [Fact] public void ConnInfo_SortByCid() { // Go: TestMonitorConnzSortedByUptimeClosedConn — sort by CID. var conns = new[] { new ConnInfo { Cid = 3 }, new ConnInfo { Cid = 1 }, new ConnInfo { Cid = 2 }, }; var sorted = conns.OrderBy(c => c.Cid).ToArray(); sorted[0].Cid.ShouldBe(1UL); sorted[1].Cid.ShouldBe(2UL); sorted[2].Cid.ShouldBe(3UL); } [Fact] public void ConnInfo_SortBySubs_Descending() { // Go: sort=subs sorts by subscription count descending. var conns = new[] { new ConnInfo { Cid = 1, NumSubs = 5 }, new ConnInfo { Cid = 2, NumSubs = 10 }, new ConnInfo { Cid = 3, NumSubs = 1 }, }; var sorted = conns.OrderByDescending(c => c.NumSubs).ToArray(); sorted[0].Cid.ShouldBe(2UL); sorted[1].Cid.ShouldBe(1UL); sorted[2].Cid.ShouldBe(3UL); } [Fact] public void ConnInfo_SortByMsgsFrom_Descending() { var conns = new[] { new ConnInfo { Cid = 1, InMsgs = 100 }, new ConnInfo { Cid = 2, InMsgs = 500 }, new ConnInfo { Cid = 3, InMsgs = 200 }, }; var sorted = conns.OrderByDescending(c => c.InMsgs).ToArray(); sorted[0].Cid.ShouldBe(2UL); sorted[1].Cid.ShouldBe(3UL); sorted[2].Cid.ShouldBe(1UL); } [Fact] public void ConnInfo_SortByStop_Descending() { // Go: TestMonitorConnzSortedByStopTimeClosedConn — sort=stop for closed conns. var now = DateTime.UtcNow; var conns = new[] { new ConnInfo { Cid = 1, Stop = now.AddMinutes(-3) }, new ConnInfo { Cid = 2, Stop = now.AddMinutes(-1) }, new ConnInfo { Cid = 3, Stop = now.AddMinutes(-2) }, }; var sorted = conns.OrderByDescending(c => c.Stop ?? DateTime.MinValue).ToArray(); sorted[0].Cid.ShouldBe(2UL); sorted[1].Cid.ShouldBe(3UL); sorted[2].Cid.ShouldBe(1UL); } // ======================================================================== // Pagination // Go reference: monitor_test.go TestSubszPagination // ======================================================================== [Fact] public void Connz_Pagination_OffsetAndLimit() { // Go: TestSubszPagination — offset and limit for paging. var allConns = Enumerable.Range(1, 20).Select(i => new ConnInfo { Cid = (ulong)i }).ToArray(); // Page 2: offset=5, limit=5 var page = allConns.Skip(5).Take(5).ToArray(); page.Length.ShouldBe(5); page[0].Cid.ShouldBe(6UL); page[4].Cid.ShouldBe(10UL); } [Fact] public void Connz_Pagination_OffsetBeyondTotal_ReturnsEmpty() { var allConns = Enumerable.Range(1, 5).Select(i => new ConnInfo { Cid = (ulong)i }).ToArray(); var page = allConns.Skip(10).Take(5).ToArray(); page.Length.ShouldBe(0); } // ======================================================================== // Closed connections — ClosedClient record // Go reference: monitor_test.go TestMonitorConnzClosedConnsRace // ======================================================================== [Fact] public void ClosedClient_RequiredFields() { // Go: TestMonitorConnzClosedConnsRace — ClosedClient captures all fields. var now = DateTime.UtcNow; var closed = new ClosedClient { Cid = 42, Ip = "192.168.1.1", Port = 50000, Start = now.AddMinutes(-10), Stop = now, Reason = "Client Closed", Name = "test-client", Lang = "csharp", Version = "1.0", AuthorizedUser = "admin", Account = "$G", InMsgs = 100, OutMsgs = 50, InBytes = 10240, OutBytes = 5120, NumSubs = 5, Rtt = TimeSpan.FromMilliseconds(1.5), }; closed.Cid.ShouldBe(42UL); closed.Ip.ShouldBe("192.168.1.1"); closed.Reason.ShouldBe("Client Closed"); closed.InMsgs.ShouldBe(100); closed.OutMsgs.ShouldBe(50); } [Fact] public void ClosedClient_DefaultValues() { var closed = new ClosedClient { Cid = 1 }; closed.Ip.ShouldBe(""); closed.Reason.ShouldBe(""); closed.Name.ShouldBe(""); closed.MqttClient.ShouldBe(""); } // ======================================================================== // ConnState enum // Go reference: monitor_test.go TestMonitorConnzBadParams // ======================================================================== [Fact] public void ConnState_AllValues() { // Go: TestMonitorConnzBadParams — verifies state filter values. Enum.GetValues().ShouldContain(ConnState.Open); Enum.GetValues().ShouldContain(ConnState.Closed); Enum.GetValues().ShouldContain(ConnState.All); } // ======================================================================== // Filter by account and user // Go reference: monitor_test.go TestMonitorConnzOperatorAccountNames // ======================================================================== [Fact] public void ConnInfo_FilterByAccount() { // Go: TestMonitorConnzOperatorAccountNames — filter by account name. var conns = new[] { new ConnInfo { Cid = 1, Account = "$G" }, new ConnInfo { Cid = 2, Account = "MYACCOUNT" }, new ConnInfo { Cid = 3, Account = "$G" }, }; var filtered = conns.Where(c => c.Account == "MYACCOUNT").ToArray(); filtered.Length.ShouldBe(1); filtered[0].Cid.ShouldBe(2UL); } [Fact] public void ConnInfo_FilterByUser() { // Go: TestMonitorAuthorizedUsers — filter by authorized user. var conns = new[] { new ConnInfo { Cid = 1, AuthorizedUser = "alice" }, new ConnInfo { Cid = 2, AuthorizedUser = "bob" }, new ConnInfo { Cid = 3, AuthorizedUser = "alice" }, }; var filtered = conns.Where(c => c.AuthorizedUser == "alice").ToArray(); filtered.Length.ShouldBe(2); } [Fact] public void ConnInfo_FilterByMqttClient() { // Go: TestMonitorMQTT — filter by MQTT client ID. var conns = new[] { new ConnInfo { Cid = 1, MqttClient = "" }, new ConnInfo { Cid = 2, MqttClient = "mqtt-device-1" }, new ConnInfo { Cid = 3, MqttClient = "mqtt-device-2" }, }; var filtered = conns.Where(c => c.MqttClient == "mqtt-device-1").ToArray(); filtered.Length.ShouldBe(1); filtered[0].Cid.ShouldBe(2UL); } // ======================================================================== // Subsz DTO // Go reference: monitor_test.go TestSubszPagination // ======================================================================== [Fact] public void Subsz_JsonShape() { // Go: TestSubszPagination — Subsz DTO JSON serialization. var subsz = new Subsz { Id = "test-server", Now = DateTime.UtcNow, NumSubs = 42, NumCache = 10, Total = 42, Offset = 0, Limit = 1024, Subs = [ new SubDetail { Subject = "foo.bar", Sid = "1", Msgs = 100, Cid = 5 }, ], }; var json = JsonSerializer.Serialize(subsz); json.ShouldContain("\"num_subscriptions\":"); json.ShouldContain("\"num_cache\":"); json.ShouldContain("\"subscriptions\":"); } [Fact] public void SubszOptions_Defaults() { var opts = new SubszOptions(); opts.Offset.ShouldBe(0); opts.Limit.ShouldBe(1024); opts.Subscriptions.ShouldBeFalse(); } // ======================================================================== // SubDetail DTO // Go reference: monitor_test.go TestMonitorConnzSortBadRequest // ======================================================================== [Fact] public void SubDetail_JsonSerialization() { // Go: TestMonitorConnzSortBadRequest — SubDetail in subscriptions_list_detail. var detail = new SubDetail { Account = "$G", Subject = "orders.>", Queue = "workers", Sid = "42", Msgs = 500, Max = 0, Cid = 7, }; var json = JsonSerializer.Serialize(detail); json.ShouldContain("\"account\":"); json.ShouldContain("\"subject\":"); json.ShouldContain("\"qgroup\":"); json.ShouldContain("\"sid\":"); json.ShouldContain("\"msgs\":"); } // ======================================================================== // ConnInfo — TLS fields // Go reference: monitor_test.go TestMonitorConnzTLSCfg // ======================================================================== [Fact] public void ConnInfo_TlsFields() { // Go: TestMonitorConnzTLSCfg — TLS connection metadata. var info = new ConnInfo { Cid = 1, TlsVersion = "TLS 1.3", TlsCipherSuite = "TLS_AES_256_GCM_SHA384", TlsPeerCertSubject = "CN=test-client", TlsFirst = true, }; info.TlsVersion.ShouldBe("TLS 1.3"); info.TlsCipherSuite.ShouldBe("TLS_AES_256_GCM_SHA384"); info.TlsPeerCertSubject.ShouldBe("CN=test-client"); info.TlsFirst.ShouldBeTrue(); } // ======================================================================== // ConnInfo — detailed subscription fields // Go reference: monitor_test.go TestMonitorConnzTLSInHandshake // ======================================================================== [Fact] public void ConnInfo_WithSubscriptionDetails() { var info = new ConnInfo { Cid = 1, Subs = ["foo.bar", "baz.>"], SubsDetail = [ new SubDetail { Subject = "foo.bar", Sid = "1", Msgs = 10 }, new SubDetail { Subject = "baz.>", Sid = "2", Msgs = 20, Queue = "q1" }, ], }; info.Subs.Length.ShouldBe(2); info.SubsDetail.Length.ShouldBe(2); info.SubsDetail[1].Queue.ShouldBe("q1"); } }