test(app): extend LoginCommandHandler tests with refresh token persistence cases RED
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
using SIGCM2.Application.Abstractions;
|
||||||
using SIGCM2.Application.Abstractions.Persistence;
|
using SIGCM2.Application.Abstractions.Persistence;
|
||||||
using SIGCM2.Application.Abstractions.Security;
|
using SIGCM2.Application.Abstractions.Security;
|
||||||
|
using SIGCM2.Application.Auth;
|
||||||
using SIGCM2.Application.Auth.Login;
|
using SIGCM2.Application.Auth.Login;
|
||||||
using SIGCM2.Domain.Entities;
|
using SIGCM2.Domain.Entities;
|
||||||
using SIGCM2.Domain.Exceptions;
|
using SIGCM2.Domain.Exceptions;
|
||||||
|
using SIGCM2.Domain.Security;
|
||||||
|
|
||||||
namespace SIGCM2.Application.Tests.Auth.Login;
|
namespace SIGCM2.Application.Tests.Auth.Login;
|
||||||
|
|
||||||
@@ -12,11 +15,22 @@ public class LoginCommandHandlerTests
|
|||||||
private readonly IUsuarioRepository _repository = Substitute.For<IUsuarioRepository>();
|
private readonly IUsuarioRepository _repository = Substitute.For<IUsuarioRepository>();
|
||||||
private readonly IPasswordHasher _hasher = Substitute.For<IPasswordHasher>();
|
private readonly IPasswordHasher _hasher = Substitute.For<IPasswordHasher>();
|
||||||
private readonly IJwtService _jwtService = Substitute.For<IJwtService>();
|
private readonly IJwtService _jwtService = Substitute.For<IJwtService>();
|
||||||
|
private readonly IRefreshTokenRepository _refreshRepo = Substitute.For<IRefreshTokenRepository>();
|
||||||
|
private readonly IRefreshTokenGenerator _refreshGenerator = Substitute.For<IRefreshTokenGenerator>();
|
||||||
|
private readonly IClientContext _clientCtx = Substitute.For<IClientContext>();
|
||||||
|
private readonly AuthOptions _authOptions = new() { AccessTokenMinutes = 60, RefreshTokenDays = 7 };
|
||||||
private readonly LoginCommandHandler _handler;
|
private readonly LoginCommandHandler _handler;
|
||||||
|
|
||||||
public LoginCommandHandlerTests()
|
public LoginCommandHandlerTests()
|
||||||
{
|
{
|
||||||
_handler = new LoginCommandHandler(_repository, _hasher, _jwtService);
|
_clientCtx.Ip.Returns("127.0.0.1");
|
||||||
|
_clientCtx.UserAgent.Returns("TestAgent");
|
||||||
|
_refreshGenerator.Generate().Returns("raw_refresh_token_value");
|
||||||
|
_refreshRepo.AddAsync(Arg.Any<RefreshToken>()).Returns(1);
|
||||||
|
|
||||||
|
_handler = new LoginCommandHandler(
|
||||||
|
_repository, _hasher, _jwtService,
|
||||||
|
_refreshRepo, _refreshGenerator, _clientCtx, _authOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scenario: valid credentials → returns token response with usuario populated
|
// Scenario: valid credentials → returns token response with usuario populated
|
||||||
@@ -100,4 +114,64 @@ public class LoginCommandHandlerTests
|
|||||||
|
|
||||||
await Assert.ThrowsAsync<InvalidCredentialsException>(() => _handler.Handle(command));
|
await Assert.ThrowsAsync<InvalidCredentialsException>(() => _handler.Handle(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// T-034: Refresh token persistence on login
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_PersistsHashedRefreshToken()
|
||||||
|
{
|
||||||
|
var usuario = new Usuario(1, "admin", "$2a$12$hash", "Admin", "Sys", null, "admin", "[\"*\"]", true);
|
||||||
|
_repository.GetByUsernameAsync("admin").Returns(usuario);
|
||||||
|
_hasher.Verify("@Diego550@", "$2a$12$hash").Returns(true);
|
||||||
|
_jwtService.GenerateAccessToken(usuario).Returns("jwt");
|
||||||
|
|
||||||
|
var command = new LoginCommand("admin", "@Diego550@");
|
||||||
|
var result = await _handler.Handle(command);
|
||||||
|
|
||||||
|
// Raw token returned to client
|
||||||
|
Assert.Equal("raw_refresh_token_value", result.RefreshToken);
|
||||||
|
|
||||||
|
// Repository received a token with the hash of the raw value — not the raw itself
|
||||||
|
var expectedHash = TokenHasher.Sha256Base64Url("raw_refresh_token_value");
|
||||||
|
await _refreshRepo.Received(1).AddAsync(Arg.Is<RefreshToken>(t =>
|
||||||
|
t.TokenHash == expectedHash &&
|
||||||
|
t.UsuarioId == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_GeneratesNewFamilyId()
|
||||||
|
{
|
||||||
|
var usuario = new Usuario(1, "admin", "$2a$12$hash", "Admin", "Sys", null, "admin", "[\"*\"]", true);
|
||||||
|
_repository.GetByUsernameAsync("admin").Returns(usuario);
|
||||||
|
_hasher.Verify(Arg.Any<string>(), Arg.Any<string>()).Returns(true);
|
||||||
|
_jwtService.GenerateAccessToken(Arg.Any<Usuario>()).Returns("jwt");
|
||||||
|
|
||||||
|
var capturedFamilies = new List<Guid>();
|
||||||
|
_refreshRepo.AddAsync(Arg.Do<RefreshToken>(t => capturedFamilies.Add(t.FamilyId))).Returns(1);
|
||||||
|
|
||||||
|
await _handler.Handle(new LoginCommand("admin", "@Diego550@"));
|
||||||
|
await _handler.Handle(new LoginCommand("admin", "@Diego550@"));
|
||||||
|
|
||||||
|
// Each login gets a unique FamilyId
|
||||||
|
Assert.Equal(2, capturedFamilies.Count);
|
||||||
|
Assert.NotEqual(capturedFamilies[0], capturedFamilies[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_UsesConfiguredRefreshTokenDays()
|
||||||
|
{
|
||||||
|
var usuario = new Usuario(1, "admin", "$2a$12$hash", "Admin", "Sys", null, "admin", "[\"*\"]", true);
|
||||||
|
_repository.GetByUsernameAsync("admin").Returns(usuario);
|
||||||
|
_hasher.Verify(Arg.Any<string>(), Arg.Any<string>()).Returns(true);
|
||||||
|
_jwtService.GenerateAccessToken(Arg.Any<Usuario>()).Returns("jwt");
|
||||||
|
|
||||||
|
var before = DateTime.UtcNow;
|
||||||
|
await _handler.Handle(new LoginCommand("admin", "@Diego550@"));
|
||||||
|
var after = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// The token added to the repo must have ExpiresAt ~7 days from now
|
||||||
|
await _refreshRepo.Received(1).AddAsync(Arg.Is<RefreshToken>(t =>
|
||||||
|
t.ExpiresAt >= before.AddDays(6).AddHours(23) &&
|
||||||
|
t.ExpiresAt <= after.AddDays(7).AddSeconds(5)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user