using FluentAssertions; using foodmarket.Application.Common.Fiscal; using foodmarket.Domain.Sales; using foodmarket.Infrastructure.Fiscal; using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace foodmarket.UnitTests; /// Контракт MockFiscalProvider'а: возвращает префикс MOCK-, тот же /// чек двумя вызовами даёт тот же FiscalNumber (идемпотентность), QR содержит /// FiscalNumber, ProviderTxId не пуст. SimulatedLatency обнулена — тест /// должен быть мгновенным. public class FiscalMockProviderTests { private static MockFiscalProvider New() => new(NullLogger.Instance) { SimulatedLatency = TimeSpan.Zero, }; private static RetailSale SaleStub(Guid? id = null) => new() { Id = id ?? Guid.NewGuid(), Number = "TST-000001", OrganizationId = Guid.NewGuid(), StoreId = Guid.NewGuid(), CurrencyId = Guid.NewGuid(), Total = 1000m, }; [Fact] public void Kind_is_Mock() => New().Kind.Should().Be(FiscalProviderKind.Mock); [Fact] public async Task Register_returns_mock_prefixed_fiscal_number() { var sale = SaleStub(); var r = await New().RegisterAsync(sale, CancellationToken.None); r.FiscalNumber.Should().StartWith("MOCK-").And.HaveLength("MOCK-".Length + 8); r.FiscalQrCode.Should().Contain(r.FiscalNumber); r.FiscalUrl.Should().StartWith("https://mock.ofd.local/check/"); r.ProviderTxId.Should().NotBeNullOrEmpty().And.StartWith("mock-tx-"); } [Fact] public async Task Register_is_idempotent_per_sale_id() { var id = Guid.NewGuid(); var r1 = await New().RegisterAsync(SaleStub(id), CancellationToken.None); var r2 = await New().RegisterAsync(SaleStub(id), CancellationToken.None); r1.FiscalNumber.Should().Be(r2.FiscalNumber); r1.ProviderTxId.Should().Be(r2.ProviderTxId); } [Fact] public async Task Different_sales_get_different_fiscal_numbers() { var a = await New().RegisterAsync(SaleStub(), CancellationToken.None); var b = await New().RegisterAsync(SaleStub(), CancellationToken.None); a.FiscalNumber.Should().NotBe(b.FiscalNumber); } [Fact] public async Task Simulated_latency_actually_delays() { var prov = new MockFiscalProvider(NullLogger.Instance) { SimulatedLatency = TimeSpan.FromMilliseconds(150), }; var sw = System.Diagnostics.Stopwatch.StartNew(); await prov.RegisterAsync(SaleStub(), CancellationToken.None); sw.Stop(); sw.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(140); } }