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>
38 lines
1.5 KiB
C#
38 lines
1.5 KiB
C#
using FluentAssertions;
|
|
using foodmarket.IntegrationTests.Support;
|
|
using Xunit;
|
|
|
|
namespace foodmarket.IntegrationTests;
|
|
|
|
[Collection(ApiCollection.Name)]
|
|
public class TenantIsolationTests
|
|
{
|
|
private readonly ApiFactory _factory;
|
|
public TenantIsolationTests(ApiFactory factory) => _factory = factory;
|
|
|
|
[Fact]
|
|
public async Task Org_B_cannot_see_org_A_data()
|
|
{
|
|
var a = new ApiActor(_factory.CreateClient());
|
|
var b = new ApiActor(_factory.CreateClient());
|
|
await a.SignupAndLoginAsync($"iso-a-{Guid.NewGuid():N}");
|
|
await b.SignupAndLoginAsync($"iso-b-{Guid.NewGuid():N}");
|
|
|
|
var marker = $"A-CP-{Guid.NewGuid():N}";
|
|
var createdId = await a.CreateCounterpartyAsync(marker);
|
|
|
|
// A видит свой контрагент.
|
|
var aList = await a.ListAsync("/api/catalog/counterparties?pageSize=200");
|
|
aList.Should().Contain(c => c.GetProperty("id").GetString() == createdId);
|
|
|
|
// B не видит ни id, ни имя контрагента A.
|
|
var bList = await b.ListAsync("/api/catalog/counterparties?pageSize=200");
|
|
bList.Should().NotContain(c => c.GetProperty("id").GetString() == createdId);
|
|
bList.Should().NotContain(c => c.GetProperty("name").GetString() == marker);
|
|
|
|
// B не может прочитать контрагент A напрямую по id (query-filter → 404).
|
|
using var direct = await b.Http.GetAsync($"/api/catalog/counterparties/{createdId}");
|
|
direct.StatusCode.Should().Be(System.Net.HttpStatusCode.NotFound);
|
|
}
|
|
}
|