172 lines
5.9 KiB
C#
172 lines
5.9 KiB
C#
|
|
namespace Application.Tests.Auth;
|
||
|
|
|
||
|
|
using Application.Auth;
|
||
|
|
using Application.Auth.Commands;
|
||
|
|
using Domain.Entities;
|
||
|
|
using Domain.Ports.Repositories;
|
||
|
|
using FluentAssertions;
|
||
|
|
using NSubstitute;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
public class LoginCommandTests
|
||
|
|
{
|
||
|
|
private readonly IUserRepository _userRepository;
|
||
|
|
private readonly IPasswordService _passwordService;
|
||
|
|
private readonly IJwtService _jwtService;
|
||
|
|
private readonly LoginCommandHandler _handler;
|
||
|
|
|
||
|
|
public LoginCommandTests()
|
||
|
|
{
|
||
|
|
_userRepository = Substitute.For<IUserRepository>();
|
||
|
|
_passwordService = Substitute.For<IPasswordService>();
|
||
|
|
_jwtService = Substitute.For<IJwtService>();
|
||
|
|
_handler = new LoginCommandHandler(_userRepository, _passwordService, _jwtService);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithValidCredentials_ShouldReturnSuccessWithToken()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var user = CreateUser("testuser", "hashed_password");
|
||
|
|
_userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(user);
|
||
|
|
_passwordService.VerifyPassword("password123", "hashed_password")
|
||
|
|
.Returns(true);
|
||
|
|
_jwtService.GenerateToken(user)
|
||
|
|
.Returns("valid.jwt.token");
|
||
|
|
|
||
|
|
var command = new LoginCommand("testuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Should().NotBeNull();
|
||
|
|
result.Success.Should().BeTrue();
|
||
|
|
result.Token.Should().Be("valid.jwt.token");
|
||
|
|
result.User.Should().NotBeNull();
|
||
|
|
result.User!.Username.Should().Be("testuser");
|
||
|
|
result.Error.Should().BeNull();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithInvalidUsername_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
_userRepository.GetByUsernameAsync("nonexistent", Arg.Any<CancellationToken>())
|
||
|
|
.Returns((User?)null);
|
||
|
|
|
||
|
|
var command = new LoginCommand("nonexistent", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Token.Should().BeNull();
|
||
|
|
result.Error.Should().Contain("incorrectos");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithInvalidPassword_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var user = CreateUser("testuser", "hashed_password");
|
||
|
|
_userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(user);
|
||
|
|
_passwordService.VerifyPassword("wrongpassword", "hashed_password")
|
||
|
|
.Returns(false);
|
||
|
|
|
||
|
|
var command = new LoginCommand("testuser", "wrongpassword");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Token.Should().BeNull();
|
||
|
|
result.Error.Should().Contain("incorrectos");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldNormalizUsernameToLowercase()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var user = CreateUser("testuser", "hashed_password");
|
||
|
|
_userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(user);
|
||
|
|
_passwordService.VerifyPassword("password123", "hashed_password")
|
||
|
|
.Returns(true);
|
||
|
|
_jwtService.GenerateToken(user)
|
||
|
|
.Returns("valid.jwt.token");
|
||
|
|
|
||
|
|
var command = new LoginCommand("TESTUSER", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeTrue();
|
||
|
|
await _userRepository.Received(1).GetByUsernameAsync("testuser", Arg.Any<CancellationToken>());
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldUpdateLastLoginOnSuccess()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var user = CreateUser("testuser", "hashed_password");
|
||
|
|
_userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(user);
|
||
|
|
_passwordService.VerifyPassword("password123", "hashed_password")
|
||
|
|
.Returns(true);
|
||
|
|
_jwtService.GenerateToken(user)
|
||
|
|
.Returns("valid.jwt.token");
|
||
|
|
|
||
|
|
var command = new LoginCommand("testuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
await _userRepository.Received(1).UpdateAsync(user, Arg.Any<CancellationToken>());
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithStudentUser_ShouldIncludeStudentInfo()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var user = CreateUserWithStudent("studentuser", "hashed_password", 1, "John Doe");
|
||
|
|
_userRepository.GetByUsernameAsync("studentuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(user);
|
||
|
|
_passwordService.VerifyPassword("password123", "hashed_password")
|
||
|
|
.Returns(true);
|
||
|
|
_jwtService.GenerateToken(user)
|
||
|
|
.Returns("valid.jwt.token");
|
||
|
|
|
||
|
|
var command = new LoginCommand("studentuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeTrue();
|
||
|
|
result.User!.StudentId.Should().Be(1);
|
||
|
|
result.User.StudentName.Should().Be("John Doe");
|
||
|
|
result.User.Role.Should().Be(UserRoles.Student);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static User CreateUser(string username, string passwordHash)
|
||
|
|
{
|
||
|
|
return User.Create(username, passwordHash, "recovery_hash", UserRoles.Student);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static User CreateUserWithStudent(string username, string passwordHash, int studentId, string studentName)
|
||
|
|
{
|
||
|
|
var user = User.Create(username, passwordHash, "recovery_hash", UserRoles.Student, studentId);
|
||
|
|
// Set up the Student navigation property
|
||
|
|
var student = new Student(studentName, Domain.ValueObjects.Email.Create($"{username}@test.com"));
|
||
|
|
typeof(User).GetProperty("Student")?.SetValue(user, student);
|
||
|
|
return user;
|
||
|
|
}
|
||
|
|
}
|