using FluentAssertions; using foodmarket.Application.Purchases.Commands; using foodmarket.Application.Sales.Commands; using foodmarket.Application.Sales.Queries; using Xunit; namespace foodmarket.UnitTests; public class GetSalesReportHandlerTests { private readonly GetSalesReportHandler _h = new(); [Fact] public async Task Groups_by_day_sums_revenue() { var d1 = new DateTime(2026, 5, 28, 10, 0, 0, DateTimeKind.Utc); var d2 = new DateTime(2026, 5, 29, 11, 0, 0, DateTimeKind.Utc); var lines = new[] { new FlatSale(Guid.NewGuid(), d1, Guid.NewGuid(), "A", 100m, 1), new FlatSale(Guid.NewGuid(), d1, Guid.NewGuid(), "B", 200m, 1), new FlatSale(Guid.NewGuid(), d2, Guid.NewGuid(), "A", 50m, 1), }; var rows = await _h.Handle(new GetSalesReportQuery(lines, "period:day"), CancellationToken.None); rows.Should().HaveCount(2); rows.First(r => r.Key == "2026-05-28").Revenue.Should().Be(300m); rows.First(r => r.Key == "2026-05-29").Revenue.Should().Be(50m); } [Fact] public async Task Groups_by_product() { var pa = Guid.NewGuid(); var pb = Guid.NewGuid(); var lines = new[] { new FlatSale(Guid.NewGuid(), DateTime.UtcNow, pa, "A", 100m, 1), new FlatSale(Guid.NewGuid(), DateTime.UtcNow, pa, "A", 50m, 1), new FlatSale(Guid.NewGuid(), DateTime.UtcNow, pb, "B", 300m, 2), }; var rows = await _h.Handle(new GetSalesReportQuery(lines, "product"), CancellationToken.None); rows.Should().HaveCount(2); rows.First(r => r.Key == pb.ToString()).Revenue.Should().Be(300m); rows.First(r => r.Key == pa.ToString()).Revenue.Should().Be(150m); } } public class CreateSupplyHandlerTests { [Fact] public async Task Computes_total_calls_writer() { var writer = new FakeWriter(); var h = new CreateSupplyHandler(writer); var cmd = new CreateSupplyCommand( Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), DateTime.UtcNow, "test", new[] { new CreateSupplyLine(Guid.NewGuid(), 2m, 100m), new CreateSupplyLine(Guid.NewGuid(), 3m, 50m), }); var result = await h.Handle(cmd, CancellationToken.None); result.Total.Should().Be(350m); // 2*100 + 3*50 result.Number.Should().Be("П-2026-000001"); writer.LastCreated.Should().NotBeNull(); writer.LastCreated!.Value.lines.Should().HaveCount(2); } private sealed class FakeWriter : ISupplyWriter { public (Guid id, decimal total, IReadOnlyList lines)? LastCreated; public Task NextNumberAsync(int year, CancellationToken ct) => Task.FromResult($"П-{year}-000001"); public Task CreateAsync(Guid supplierId, Guid storeId, Guid currencyId, DateTime date, string? notes, string number, IReadOnlyList lines, decimal total, CancellationToken ct) { var id = Guid.NewGuid(); LastCreated = (id, total, lines); return Task.FromResult(id); } } } public class PostRetailSaleHandlerTests { [Fact] public async Task Insufficient_payment_returns_error() { var poster = new FakePoster(); var h = new PostRetailSaleHandler(poster); var result = await h.Handle(new PostRetailSaleCommand( Guid.NewGuid(), PaidCash: 100m, PaidCard: 0m, Total: 500m, Lines: new[] { new PostSaleLine(Guid.NewGuid(), 1m, 500m) }), CancellationToken.None); result.Posted.Should().BeFalse(); result.ErrorField.Should().Be("PaidCash"); poster.Called.Should().BeFalse(); } [Fact] public async Task Empty_lines_returns_error() { var poster = new FakePoster(); var h = new PostRetailSaleHandler(poster); var result = await h.Handle(new PostRetailSaleCommand( Guid.NewGuid(), 100m, 0m, 100m, Array.Empty()), CancellationToken.None); result.Posted.Should().BeFalse(); result.ErrorField.Should().Be("lines"); } [Fact] public async Task Sufficient_payment_calls_poster() { var poster = new FakePoster(); var h = new PostRetailSaleHandler(poster); var result = await h.Handle(new PostRetailSaleCommand( Guid.NewGuid(), 500m, 0m, 500m, new[] { new PostSaleLine(Guid.NewGuid(), 1m, 500m) }), CancellationToken.None); result.Posted.Should().BeTrue(); poster.Called.Should().BeTrue(); } private sealed class FakePoster : IRetailSalePoster { public bool Called { get; private set; } public Task PostAsync(Guid saleId, IReadOnlyList lines, CancellationToken ct) { Called = true; return Task.CompletedTask; } } }