Files
SIG-CM2.0/tests/SIGCM2.Application.Tests/Pricing/ChargeableChars/ListChargeableCharConfigHandlerTests.cs
dmolinari f7fb76219a refactor+feat(backend): ChargeableCharConfig por ProductType + Reactivate + Delete endpoints (PRC-001)
Part A — MedioId → ProductTypeId rename across all C# layers:
  Domain, Application, Infrastructure, API, all test projects.
  Solution was non-compilable after BD refactor (5c1675e); now compiles clean (0 errors).

Part B — PATCH /api/v1/admin/chargeable-chars/{id}/reactivate:
  ReactivateChargeableCharConfigCommand/Handler, SP guard maps 50410/50411/50412
  → ChargeableCharConfigReactivationNotAllowedException(Reason) → HTTP 409.

Part C — DELETE /api/v1/admin/chargeable-chars/{id}:
  DeleteChargeableCharConfigCommand/Handler, physical DELETE on SYSTEM_VERSIONED table.
  KeyNotFoundException → 404 via ExceptionFilter.

Tests: +30 unit tests (TDD RED→GREEN). All 1266 unit tests pass.
2026-04-21 10:54:47 -03:00

120 lines
4.5 KiB
C#

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(ProductTypeId: 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_FiltersByProductTypeId_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>());
}
}