Files
arnes/tests/unit/test_auth.py
rikrdo 3ff9b70e4c 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.
2026-05-17 23:25:35 +02:00

262 lines
10 KiB
Python

"""Unit tests for authentication service."""
import unittest
from unittest.mock import MagicMock, patch
from src.models.auth import LoginRequest, TokenPayload
from src.services.auth_service import (
AuthService, auth_service,
InvalidCredentialsError, AccountLockedError, InvalidTokenError
)
from src.services.token_service import TokenService
from src.services.session_store import SessionStore
class TestAuthService(unittest.TestCase):
"""Test cases for AuthService."""
def setUp(self):
"""Set up test fixtures."""
self.mock_token_service = MagicMock(spec=TokenService)
self.mock_session_store = MagicMock(spec=SessionStore)
self.auth_service = AuthService(
token_svc=self.mock_token_service,
session_store=self.mock_session_store
)
# Default mock returns
self.mock_token_service.create_access_token.return_value = ("access_token_123", "token_id_123")
self.mock_token_service.create_refresh_token.return_value = "refresh_token_456"
def test_login_success(self):
"""Test successful login."""
request = LoginRequest(email="alice@example.com", password="SecurePass123!")
with patch.object(self.auth_service, '_get_user_by_email') as mock_get_user:
mock_get_user.return_value = {
"id": "user-001",
"email": "alice@example.com",
"password_hash": "hashed_password",
"role": "user",
"active": True
}
with patch.object(self.auth_service, '_verify_password', return_value=True):
result = self.auth_service.login(request, "127.0.0.1")
self.assertTrue(result.success)
self.assertEqual(result.access_token, "access_token_123")
self.assertEqual(result.refresh_token, "refresh_token_456")
self.mock_session_store.create_session.assert_called_once()
def test_login_invalid_credentials(self):
"""Test login with invalid credentials."""
request = LoginRequest(email="alice@example.com", password="WrongPassword!")
with patch.object(self.auth_service, '_get_user_by_email') as mock_get_user:
mock_get_user.return_value = {
"id": "user-001",
"email": "alice@example.com",
"password_hash": "hashed_password",
"role": "user",
"active": True
}
with patch.object(self.auth_service, '_verify_password', return_value=False):
with self.assertRaises(InvalidCredentialsError):
self.auth_service.login(request, "127.0.0.1")
def test_login_nonexistent_user(self):
"""Test login with nonexistent user."""
request = LoginRequest(email="nonexistent@test.com", password="AnyPassword123!")
with patch.object(self.auth_service, '_get_user_by_email', return_value=None):
with self.assertRaises(InvalidCredentialsError):
self.auth_service.login(request, "127.0.0.1")
def test_login_inactive_account(self):
"""Test login with inactive account."""
request = LoginRequest(email="alice@example.com", password="SecurePass123!")
with patch.object(self.auth_service, '_get_user_by_email') as mock_get_user:
mock_get_user.return_value = {
"id": "user-001",
"email": "alice@example.com",
"password_hash": "hashed_password",
"role": "user",
"active": False
}
with self.assertRaises(InvalidCredentialsError) as ctx:
self.auth_service.login(request, "127.0.0.1")
self.assertIn("desactivada", str(ctx.exception))
def test_logout_success(self):
"""Test successful logout."""
result = self.auth_service.logout("token_id_123", "user-001")
self.assertTrue(result)
self.mock_session_store.revoke_session.assert_called_once_with("token_id_123")
def test_logout_all_sessions(self):
"""Test logout all sessions."""
self.mock_session_store.revoke_all_user_sessions.return_value = 3
result = self.auth_service.logout_all("user-001")
self.assertEqual(result, 3)
self.mock_session_store.revoke_all_user_sessions.assert_called_once_with("user-001")
def test_refresh_success(self):
"""Test successful token refresh."""
mock_payload = TokenPayload(
sub="user-001",
email="alice@example.com",
role="user",
iat=1234567890,
exp=1234567890 + 900,
jti="token_id_123",
type="refresh"
)
self.mock_token_service.verify_token.return_value = mock_payload
self.mock_session_store.is_session_valid.return_value = True
self.mock_token_service.create_access_token.return_value = ("new_access_token", "new_token_id")
result = self.auth_service.refresh("valid_refresh_token")
self.assertTrue(result.success)
self.assertEqual(result.access_token, "new_access_token")
def test_refresh_invalid_token(self):
"""Test refresh with invalid token."""
self.mock_token_service.verify_token.side_effect = InvalidTokenError("Invalid token")
with self.assertRaises(InvalidTokenError):
self.auth_service.refresh("invalid_token")
def test_refresh_revoked_session(self):
"""Test refresh with revoked session."""
mock_payload = TokenPayload(
sub="user-001",
email="alice@example.com",
role="user",
iat=1234567890,
exp=1234567890 + 900,
jti="token_id_123",
type="refresh"
)
self.mock_token_service.verify_token.return_value = mock_payload
self.mock_session_store.is_session_valid.return_value = False
with self.assertRaises(InvalidTokenError):
self.auth_service.refresh("valid_refresh_token")
def test_validate_token_valid(self):
"""Test token validation with valid token."""
mock_payload = TokenPayload(
sub="user-001",
email="alice@example.com",
role="user",
iat=1234567890,
exp=1234567890 + 900,
jti="token_id_123"
)
self.mock_token_service.verify_token.return_value = mock_payload
self.mock_session_store.is_session_valid.return_value = True
is_valid, payload, error = self.auth_service.validate_token("valid_token")
self.assertTrue(is_valid)
self.assertEqual(payload.sub, "user-001")
self.assertIsNone(error)
def test_validate_token_revoked(self):
"""Test token validation with revoked session."""
mock_payload = TokenPayload(
sub="user-001",
email="alice@example.com",
role="user",
iat=1234567890,
exp=1234567890 + 900,
jti="token_id_123"
)
self.mock_token_service.verify_token.return_value = mock_payload
self.mock_session_store.is_session_valid.return_value = False
is_valid, payload, error = self.auth_service.validate_token("valid_token")
self.assertFalse(is_valid)
self.assertEqual(error, "Session revoked")
def test_validate_token_expired(self):
"""Test token validation with expired token."""
from jwt.exceptions import ExpiredSignatureError
self.mock_token_service.verify_token.side_effect = ExpiredSignatureError("Token expired")
is_valid, payload, error = self.auth_service.validate_token("expired_token")
self.assertFalse(is_valid)
self.assertIn("expired", error.lower())
class TestRateLimiting(unittest.TestCase):
"""Test rate limiting functionality."""
def setUp(self):
self.auth_service = AuthService()
def test_rate_limit_allows_first_attempts(self):
"""Test rate limit allows initial attempts."""
import time
ip = "10.0.0.1"
# Clear any existing rate limit data
self.auth_service._rate_limit_store = {ip: []}
request = LoginRequest(email="test@test.com", password="wrong")
with patch.object(self.auth_service, '_get_user_by_email', return_value=None):
# First 9 attempts should raise InvalidCredentialsError (not locked)
for i in range(9):
try:
self.auth_service.login(request, ip)
except InvalidCredentialsError:
pass # Expected - user doesn't exist
except AccountLockedError:
self.fail("Should not be locked after 9 attempts")
# 10th attempt should still raise InvalidCredentialsError (not locked yet)
try:
self.auth_service.login(request, ip)
except InvalidCredentialsError:
pass # Still not locked
except AccountLockedError:
self.fail("Should not be locked after 10 attempts")
# Verify rate limit counter is at 10
self.assertEqual(len(self.auth_service._rate_limit_store[ip]), 10)
def test_rate_limit_blocks_after_threshold(self):
"""Test rate limit blocks after threshold."""
import time
ip = "10.0.0.2"
# Pre-fill rate limit
now = time.time()
self.auth_service._rate_limit_store[ip] = [now - 50] * 10
request = LoginRequest(email="test@test.com", password="wrong")
with patch.object(self.auth_service, '_get_user_by_email', return_value=None):
with self.assertRaises(AccountLockedError):
self.auth_service.login(request, ip)
if __name__ == "__main__":
unittest.main()