using NSubstitute; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Abstractions.Security; using SIGCM2.Application.Usuarios.ChangeMyPassword; using SIGCM2.Domain.Entities; using SIGCM2.Domain.Exceptions; namespace SIGCM2.Application.Tests.Usuarios; public class ChangeMyPasswordCommandHandlerTests { private readonly IUsuarioRepository _repo = Substitute.For(); private readonly IPasswordHasher _hasher = Substitute.For(); private readonly IRefreshTokenRepository _refreshRepo = Substitute.For(); private readonly ChangeMyPasswordCommandHandler _handler; public ChangeMyPasswordCommandHandlerTests() { _handler = new ChangeMyPasswordCommandHandler(_repo, _hasher); } private static Usuario MakeUser(int id = 1, bool mustChangePassword = false) => new(id, "user" + id, "$2a$12$oldhash", "Test", "User", null, "cajero", "[]", true, mustChangePassword: mustChangePassword); [Fact] public async Task Handle_Happy_Path_Hashes_New_Password_Clears_MustChange() { var user = MakeUser(1, mustChangePassword: true); _repo.GetByIdAsync(1, Arg.Any()).Returns(user); _hasher.Verify("oldPass1!", "$2a$12$oldhash").Returns(true); _hasher.Hash("newPass2!").Returns("$2a$12$newhash"); await _handler.Handle(new ChangeMyPasswordCommand(1, "oldPass1!", "newPass2!")); await _repo.Received(1).UpdatePasswordAsync(1, "$2a$12$newhash", false, Arg.Any()); } [Fact] public async Task Handle_Throws_InvalidOldPasswordException_When_Wrong_Old() { var user = MakeUser(1); _repo.GetByIdAsync(1, Arg.Any()).Returns(user); _hasher.Verify(Arg.Any(), Arg.Any()).Returns(false); await Assert.ThrowsAsync( () => _handler.Handle(new ChangeMyPasswordCommand(1, "wrongPass!", "newPass2!"))); } [Fact] public async Task Handle_Throws_UsuarioNotFoundException_When_Not_Found() { _repo.GetByIdAsync(9999, Arg.Any()).Returns((Usuario?)null); await Assert.ThrowsAsync( () => _handler.Handle(new ChangeMyPasswordCommand(9999, "old", "new1234"))); } [Fact] public async Task Handle_Does_NOT_Revoke_Own_Refresh_Tokens() { var user = MakeUser(1); _repo.GetByIdAsync(1, Arg.Any()).Returns(user); _hasher.Verify(Arg.Any(), Arg.Any()).Returns(true); _hasher.Hash(Arg.Any()).Returns("$2a$12$newhash"); await _handler.Handle(new ChangeMyPasswordCommand(1, "oldPass1!", "newPass2!")); // spec REQ-BCP-05: change password does NOT revoke own tokens await _refreshRepo.DidNotReceive().RevokeAllActiveForUserAsync(Arg.Any(), Arg.Any(), Arg.Any()); } }