Move 39 monitoring, events, and system endpoint test files from NATS.Server.Tests into a dedicated NATS.Server.Monitoring.Tests project. Update namespaces, replace private GetFreePort/ReadUntilAsync with TestUtilities shared helpers, add InternalsVisibleTo, and register in the solution file. All 439 tests pass.
821 lines
29 KiB
C#
821 lines
29 KiB
C#
// 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;
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Monitoring.Tests.Monitoring;
|
|
|
|
/// <summary>
|
|
/// Tests covering /connz endpoint behavior, ported from the Go server's monitor_test.go.
|
|
/// </summary>
|
|
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 = 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz returns empty connections when no clients are connected.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_returns_empty_when_no_clients()
|
|
{
|
|
var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
|
|
connz.ShouldNotBeNull();
|
|
connz.NumConns.ShouldBe(0);
|
|
connz.Total.ShouldBe(0);
|
|
connz.Conns.Length.ShouldBe(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz lists active connections with populated identity fields.
|
|
/// </summary>
|
|
[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<Connz>($"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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz default limit is 1024 and offset is 0.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_default_limit_and_offset()
|
|
{
|
|
var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
|
|
connz.ShouldNotBeNull();
|
|
connz.Limit.ShouldBe(1024); // Go: DefaultConnListSize
|
|
connz.Offset.ShouldBe(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithSubs (line 442).
|
|
/// Verifies /connz?subs=1 includes subscriptions list.
|
|
/// </summary>
|
|
[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<Connz>($"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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithSubsDetail (line 463).
|
|
/// Verifies /connz?subs=detail includes subscription detail objects.
|
|
/// </summary>
|
|
[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<Connz>($"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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithNamedClient (line 1851).
|
|
/// Verifies /connz exposes client name set in CONNECT options.
|
|
/// </summary>
|
|
[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<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
|
|
connz.ShouldNotBeNull();
|
|
connz.Conns.Length.ShouldBe(1);
|
|
connz.Conns[0].Name.ShouldBe("test-client");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithOffsetAndLimit (line 732).
|
|
/// Verifies /connz pagination with offset and limit parameters.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_pagination_with_offset_and_limit()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzDefaultSorted (line 806).
|
|
/// Verifies /connz defaults to ascending CID sort order.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_default_sorted_by_cid_ascending()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
try
|
|
{
|
|
for (var i = 0; i < 4; i++)
|
|
sockets.Add(await ConnectClientAsync("{}"));
|
|
|
|
await Task.Delay(200);
|
|
|
|
var connz = await _http.GetFromJsonAsync<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByCid (line 827).
|
|
/// Verifies /connz?sort=cid returns connections sorted by CID.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_cid()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
try
|
|
{
|
|
for (var i = 0; i < 4; i++)
|
|
sockets.Add(await ConnectClientAsync("{}"));
|
|
|
|
await Task.Delay(200);
|
|
|
|
var connz = await _http.GetFromJsonAsync<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByStart (line 849).
|
|
/// Verifies /connz?sort=start returns connections sorted by start time.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_start()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
|
|
/// Verifies /connz?sort=bytes_to returns connections sorted by out_bytes descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_bytes_to()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
|
|
/// Verifies /connz?sort=msgs_to returns connections sorted by out_msgs descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_msgs_to()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
|
|
/// Verifies /connz?sort=msgs_from returns connections sorted by in_msgs descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_msgs_from()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedBySubs (line 950).
|
|
/// Verifies /connz?sort=subs returns connections sorted by subscription count descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_subs()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByLast (line 976).
|
|
/// Verifies /connz?sort=last returns connections sorted by last_activity descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_last_activity()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByUptime (line 1007).
|
|
/// Verifies /connz?sort=uptime returns connections sorted by uptime descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_uptime()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByIdle (line 1202).
|
|
/// Verifies /connz?sort=idle returns connections sorted by idle time descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_idle()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
|
|
/// Verifies /connz?state=closed returns recently disconnected clients.
|
|
/// </summary>
|
|
[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<Connz>($"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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByStopOnOpen (line 1074).
|
|
/// Verifies /connz?sort=stop&state=open falls back to CID sort without error.
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByReason (line 1141).
|
|
/// Verifies /connz?sort=reason&state=closed sorts by close reason.
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByReasonOnOpen (line 1180).
|
|
/// Verifies /connz?sort=reason&state=open falls back to CID sort without error.
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortByRTT (line 5979).
|
|
/// Verifies /connz?sort=rtt does not error.
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz per-connection message stats are populated after pub/sub.
|
|
/// </summary>
|
|
[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<Connz>($"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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzRTT (line 583).
|
|
/// Verifies /connz includes RTT field for connected clients.
|
|
/// </summary>
|
|
[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<Connz>($"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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzLastActivity (line 638).
|
|
/// Verifies /connz last_activity is updated after message activity.
|
|
/// </summary>
|
|
[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<Connz>($"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<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
|
|
var updated = connz2!.Conns[0].LastActivity;
|
|
|
|
// Activity should have updated
|
|
updated.ShouldBeGreaterThanOrEqualTo(initial);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConcurrentMonitoring (line 2148).
|
|
/// Verifies concurrent /connz requests do not cause errors.
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz JSON uses correct Go-compatible field names.
|
|
/// </summary>
|
|
[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\"");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
|
|
/// Verifies /connz?state=all returns both open and closed connections.
|
|
/// </summary>
|
|
[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<Connz>($"http://127.0.0.1:{_monitorPort}/connz?state=all");
|
|
connz.ShouldNotBeNull();
|
|
connz.Total.ShouldBeGreaterThanOrEqualTo(2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnz (line 367).
|
|
/// Verifies /connz server_id matches the server's ID.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_server_id_matches_server()
|
|
{
|
|
var varz = await _http.GetFromJsonAsync<Varz>($"http://127.0.0.1:{_monitorPort}/varz");
|
|
var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
|
|
|
|
connz!.Id.ShouldBe(varz!.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByPending (line 925).
|
|
/// Verifies /connz?sort=pending returns connections sorted by pending bytes descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_pending()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
|
|
/// Verifies /connz?sort=bytes_from returns connections sorted by in_bytes descending.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_sort_by_bytes_from()
|
|
{
|
|
var sockets = new List<Socket>();
|
|
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<Connz>($"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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private async Task<Socket> 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;
|
|
}
|
|
|
|
}
|