food-market/tests/food-market.IntegrationTests/PermissionTests.cs
nns f2dad91e05 test(integration): Testcontainers.PostgreSql + WebApplicationFactory, 10 тестов (P1-21)
ApiFactory поднимает реальный API на одноразовом postgres:16-alpine (Ryuk off —
сеть к Docker Hub нестабильна, образ закэширован; RateLimiting off через env, т.к.
лимитер читает конфиг эагерно). Program сделан public partial для фабрики.

Сценарии (10 зелёных):
- signup-flow: signup→token→/api/me с org; дубль-signup 400; слабый пароль 400.
- tenant isolation A vs B: контрагент A не виден B (список + прямой GET 404).
- permission: кастомная роль без ProductsEdit → PUT товара 403, GET 200; админ не 403.
- supply post→unpost: остаток 0→10, Cost=70 (скользящее среднее), unpost→0; двойной post 409.
- retail overselling: продажа сверх остатка → 409; недоплата → 400.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 03:14:01 +05:00

65 lines
2.8 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 System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Xunit;
namespace foodmarket.IntegrationTests;
[Collection(ApiCollection.Name)]
public class PermissionTests
{
private readonly ApiFactory _factory;
public PermissionTests(ApiFactory factory) => _factory = factory;
[Fact]
public async Task Custom_role_without_ProductsEdit_gets_403_on_put_product()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"permadmin-{Guid.NewGuid():N}");
// Кастомная роль: просмотр товаров, но БЕЗ права правки.
var roleResp = await admin.Http.PostAsJsonAsync("/api/organization/employee-roles", new
{
name = $"Viewer-{Guid.NewGuid():N}",
description = "view only",
permissions = new { productsView = true, productsEdit = false },
});
roleResp.EnsureSuccessStatusCode();
var roleId = (await roleResp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString();
// Сотрудник с логином на этой роли.
var empEmail = $"viewer-{Guid.NewGuid():N}@example.kz";
var empResp = await admin.Http.PostAsJsonAsync("/api/organization/employees", new
{
lastName = "Просмотров", firstName = "Вью", roleId,
isActive = true, createAccount = true, email = empEmail,
});
empResp.EnsureSuccessStatusCode();
var pwd = (await empResp.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("generatedPassword").GetString()!;
var viewer = new ApiActor(_factory.CreateClient());
viewer.UseToken(await viewer.TokenAsync(empEmail, pwd));
// Авторизация проверяется до биндинга тела: несуществующий id → 403, если нет права.
using var put = await viewer.Http.PutAsJsonAsync($"/api/catalog/products/{Guid.NewGuid()}", new { });
put.StatusCode.Should().Be(HttpStatusCode.Forbidden);
// Чтение (ProductsView) разрешено.
using var get = await viewer.Http.GetAsync("/api/catalog/products");
get.StatusCode.Should().Be(HttpStatusCode.OK);
}
[Fact]
public async Task Admin_passes_permission_gate()
{
var admin = new ApiActor(_factory.CreateClient());
await admin.SignupAndLoginAsync($"permadmin2-{Guid.NewGuid():N}");
// Админ проходит permission-гейт (дальше — 404/400 на несуществующем товаре, но НЕ 403).
using var put = await admin.Http.PutAsJsonAsync($"/api/catalog/products/{Guid.NewGuid()}", new { });
put.StatusCode.Should().NotBe(HttpStatusCode.Forbidden);
}
}