Fix LeafNodeManager.StartAsync to launch solicited connections for RemoteLeaves (config-file-parsed remotes) in addition to the programmatic Remotes list, and update ParseEndpoint to handle nats-leaf:// scheme URLs. Add LeafNodeFixture that polls /leafz for connection readiness and three E2E tests covering hub→leaf delivery, leaf→hub delivery, and subject-scoped propagation.
93 lines
2.8 KiB
C#
93 lines
2.8 KiB
C#
using System.Net.Http.Json;
|
|
using NATS.Client.Core;
|
|
|
|
namespace NATS.E2E.Tests.Infrastructure;
|
|
|
|
public sealed class LeafNodeFixture : IAsyncLifetime
|
|
{
|
|
private NatsServerProcess _hub = null!;
|
|
private NatsServerProcess _leaf = null!;
|
|
|
|
public int HubPort => _hub.Port;
|
|
public int LeafPort => _leaf.Port;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
var leafListenPort = NatsServerProcess.AllocateFreePort();
|
|
|
|
var hubConfig = $$"""
|
|
server_name: hub
|
|
leafnodes {
|
|
listen: 127.0.0.1:{{leafListenPort}}
|
|
}
|
|
""";
|
|
|
|
_hub = NatsServerProcess.WithConfig(hubConfig, enableMonitoring: true);
|
|
await _hub.StartAsync();
|
|
|
|
var leafConfig = $$"""
|
|
server_name: leaf
|
|
leafnodes {
|
|
remotes [
|
|
{ url: "nats-leaf://127.0.0.1:{{leafListenPort}}" }
|
|
]
|
|
}
|
|
""";
|
|
|
|
_leaf = NatsServerProcess.WithConfig(leafConfig);
|
|
await _leaf.StartAsync();
|
|
|
|
// Poll hub's /leafz until it reports 1 connected leaf node
|
|
await WaitForLeafConnectionAsync();
|
|
}
|
|
|
|
private async Task WaitForLeafConnectionAsync()
|
|
{
|
|
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
|
|
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
|
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));
|
|
|
|
while (await timer.WaitForNextTickAsync(deadline.Token).ConfigureAwait(false))
|
|
{
|
|
try
|
|
{
|
|
var leafz = await http.GetFromJsonAsync<Leafz>(
|
|
$"http://127.0.0.1:{_hub.MonitorPort!.Value}/leafz",
|
|
deadline.Token);
|
|
|
|
if (leafz?.NumLeafs >= 1)
|
|
return;
|
|
}
|
|
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException
|
|
&& !deadline.IsCancellationRequested)
|
|
{
|
|
// Monitor not yet ready or per-request timeout — retry on next tick
|
|
_ = ex;
|
|
}
|
|
}
|
|
|
|
throw new TimeoutException("Leaf node did not connect to hub within 30s.");
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
await _leaf.DisposeAsync();
|
|
await _hub.DisposeAsync();
|
|
}
|
|
|
|
public NatsConnection CreateHubClient()
|
|
=> new(new NatsOpts { Url = $"nats://127.0.0.1:{HubPort}" });
|
|
|
|
public NatsConnection CreateLeafClient()
|
|
=> new(new NatsOpts { Url = $"nats://127.0.0.1:{LeafPort}" });
|
|
|
|
private sealed class Leafz
|
|
{
|
|
[System.Text.Json.Serialization.JsonPropertyName("num_leafs")]
|
|
public int NumLeafs { get; init; }
|
|
}
|
|
}
|
|
|
|
[CollectionDefinition("E2E-LeafNode")]
|
|
public class LeafNodeCollection : ICollectionFixture<LeafNodeFixture>;
|