Files
natsdotnet/tests/NATS.Server.Tests/Monitoring/MonitorGoParityTests.cs
Joseph Doherty 579063dabd test(parity): port 373 Go tests across protocol and services subsystems (C11+E15)
Protocol (C11):
- ClientProtocolGoParityTests: 45 tests (header stripping, tracing, limits, NRG)
- ConsumerGoParityTests: 60 tests (filters, actions, pinned, priority groups)
- JetStreamGoParityTests: 38 tests (stream CRUD, purge, mirror, retention)

Services (E15):
- MqttGoParityTests: 65 tests (packet parsing, QoS, retained, sessions)
- WsGoParityTests: 58 tests (compression, JWT auth, frame encoding)
- EventGoParityTests: 56 tests (event DTOs, serialization, health checks)
- AccountGoParityTests: 28 tests (route mapping, system account, limits)
- MonitorGoParityTests: 23 tests (connz filtering, pagination, sort)

DB: 1,148/2,937 mapped (39.1%), up from 1,012 (34.5%)
2026-02-24 16:52:15 -05:00

456 lines
15 KiB
C#

// 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;
/// <summary>
/// Parity tests ported from Go server/monitor_test.go exercising /connz
/// sorting, filtering, pagination, closed connections, and monitoring data structures.
/// </summary>
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<SortOpt>();
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<ConnState>().ShouldContain(ConnState.Open);
Enum.GetValues<ConnState>().ShouldContain(ConnState.Closed);
Enum.GetValues<ConnState>().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");
}
}