using FluentAssertions; using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Common; using SIGCM2.Application.Pricing.ChargeableChars; using SIGCM2.Application.Pricing.ChargeableChars.List; using SIGCM2.Domain.Pricing.ChargeableChars; namespace SIGCM2.Application.Tests.Pricing.ChargeableChars; /// /// PRC-001 — ListChargeableCharConfigQueryHandler tests. /// Covers: happy path with items, empty page, projection to DTO, pagination metadata. /// public class ListChargeableCharConfigHandlerTests { private readonly IChargeableCharConfigRepository _repo = Substitute.For(); private readonly ListChargeableCharConfigQueryHandler _handler; private static readonly DateOnly Today = new(2026, 4, 20); private static ChargeableCharConfig MakeConfig(long id, string symbol, decimal price) => ChargeableCharConfig.Rehydrate(id, null, symbol, ChargeableCharCategories.Currency, price, Today, null, true); public ListChargeableCharConfigHandlerTests() { _handler = new ListChargeableCharConfigQueryHandler(_repo); } // ── Happy path ────────────────────────────────────────────────────────────── [Fact] public async Task Handle_WithItems_ReturnsPagedDtos() { var items = new List { MakeConfig(1, "$", 1.0m), MakeConfig(2, "%", 0.5m), }; _repo.ListAsync(null, true, 0, 20, Arg.Any()) .Returns(items); _repo.CountAsync(null, true, Arg.Any()) .Returns(2); var query = new ListChargeableCharConfigQuery(MedioId: null, ActiveOnly: true, Page: 1, PageSize: 20); var result = await _handler.Handle(query); result.Items.Should().HaveCount(2); result.Total.Should().Be(2); result.Page.Should().Be(1); result.PageSize.Should().Be(20); } [Fact] public async Task Handle_WithItems_ProjectsToDto() { var items = new List { MakeConfig(5, "$", 1.5m) }; _repo.ListAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(items); _repo.CountAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(1); var query = new ListChargeableCharConfigQuery(null, true, 1, 20); var result = await _handler.Handle(query); var dto = result.Items[0]; dto.Id.Should().Be(5); dto.Symbol.Should().Be("$"); dto.PricePerUnit.Should().Be(1.5m); dto.ValidFrom.Should().Be(Today); dto.IsActive.Should().BeTrue(); } [Fact] public async Task Handle_EmptyPage_ReturnsEmptyList() { _repo.ListAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new List()); _repo.CountAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(0); var query = new ListChargeableCharConfigQuery(null, true, 1, 20); var result = await _handler.Handle(query); result.Items.Should().BeEmpty(); result.Total.Should().Be(0); } [Fact] public async Task Handle_SkipIsComputed_FromPageAndPageSize() { // Page 3, PageSize 10 → skip = 20 _repo.ListAsync(null, false, 20, 10, Arg.Any()) .Returns(new List()); _repo.CountAsync(null, false, Arg.Any()) .Returns(0); var query = new ListChargeableCharConfigQuery(null, false, 3, 10); await _handler.Handle(query); await _repo.Received(1).ListAsync(null, false, 20, 10, Arg.Any()); } [Fact] public async Task Handle_FiltersByMedioId_WhenProvided() { _repo.ListAsync(7L, true, 0, 20, Arg.Any()) .Returns(new List()); _repo.CountAsync(7L, true, Arg.Any()) .Returns(0); var query = new ListChargeableCharConfigQuery(7L, true, 1, 20); await _handler.Handle(query); await _repo.Received(1).ListAsync(7L, true, 0, 20, Arg.Any()); } }