279 lines
11 KiB
C#
279 lines
11 KiB
C#
|
|
namespace Application.Tests.Auth;
|
||
|
|
|
||
|
|
using Application.Auth;
|
||
|
|
using Application.Auth.Commands;
|
||
|
|
using Domain.Entities;
|
||
|
|
using Domain.Ports.Repositories;
|
||
|
|
using Domain.ValueObjects;
|
||
|
|
using FluentAssertions;
|
||
|
|
using NSubstitute;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
public class ActivateAccountCommandTests
|
||
|
|
{
|
||
|
|
private readonly IStudentRepository _studentRepository;
|
||
|
|
private readonly IUserRepository _userRepository;
|
||
|
|
private readonly IPasswordService _passwordService;
|
||
|
|
private readonly IJwtService _jwtService;
|
||
|
|
private readonly IUnitOfWork _unitOfWork;
|
||
|
|
private readonly ActivateAccountHandler _handler;
|
||
|
|
|
||
|
|
public ActivateAccountCommandTests()
|
||
|
|
{
|
||
|
|
_studentRepository = Substitute.For<IStudentRepository>();
|
||
|
|
_userRepository = Substitute.For<IUserRepository>();
|
||
|
|
_passwordService = Substitute.For<IPasswordService>();
|
||
|
|
_jwtService = Substitute.For<IJwtService>();
|
||
|
|
_unitOfWork = Substitute.For<IUnitOfWork>();
|
||
|
|
|
||
|
|
_passwordService.HashPassword(Arg.Any<string>()).Returns("hashed_value");
|
||
|
|
|
||
|
|
_handler = new ActivateAccountHandler(
|
||
|
|
_studentRepository,
|
||
|
|
_userRepository,
|
||
|
|
_passwordService,
|
||
|
|
_jwtService,
|
||
|
|
_unitOfWork
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithValidActivationCode_ShouldActivateAccount()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
_studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
|
||
|
|
.Returns(student);
|
||
|
|
_jwtService.GenerateToken(Arg.Any<User>())
|
||
|
|
.Returns("new.jwt.token");
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeTrue();
|
||
|
|
result.Token.Should().Be("new.jwt.token");
|
||
|
|
result.RecoveryCode.Should().NotBeNullOrEmpty();
|
||
|
|
result.Error.Should().BeNull();
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithInvalidActivationCode_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("INVALIDCODE", Arg.Any<string>())
|
||
|
|
.Returns(false);
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("INVALIDCODE", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Error.Should().Contain("invalido");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithExpiredActivationCode_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithExpiredActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Error.Should().Contain("expirado");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithExistingUsername_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("existinguser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(true);
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "existinguser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Error.Should().Contain("ya existe");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_WithShortPassword_ShouldReturnError()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "12345"); // 5 chars
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Success.Should().BeFalse();
|
||
|
|
result.Error.Should().Contain("al menos 6 caracteres");
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldCreateUserWithStudentRole()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
SetEntityId(student, 5);
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
_studentRepository.GetByIdAsync(5, Arg.Any<CancellationToken>())
|
||
|
|
.Returns(student);
|
||
|
|
_jwtService.GenerateToken(Arg.Any<User>())
|
||
|
|
.Returns("new.jwt.token");
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
await _userRepository.Received(1).AddAsync(
|
||
|
|
Arg.Is<User>(u => u.Role == UserRoles.Student && u.StudentId == 5),
|
||
|
|
Arg.Any<CancellationToken>()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldGenerateRecoveryCode()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
_studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
|
||
|
|
.Returns(student);
|
||
|
|
_jwtService.GenerateToken(Arg.Any<User>())
|
||
|
|
.Returns("new.jwt.token");
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.RecoveryCode.Should().NotBeNullOrEmpty();
|
||
|
|
result.RecoveryCode!.Length.Should().Be(12);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldClearActivationCodeAfterSuccess()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
_studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
|
||
|
|
.Returns(student);
|
||
|
|
_jwtService.GenerateToken(Arg.Any<User>())
|
||
|
|
.Returns("new.jwt.token");
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task Handle_ShouldReturnJwtTokenForAutoLogin()
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
|
||
|
|
_studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
|
||
|
|
.Returns(new List<Student> { student });
|
||
|
|
_passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
|
||
|
|
.Returns(true);
|
||
|
|
_userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
|
||
|
|
.Returns(false);
|
||
|
|
_studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
|
||
|
|
.Returns(student);
|
||
|
|
_jwtService.GenerateToken(Arg.Any<User>())
|
||
|
|
.Returns("auto.login.token");
|
||
|
|
|
||
|
|
var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");
|
||
|
|
|
||
|
|
// Act
|
||
|
|
var result = await _handler.Handle(command, CancellationToken.None);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
result.Token.Should().Be("auto.login.token");
|
||
|
|
_jwtService.Received(1).GenerateToken(Arg.Any<User>());
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Student CreateStudentWithActivationCode(string name, string email, string activationHash)
|
||
|
|
{
|
||
|
|
var student = new Student(name, Email.Create(email));
|
||
|
|
typeof(Student).GetProperty("ActivationCodeHash")?.SetValue(student, activationHash);
|
||
|
|
typeof(Student).GetProperty("ActivationExpiresAt")?.SetValue(student, DateTime.UtcNow.AddDays(2));
|
||
|
|
return student;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Student CreateStudentWithExpiredActivationCode(string name, string email, string activationHash)
|
||
|
|
{
|
||
|
|
var student = new Student(name, Email.Create(email));
|
||
|
|
typeof(Student).GetProperty("ActivationCodeHash")?.SetValue(student, activationHash);
|
||
|
|
typeof(Student).GetProperty("ActivationExpiresAt")?.SetValue(student, DateTime.UtcNow.AddDays(-1));
|
||
|
|
return student;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void SetEntityId<T>(T entity, int id) where T : class
|
||
|
|
{
|
||
|
|
typeof(T).GetProperty("Id")?.SetValue(entity, id);
|
||
|
|
}
|
||
|
|
}
|