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.
264 lines
8.3 KiB
C#
264 lines
8.3 KiB
C#
// Go: TestMonitorConnzWithRoutes server/monitor_test.go:1405
|
|
// Go: TestMonitorRoutezRace server/monitor_test.go:2210
|
|
// Go: TestMonitorRoutezRTT server/monitor_test.go:3919
|
|
// Go: TestMonitorRoutezPoolSize server/monitor_test.go:5705
|
|
// Go: TestMonitorClusterEmptyWhenNotDefined server/monitor_test.go:2456
|
|
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Net.Sockets;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server.Configuration;
|
|
using NATS.Server.Monitoring;
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Monitoring.Tests.Monitoring;
|
|
|
|
/// <summary>
|
|
/// Tests covering /routez endpoint behavior, ported from the Go server's monitor_test.go.
|
|
/// </summary>
|
|
public class MonitorRoutezTests
|
|
{
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies that /routez returns valid JSON with routes and num_routes fields.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_returns_routes_and_num_routes()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var body = await fx.GetStringAsync("/routez");
|
|
body.ShouldContain("routes");
|
|
body.ShouldContain("num_routes");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies /routez num_routes is 0 when no cluster routes are configured.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_num_routes_is_zero_without_cluster()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var doc = await fx.GetJsonDocumentAsync("/routez");
|
|
doc.RootElement.GetProperty("num_routes").GetInt32().ShouldBe(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies /connz does not include route connections (they appear under /routez only).
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Connz_does_not_include_route_connections()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var connz = await fx.GetFromJsonAsync<Connz>("/connz");
|
|
connz.ShouldNotBeNull();
|
|
// Without any clients, connz should be empty
|
|
connz.NumConns.ShouldBe(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorRoutezRace (line 2210).
|
|
/// Verifies concurrent /routez requests do not cause errors or data corruption.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_handles_concurrent_requests()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var tasks = Enumerable.Range(0, 10).Select(async _ =>
|
|
{
|
|
var response = await fx.GetAsync("/routez");
|
|
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
|
});
|
|
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorClusterEmptyWhenNotDefined (line 2456).
|
|
/// Verifies /varz cluster section has empty name when no cluster is configured.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Varz_cluster_empty_when_not_defined()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var varz = await fx.GetFromJsonAsync<Varz>("/varz");
|
|
varz.ShouldNotBeNull();
|
|
varz.Cluster.ShouldNotBeNull();
|
|
varz.Cluster.Name.ShouldBe("");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies /routez JSON field naming matches Go server format.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_json_uses_expected_field_names()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var body = await fx.GetStringAsync("/routez");
|
|
body.ShouldContain("\"routes\"");
|
|
body.ShouldContain("\"num_routes\"");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorCluster (line 2724).
|
|
/// Verifies /varz includes cluster section even when cluster is enabled.
|
|
/// Note: The .NET server currently initializes the cluster section with defaults;
|
|
/// the Go server populates it with cluster config. This test verifies the section exists.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Varz_includes_cluster_section_when_cluster_enabled()
|
|
{
|
|
await using var fx = await RoutezFixture.StartWithClusterAsync();
|
|
|
|
var varz = await fx.GetFromJsonAsync<Varz>("/varz");
|
|
varz.ShouldNotBeNull();
|
|
varz.Cluster.ShouldNotBeNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies /routez response includes routes field even when num_routes is 0.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_includes_routes_field_even_when_empty()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var doc = await fx.GetJsonDocumentAsync("/routez");
|
|
doc.RootElement.TryGetProperty("routes", out _).ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorConnzWithRoutes (line 1405).
|
|
/// Verifies /routez returns HTTP 200 OK.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_returns_http_200()
|
|
{
|
|
await using var fx = await RoutezFixture.StartAsync();
|
|
|
|
var response = await fx.GetAsync("/routez");
|
|
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Go: TestMonitorCluster (line 2724).
|
|
/// Verifies /routez endpoint is accessible when cluster is configured.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Routez_accessible_with_cluster_config()
|
|
{
|
|
await using var fx = await RoutezFixture.StartWithClusterAsync();
|
|
|
|
var response = await fx.GetAsync("/routez");
|
|
response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
|
|
|
var body = await response.Content.ReadAsStringAsync();
|
|
body.ShouldContain("routes");
|
|
}
|
|
}
|
|
|
|
internal sealed class RoutezFixture : IAsyncDisposable
|
|
{
|
|
private readonly NatsServer _server;
|
|
private readonly CancellationTokenSource _cts;
|
|
private readonly HttpClient _http;
|
|
private readonly int _monitorPort;
|
|
|
|
private RoutezFixture(NatsServer server, CancellationTokenSource cts, HttpClient http, int monitorPort)
|
|
{
|
|
_server = server;
|
|
_cts = cts;
|
|
_http = http;
|
|
_monitorPort = monitorPort;
|
|
}
|
|
|
|
public static async Task<RoutezFixture> StartAsync()
|
|
{
|
|
var monitorPort = TestPortAllocator.GetFreePort();
|
|
var options = new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
MonitorPort = monitorPort,
|
|
};
|
|
|
|
return await CreateAndStartAsync(options, monitorPort);
|
|
}
|
|
|
|
public static async Task<RoutezFixture> StartWithClusterAsync()
|
|
{
|
|
var monitorPort = TestPortAllocator.GetFreePort();
|
|
var options = new NatsOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
MonitorPort = monitorPort,
|
|
Cluster = new ClusterOptions
|
|
{
|
|
Host = "127.0.0.1",
|
|
Port = 0,
|
|
Name = "test-cluster",
|
|
},
|
|
};
|
|
|
|
return await CreateAndStartAsync(options, monitorPort);
|
|
}
|
|
|
|
private static async Task<RoutezFixture> CreateAndStartAsync(NatsOptions options, int monitorPort)
|
|
{
|
|
var server = new NatsServer(options, NullLoggerFactory.Instance);
|
|
var cts = new CancellationTokenSource();
|
|
_ = server.StartAsync(cts.Token);
|
|
await server.WaitForReadyAsync();
|
|
|
|
var http = new HttpClient();
|
|
for (var i = 0; i < 50; i++)
|
|
{
|
|
try
|
|
{
|
|
var response = await http.GetAsync($"http://127.0.0.1:{monitorPort}/healthz");
|
|
if (response.IsSuccessStatusCode) break;
|
|
}
|
|
catch { }
|
|
await Task.Delay(50);
|
|
}
|
|
|
|
return new RoutezFixture(server, cts, http, monitorPort);
|
|
}
|
|
|
|
public Task<string> GetStringAsync(string path)
|
|
=> _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}{path}");
|
|
|
|
public Task<HttpResponseMessage> GetAsync(string path)
|
|
=> _http.GetAsync($"http://127.0.0.1:{_monitorPort}{path}");
|
|
|
|
public Task<T?> GetFromJsonAsync<T>(string path)
|
|
=> _http.GetFromJsonAsync<T>($"http://127.0.0.1:{_monitorPort}{path}");
|
|
|
|
public async Task<JsonDocument> GetJsonDocumentAsync(string path)
|
|
{
|
|
var body = await GetStringAsync(path);
|
|
return JsonDocument.Parse(body);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_http.Dispose();
|
|
await _cts.CancelAsync();
|
|
_server.Dispose();
|
|
_cts.Dispose();
|
|
}
|
|
|
|
}
|