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( $"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;