From 2cd25e10365e657b876ab0b7ed7935e1a46e16f6 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Fri, 17 Apr 2026 18:09:44 -0300 Subject: [PATCH] test(adm-009): IngresosBrutos handler tests mirror (Red) --- ...CreateIngresosBrutosCommandHandlerTests.cs | 86 +++++++++++++ ...tivateIngresosBrutosCommandHandlerTests.cs | 73 +++++++++++ .../GetIngresosBrutosByIdQueryHandlerTests.cs | 54 ++++++++ ...istorialIngresosBrutosQueryHandlerTests.cs | 57 +++++++++ .../ListIngresosBrutosQueryHandlerTests.cs | 57 +++++++++ ...ersionIngresosBrutosCommandHandlerTests.cs | 121 ++++++++++++++++++ ...tivateIngresosBrutosCommandHandlerTests.cs | 73 +++++++++++ ...UpdateIngresosBrutosCommandHandlerTests.cs | 85 ++++++++++++ 8 files changed, 606 insertions(+) create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/Create/CreateIngresosBrutosCommandHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/Deactivate/DeactivateIngresosBrutosCommandHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/GetById/GetIngresosBrutosByIdQueryHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/GetHistorial/GetHistorialIngresosBrutosQueryHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/List/ListIngresosBrutosQueryHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/NuevaVersion/NuevaVersionIngresosBrutosCommandHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/Reactivate/ReactivateIngresosBrutosCommandHandlerTests.cs create mode 100644 tests/SIGCM2.Application.Tests/IngresosBrutos/Update/UpdateIngresosBrutosCommandHandlerTests.cs diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/Create/CreateIngresosBrutosCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/Create/CreateIngresosBrutosCommandHandlerTests.cs new file mode 100644 index 0000000..437f152 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/Create/CreateIngresosBrutosCommandHandlerTests.cs @@ -0,0 +1,86 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; +using SIGCM2.Application.IngresosBrutos.Create; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.Create; + +public class CreateIngresosBrutosCommandHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); + private readonly CreateIngresosBrutosCommandHandler _handler; + + private static CreateIngresosBrutosCommand ValidCommand() => new( + Provincia: ProvinciaArgentina.BuenosAires, + Descripcion: "IIBB Buenos Aires", + Alicuota: 3.5m, + VigenciaDesde: new DateOnly(2024, 1, 1)); + + public CreateIngresosBrutosCommandHandlerTests() + { + _handler = new CreateIngresosBrutosCommandHandler(_repo, _audit); + _repo.InsertAsync(Arg.Any(), Arg.Any()).Returns(55); + } + + [Fact] + public async Task Handle_HappyPath_ReturnsDtoWithIdFromRepository() + { + var result = await _handler.Handle(ValidCommand()); + + Assert.Equal(55, result.Id); + } + + [Fact] + public async Task Handle_HappyPath_DtoContainsCorrectFields() + { + var result = await _handler.Handle(ValidCommand()); + + Assert.Equal(ProvinciaArgentina.BuenosAires, result.Provincia); + Assert.Equal(3.5m, result.Alicuota); + Assert.True(result.Activo); + } + + [Fact] + public async Task Handle_HappyPath_CallsAuditWithCreateAction() + { + await _handler.Handle(ValidCommand()); + + await _audit.Received(1).LogAsync( + action: "ingresos_brutos.create", + targetType: "IngresosBrutos", + targetId: "55", + metadata: Arg.Any(), + ct: Arg.Any()); + } + + [Fact] + public async Task Handle_AuditLoggerThrows_ExceptionBubblesUp() + { + _audit.LogAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(Task.FromException(new InvalidOperationException("audit fail"))); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand())); + } + + // ── triangulation: zero alicuota ───────────────────────────────────────── + + [Fact] + public async Task Handle_WithZeroAlicuota_ReturnsDtoWithCorrectAlicuota() + { + var cmd = new CreateIngresosBrutosCommand( + Provincia: ProvinciaArgentina.CiudadAutonomaDeBuenosAires, + Descripcion: "IIBB CABA", + Alicuota: 0m, + VigenciaDesde: new DateOnly(2024, 1, 1)); + + var result = await _handler.Handle(cmd); + + Assert.Equal(0m, result.Alicuota); + Assert.Equal(ProvinciaArgentina.CiudadAutonomaDeBuenosAires, result.Provincia); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/Deactivate/DeactivateIngresosBrutosCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/Deactivate/DeactivateIngresosBrutosCommandHandlerTests.cs new file mode 100644 index 0000000..7e70669 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/Deactivate/DeactivateIngresosBrutosCommandHandlerTests.cs @@ -0,0 +1,73 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; +using SIGCM2.Application.IngresosBrutos.Deactivate; +using SIGCM2.Domain.Exceptions; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.Deactivate; + +public class DeactivateIngresosBrutosCommandHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); + private readonly DeactivateIngresosBrutosCommandHandler _handler; + + private static IibbEntity MakeEntity(bool activo = true) => + IibbEntity.FromDb( + id: 1, provincia: ProvinciaArgentina.BuenosAires, descripcion: "IIBB BA", + alicuota: 3m, activo: activo, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: null, predecesorId: null, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + public DeactivateIngresosBrutosCommandHandlerTests() + { + _handler = new DeactivateIngresosBrutosCommandHandler(_repo, _audit); + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity()); + _repo.SetActivoAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + } + + [Fact] + public async Task Handle_NotFound_ThrowsIngresosBrutosNotFoundException() + { + _repo.GetByIdAsync(99, Arg.Any()).Returns((IibbEntity?)null); + + await Assert.ThrowsAsync( + () => _handler.Handle(new DeactivateIngresosBrutosCommand(99))); + } + + [Fact] + public async Task Handle_HappyPath_CallsSetActivoFalse() + { + await _handler.Handle(new DeactivateIngresosBrutosCommand(1)); + + await _repo.Received(1).SetActivoAsync(1, false, Arg.Any()); + } + + [Fact] + public async Task Handle_HappyPath_CallsAuditDeactivate() + { + await _handler.Handle(new DeactivateIngresosBrutosCommand(1)); + + await _audit.Received(1).LogAsync( + action: "ingresos_brutos.deactivate", + targetType: "IngresosBrutos", + targetId: "1", + metadata: Arg.Any(), + ct: Arg.Any()); + } + + [Fact] + public async Task Handle_AlreadyInactive_IsIdempotent_NoAudit() + { + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity(activo: false)); + + await _handler.Handle(new DeactivateIngresosBrutosCommand(1)); + + await _audit.DidNotReceive().LogAsync( + Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/GetById/GetIngresosBrutosByIdQueryHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/GetById/GetIngresosBrutosByIdQueryHandlerTests.cs new file mode 100644 index 0000000..7cd0247 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/GetById/GetIngresosBrutosByIdQueryHandlerTests.cs @@ -0,0 +1,54 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.IngresosBrutos.GetById; +using SIGCM2.Domain.Exceptions; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.GetById; + +public class GetIngresosBrutosByIdQueryHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly GetIngresosBrutosByIdQueryHandler _handler; + + private static IibbEntity MakeEntity(int id = 1) => + IibbEntity.FromDb( + id: id, provincia: ProvinciaArgentina.Cordoba, descripcion: "IIBB Córdoba", + alicuota: 4m, activo: true, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: null, predecesorId: null, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + public GetIngresosBrutosByIdQueryHandlerTests() + { + _handler = new GetIngresosBrutosByIdQueryHandler(_repo); + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity()); + } + + [Fact] + public async Task Handle_Found_ReturnsDtoWithCorrectId() + { + var result = await _handler.Handle(new GetIngresosBrutosByIdQuery(1)); + + Assert.Equal(1, result.Id); + } + + [Fact] + public async Task Handle_Found_ReturnsDtoWithCorrectProvincia() + { + var result = await _handler.Handle(new GetIngresosBrutosByIdQuery(1)); + + Assert.Equal(ProvinciaArgentina.Cordoba, result.Provincia); + Assert.Equal(4m, result.Alicuota); + } + + [Fact] + public async Task Handle_NotFound_ThrowsIngresosBrutosNotFoundException() + { + _repo.GetByIdAsync(99, Arg.Any()).Returns((IibbEntity?)null); + + await Assert.ThrowsAsync( + () => _handler.Handle(new GetIngresosBrutosByIdQuery(99))); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/GetHistorial/GetHistorialIngresosBrutosQueryHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/GetHistorial/GetHistorialIngresosBrutosQueryHandlerTests.cs new file mode 100644 index 0000000..30e15b5 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/GetHistorial/GetHistorialIngresosBrutosQueryHandlerTests.cs @@ -0,0 +1,57 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.IngresosBrutos.GetHistorial; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.GetHistorial; + +public class GetHistorialIngresosBrutosQueryHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly GetHistorialIngresosBrutosQueryHandler _handler; + + public GetHistorialIngresosBrutosQueryHandlerTests() + { + _handler = new GetHistorialIngresosBrutosQueryHandler(_repo); + } + + private static IibbEntity MakeEntity(int id, int? predecesorId, DateOnly desde) => + IibbEntity.FromDb( + id: id, provincia: ProvinciaArgentina.BuenosAires, + descripcion: "IIBB BA", alicuota: 3m, activo: true, + vigenciaDesde: desde, + vigenciaHasta: predecesorId.HasValue ? desde.AddYears(1).AddDays(-1) : null, + predecesorId: predecesorId, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + [Fact] + public async Task Handle_ChainOf2_ReturnsListWith2ItemsInOrder() + { + var chain = new List + { + MakeEntity(1, null, new DateOnly(2023, 1, 1)), + MakeEntity(2, 1, new DateOnly(2024, 1, 1)), + }; + _repo.GetHistorialAsync(2, Arg.Any()).Returns(chain); + + var result = await _handler.Handle(new GetHistorialIngresosBrutosQuery(2)); + + Assert.Equal(2, result.Count); + Assert.Equal(1, result[0].Version); + Assert.Equal(2, result[1].Version); + } + + [Fact] + public async Task Handle_SingleVersion_Returns1Item() + { + var chain = new List + { MakeEntity(1, null, new DateOnly(2024, 1, 1)) }; + _repo.GetHistorialAsync(1, Arg.Any()).Returns(chain); + + var result = await _handler.Handle(new GetHistorialIngresosBrutosQuery(1)); + + Assert.Single(result); + Assert.Equal(ProvinciaArgentina.BuenosAires, result[0].Provincia); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/List/ListIngresosBrutosQueryHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/List/ListIngresosBrutosQueryHandlerTests.cs new file mode 100644 index 0000000..bf54595 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/List/ListIngresosBrutosQueryHandlerTests.cs @@ -0,0 +1,57 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Common; +using SIGCM2.Application.IngresosBrutos.List; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.List; + +public class ListIngresosBrutosQueryHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly ListIngresosBrutosQueryHandler _handler; + + private static IibbEntity MakeEntity(int id, ProvinciaArgentina prov) => + IibbEntity.FromDb( + id: id, provincia: prov, descripcion: "IIBB test", + alicuota: 3m, activo: true, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: null, predecesorId: null, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + public ListIngresosBrutosQueryHandlerTests() + { + _handler = new ListIngresosBrutosQueryHandler(_repo); + } + + [Fact] + public async Task Handle_WithItems_ReturnsPagedResultWithMappedDtos() + { + var items = new List + { + MakeEntity(1, ProvinciaArgentina.BuenosAires), + MakeEntity(2, ProvinciaArgentina.Cordoba), + }; + _repo.ListAsync(Arg.Any(), Arg.Any()) + .Returns(new PagedResult(items, 1, 10, 2)); + + var result = await _handler.Handle(new ListIngresosBrutosQuery(1, 10, null, null)); + + Assert.Equal(2, result.Items.Count); + Assert.Equal(2, result.Total); + } + + [Fact] + public async Task Handle_PassesProvinciaFilterToRepository() + { + _repo.ListAsync(Arg.Any(), Arg.Any()) + .Returns(new PagedResult(new List(), 1, 10, 0)); + + await _handler.Handle(new ListIngresosBrutosQuery(1, 10, true, ProvinciaArgentina.Cordoba)); + + await _repo.Received(1).ListAsync( + Arg.Is(q => q.Provincia == ProvinciaArgentina.Cordoba && q.Activo == true), + Arg.Any()); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/NuevaVersion/NuevaVersionIngresosBrutosCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/NuevaVersion/NuevaVersionIngresosBrutosCommandHandlerTests.cs new file mode 100644 index 0000000..15570a5 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/NuevaVersion/NuevaVersionIngresosBrutosCommandHandlerTests.cs @@ -0,0 +1,121 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; +using SIGCM2.Application.IngresosBrutos.NuevaVersion; +using SIGCM2.Domain.Exceptions; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.NuevaVersion; + +public class NuevaVersionIngresosBrutosCommandHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); + private readonly NuevaVersionIngresosBrutosCommandHandler _handler; + + private static IibbEntity MakePredecesora(int id = 1, DateOnly? vigenciaHasta = null) => + IibbEntity.FromDb( + id: id, provincia: ProvinciaArgentina.BuenosAires, descripcion: "IIBB BA", + alicuota: 3m, activo: true, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: vigenciaHasta, + predecesorId: null, + fechaCreacion: DateTime.UtcNow, + fechaModificacion: null); + + private static NuevaVersionIngresosBrutosCommand ValidCommand() => new( + PredecesoraId: 1, + NuevaAlicuota: 5m, + VigenciaDesde: new DateOnly(2025, 1, 1)); + + public NuevaVersionIngresosBrutosCommandHandlerTests() + { + _handler = new NuevaVersionIngresosBrutosCommandHandler(_repo, _audit); + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakePredecesora()); + _repo.UpdateCierreVigenciaAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + _repo.InsertAsync(Arg.Any(), Arg.Any()).Returns(88); + } + + [Fact] + public async Task Handle_HappyPath_ReturnsDtoWithCorrectIds() + { + var result = await _handler.Handle(ValidCommand()); + + Assert.Equal(1, result.PredecesoraId); + Assert.Equal(88, result.NuevaVersionId); + } + + [Fact] + public async Task Handle_HappyPath_CallsUpdateCierreVigenciaOnce() + { + await _handler.Handle(ValidCommand()); + + await _repo.Received(1).UpdateCierreVigenciaAsync( + 1, new DateOnly(2024, 12, 31), Arg.Any()); + } + + [Fact] + public async Task Handle_HappyPath_CallsInsertWithCorrectAlicuota() + { + await _handler.Handle(ValidCommand()); + + await _repo.Received(1).InsertAsync( + Arg.Is(e => e.Alicuota == 5m && e.PredecesorId == 1), + Arg.Any()); + } + + [Fact] + public async Task Handle_HappyPath_CallsAuditOnceWithCorrectAction() + { + await _handler.Handle(ValidCommand()); + + await _audit.Received(1).LogAsync( + action: "ingresos_brutos.nueva_version", + targetType: "IngresosBrutos", + targetId: Arg.Any(), + metadata: Arg.Any(), + ct: Arg.Any()); + } + + [Fact] + public async Task Handle_PredecesoraNotFound_ThrowsIngresosBrutosNotFoundException() + { + _repo.GetByIdAsync(999, Arg.Any()) + .Returns((IibbEntity?)null); + + await Assert.ThrowsAsync( + () => _handler.Handle(new NuevaVersionIngresosBrutosCommand(999, 5m, new DateOnly(2025, 1, 1)))); + } + + [Fact] + public async Task Handle_PredecesoraYaCerrada_ThrowsPredecesorYaCerradoException() + { + _repo.GetByIdAsync(1, Arg.Any()) + .Returns(MakePredecesora(vigenciaHasta: new DateOnly(2024, 12, 31))); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand())); + } + + [Fact] + public async Task Handle_UpdateCierreVigenciaReturnsFalse_ThrowsPredecesorYaCerradoException() + { + _repo.UpdateCierreVigenciaAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(false); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand())); + } + + [Fact] + public async Task Handle_AuditLoggerThrows_ExceptionBubblesUp() + { + _audit.LogAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(Task.FromException(new InvalidOperationException("audit fail"))); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand())); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/Reactivate/ReactivateIngresosBrutosCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/Reactivate/ReactivateIngresosBrutosCommandHandlerTests.cs new file mode 100644 index 0000000..f291764 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/Reactivate/ReactivateIngresosBrutosCommandHandlerTests.cs @@ -0,0 +1,73 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; +using SIGCM2.Application.IngresosBrutos.Reactivate; +using SIGCM2.Domain.Exceptions; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.Reactivate; + +public class ReactivateIngresosBrutosCommandHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); + private readonly ReactivateIngresosBrutosCommandHandler _handler; + + private static IibbEntity MakeEntity(bool activo = false) => + IibbEntity.FromDb( + id: 1, provincia: ProvinciaArgentina.BuenosAires, descripcion: "IIBB BA", + alicuota: 3m, activo: activo, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: null, predecesorId: null, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + public ReactivateIngresosBrutosCommandHandlerTests() + { + _handler = new ReactivateIngresosBrutosCommandHandler(_repo, _audit); + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity()); + _repo.SetActivoAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); + } + + [Fact] + public async Task Handle_NotFound_ThrowsIngresosBrutosNotFoundException() + { + _repo.GetByIdAsync(99, Arg.Any()).Returns((IibbEntity?)null); + + await Assert.ThrowsAsync( + () => _handler.Handle(new ReactivateIngresosBrutosCommand(99))); + } + + [Fact] + public async Task Handle_HappyPath_CallsSetActivoTrue() + { + await _handler.Handle(new ReactivateIngresosBrutosCommand(1)); + + await _repo.Received(1).SetActivoAsync(1, true, Arg.Any()); + } + + [Fact] + public async Task Handle_HappyPath_CallsAuditReactivate() + { + await _handler.Handle(new ReactivateIngresosBrutosCommand(1)); + + await _audit.Received(1).LogAsync( + action: "ingresos_brutos.reactivate", + targetType: "IngresosBrutos", + targetId: "1", + metadata: Arg.Any(), + ct: Arg.Any()); + } + + [Fact] + public async Task Handle_AlreadyActive_IsIdempotent_NoAudit() + { + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity(activo: true)); + + await _handler.Handle(new ReactivateIngresosBrutosCommand(1)); + + await _audit.DidNotReceive().LogAsync( + Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()); + } +} diff --git a/tests/SIGCM2.Application.Tests/IngresosBrutos/Update/UpdateIngresosBrutosCommandHandlerTests.cs b/tests/SIGCM2.Application.Tests/IngresosBrutos/Update/UpdateIngresosBrutosCommandHandlerTests.cs new file mode 100644 index 0000000..a072775 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/IngresosBrutos/Update/UpdateIngresosBrutosCommandHandlerTests.cs @@ -0,0 +1,85 @@ +using NSubstitute; +using SIGCM2.Application.Abstractions.Persistence; +using SIGCM2.Application.Audit; +using SIGCM2.Application.IngresosBrutos.Update; +using SIGCM2.Domain.Exceptions; +using SIGCM2.Domain.Fiscal; +using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; + +namespace SIGCM2.Application.Tests.IngresosBrutos.Update; + +public class UpdateIngresosBrutosCommandHandlerTests +{ + private readonly IIngresosBrutosRepository _repo = Substitute.For(); + private readonly IAuditLogger _audit = Substitute.For(); + private readonly UpdateIngresosBrutosCommandHandler _handler; + + private static IibbEntity MakeEntity(int id = 1) => + IibbEntity.FromDb( + id: id, provincia: ProvinciaArgentina.BuenosAires, descripcion: "IIBB BA", + alicuota: 3m, activo: true, + vigenciaDesde: new DateOnly(2024, 1, 1), + vigenciaHasta: null, predecesorId: null, + fechaCreacion: DateTime.UtcNow, fechaModificacion: null); + + private static UpdateIngresosBrutosCommand ValidCommand(int id = 1) => new( + Id: id, Descripcion: "IIBB BA actualizado", Activo: true); + + public UpdateIngresosBrutosCommandHandlerTests() + { + _handler = new UpdateIngresosBrutosCommandHandler(_repo, _audit); + _repo.GetByIdAsync(1, Arg.Any()).Returns(MakeEntity()); + _repo.UpdateCosmeticoAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()).Returns(true); + } + + [Fact] + public async Task Handle_NotFound_ThrowsIngresosBrutosNotFoundException() + { + _repo.GetByIdAsync(99, Arg.Any()).Returns((IibbEntity?)null); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand(99))); + } + + [Fact] + public async Task Handle_HappyPath_ReturnsDtoWithUpdatedDescription() + { + var result = await _handler.Handle(ValidCommand()); + + Assert.Equal("IIBB BA actualizado", result.Descripcion); + } + + [Fact] + public async Task Handle_HappyPath_CallsUpdateCosmeticoOnce() + { + await _handler.Handle(ValidCommand()); + + await _repo.Received(1).UpdateCosmeticoAsync( + 1, "IIBB BA actualizado", true, Arg.Any()); + } + + [Fact] + public async Task Handle_HappyPath_CallsAuditWithUpdateAction() + { + await _handler.Handle(ValidCommand()); + + await _audit.Received(1).LogAsync( + action: "ingresos_brutos.update", + targetType: "IngresosBrutos", + targetId: "1", + metadata: Arg.Any(), + ct: Arg.Any()); + } + + [Fact] + public async Task Handle_AuditLoggerThrows_ExceptionBubblesUp() + { + _audit.LogAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()) + .Returns(Task.FromException(new InvalidOperationException("audit fail"))); + + await Assert.ThrowsAsync( + () => _handler.Handle(ValidCommand())); + } +}