2026-04-20 12:24:06 -03:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// PRC-001 — ListChargeableCharConfigQueryHandler tests.
|
|
|
|
|
/// Covers: happy path with items, empty page, projection to DTO, pagination metadata.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class ListChargeableCharConfigHandlerTests
|
|
|
|
|
{
|
|
|
|
|
private readonly IChargeableCharConfigRepository _repo = Substitute.For<IChargeableCharConfigRepository>();
|
|
|
|
|
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<ChargeableCharConfig>
|
|
|
|
|
{
|
|
|
|
|
MakeConfig(1, "$", 1.0m),
|
|
|
|
|
MakeConfig(2, "%", 0.5m),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_repo.ListAsync(null, true, 0, 20, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(items);
|
|
|
|
|
_repo.CountAsync(null, true, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(2);
|
|
|
|
|
|
2026-04-21 10:54:47 -03:00
|
|
|
var query = new ListChargeableCharConfigQuery(ProductTypeId: null, ActiveOnly: true, Page: 1, PageSize: 20);
|
2026-04-20 12:24:06 -03:00
|
|
|
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<ChargeableCharConfig> { MakeConfig(5, "$", 1.5m) };
|
|
|
|
|
|
|
|
|
|
_repo.ListAsync(Arg.Any<long?>(), Arg.Any<bool>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(items);
|
|
|
|
|
_repo.CountAsync(Arg.Any<long?>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
|
|
|
|
|
.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<long?>(), Arg.Any<bool>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(new List<ChargeableCharConfig>());
|
|
|
|
|
_repo.CountAsync(Arg.Any<long?>(), Arg.Any<bool>(), Arg.Any<CancellationToken>())
|
|
|
|
|
.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<CancellationToken>())
|
|
|
|
|
.Returns(new List<ChargeableCharConfig>());
|
|
|
|
|
_repo.CountAsync(null, false, Arg.Any<CancellationToken>())
|
|
|
|
|
.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<CancellationToken>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-04-21 10:54:47 -03:00
|
|
|
public async Task Handle_FiltersByProductTypeId_WhenProvided()
|
2026-04-20 12:24:06 -03:00
|
|
|
{
|
|
|
|
|
_repo.ListAsync(7L, true, 0, 20, Arg.Any<CancellationToken>())
|
|
|
|
|
.Returns(new List<ChargeableCharConfig>());
|
|
|
|
|
_repo.CountAsync(7L, true, Arg.Any<CancellationToken>())
|
|
|
|
|
.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<CancellationToken>());
|
|
|
|
|
}
|
|
|
|
|
}
|