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, TimeProvider.System); _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())); } }