using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Permisos.Assign; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Permisos.Assign; public class AssignPermisosToRolCommandHandlerTests { private readonly IRolRepository _rolRepository = Substitute.For(); private readonly IPermisoRepository _permisoRepository = Substitute.For(); private readonly IRolPermisoRepository _rolPermisoRepository = Substitute.For(); private readonly AssignPermisosToRolCommandHandler _handler; public AssignPermisosToRolCommandHandlerTests() { _handler = new AssignPermisosToRolCommandHandler(_rolRepository, _permisoRepository, _rolPermisoRepository); } private static Rol MakeRol(int id, string codigo) => new(id, codigo, codigo, null, true, DateTime.UtcNow, null); private static Permiso MakePermiso(int id, string codigo, string modulo = "ventas") => Permiso.ForRead(id, codigo, codigo, null, modulo, true, DateTime.UtcNow); [Fact] public async Task Handle_HappyPath_CallsReplaceWithCorrectIds() { _rolRepository.GetByCodigoAsync("cajero").Returns(MakeRol(5, "cajero")); var permisoCrear = MakePermiso(1, "ventas:contado:crear"); var permisoFact = MakePermiso(2, "ventas:contado:facturar"); _permisoRepository.GetByCodigosAsync(Arg.Any>()) .Returns(new List { permisoCrear, permisoFact }); var codigos = new List { "ventas:contado:crear", "ventas:contado:facturar" }; await _handler.Handle(new AssignPermisosToRolCommand("cajero", codigos)); await _rolPermisoRepository.Received(1).ReplaceForRolAsync( 5, Arg.Is>(ids => ids.SequenceEqual(new[] { 1, 2 }))); } [Fact] public async Task Handle_RolInexistente_ThrowsRolNotFoundException() { _rolRepository.GetByCodigoAsync("fantasma").Returns((Rol?)null); var ex = await Assert.ThrowsAsync( () => _handler.Handle(new AssignPermisosToRolCommand("fantasma", new[] { "ventas:contado:crear" }))); Assert.Equal("fantasma", ex.Codigo); } [Fact] public async Task Handle_RolInexistente_DoesNotCallReplace() { _rolRepository.GetByCodigoAsync("fantasma").Returns((Rol?)null); await Assert.ThrowsAsync( () => _handler.Handle(new AssignPermisosToRolCommand("fantasma", new[] { "ventas:contado:crear" }))); await _rolPermisoRepository.DidNotReceive().ReplaceForRolAsync(Arg.Any(), Arg.Any>()); } [Fact] public async Task Handle_PermisoInexistente_ThrowsPermisoNotFoundException() { _rolRepository.GetByCodigoAsync("cajero").Returns(MakeRol(5, "cajero")); // Repo devuelve 0 permisos (ningún código matchea en BD) _permisoRepository.GetByCodigosAsync(Arg.Any>()) .Returns(new List()); var ex = await Assert.ThrowsAsync( () => _handler.Handle(new AssignPermisosToRolCommand("cajero", new[] { "permiso:inexistente" }))); Assert.Equal("permiso:inexistente", ex.Codigo); } [Fact] public async Task Handle_PartialPermisoMatch_ThrowsPermisoNotFoundForMissing() { _rolRepository.GetByCodigoAsync("cajero").Returns(MakeRol(5, "cajero")); // Solo devuelve 1 de 2 — el segundo no existe _permisoRepository.GetByCodigosAsync(Arg.Any>()) .Returns(new List { MakePermiso(1, "ventas:contado:crear") }); var ex = await Assert.ThrowsAsync( () => _handler.Handle(new AssignPermisosToRolCommand("cajero", new[] { "ventas:contado:crear", "permiso:inexistente" }))); Assert.Equal("permiso:inexistente", ex.Codigo); } [Fact] public async Task Handle_EmptyList_CallsReplaceWithEmptyIds() { // Para roles no-admin, lista vacía es válida _rolRepository.GetByCodigoAsync("reportes").Returns(MakeRol(3, "reportes")); _permisoRepository.GetByCodigosAsync(Arg.Any>()) .Returns(new List()); await _handler.Handle(new AssignPermisosToRolCommand("reportes", new List())); await _rolPermisoRepository.Received(1).ReplaceForRolAsync( 3, Arg.Is>(ids => !ids.Any())); } [Fact] public async Task Handle_IdempotentCall_CallsReplaceExactlyOnce() { _rolRepository.GetByCodigoAsync("cajero").Returns(MakeRol(5, "cajero")); _permisoRepository.GetByCodigosAsync(Arg.Any>()) .Returns(new List { MakePermiso(1, "ventas:contado:crear") }); var cmd = new AssignPermisosToRolCommand("cajero", new[] { "ventas:contado:crear" }); await _handler.Handle(cmd); await _handler.Handle(cmd); await _rolPermisoRepository.Received(2).ReplaceForRolAsync(Arg.Any(), Arg.Any>()); } }