using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Hosting; namespace Prefab.Tests.Infrastructure; /// /// Hosts a project entry point in-process with Kestrel so integration tests hit real HTTP. /// public sealed class KestrelInProcApp : WebApplicationFactory, IAsyncLifetime where TEntryPoint : class { private readonly AspireInfraHost _infra; private readonly ProjectResource _project; private readonly int? _port; private readonly IDictionary _overrides; private readonly Action? _configureServices; private Dictionary _config = new(StringComparer.OrdinalIgnoreCase); private Uri? _httpAddress; public KestrelInProcApp( AspireInfraHost infra, ProjectResource project, int? port, IDictionary? overrides = null, Action? configureServices = null) { _infra = infra; _project = project; _port = port; _overrides = overrides ?? new Dictionary(StringComparer.OrdinalIgnoreCase); _configureServices = configureServices; this.UseKestrel(_port ?? 0); } public Uri HttpAddress => _httpAddress ?? throw new InvalidOperationException("Application not started."); public IServiceProvider AppServices => Services; public async ValueTask InitializeAsync() { _config = await AspireInfraHost.ResolveConfigForProjectAsync(_project, _infra.Services); foreach (var (key, value) in _overrides) { _config[key.Replace("__", ":")] = value; } using var client = CreateClient(); _httpAddress = client.BaseAddress ?? ResolveServerAddress(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { foreach (var (key, value) in _config) { if (string.Equals(key, "ASPNETCORE_URLS", StringComparison.OrdinalIgnoreCase)) { continue; } if (value is null) { continue; } builder.UseSetting(key, value); } builder.UseSetting("ASPNETCORE_ENVIRONMENT", "Testing"); builder.ConfigureServices(services => { RemoveHostedService(services, "Prefab.Web.Workers.DataSeeder"); RemoveHostedService(services, "Prefab.Catalog.Api.Workers.DataSeeder"); _configureServices?.Invoke(services); }); } public override ValueTask DisposeAsync() => base.DisposeAsync(); private Uri ResolveServerAddress() { var server = Services.GetRequiredService(); var feature = server.Features.Get(); var address = feature?.Addresses.FirstOrDefault(); if (address is not null && Uri.TryCreate(address, UriKind.Absolute, out var uri)) { return uri; } // Fallback to localhost with known (or dynamic) port. var port = _port.GetValueOrDefault(0); return new Uri($"http://127.0.0.1:{port}"); } private static void RemoveHostedService(IServiceCollection services, string typeFullName) { var descriptors = services .Where(s => s.ServiceType == typeof(IHostedService) && s.ImplementationType?.FullName == typeFullName) .ToList(); foreach (var descriptor in descriptors) { services.Remove(descriptor); } } }