using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using PnvPanel.Application.Common.Interfaces; using Testcontainers.PostgreSql; using Xunit; namespace PnvPanel.IntegrationTests.TestSupport; /// /// Реальный Postgres через Testcontainers (не InMemory/Sqlite — нужно проверить Postgres-специфичное /// поведение: pg_advisory_xact_lock для квоты конфигов, uuid[]/jsonb колонки). Program.cs сам /// применяет миграции и сидит роли/админа при старте хоста — свежий контейнер становится полностью /// готовой БД без ручных шагов. /// public sealed class PnvPanelWebApplicationFactory : WebApplicationFactory, IAsyncLifetime { public const string AdminUserName = "test-admin"; public const string AdminPassword = "TestAdmin123!"; private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder() .WithImage("postgres:17-alpine") .WithDatabase("pnvpanel") .WithUsername("pnvpanel") .WithPassword("pnvpanel") .Build(); public async Task InitializeAsync() { await _postgres.StartAsync(); } async Task IAsyncLifetime.DisposeAsync() { await _postgres.StopAsync(); await base.DisposeAsync(); } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Development"); builder.ConfigureAppConfiguration( (_, config) => { config.AddInMemoryCollection( new Dictionary { ["ConnectionStrings:Default"] = _postgres.GetConnectionString(), ["AdminSeed:Username"] = AdminUserName, ["AdminSeed:Password"] = AdminPassword, // Пусто — TelegramBotHostedService при пустом токене не стартует (см. Api/Telegram/TelegramBotHostedService.cs). ["Telegram:BotToken"] = "", // Весь collection делит один TestServer/host — все запросы идут от одного "клиента", // дефолтный лимит 20/мин быстро исчерпывается. Поднимаем для тестового окружения. ["RateLimiting:AuthPermitLimit"] = "10000", } ); } ); builder.ConfigureServices(services => { // Реальной панели 3x-ui в тестах нет — подменяем гейтвей заглушкой. services.RemoveAll(); services.AddSingleton(); }); } }