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
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>
75 lines
4.4 KiB
C#
75 lines
4.4 KiB
C#
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(),
|
||
});
|
||
});
|
||
}
|
||
}
|