using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Testcontainers.PostgreSql; using Xunit; namespace foodmarket.IntegrationTests.Support; /// Поднимает реальный API (WebApplicationFactory) поверх одноразового /// Postgres-контейнера (Testcontainers). Миграции применяются на старте host'а /// (Program.Migrate), сидеры наполняют справочники и SuperAdmin. Запускается /// один раз на всю коллекцию тестов. public sealed class ApiFactory : WebApplicationFactory, IAsyncLifetime { static ApiFactory() { // Ryuk (reaper) тянет образ из Docker Hub — на этом хосте сеть к внешним // реестрам нестабильна, а postgres:16-alpine уже закэширован. Выключаем. Environment.SetEnvironmentVariable("TESTCONTAINERS_RYUK_DISABLED", "true"); // Лимитер читает конфиг ЭАГЕРНО при регистрации сервисов — поэтому // переопределяем через env-переменную (её CreateBuilder подхватывает до // регистрации), а не через ConfigureAppConfiguration (применяется позже). // Тесты логинятся десятки раз с одного loopback-IP — иначе 429. Environment.SetEnvironmentVariable("RateLimiting__Enabled", "false"); // Тише логи в тестовом прогоне (OpenIddict сыплет Debug-событиями). Environment.SetEnvironmentVariable("Serilog__MinimumLevel__Default", "Warning"); } private readonly PostgreSqlContainer _db = new PostgreSqlBuilder() .WithImage("postgres:16-alpine") .WithDatabase("food_market") .WithUsername("food_market") .WithPassword("food_market_test") .Build(); public async Task InitializeAsync() => await _db.StartAsync(); public new async Task DisposeAsync() { await _db.DisposeAsync(); await base.DisposeAsync(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { // Development — простые dev-ключи OpenIddict (без генерации сертификатов), // 3-сегментный токен. Лимитер выключаем: тесты логинятся много раз с одного IP. builder.UseEnvironment("Development"); builder.ConfigureAppConfiguration((_, cfg) => { // Строку подключения AddDbContext читает лениво — здесь override // срабатывает. RateLimiting выключён через env (см. static ctor). cfg.AddInMemoryCollection(new Dictionary { ["ConnectionStrings:Default"] = _db.GetConnectionString(), }); }); } }