using FluentAssertions; using Microsoft.Extensions.Time.Testing; using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Audit; using SIGCM2.Application.Rubros.Update; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Rubros.Update; public class UpdateRubroCommandHandlerTests { private readonly IRubroRepository _repo = Substitute.For(); private readonly IAuditLogger _audit = Substitute.For(); private readonly FakeTimeProvider _timeProvider = new(new DateTimeOffset(2026, 4, 18, 12, 0, 0, TimeSpan.Zero)); private readonly UpdateRubroCommandHandler _handler; private static Rubro ExistingRubro(int id = 3) => new(id, null, "Autos", 0, activo: true, tarifarioBaseId: null, fechaCreacion: DateTime.UtcNow, fechaModificacion: null); public UpdateRubroCommandHandlerTests() { _repo.ExistsByNombreUnderParentAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(false); _handler = new UpdateRubroCommandHandler(_repo, _audit, _timeProvider); } // ── Happy path: rename ─────────────────────────────────────────────────── [Fact] public async Task Handle_HappyPath_Rename_ReturnsUpdatedDto() { _repo.GetByIdAsync(3, Arg.Any()).Returns(ExistingRubro()); var result = await _handler.Handle(new UpdateRubroCommand(Id: 3, Nombre: "Vehiculos")); result.Nombre.Should().Be("Vehiculos"); result.Id.Should().Be(3); } [Fact] public async Task Handle_HappyPath_Rename_CallsUpdateAsync() { _repo.GetByIdAsync(3, Arg.Any()).Returns(ExistingRubro()); await _handler.Handle(new UpdateRubroCommand(Id: 3, Nombre: "Vehiculos")); await _repo.Received(1).UpdateAsync( Arg.Is(r => r.Nombre == "Vehiculos"), Arg.Any()); } [Fact] public async Task Handle_HappyPath_Rename_CallsAuditLog() { _repo.GetByIdAsync(3, Arg.Any()).Returns(ExistingRubro()); await _handler.Handle(new UpdateRubroCommand(Id: 3, Nombre: "Vehiculos")); await _audit.Received(1).LogAsync( action: "rubro.updated", targetType: "Rubro", targetId: "3", metadata: Arg.Any(), ct: Arg.Any()); } // ── Not found → RubroNotFoundException ────────────────────────────────── [Fact] public async Task Handle_NotFound_ThrowsRubroNotFoundException() { _repo.GetByIdAsync(99, Arg.Any()).Returns((Rubro?)null); var act = () => _handler.Handle(new UpdateRubroCommand(Id: 99, Nombre: "Cualquiera")); await act.Should().ThrowAsync() .Where(ex => ex.Id == 99); } // ── Duplicate name CI under same parent → RubroNombreDuplicadoEnPadreException [Fact] public async Task Handle_DuplicateNameUnderParent_ThrowsRubroNombreDuplicadoEnPadreException() { _repo.GetByIdAsync(3, Arg.Any()).Returns(ExistingRubro()); _repo.ExistsByNombreUnderParentAsync(null, "Motos", 3, Arg.Any()) .Returns(true); var act = () => _handler.Handle(new UpdateRubroCommand(Id: 3, Nombre: "Motos")); await act.Should().ThrowAsync(); } [Fact] public async Task Handle_DuplicateName_DoesNotCallAuditLog() { _repo.GetByIdAsync(3, Arg.Any()).Returns(ExistingRubro()); _repo.ExistsByNombreUnderParentAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(true); try { await _handler.Handle(new UpdateRubroCommand(Id: 3, Nombre: "Motos")); } catch { } await _audit.DidNotReceive().LogAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); } }