refactor: complete bootstrap of ARNES agent harness framework
- 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.
This commit is contained in:
185
tests/unit/test_password.py
Normal file
185
tests/unit/test_password.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user