feat(application): commands/queries + IChargeableCharConfigService (PRC-001)
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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);
|
||||
|
||||
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<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]
|
||||
public async Task Handle_FiltersByMedioId_WhenProvided()
|
||||
{
|
||||
_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>());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user