using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Audit; using SIGCM2.Application.Medios.Create; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Medios.Create; public class CreateMedioCommandHandlerTests { private readonly IMedioRepository _repo = Substitute.For(); private readonly IAuditLogger _audit = Substitute.For(); private readonly CreateMedioCommandHandler _handler; private static CreateMedioCommand ValidCommand() => new( Codigo: "DIARIO_LA_VOZ", Nombre: "Diario La Voz", Tipo: TipoMedio.Diario, PlataformaEmpresaId: null); public CreateMedioCommandHandlerTests() { _handler = new CreateMedioCommandHandler(_repo, _audit); _repo.ExistsByCodigoAsync(Arg.Any(), Arg.Any()).Returns(false); _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(1); } // ── duplicate → throws ─────────────────────────────────────────────────── [Fact] public async Task Handle_DuplicateCodigo_ThrowsMedioCodigoDuplicadoException() { _repo.ExistsByCodigoAsync("DIARIO_LA_VOZ", Arg.Any()).Returns(true); await Assert.ThrowsAsync( () => _handler.Handle(ValidCommand())); } [Fact] public async Task Handle_DuplicateCodigo_DoesNotCallAddAsync() { _repo.ExistsByCodigoAsync(Arg.Any(), Arg.Any()).Returns(true); try { await _handler.Handle(ValidCommand()); } catch (MedioCodigoDuplicadoException) { } await _repo.DidNotReceive().AddAsync(Arg.Any(), Arg.Any()); } // ── happy path ─────────────────────────────────────────────────────────── [Fact] public async Task Handle_HappyPath_ReturnsDtoWithIdFromRepository() { _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(42); var result = await _handler.Handle(ValidCommand()); Assert.Equal(42, result.Id); } [Fact] public async Task Handle_HappyPath_DtoContainsCorrectFields() { _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(5); var result = await _handler.Handle(ValidCommand()); Assert.Equal("DIARIO_LA_VOZ", result.Codigo); Assert.Equal("Diario La Voz", result.Nombre); Assert.Equal(TipoMedio.Diario, result.Tipo); Assert.Null(result.PlataformaEmpresaId); Assert.True(result.Activo); } [Fact] public async Task Handle_HappyPath_NormalizesCodigoToUppercase() { var cmd = new CreateMedioCommand("diario_la_voz", "Diario La Voz", TipoMedio.Diario, null); await _handler.Handle(cmd); await _repo.Received(1).AddAsync( Arg.Is(m => m.Codigo == "DIARIO_LA_VOZ"), Arg.Any()); } [Fact] public async Task Handle_HappyPath_CallsAuditLogWithCreateAction() { _repo.AddAsync(Arg.Any(), Arg.Any()).Returns(7); await _handler.Handle(ValidCommand()); await _audit.Received(1).LogAsync( action: "medio.create", targetType: "Medio", targetId: "7", metadata: Arg.Any(), ct: Arg.Any()); } [Fact] public async Task Handle_HappyPath_ChecksNormalizedCodigoForDuplicate() { var cmd = new CreateMedioCommand("diario_la_voz", "Diario La Voz", TipoMedio.Diario, null); await _handler.Handle(cmd); // ExistsByCodigoAsync should be called with the uppercased version await _repo.Received(1).ExistsByCodigoAsync("DIARIO_LA_VOZ", Arg.Any()); } }