- Add complete agent harness structure with 8 roles (leader, triager, architect, implementer, reviewer, security, qa, documenter) - Implement strict workflow with 9 stages and mandatory gates - Add comprehensive verification script and runtime status tracking - Create artifact-based evidence system with contracts and schemas - Add agent policy matrix with permissions and anti-cheat rules - Include test suite (44 tests passing) and CI-ready structure - Add documentation: README, HOWTO, CHECKPOINTS, templates - Configure model routing policies and token-aware task assignment - Add BDD/SDD specification guides and feature templates - Include starter pack for quick project onboarding All verification checks pass. Framework ready for production use.
185 lines
6.3 KiB
Python
185 lines
6.3 KiB
Python
"""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() |