food-market/tests/food-market.IntegrationTests/Support/ApiFactory.cs
nns 7f8750f30f
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
test(s29): unit (6) + integration (7) + e2e scenario для simple-mode (шаг 7/8)
Unit — AdvancedModeFilterTests (6/6, ~3с):
- allow when IsAdvancedMode=true
- 403 ProblemDetails с code=advanced-mode-required when simple
- SuperAdmin bypass
- InvalidateCache делает следующее чтение свежим
- unauthenticated — не трогаем ([Authorize] выше)
- authenticated без org — fail-closed 403

Integration — AdvancedModeTests (7/7, ~25с):
- /api/me.advancedMode=false по умолчанию
- GET на скрытый endpoint в простом режиме → 403 ProblemDetails
- org-admin не может переключить → 403
- SuperAdmin переключает → 200 + audit; идемпотент: повторный → changed=false
- /api/me обновилось, demand открылся
- данные пресета сохраняются через on→off→on
- архивную org переключать нельзя → 409

Доп. фикс: ApiFactory теперь прокидывает ConnectionStrings__Default через
Environment.SetEnvironmentVariable в InitializeAsync. ConfigureAppConfiguration
опаздывает к моменту чтения строки в Program.cs:86 (var poolTunedConnString=...)
— интеграционные тесты падали с «Connection refused к 127.0.0.1:5432». env-var
читает CreateBuilder сразу, строка подключения теперь корректно берётся из
Testcontainers. Это общий фикс для всей сюиты.

E2E — tests/e2e/scenarios/stage-simple-mode.{yml,steps.ts} (7 шагов):
sm01 signup→simple default; sm02 org-admin forbidden; sm03 SuperAdmin
toggle + audit; sm04 /api/me reflects + endpoint opens; sm05 data
survives toggle cycle; sm06 archived→409; sm07 Playwright UI показывает
страницу «Недоступно в простом режиме».

Quality-watchdog: GET /api/catalog/price-types не гейтится (только
мутации) — патч смока не требуется.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 12:02:44 +05:00

75 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Testcontainers.PostgreSql;
using Xunit;
namespace foodmarket.IntegrationTests.Support;
/// <summary>Поднимает реальный API (WebApplicationFactory) поверх одноразового
/// Postgres-контейнера (Testcontainers). Миграции применяются на старте host'а
/// (Program.Migrate), сидеры наполняют справочники и SuperAdmin. Запускается
/// один раз на всю коллекцию тестов.</summary>
public sealed class ApiFactory : WebApplicationFactory<Program>, 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-событиями).
// Ошибки (Error) пропускаем — нужно для отладки 500 в тестах.
Environment.SetEnvironmentVariable("Serilog__MinimumLevel__Default", "Warning");
// Hangfire-сервер не нужен в интеграционных тестах: он бы создавал свои
// таблицы в одноразовом контейнере и удерживал коннекшен. Сам клиент
// (AddHangfire) остаётся — recurring jobs не регистрируются.
Environment.SetEnvironmentVariable("Hangfire__Enabled", "false");
}
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();
// Program.cs читает ConnectionStrings:Default из builder.Configuration
// ЕЩЁ ДО сборки host'а — поэтому ConfigureAppConfiguration ниже успевает
// только для последующих читателей (например, Hangfire на строке 395).
// Чтобы pool-tuned conn string (строка 86) взял значение контейнера —
// прокидываем через env-var, которую CreateBuilder подхватывает в самом
// начале. Приоритет Environment > appsettings.Development.json.
Environment.SetEnvironmentVariable("ConnectionStrings__Default", _db.GetConnectionString());
}
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<string, string?>
{
["ConnectionStrings:Default"] = _db.GetConnectionString(),
});
});
}
}