using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;
namespace foodmarket.IntegrationTests;
/// Sprint 29 — Simple/Advanced mode: integration-тесты по чек-листу
/// задачи:
///
/// - простая org → GET на скрытый эндпоинт возвращает 403 + ProblemDetails;
/// - org-admin не может переключить режим (403);
/// - SuperAdmin переключает → 200 + запись в SuperAdminAuditLog;
/// - /api/me отдаёт корректный advancedMode;
/// - данные скрытого модуля сохраняются после выкл→вкл.
///
///
[Collection(ApiCollection.Name)]
public class AdvancedModeTests
{
private readonly ApiFactory _factory;
public AdvancedModeTests(ApiFactory factory) => _factory = factory;
private const string SuperAdminEmail = "admin@food-market.local";
private const string SuperAdminPassword = "Admin12345!";
private async Task LoginSuperAdminAsync()
{
var sa = new ApiActor(_factory.CreateClient());
sa.UseToken(await sa.TokenAsync(SuperAdminEmail, SuperAdminPassword));
return sa;
}
private static async Task OrgIdFromMeAsync(ApiActor actor)
{
var me = await actor.GetJsonAsync("/api/me");
return me.GetProperty("orgId").GetString()!;
}
[Fact]
public async Task Me_returns_advancedMode_false_by_default()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-default-{Guid.NewGuid():N}");
var me = await admin.GetJsonAsync("/api/me");
me.GetProperty("advancedMode").GetBoolean().Should().BeFalse(
"новая организация стартует в простом режиме");
}
[Fact]
public async Task Simple_mode_gets_403_ProblemDetails_on_hidden_endpoint()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-hidden-{Guid.NewGuid():N}");
using var resp = await admin.Http.GetAsync("/api/sales/demands");
resp.StatusCode.Should().Be(HttpStatusCode.Forbidden,
"demand-эндпоинт скрыт в простом режиме");
// ProblemDetails с нашим кодом.
var body = await resp.Content.ReadFromJsonAsync();
body.GetProperty("status").GetInt32().Should().Be(403);
body.GetProperty("code").GetString().Should().Be("advanced-mode-required");
}
[Fact]
public async Task Org_admin_cannot_toggle_advanced_mode()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-noadmin-{Guid.NewGuid():N}");
var orgId = await OrgIdFromMeAsync(admin);
using var resp = await admin.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode",
new { enabled = true });
resp.StatusCode.Should().Be(HttpStatusCode.Forbidden,
"SuperAdmin-эндпоинт недоступен обычному админу орги");
}
[Fact]
public async Task SuperAdmin_can_toggle_and_writes_audit_entry()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-toggle-{Guid.NewGuid():N}");
var orgId = await OrgIdFromMeAsync(admin);
var sa = await LoginSuperAdminAsync();
using var on = await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true });
on.StatusCode.Should().Be(HttpStatusCode.OK);
var payload = await on.Content.ReadFromJsonAsync();
payload.GetProperty("isAdvancedMode").GetBoolean().Should().BeTrue();
payload.GetProperty("changed").GetBoolean().Should().BeTrue();
// Повторный тот же вызов — changed=false, в аудит не пишется.
using var again = await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true });
again.StatusCode.Should().Be(HttpStatusCode.OK);
(await again.Content.ReadFromJsonAsync())
.GetProperty("changed").GetBoolean().Should().BeFalse();
// Audit: SuperAdminAuditLog содержит запись EnableAdvancedMode по этой org.
var auditList = await sa.ListAsync($"/api/super-admin/audit-log?organizationId={orgId}&pageSize=50");
auditList.Should().Contain(e =>
e.GetProperty("actionType").GetString() == "EnableAdvancedMode"
&& e.GetProperty("organizationId").GetString() == orgId);
}
[Fact]
public async Task Me_advancedMode_switches_with_toggle_and_endpoint_opens()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-switch-{Guid.NewGuid():N}");
var orgId = await OrgIdFromMeAsync(admin);
// Простой режим — demand 403.
(await admin.Http.GetAsync("/api/sales/demands")).StatusCode
.Should().Be(HttpStatusCode.Forbidden);
var sa = await LoginSuperAdminAsync();
(await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true }))
.EnsureSuccessStatusCode();
// /api/me теперь advancedMode=true.
var me2 = await admin.GetJsonAsync("/api/me");
me2.GetProperty("advancedMode").GetBoolean().Should().BeTrue();
// Demand-эндпоинт открылся.
var open = await admin.Http.GetAsync("/api/sales/demands");
open.StatusCode.Should().NotBe(HttpStatusCode.Forbidden,
"после включения расширенного режима demand-эндпоинт доступен");
}
[Fact]
public async Task Data_in_hidden_module_survives_toggle_cycle()
{
// Поведение «данные не удаляются» проверяется через UserPreset:
// создаём пресет в advanced-режиме, выключаем → заново включаем →
// пресет на месте.
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-cycle-{Guid.NewGuid():N}");
var orgId = await OrgIdFromMeAsync(admin);
var sa = await LoginSuperAdminAsync();
(await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true }))
.EnsureSuccessStatusCode();
// Создаём пресет.
var presetName = $"preset-{Guid.NewGuid():N}";
using var create = await admin.Http.PostAsJsonAsync("/api/user/presets", new
{
pageKey = "products",
name = presetName,
configJson = "{\"q\":\"test\"}",
});
create.StatusCode.Should().Be(HttpStatusCode.OK);
// Выключаем режим — пресет временно недоступен (403), но не удалён.
(await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = false }))
.EnsureSuccessStatusCode();
(await admin.Http.GetAsync("/api/user/presets?pageKey=products")).StatusCode
.Should().Be(HttpStatusCode.Forbidden);
// Снова включаем — пресет виден.
(await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true }))
.EnsureSuccessStatusCode();
var list = await admin.ListAsync("/api/user/presets?pageKey=products");
list.Should().Contain(e => e.GetProperty("name").GetString() == presetName,
"данные пресета сохраняются при переключении режима");
}
[Fact]
public async Task Toggle_fails_for_archived_org_with_409()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"adv-arch-{Guid.NewGuid():N}");
var orgId = await OrgIdFromMeAsync(admin);
var sa = await LoginSuperAdminAsync();
// Нужно узнать имя для подтверждения архивирования.
var detail = await sa.GetJsonAsync($"/api/super-admin/organizations/{orgId}");
var name = detail.GetProperty("name").GetString()!;
(await sa.Http.PostAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/archive", new { confirmationName = name }))
.EnsureSuccessStatusCode();
using var put = await sa.Http.PutAsJsonAsync(
$"/api/super-admin/organizations/{orgId}/advanced-mode", new { enabled = true });
put.StatusCode.Should().Be(HttpStatusCode.Conflict,
"архивную org переключать нельзя");
}
}