using FluentAssertions; using FluentValidation.TestHelper; using foodmarket.Api.Controllers.Organizations; using foodmarket.Api.Controllers.Purchases; using foodmarket.Api.Controllers.Sales; using foodmarket.Api.Infrastructure.Validation; using foodmarket.Application.Catalog; using Xunit; namespace foodmarket.UnitTests; public class SupplyInputValidatorTests { private readonly SupplyInputValidator _v = new(); [Fact] public void Rejects_empty_supplier() { var input = new SuppliesController.SupplyInput( DateTime.UtcNow, Guid.Empty, Guid.NewGuid(), Guid.NewGuid(), "", new[] { new SuppliesController.SupplyLineInput(Guid.NewGuid(), 1m, 100m) }); _v.TestValidate(input).ShouldHaveValidationErrorFor(x => x.SupplierId); } [Fact] public void Rejects_empty_lines() { var input = new SuppliesController.SupplyInput( DateTime.UtcNow, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "", Array.Empty()); _v.TestValidate(input).ShouldHaveValidationErrorFor(x => x.Lines); } [Fact] public void Rejects_zero_quantity_in_line() { var input = new SuppliesController.SupplyInput( DateTime.UtcNow, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "", new[] { new SuppliesController.SupplyLineInput(Guid.NewGuid(), 0m, 100m) }); _v.TestValidate(input).ShouldHaveValidationErrorFor("Lines[0].Quantity"); } [Fact] public void Rejects_future_date() { var input = new SuppliesController.SupplyInput( DateTime.UtcNow.AddDays(5), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "", new[] { new SuppliesController.SupplyLineInput(Guid.NewGuid(), 1m, 100m) }); _v.TestValidate(input).ShouldHaveValidationErrorFor(x => x.Date); } [Fact] public void Accepts_valid() { var input = new SuppliesController.SupplyInput( DateTime.UtcNow.AddHours(-1), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "Notes", new[] { new SuppliesController.SupplyLineInput(Guid.NewGuid(), 5m, 100m) }); _v.TestValidate(input).ShouldNotHaveAnyValidationErrors(); } } public class RetailSaleInputValidatorTests { private readonly RetailSaleInputValidator _v = new(); [Fact] public void Rejects_negative_paid() { var input = new RetailSalesController.RetailSaleInput( DateTime.UtcNow, Guid.NewGuid(), null, null, Guid.NewGuid(), foodmarket.Domain.Sales.PaymentMethod.Cash, PaidCash: -1m, PaidCard: 0m, Notes: "", Lines: new[] { new RetailSalesController.RetailSaleLineInput(Guid.NewGuid(), 1m, 100m, 0m, 12m) }); _v.TestValidate(input).ShouldHaveValidationErrorFor(x => x.PaidCash); } [Fact] public void Rejects_empty_lines() { var input = new RetailSalesController.RetailSaleInput( DateTime.UtcNow, Guid.NewGuid(), null, null, Guid.NewGuid(), foodmarket.Domain.Sales.PaymentMethod.Cash, 0m, 0m, "", Array.Empty()); _v.TestValidate(input).ShouldHaveValidationErrorFor(x => x.Lines); } } public class ProductInputValidatorTests { private readonly ProductInputValidator _v = new(); private static ProductInput Make(string name = "Молоко", decimal vat = 12, Guid? unitId = null, decimal? minStock = null, decimal? maxStock = null) => new( Name: name, Article: null, Description: null, UnitOfMeasureId: unitId ?? Guid.NewGuid(), Vat: vat, VatEnabled: true, ProductGroupId: Guid.NewGuid(), DefaultSupplierId: null, CountryOfOriginId: null, IsService: false, Packaging: foodmarket.Domain.Catalog.Packaging.Piece, IsMarked: false, MinStock: minStock, MaxStock: maxStock, ReferencePrice: null, PurchaseCurrencyId: null, ImageUrl: null, Prices: Array.Empty(), Barcodes: Array.Empty()); [Fact] public void Empty_name_rejected() => _v.TestValidate(Make(name: "")).ShouldHaveValidationErrorFor(x => x.Name); [Fact] public void Vat_out_of_range_rejected() => _v.TestValidate(Make(vat: 150m)).ShouldHaveValidationErrorFor(x => x.Vat); [Fact] public void Min_greater_than_max_rejected() { var r = _v.TestValidate(Make(minStock: 10, maxStock: 5)); r.Errors.Should().Contain(e => e.ErrorMessage.Contains("MinStock")); } [Fact] public void Valid_passes() => _v.TestValidate(Make()).ShouldNotHaveAnyValidationErrors(); } public class CounterpartyInputValidatorTests { private readonly CounterpartyInputValidator _v = new(); [Fact] public void Bin_must_be_12_digits() { var bad = new CounterpartyInput("ТОО", null, foodmarket.Domain.Catalog.CounterpartyType.LegalEntity, Bin: "123", Iin: null, TaxNumber: null, CountryId: null, Address: null, Phone: null, Email: null, BankName: null, BankAccount: null, Bik: null, ContactPerson: null, Notes: null); _v.TestValidate(bad).ShouldHaveValidationErrorFor(x => x.Bin); } [Fact] public void Invalid_email_rejected() { var bad = new CounterpartyInput("ТОО", null, foodmarket.Domain.Catalog.CounterpartyType.LegalEntity, Bin: "123456789012", Iin: null, TaxNumber: null, CountryId: null, Address: null, Phone: null, Email: "not-an-email", BankName: null, BankAccount: null, Bik: null, ContactPerson: null, Notes: null); _v.TestValidate(bad).ShouldHaveValidationErrorFor(x => x.Email); } } public class EmployeeInputValidatorTests { private readonly EmployeeInputValidator _v = new(); [Fact] public void SendInvite_without_email_rejected() { var bad = new EmployeesController.EmployeeInput( "Иванов", "Иван", null, null, Email: null, null, null, null, null, null, Guid.NewGuid(), true, null, CreateAccount: true, SendInvite: true); _v.TestValidate(bad).ShouldHaveValidationErrorFor(x => x.Email); } [Fact] public void CreateAccount_without_email_rejected() { var bad = new EmployeesController.EmployeeInput( "Иванов", "Иван", null, null, Email: null, null, null, null, null, null, Guid.NewGuid(), true, null, CreateAccount: true); _v.TestValidate(bad).ShouldHaveValidationErrorFor(x => x.Email); } [Fact] public void Valid_passes() { var ok = new EmployeesController.EmployeeInput( "Иванов", "Иван", null, null, "iv@x.kz", null, null, null, null, null, Guid.NewGuid(), true, null, CreateAccount: true, SendInvite: true); _v.TestValidate(ok).ShouldNotHaveAnyValidationErrors(); } }