"""Unit tests for password service.""" import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import unittest from src.services.password_service import PasswordService class TestPasswordValidator(unittest.TestCase): """Tests for password validation.""" def setUp(self): self.service = PasswordService() def test_valid_password_all_requirements(self): """Test password with all requirements met.""" is_valid, error = self.service.validate_password_strength("Password123!") self.assertTrue(is_valid) self.assertEqual(error, "") def test_password_too_short(self): """Test password shorter than 8 characters.""" is_valid, error = self.service.validate_password_strength("Pass1!") self.assertFalse(is_valid) self.assertIn("al menos 8 caracteres", error) def test_password_too_long(self): """Test password longer than 128 characters.""" long_pass = "A" * 129 + "a1!" is_valid, error = self.service.validate_password_strength(long_pass) self.assertFalse(is_valid) self.assertIn("máximo 128 caracteres", error) def test_password_no_uppercase(self): """Test password without uppercase letter.""" is_valid, error = self.service.validate_password_strength("password123!") self.assertFalse(is_valid) self.assertIn("al menos una mayúscula", error) def test_password_no_lowercase(self): """Test password without lowercase letter.""" is_valid, error = self.service.validate_password_strength("PASSWORD123!") self.assertFalse(is_valid) self.assertIn("al menos una minúscula", error) def test_password_no_number(self): """Test password without number.""" is_valid, error = self.service.validate_password_strength("PasswordABC!") self.assertFalse(is_valid) self.assertIn("al menos un número", error) def test_password_no_special_char(self): """Test password without special character.""" is_valid, error = self.service.validate_password_strength("Password123") self.assertFalse(is_valid) self.assertIn("carácter especial", error) class TestPasswordService(unittest.TestCase): """Tests for password service operations.""" def setUp(self): self.service = PasswordService() def test_change_password_success(self): """Test successful password change.""" success, status, error = self.service.change_password( "user-123", "OldPass123!", "NewPass456@", "NewPass456@" ) self.assertTrue(success) self.assertEqual(status, 200) self.assertIsNone(error) def test_change_password_wrong_current(self): """Test password change with wrong current password.""" success, status, error = self.service.change_password( "user-123", "WrongPass123!", "NewPass456@", "NewPass456@" ) self.assertFalse(success) self.assertEqual(status, 401) self.assertEqual(error, "La contraseña actual es incorrecta") def test_change_password_mismatch(self): """Test password change with mismatching passwords.""" success, status, error = self.service.change_password( "user-123", "OldPass123!", "NewPass456@", "DifferentPass789!" ) self.assertFalse(success) self.assertEqual(status, 400) self.assertEqual(error, "Las contraseñas no coinciden") def test_change_password_weak(self): """Test password change with weak password.""" success, status, error = self.service.change_password( "user-123", "OldPass123!", "weak", "weak" ) self.assertFalse(success) self.assertEqual(status, 400) def test_change_password_nonexistent_user(self): """Test password change for nonexistent user.""" success, status, error = self.service.change_password( "nonexistent", "AnyPass123!", "NewPass456@", "NewPass456@" ) self.assertFalse(success) self.assertEqual(status, 404) self.assertEqual(error, "Usuario no encontrado") def test_change_password_reuse_history(self): """Test password change with password from history.""" # First change self.service.change_password( "user-123", "OldPass123!", "NewPass456@", "NewPass456@" ) # Try to reuse current (which is now in history) success, status, error = self.service.change_password( "user-123", "NewPass456@", "OldPass123!", "OldPass123!" ) self.assertFalse(success) self.assertEqual(status, 400) self.assertIn("no puede ser igual a la anterior", error) def test_rate_limit_after_5_attempts(self): """Test rate limiting after 5 failed attempts.""" # Make 5 failed attempts for _ in range(5): self.service.change_password( "user-123", "WrongPass123!", "NewPass456@", "NewPass456@" ) # 6th attempt should be rate limited success, status, error = self.service.change_password( "user-123", "OldPass123!", "NewPass456@", "NewPass456@" ) self.assertFalse(success) self.assertEqual(status, 429) self.assertIn("Demasiados intentos", error) def test_sessions_invalidated_after_change(self): """Test that sessions are invalidated after password change.""" # Add some sessions self.service._sessions["user-123"] = ["token1", "token2", "token3"] # Change password self.service.change_password( "user-123", "OldPass123!", "NewPass456@", "NewPass456@" ) # Sessions should be cleared self.assertEqual(len(self.service._sessions.get("user-123", [])), 0) if __name__ == "__main__": unittest.main()