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 SupplyPostUnpostTests { private readonly ApiFactory _factory; public SupplyPostUnpostTests(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_supply_raises_stock_and_sets_cost_unpost_reverts() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"supply-{Guid.NewGuid():N}"); var refs = await api.LoadRefsAsync(); var supplierId = await api.CreateCounterpartyAsync($"Supplier-{Guid.NewGuid():N}"); var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", retailPrice: 150m, RandomBarcode()); (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(0m, "новый товар без приёмок"); // Draft supply: 10 шт по 70. var draftResp = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new { date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId, notes = "integration", lines = new[] { new { productId, quantity = 10m, unitPrice = 70m } }, }); draftResp.EnsureSuccessStatusCode(); var supplyId = (await draftResp.Content.ReadFromJsonAsync()).GetProperty("id").GetString(); // Post → остаток 10, себестоимость 70 (скользящее среднее с нуля). using var post = await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supplyId}/post", new { }); post.IsSuccessStatusCode.Should().BeTrue($"post вернул {(int)post.StatusCode}: {await post.Content.ReadAsStringAsync()}"); (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(10m); var product = await api.GetJsonAsync($"/api/catalog/products/{productId}"); product.GetProperty("cost").GetDecimal().Should().Be(70m); // Unpost → остаток возвращается к 0. using var unpost = await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supplyId}/unpost", new { }); unpost.IsSuccessStatusCode.Should().BeTrue($"unpost вернул {(int)unpost.StatusCode}: {await unpost.Content.ReadAsStringAsync()}"); (await api.StockOfAsync(refs.StoreId, productId)).Should().Be(0m); } [Fact] public async Task Double_post_is_rejected() { var api = new ApiActor(_factory.CreateClient()); await api.SignupAndLoginAsync($"dblpost-{Guid.NewGuid():N}"); var refs = await api.LoadRefsAsync(); var supplierId = await api.CreateCounterpartyAsync($"Supplier-{Guid.NewGuid():N}"); var productId = await api.CreateProductAsync(refs, $"Prod-{Guid.NewGuid():N}", 150m, RandomBarcode()); var draftResp = await api.Http.PostAsJsonAsync("/api/purchases/supplies", new { date = DateTime.UtcNow, supplierId, storeId = refs.StoreId, currencyId = refs.CurrencyId, notes = "dbl", lines = new[] { new { productId, quantity = 3m, unitPrice = 50m } }, }); draftResp.EnsureSuccessStatusCode(); var supplyId = (await draftResp.Content.ReadFromJsonAsync()).GetProperty("id").GetString(); (await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supplyId}/post", new { })) .IsSuccessStatusCode.Should().BeTrue(); using var second = await api.Http.PostAsJsonAsync($"/api/purchases/supplies/{supplyId}/post", new { }); ((int)second.StatusCode).Should().Be(409); } }