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 RetailOversellingTests { private readonly ApiFactory _factory; public RetailOversellingTests(ApiFactory factory) => _factory = factory; private static string RandomBarcode() => string.Concat(Enumerable.Range(0, 13).Select(_ => Random.Shared.Next(0, 10))); [Fact] public async Task Posting_sale_above_available_stock_is_rejected() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"oversell-{Guid.NewGuid():N}"); var refs = await api.LoadRefsAsync(); var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", retailPrice: 100m, RandomBarcode()); // Остатка нет (приёмок не делали). Продаём 5 шт. paidCash покрывает итог, // чтобы дойти до проверки остатка (а не упасть на валидации оплаты). var draftResp = await api.Http.PostAsJsonAsync("/api/sales/retail", new { date = DateTime.UtcNow, storeId = refs.StoreId, retailPointId = (string?)null, customerId = (string?)null, currencyId = refs.CurrencyId, payment = 0, paidCash = 500m, paidCard = 0m, notes = "oversell", lines = new[] { new { productId, quantity = 5m, unitPrice = 100m, discount = 0m, vatPercent = 0m } }, }); draftResp.EnsureSuccessStatusCode(); var saleId = (await draftResp.Content.ReadFromJsonAsync()).GetProperty("id").GetString(); using var post = await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/post", new { }); ((int)post.StatusCode).Should().Be(409, "продажа сверх остатка должна блокироваться"); } [Fact] public async Task Posting_sale_with_underpayment_is_rejected() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"underpay-{Guid.NewGuid():N}"); var refs = await api.LoadRefsAsync(); var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 100m, RandomBarcode()); // Итог 200, оплачено 100 → недоплата → 400 (до проверки остатка). var draftResp = await api.Http.PostAsJsonAsync("/api/sales/retail", new { date = DateTime.UtcNow, storeId = refs.StoreId, retailPointId = (string?)null, customerId = (string?)null, currencyId = refs.CurrencyId, payment = 0, paidCash = 100m, paidCard = 0m, notes = "underpay", lines = new[] { new { productId, quantity = 2m, unitPrice = 100m, discount = 0m, vatPercent = 0m } }, }); draftResp.EnsureSuccessStatusCode(); var saleId = (await draftResp.Content.ReadFromJsonAsync()).GetProperty("id").GetString(); using var post = await api.Http.PostAsJsonAsync($"/api/sales/retail/{saleId}/post", new { }); ((int)post.StatusCode).Should().Be(400, "недоплата должна блокировать проведение"); } }