""" Test unitari per AuthService (mock del DB). """ import os import uuid from datetime import UTC, datetime, timedelta from unittest.mock import AsyncMock, MagicMock, patch import pytest os.environ.setdefault("ENCRYPTION_KEY", "a" * 64) os.environ.setdefault("SECRET_KEY", "test-secret-key-only") os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost:5432/test") os.environ.setdefault("DATABASE_URL_SYNC", "postgresql://test:test@localhost:5432/test") from app.core.exceptions import ( AccountDisabledError, AccountLockedError, InvalidCredentialsError, TOTPRequiredError, ) from app.core.security import hash_password from app.models.user import User def make_user(**kwargs) -> User: """Factory per creare un utente mock.""" user = MagicMock(spec=User) user.id = kwargs.get("id", uuid.uuid4()) user.tenant_id = kwargs.get("tenant_id", uuid.uuid4()) user.email = kwargs.get("email", "test@example.com") user.password_hash = kwargs.get("password_hash", hash_password("Password1!")) user.role = kwargs.get("role", "operator") user.is_active = kwargs.get("is_active", True) user.totp_enabled = kwargs.get("totp_enabled", False) user.totp_secret = kwargs.get("totp_secret", None) user.failed_login_count = kwargs.get("failed_login_count", 0) user.locked_until = kwargs.get("locked_until", None) return user class TestAuthServiceLogin: @pytest.fixture def mock_db(self): db = AsyncMock() db.add = MagicMock() db.execute = AsyncMock() db.flush = AsyncMock() return db @pytest.mark.asyncio async def test_login_with_correct_credentials(self, mock_db): from app.services.auth_service import AuthService user = make_user(password_hash=hash_password("Password1!")) # Mock query utente mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = user mock_db.execute.return_value = mock_result service = AuthService(mock_db) with patch.object(service, "_handle_failed_login", new_callable=AsyncMock): with patch.object(service, "_reset_failed_login", new_callable=AsyncMock): with patch.object(service, "_log_audit", new_callable=AsyncMock): access, refresh = await service.login( email="test@example.com", password="Password1!", totp_code=None, ) assert access is not None assert refresh is not None @pytest.mark.asyncio async def test_login_user_not_found_raises(self, mock_db): from app.services.auth_service import AuthService mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = None mock_db.execute.return_value = mock_result service = AuthService(mock_db) with patch.object(service, "_log_audit", new_callable=AsyncMock): with pytest.raises(InvalidCredentialsError): await service.login("notfound@example.com", "Password1!", None) @pytest.mark.asyncio async def test_login_inactive_user_raises(self, mock_db): from app.services.auth_service import AuthService user = make_user(is_active=False) mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = user mock_db.execute.return_value = mock_result service = AuthService(mock_db) with pytest.raises(AccountDisabledError): await service.login("test@example.com", "Password1!", None) @pytest.mark.asyncio async def test_login_locked_account_raises(self, mock_db): from app.services.auth_service import AuthService user = make_user( locked_until=datetime.now(UTC) + timedelta(minutes=10) ) mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = user mock_db.execute.return_value = mock_result service = AuthService(mock_db) with pytest.raises(AccountLockedError): await service.login("test@example.com", "Password1!", None) @pytest.mark.asyncio async def test_login_wrong_password_raises(self, mock_db): from app.services.auth_service import AuthService user = make_user(password_hash=hash_password("CorrectPassword1!")) mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = user mock_db.execute.return_value = mock_result service = AuthService(mock_db) with patch.object(service, "_handle_failed_login", new_callable=AsyncMock): with patch.object(service, "_log_audit", new_callable=AsyncMock): with pytest.raises(InvalidCredentialsError): await service.login("test@example.com", "WrongPassword1!", None) @pytest.mark.asyncio async def test_login_totp_required_when_enabled(self, mock_db): from app.services.auth_service import AuthService user = make_user( password_hash=hash_password("Password1!"), totp_enabled=True, ) mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = user mock_db.execute.return_value = mock_result service = AuthService(mock_db) with patch.object(service, "_reset_failed_login", new_callable=AsyncMock): with patch.object(service, "_log_audit", new_callable=AsyncMock): with pytest.raises(TOTPRequiredError): await service.login("test@example.com", "Password1!", totp_code=None)