using FluentAssertions; using Microsoft.Extensions.Time.Testing; using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Audit; using SIGCM2.Application.Rubros.Deactivate; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Rubros.Deactivate; public class DeactivateRubroCommandHandlerTests { 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 DeactivateRubroCommandHandler _handler; private static Rubro LeafRubro(int id = 10) => new(id, null, "Autos", 0, activo: true, tarifarioBaseId: null, fechaCreacion: DateTime.UtcNow, fechaModificacion: null); public DeactivateRubroCommandHandlerTests() { _repo.CountActiveChildrenAsync(Arg.Any(), Arg.Any()).Returns(0); _handler = new DeactivateRubroCommandHandler(_repo, _audit, _timeProvider); } // ── Happy path: leaf soft-delete ───────────────────────────────────────── [Fact] public async Task Handle_LeafRubro_SoftDeletes() { _repo.GetByIdAsync(10, Arg.Any()).Returns(LeafRubro()); var result = await _handler.Handle(new DeactivateRubroCommand(Id: 10)); result.Id.Should().Be(10); result.Activo.Should().BeFalse(); } [Fact] public async Task Handle_LeafRubro_CallsUpdateAsync() { _repo.GetByIdAsync(10, Arg.Any()).Returns(LeafRubro()); await _handler.Handle(new DeactivateRubroCommand(Id: 10)); await _repo.Received(1).UpdateAsync( Arg.Is(r => r.Id == 10 && !r.Activo), Arg.Any()); } [Fact] public async Task Handle_LeafRubro_CallsAuditLog() { _repo.GetByIdAsync(10, Arg.Any()).Returns(LeafRubro()); await _handler.Handle(new DeactivateRubroCommand(Id: 10)); await _audit.Received(1).LogAsync( action: "rubro.deleted", targetType: "Rubro", targetId: "10", metadata: Arg.Any(), ct: Arg.Any()); } // ── Has active children → RubroTieneHijosActivosException ─────────────── [Fact] public async Task Handle_HasActiveChildren_ThrowsRubroTieneHijosActivosException() { _repo.GetByIdAsync(5, Arg.Any()).Returns(LeafRubro(5)); _repo.CountActiveChildrenAsync(5, Arg.Any()).Returns(3); var act = () => _handler.Handle(new DeactivateRubroCommand(Id: 5)); await act.Should().ThrowAsync() .Where(ex => ex.Id == 5 && ex.Count == 3); } [Fact] public async Task Handle_HasActiveChildren_DoesNotCallAuditLog() { _repo.GetByIdAsync(5, Arg.Any()).Returns(LeafRubro(5)); _repo.CountActiveChildrenAsync(5, Arg.Any()).Returns(1); try { await _handler.Handle(new DeactivateRubroCommand(Id: 5)); } catch { } await _audit.DidNotReceive().LogAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), 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 DeactivateRubroCommand(Id: 99)); await act.Should().ThrowAsync(); } }