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>
65 lines
2.8 KiB
C#
65 lines
2.8 KiB
C#
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);
|
||
}
|
||
}
|