using Examples.Billing.Microservice.Endpoints; using Examples.Inventory.Microservice.Endpoints; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using StellaOps.Microservice; using StellaOps.Router.Common.Abstractions; using StellaOps.Router.Common.Enums; using StellaOps.Router.Transport.InMemory; using Xunit; namespace Examples.Integration.Tests; /// /// Test fixture that sets up the gateway and microservices for integration testing. /// Uses in-memory transport for fast, isolated tests. /// public sealed class GatewayFixture : IAsyncLifetime { private readonly InMemoryConnectionRegistry _registry = new(); private WebApplicationFactory? _gatewayFactory; private IHost? _billingHost; private IHost? _inventoryHost; public HttpClient GatewayClient { get; private set; } = null!; public async Task InitializeAsync() { // Start the gateway _gatewayFactory = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); builder.ConfigureServices(services => { services.RemoveAll(); services.AddSingleton(_registry); }); }); GatewayClient = _gatewayFactory.CreateClient(); // Start billing microservice var billingBuilder = Host.CreateApplicationBuilder(); billingBuilder.Services.AddStellaMicroservice(options => { options.ServiceName = "billing"; options.Version = "1.0.0"; options.Region = "test"; options.InstanceId = "billing-test"; options.Routers = [ new RouterEndpointConfig { Host = "localhost", Port = 5100, TransportType = TransportType.InMemory } ]; }); billingBuilder.Services.AddScoped(); billingBuilder.Services.AddScoped(); billingBuilder.Services.AddScoped(); billingBuilder.Services.AddSingleton(_registry); billingBuilder.Services.AddInMemoryTransportClient(); _billingHost = billingBuilder.Build(); await _billingHost.StartAsync(); // Start inventory microservice var inventoryBuilder = Host.CreateApplicationBuilder(); inventoryBuilder.Services.AddStellaMicroservice(options => { options.ServiceName = "inventory"; options.Version = "1.0.0"; options.Region = "test"; options.InstanceId = "inventory-test"; options.Routers = [ new RouterEndpointConfig { Host = "localhost", Port = 5100, TransportType = TransportType.InMemory } ]; }); inventoryBuilder.Services.AddScoped(); inventoryBuilder.Services.AddScoped(); inventoryBuilder.Services.AddSingleton(_registry); inventoryBuilder.Services.AddInMemoryTransportClient(); _inventoryHost = inventoryBuilder.Build(); await _inventoryHost.StartAsync(); await WaitForGatewayReadyAsync(TimeSpan.FromSeconds(5)); } public async Task DisposeAsync() { GatewayClient.Dispose(); if (_billingHost is not null) { await _billingHost.StopAsync(); _billingHost.Dispose(); } if (_inventoryHost is not null) { await _inventoryHost.StopAsync(); _inventoryHost.Dispose(); } _gatewayFactory?.Dispose(); } private async Task WaitForGatewayReadyAsync(TimeSpan timeout) { if (_gatewayFactory is null) { throw new InvalidOperationException("Gateway factory not initialized."); } var routingState = _gatewayFactory.Services.GetRequiredService(); var deadline = DateTimeOffset.UtcNow.Add(timeout); while (DateTimeOffset.UtcNow < deadline) { var connections = routingState.GetAllConnections(); if (connections.Count >= 2 && routingState.ResolveEndpoint("GET", "/items") is not null && routingState.ResolveEndpoint("POST", "/invoices") is not null) { return; } await Task.Delay(50); } var currentConnections = routingState.GetAllConnections(); throw new TimeoutException( $"Gateway routing state not ready after {timeout}. Connections={currentConnections.Count}."); } }