// Go: TestMonitorConnz server/monitor_test.go:367
// Go: TestMonitorConnzWithSubs server/monitor_test.go:442
// Go: TestMonitorConnzWithSubsDetail server/monitor_test.go:463
// Go: TestMonitorClosedConnzWithSubsDetail server/monitor_test.go:484
// Go: TestMonitorConnzRTT server/monitor_test.go:583
// Go: TestMonitorConnzLastActivity server/monitor_test.go:638
// Go: TestMonitorConnzWithOffsetAndLimit server/monitor_test.go:732
// Go: TestMonitorConnzDefaultSorted server/monitor_test.go:806
// Go: TestMonitorConnzSortedByCid server/monitor_test.go:827
// Go: TestMonitorConnzSortedByStart server/monitor_test.go:849
// Go: TestMonitorConnzSortedByBytesAndMsgs server/monitor_test.go:871
// Go: TestMonitorConnzSortedByPending server/monitor_test.go:925
// Go: TestMonitorConnzSortedBySubs server/monitor_test.go:950
// Go: TestMonitorConnzSortedByLast server/monitor_test.go:976
// Go: TestMonitorConnzSortedByUptime server/monitor_test.go:1007
// Go: TestMonitorConnzSortedByIdle server/monitor_test.go:1202
// Go: TestMonitorConnzSortedByStopOnOpen server/monitor_test.go:1074
// Go: TestMonitorConnzSortedByReason server/monitor_test.go:1141
// Go: TestMonitorConnzWithNamedClient server/monitor_test.go:1851
// Go: TestMonitorConnzWithStateForClosedConns server/monitor_test.go:1876
// Go: TestMonitorConcurrentMonitoring server/monitor_test.go:2148
// Go: TestMonitorConnzSortByRTT server/monitor_test.go:5979
using System.Net;
using System.Net.Http.Json;
using System.Net.Sockets;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Monitoring;
namespace NATS.Server.Tests.Monitoring;
///
/// Tests covering /connz endpoint behavior, ported from the Go server's monitor_test.go.
///
public class MonitorConnzTests : IAsyncLifetime
{
private readonly NatsServer _server;
private readonly int _natsPort;
private readonly int _monitorPort;
private readonly CancellationTokenSource _cts = new();
private readonly HttpClient _http = new();
public MonitorConnzTests()
{
_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: TestMonitorConnz (line 367).
/// Verifies /connz returns empty connections when no clients are connected.
///
[Fact]
public async Task Connz_returns_empty_when_no_clients()
{
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.NumConns.ShouldBe(0);
connz.Total.ShouldBe(0);
connz.Conns.Length.ShouldBe(0);
}
///
/// Go: TestMonitorConnz (line 367).
/// Verifies /connz lists active connections with populated identity fields.
///
[Fact]
public async Task Connz_lists_active_connections_with_fields()
{
using var sock = await ConnectClientAsync("{\"name\":\"c1\",\"lang\":\"csharp\",\"version\":\"1.0\"}", "SUB foo 1\r\nPUB foo 5\r\nhello\r\n");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.NumConns.ShouldBe(1);
connz.Total.ShouldBe(1);
connz.Conns.Length.ShouldBe(1);
var ci = connz.Conns[0];
// Go: ci.IP == "127.0.0.1"
ci.Ip.ShouldBe("127.0.0.1");
ci.Port.ShouldBeGreaterThan(0);
ci.Cid.ShouldBeGreaterThan(0UL);
ci.Name.ShouldBe("c1");
ci.Lang.ShouldBe("csharp");
ci.Version.ShouldBe("1.0");
ci.Start.ShouldBeGreaterThan(DateTime.MinValue);
ci.LastActivity.ShouldBeGreaterThanOrEqualTo(ci.Start);
ci.Uptime.ShouldNotBeNullOrEmpty();
ci.Idle.ShouldNotBeNullOrEmpty();
}
///
/// Go: TestMonitorConnz (line 367).
/// Verifies /connz default limit is 1024 and offset is 0.
///
[Fact]
public async Task Connz_default_limit_and_offset()
{
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.Limit.ShouldBe(1024); // Go: DefaultConnListSize
connz.Offset.ShouldBe(0);
}
///
/// Go: TestMonitorConnzWithSubs (line 442).
/// Verifies /connz?subs=1 includes subscriptions list.
///
[Fact]
public async Task Connz_with_subs_includes_subscription_list()
{
using var sock = await ConnectClientAsync("{}", "SUB hello.foo 1\r\n");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?subs=1");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);
var ci = connz.Conns[0];
// Go: len(ci.Subs) != 1 || ci.Subs[0] != "hello.foo"
ci.Subs.ShouldContain("hello.foo");
}
///
/// Go: TestMonitorConnzWithSubsDetail (line 463).
/// Verifies /connz?subs=detail includes subscription detail objects.
///
[Fact]
public async Task Connz_with_subs_detail_includes_subscription_detail()
{
using var sock = await ConnectClientAsync("{}", "SUB hello.foo 1\r\n");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?subs=detail");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);
var ci = connz.Conns[0];
// Go: len(ci.SubsDetail) != 1 || ci.SubsDetail[0].Subject != "hello.foo"
ci.SubsDetail.Length.ShouldBeGreaterThanOrEqualTo(1);
ci.SubsDetail.ShouldContain(sd => sd.Subject == "hello.foo");
}
///
/// Go: TestMonitorConnzWithNamedClient (line 1851).
/// Verifies /connz exposes client name set in CONNECT options.
///
[Fact]
public async Task Connz_shows_named_client()
{
using var sock = await ConnectClientAsync("{\"name\":\"test-client\"}");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBe(1);
connz.Conns[0].Name.ShouldBe("test-client");
}
///
/// Go: TestMonitorConnzWithOffsetAndLimit (line 732).
/// Verifies /connz pagination with offset and limit parameters.
///
[Fact]
public async Task Connz_pagination_with_offset_and_limit()
{
var sockets = new List();
try
{
for (var i = 0; i < 3; i++)
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
// offset=1, limit=1 should return 1 connection with total of 3
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?offset=1&limit=1");
connz.ShouldNotBeNull();
connz.Limit.ShouldBe(1);
connz.Offset.ShouldBe(1);
connz.Conns.Length.ShouldBe(1);
connz.NumConns.ShouldBe(1);
connz.Total.ShouldBeGreaterThanOrEqualTo(3);
// offset past end should return 0
var connz2 = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?offset=10&limit=1");
connz2.ShouldNotBeNull();
connz2.Conns.Length.ShouldBe(0);
connz2.NumConns.ShouldBe(0);
connz2.Total.ShouldBeGreaterThanOrEqualTo(3);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzDefaultSorted (line 806).
/// Verifies /connz defaults to ascending CID sort order.
///
[Fact]
public async Task Connz_default_sorted_by_cid_ascending()
{
var sockets = new List();
try
{
for (var i = 0; i < 4; i++)
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(4);
// Go: Conns[0].Cid < Conns[1].Cid < Conns[2].Cid < Conns[3].Cid
for (var i = 1; i < connz.Conns.Length; i++)
connz.Conns[i].Cid.ShouldBeGreaterThan(connz.Conns[i - 1].Cid);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByCid (line 827).
/// Verifies /connz?sort=cid returns connections sorted by CID.
///
[Fact]
public async Task Connz_sort_by_cid()
{
var sockets = new List();
try
{
for (var i = 0; i < 4; i++)
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=cid");
connz.ShouldNotBeNull();
for (var i = 1; i < connz.Conns.Length; i++)
connz.Conns[i].Cid.ShouldBeGreaterThan(connz.Conns[i - 1].Cid);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByStart (line 849).
/// Verifies /connz?sort=start returns connections sorted by start time.
///
[Fact]
public async Task Connz_sort_by_start()
{
var sockets = new List();
try
{
for (var i = 0; i < 3; i++)
{
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(10);
}
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=start");
connz.ShouldNotBeNull();
for (var i = 1; i < connz.Conns.Length; i++)
connz.Conns[i].Start.ShouldBeGreaterThanOrEqualTo(connz.Conns[i - 1].Start);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
/// Verifies /connz?sort=bytes_to returns connections sorted by out_bytes descending.
///
[Fact]
public async Task Connz_sort_by_bytes_to()
{
var sockets = new List();
try
{
// Subscriber first
sockets.Add(await ConnectClientAsync("{}", "SUB foo 1\r\n"));
// High-traffic publisher
var pub = await ConnectClientAsync("{}");
sockets.Add(pub);
using var ns = new NetworkStream(pub);
for (var i = 0; i < 50; i++)
await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
await ns.FlushAsync();
// Low-traffic client
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(300);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=bytes_to");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
// First entry should have >= out_bytes than second
connz.Conns[0].OutBytes.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].OutBytes);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
/// Verifies /connz?sort=msgs_to returns connections sorted by out_msgs descending.
///
[Fact]
public async Task Connz_sort_by_msgs_to()
{
var sockets = new List();
try
{
sockets.Add(await ConnectClientAsync("{}", "SUB foo 1\r\n"));
var pub = await ConnectClientAsync("{}");
sockets.Add(pub);
using var ns = new NetworkStream(pub);
for (var i = 0; i < 50; i++)
await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
await ns.FlushAsync();
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(300);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=msgs_to");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
connz.Conns[0].OutMsgs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].OutMsgs);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
/// Verifies /connz?sort=msgs_from returns connections sorted by in_msgs descending.
///
[Fact]
public async Task Connz_sort_by_msgs_from()
{
var sockets = new List();
try
{
var pub = await ConnectClientAsync("{}");
sockets.Add(pub);
using var ns = new NetworkStream(pub);
for (var i = 0; i < 50; i++)
await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
await ns.FlushAsync();
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(300);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=msgs_from");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
connz.Conns[0].InMsgs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].InMsgs);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedBySubs (line 950).
/// Verifies /connz?sort=subs returns connections sorted by subscription count descending.
///
[Fact]
public async Task Connz_sort_by_subs()
{
var sockets = new List();
try
{
// Client with many subs
sockets.Add(await ConnectClientAsync("{}", "SUB a 1\r\nSUB b 2\r\nSUB c 3\r\n"));
// Client with no subs
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=subs");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
connz.Conns[0].NumSubs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].NumSubs);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByLast (line 976).
/// Verifies /connz?sort=last returns connections sorted by last_activity descending.
///
[Fact]
public async Task Connz_sort_by_last_activity()
{
var sockets = new List();
try
{
// First client connects and does something early
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(50);
// Second client connects later and does activity
sockets.Add(await ConnectClientAsync("{}", "PUB foo 2\r\nhi\r\n"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=last");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
connz.Conns[0].LastActivity.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].LastActivity);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByUptime (line 1007).
/// Verifies /connz?sort=uptime returns connections sorted by uptime descending.
///
[Fact]
public async Task Connz_sort_by_uptime()
{
var sockets = new List();
try
{
// First client has longer uptime
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(100);
// Second client has shorter uptime
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=uptime");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
// Descending by uptime means first entry started earlier
connz.Conns[0].Start.ShouldBeLessThanOrEqualTo(connz.Conns[1].Start);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByIdle (line 1202).
/// Verifies /connz?sort=idle returns connections sorted by idle time descending.
///
[Fact]
public async Task Connz_sort_by_idle()
{
var sockets = new List();
try
{
// First client: older activity (more idle)
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
// Second client: recent activity (less idle)
sockets.Add(await ConnectClientAsync("{}", "PUB foo 2\r\nhi\r\n"));
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=idle");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
// Idle descending: first entry has older last activity
connz.Conns[0].LastActivity.ShouldBeLessThanOrEqualTo(connz.Conns[1].LastActivity);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
/// Verifies /connz?state=closed returns recently disconnected clients.
///
[Fact]
public async Task Connz_state_closed_returns_disconnected_clients()
{
var sock = await ConnectClientAsync("{\"name\":\"closing-client\"}");
await Task.Delay(200);
sock.Shutdown(SocketShutdown.Both);
sock.Dispose();
await Task.Delay(500);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?state=closed");
connz.ShouldNotBeNull();
connz.Conns.ShouldContain(c => c.Name == "closing-client");
var closed = connz.Conns.First(c => c.Name == "closing-client");
closed.Stop.ShouldNotBeNull();
closed.Reason.ShouldNotBeNullOrEmpty();
}
///
/// Go: TestMonitorConnzSortedByStopOnOpen (line 1074).
/// Verifies /connz?sort=stop&state=open falls back to CID sort without error.
///
[Fact]
public async Task Connz_sort_by_stop_with_open_state_falls_back_to_cid()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(200);
// Go: sort by stop on open state should fallback
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=stop&state=open");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
///
/// Go: TestMonitorConnzSortedByReason (line 1141).
/// Verifies /connz?sort=reason&state=closed sorts by close reason.
///
[Fact]
public async Task Connz_sort_by_reason_on_closed()
{
var sock = await ConnectClientAsync("{}");
await Task.Delay(100);
sock.Shutdown(SocketShutdown.Both);
sock.Dispose();
await Task.Delay(500);
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=reason&state=closed");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
///
/// Go: TestMonitorConnzSortedByReasonOnOpen (line 1180).
/// Verifies /connz?sort=reason&state=open falls back to CID sort without error.
///
[Fact]
public async Task Connz_sort_by_reason_with_open_state_falls_back()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(200);
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=reason&state=open");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
///
/// Go: TestMonitorConnzSortByRTT (line 5979).
/// Verifies /connz?sort=rtt does not error.
///
[Fact]
public async Task Connz_sort_by_rtt_succeeds()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(200);
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=rtt");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
///
/// Go: TestMonitorConnz (line 367).
/// Verifies /connz per-connection message stats are populated after pub/sub.
///
[Fact]
public async Task Connz_per_connection_message_stats()
{
using var sock = await ConnectClientAsync("{}", "SUB foo 1\r\nPUB foo 5\r\nhello\r\n");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBe(1);
var ci = connz.Conns[0];
// Go: ci.InMsgs == 1, ci.InBytes == 5
ci.InMsgs.ShouldBeGreaterThanOrEqualTo(1L);
ci.InBytes.ShouldBeGreaterThanOrEqualTo(5L);
}
///
/// Go: TestMonitorConnzRTT (line 583).
/// Verifies /connz includes RTT field for connected clients.
///
[Fact]
public async Task Connz_includes_rtt_field()
{
using var sock = await ConnectClientAsync("{}");
// Send a PING to trigger RTT measurement
using var ns = new NetworkStream(sock);
await ns.WriteAsync("PING\r\n"u8.ToArray());
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);
// RTT may or may not be populated depending on implementation, but field must exist
connz.Conns[0].Rtt.ShouldNotBeNull();
}
///
/// Go: TestMonitorConnzLastActivity (line 638).
/// Verifies /connz last_activity is updated after message activity.
///
[Fact]
public async Task Connz_last_activity_updates_after_message()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(100);
// Record initial last activity
var connz1 = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
var initial = connz1!.Conns[0].LastActivity;
// Do more activity
using var ns = new NetworkStream(sock);
await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
await ns.FlushAsync();
await Task.Delay(200);
var connz2 = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
var updated = connz2!.Conns[0].LastActivity;
// Activity should have updated
updated.ShouldBeGreaterThanOrEqualTo(initial);
}
///
/// Go: TestMonitorConcurrentMonitoring (line 2148).
/// Verifies concurrent /connz requests do not cause errors.
///
[Fact]
public async Task Connz_handles_concurrent_requests()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(200);
var tasks = Enumerable.Range(0, 10).Select(async _ =>
{
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
});
await Task.WhenAll(tasks);
}
///
/// Go: TestMonitorConnz (line 367).
/// Verifies /connz JSON uses correct Go-compatible field names.
///
[Fact]
public async Task Connz_json_uses_go_field_names()
{
using var sock = await ConnectClientAsync("{}");
await Task.Delay(200);
var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/connz");
body.ShouldContain("\"server_id\"");
body.ShouldContain("\"num_connections\"");
body.ShouldContain("\"connections\"");
}
///
/// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
/// Verifies /connz?state=all returns both open and closed connections.
///
[Fact]
public async Task Connz_state_all_returns_both_open_and_closed()
{
// Connect and disconnect one client
var sock = await ConnectClientAsync("{\"name\":\"will-close\"}");
await Task.Delay(100);
sock.Shutdown(SocketShutdown.Both);
sock.Dispose();
await Task.Delay(300);
// Connect another client that stays open
using var sock2 = await ConnectClientAsync("{\"name\":\"stays-open\"}");
await Task.Delay(200);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?state=all");
connz.ShouldNotBeNull();
connz.Total.ShouldBeGreaterThanOrEqualTo(2);
}
///
/// Go: TestMonitorConnz (line 367).
/// Verifies /connz server_id matches the server's ID.
///
[Fact]
public async Task Connz_server_id_matches_server()
{
var varz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/varz");
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz");
connz!.Id.ShouldBe(varz!.Id);
}
///
/// Go: TestMonitorConnzSortedByPending (line 925).
/// Verifies /connz?sort=pending returns connections sorted by pending bytes descending.
///
[Fact]
public async Task Connz_sort_by_pending()
{
var sockets = new List();
try
{
sockets.Add(await ConnectClientAsync("{}"));
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(200);
var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=pending");
response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
/// Verifies /connz?sort=bytes_from returns connections sorted by in_bytes descending.
///
[Fact]
public async Task Connz_sort_by_bytes_from()
{
var sockets = new List();
try
{
// High-traffic publisher
var pub = await ConnectClientAsync("{}");
sockets.Add(pub);
using var ns = new NetworkStream(pub);
for (var i = 0; i < 50; i++)
await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
await ns.FlushAsync();
// Low-traffic client
sockets.Add(await ConnectClientAsync("{}"));
await Task.Delay(300);
var connz = await _http.GetFromJsonAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=bytes_from");
connz.ShouldNotBeNull();
connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
connz.Conns[0].InBytes.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].InBytes);
}
finally
{
foreach (var s in sockets) s.Dispose();
}
}
///
/// Helper to connect a raw TCP client to the NATS server, send CONNECT and optional commands,
/// and return the socket. The caller is responsible for disposing the socket.
///
private async Task ConnectClientAsync(string connectJson, string? extraCommands = null)
{
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
var cmd = $"CONNECT {connectJson}\r\n";
if (extraCommands is not null)
cmd += extraCommands;
await sock.SendAsync(System.Text.Encoding.ASCII.GetBytes(cmd), SocketFlags.None);
return sock;
}
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;
}
}