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 RegisterCommandTests { private readonly IUserRepository _userRepository; private readonly IStudentRepository _studentRepository; private readonly IPasswordService _passwordService; private readonly IJwtService _jwtService; private readonly IUnitOfWork _unitOfWork; private readonly RegisterCommandHandler _handler; public RegisterCommandTests() { _userRepository = Substitute.For(); _studentRepository = Substitute.For(); _passwordService = Substitute.For(); _jwtService = Substitute.For(); _unitOfWork = Substitute.For(); _passwordService.HashPassword(Arg.Any()).Returns("hashed_value"); _handler = new RegisterCommandHandler( _userRepository, _studentRepository, _passwordService, _jwtService, _unitOfWork ); } [Fact] public async Task Handle_WithValidData_ShouldCreateUser() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand("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_WithExistingUsername_ShouldReturnError() { // Arrange _userRepository.ExistsAsync("existinguser", Arg.Any()) .Returns(true); var command = new RegisterCommand("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 _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); var command = new RegisterCommand("newuser", "12345"); // 5 chars, less than 6 // 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_WithNameAndEmail_ShouldCreateStudent() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand( "newuser", "password123", Name: "John Doe", Email: "john@example.com" ); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Success.Should().BeTrue(); _studentRepository.Received(1).Add(Arg.Is(s => s.Name == "John Doe")); await _unitOfWork.Received().SaveChangesAsync(Arg.Any()); } [Fact] public async Task Handle_WithInvalidEmail_ShouldReturnError() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); var command = new RegisterCommand( "newuser", "password123", Name: "John Doe", Email: "invalid-email" ); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Success.Should().BeFalse(); result.Error.Should().NotBeNullOrEmpty(); } [Fact] public async Task Handle_ShouldGenerateRecoveryCode() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand("newuser", "password123"); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.RecoveryCode.Should().NotBeNullOrEmpty(); result.RecoveryCode!.Length.Should().Be(12); result.RecoveryCode.Should().MatchRegex("^[A-Z0-9]+$"); } [Fact] public async Task Handle_ShouldHashPasswordBeforeSaving() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand("newuser", "password123"); // Act await _handler.Handle(command, CancellationToken.None); // Assert _passwordService.Received(1).HashPassword("password123"); await _userRepository.Received(1).AddAsync( Arg.Is(u => u.PasswordHash == "hashed_value"), Arg.Any() ); } [Fact] public async Task Handle_ShouldNormalizeUsernameToLowercase() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand("NEWUSER", "password123"); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Success.Should().BeTrue(); result.User!.Username.Should().Be("newuser"); } [Fact] public async Task Handle_NewUserShouldHaveStudentRole() { // Arrange _userRepository.ExistsAsync("newuser", Arg.Any()) .Returns(false); _jwtService.GenerateToken(Arg.Any()) .Returns("new.jwt.token"); var command = new RegisterCommand("newuser", "password123"); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.User!.Role.Should().Be(UserRoles.Student); } }