using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Audit; using SIGCM2.Application.Secciones.Create; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Secciones.Create; public class CreateSeccionCommandHandlerTests { private readonly ISeccionRepository _repo = Substitute.For(); private readonly IMedioRepository _medioRepo = Substitute.For(); private readonly IAuditLogger _audit = Substitute.For(); private readonly CreateSeccionCommandHandler _handler; private static Medio MakeMedio(int id = 1, bool activo = true) => new(id, "COD" + id, "Medio " + id, TipoMedio.Diario, null, activo, DateTime.UtcNow, null); private static CreateSeccionCommand ValidCommand() => new( MedioId: 1, Codigo: "CLASIFICADOS_LUNES", Nombre: "Clasificados Lunes", Tipo: "clasificados"); public CreateSeccionCommandHandlerTests() { _handler = new CreateSeccionCommandHandler(_repo, _medioRepo, _audit); _medioRepo.GetByIdAsync(1, Arg.Any()).Returns(MakeMedio(1)); _repo.ExistsByCodigoInMedioAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(false); _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(10); } // ── medio not found → throws ───────────────────────────────────────────── [Fact] public async Task Handle_MedioNotFound_ThrowsMedioNotFoundException() { _medioRepo.GetByIdAsync(1, Arg.Any()).Returns((Medio?)null); await Assert.ThrowsAsync( () => _handler.Handle(ValidCommand())); } // ── duplicate codigo in medio → throws ────────────────────────────────── [Fact] public async Task Handle_DuplicateCodigoInMedio_ThrowsSeccionCodigoDuplicadoEnMedioException() { _repo.ExistsByCodigoInMedioAsync(1, "CLASIFICADOS_LUNES", Arg.Any()).Returns(true); await Assert.ThrowsAsync( () => _handler.Handle(ValidCommand())); } [Fact] public async Task Handle_DuplicateCodigo_DoesNotCallAddAsync() { _repo.ExistsByCodigoInMedioAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(true); try { await _handler.Handle(ValidCommand()); } catch (SeccionCodigoDuplicadoEnMedioException) { } await _repo.DidNotReceive().AddAsync(Arg.Any(), Arg.Any()); } // ── happy path ─────────────────────────────────────────────────────────── [Fact] public async Task Handle_HappyPath_ReturnsDtoWithIdFromRepository() { _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(10); var result = await _handler.Handle(ValidCommand()); Assert.Equal(10, result.Id); } [Fact] public async Task Handle_HappyPath_DtoContainsCorrectFields() { var result = await _handler.Handle(ValidCommand()); Assert.Equal(1, result.MedioId); Assert.Equal("CLASIFICADOS_LUNES", result.Codigo); Assert.Equal("Clasificados Lunes", result.Nombre); Assert.Equal("clasificados", result.Tipo); Assert.True(result.Activo); } [Fact] public async Task Handle_HappyPath_CallsAuditWithCreateAction() { _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(10); await _handler.Handle(ValidCommand()); await _audit.Received(1).LogAsync( action: "seccion.create", targetType: "Seccion", targetId: "10", metadata: Arg.Any(), ct: Arg.Any()); } [Fact] public async Task Handle_InactiveMedio_ThrowsMedioNotFoundException() { _medioRepo.GetByIdAsync(1, Arg.Any()).Returns(MakeMedio(1, false)); await Assert.ThrowsAsync( () => _handler.Handle(ValidCommand())); } }