"""Tests for authentication service.""" # ruff: noqa: S106, SLF001, PLC0415 import pytest import pytest_asyncio from fastapi import HTTPException from sqlmodel.ext.asyncio.session import AsyncSession from app.models.plan import Plan from app.models.user import User from app.schemas.auth import UserLoginRequest, UserRegisterRequest from app.services.auth import AuthService # Constants HTTP_400_BAD_REQUEST = 400 HTTP_401_UNAUTHORIZED = 401 HTTP_404_NOT_FOUND = 404 class TestAuthService: """Test authentication service operations.""" @pytest_asyncio.fixture def auth_service(self, test_session: AsyncSession) -> AuthService: """Create an auth service instance.""" return AuthService(test_session) @pytest.mark.asyncio async def test_register_success( self, auth_service: AuthService, ensure_plans: tuple[Plan, Plan], test_user_data: dict[str, str], ) -> None: """Test successful user registration.""" request = UserRegisterRequest(**test_user_data) response = await auth_service.register(request) # Check user data assert response.user.email == test_user_data["email"] assert response.user.name == test_user_data["name"] assert response.user.role == "admin" # First user gets admin role assert response.user.is_active is True # First user gets pro plan free_plan, pro_plan = ensure_plans assert response.user.credits == pro_plan.credits assert response.user.plan["code"] == pro_plan.code # Check token assert response.token.access_token is not None assert response.token.token_type == "bearer" assert response.token.expires_in > 0 @pytest.mark.asyncio async def test_register_duplicate_email( self, auth_service: AuthService, test_user: User, ) -> None: """Test registration with duplicate email.""" request = UserRegisterRequest( email=test_user.email, password="password123", name="Another User", ) with pytest.raises(HTTPException) as exc_info: await auth_service.register(request) assert exc_info.value.status_code == HTTP_400_BAD_REQUEST assert "Email address is already registered" in exc_info.value.detail @pytest.mark.asyncio async def test_login_success( self, auth_service: AuthService, test_user: User, test_login_data: dict[str, str], ) -> None: """Test successful user login.""" request = UserLoginRequest(**test_login_data) response = await auth_service.login(request) # Check user data assert response.user.id == test_user.id assert response.user.email == test_user.email assert response.user.name == test_user.name assert response.user.role == test_user.role assert response.user.is_active == test_user.is_active # Check token assert response.token.access_token is not None assert response.token.token_type == "bearer" assert response.token.expires_in > 0 @pytest.mark.asyncio async def test_login_invalid_email(self, auth_service: AuthService) -> None: """Test login with invalid email.""" request = UserLoginRequest( email="nonexistent@example.com", password="password123", ) with pytest.raises(HTTPException) as exc_info: await auth_service.login(request) assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED assert "Invalid email or password" in exc_info.value.detail @pytest.mark.asyncio async def test_login_invalid_password( self, auth_service: AuthService, test_user: User, ) -> None: """Test login with invalid password.""" request = UserLoginRequest(email=test_user.email, password="wrongpassword") with pytest.raises(HTTPException) as exc_info: await auth_service.login(request) assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED assert "Invalid email or password" in exc_info.value.detail @pytest.mark.asyncio async def test_login_inactive_user( self, auth_service: AuthService, test_user: User, test_session: AsyncSession, ) -> None: """Test login with inactive user.""" # Store the email before deactivating user_email = test_user.email # Deactivate the user test_user.is_active = False await test_session.commit() request = UserLoginRequest(email=user_email, password="testpassword123") with pytest.raises(HTTPException) as exc_info: await auth_service.login(request) assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED assert "Account is deactivated" in exc_info.value.detail @pytest.mark.asyncio async def test_login_user_without_password( self, auth_service: AuthService, test_user: User, test_session: AsyncSession, ) -> None: """Test login with user that has no password hash.""" # Store the email before removing password user_email = test_user.email # Remove password hash test_user.password_hash = None await test_session.commit() request = UserLoginRequest(email=user_email, password="anypassword") with pytest.raises(HTTPException) as exc_info: await auth_service.login(request) assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED assert "Invalid email or password" in exc_info.value.detail @pytest.mark.asyncio async def test_get_current_user_success( self, auth_service: AuthService, test_user: User, ) -> None: """Test getting current user successfully.""" user = await auth_service.get_current_user(test_user.id) assert user.id == test_user.id assert user.email == test_user.email assert user.name == test_user.name assert user.is_active == test_user.is_active @pytest.mark.asyncio async def test_get_current_user_not_found(self, auth_service: AuthService) -> None: """Test getting current user when user doesn't exist.""" with pytest.raises(HTTPException) as exc_info: await auth_service.get_current_user(99999) assert exc_info.value.status_code == HTTP_404_NOT_FOUND assert "User not found" in exc_info.value.detail @pytest.mark.asyncio async def test_get_current_user_inactive( self, auth_service: AuthService, test_user: User, test_session: AsyncSession, ) -> None: """Test getting current user when user is inactive.""" # Store the user ID before deactivating user_id = test_user.id # Deactivate the user test_user.is_active = False await test_session.commit() with pytest.raises(HTTPException) as exc_info: await auth_service.get_current_user(user_id) assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED assert "Account is deactivated" in exc_info.value.detail @pytest.mark.asyncio async def test_create_access_token( self, auth_service: AuthService, test_user: User, ) -> None: """Test access token creation.""" token_response = auth_service._create_access_token(test_user) assert token_response.access_token is not None assert token_response.token_type == "bearer" assert token_response.expires_in > 0 # Verify token contains correct data from app.utils.auth import JWTUtils decoded = JWTUtils.decode_access_token(token_response.access_token) assert decoded["sub"] == str(test_user.id) assert decoded["email"] == test_user.email assert decoded["role"] == test_user.role @pytest.mark.asyncio async def test_create_user_response( self, auth_service: AuthService, test_user: User, test_session: AsyncSession, ) -> None: """Test user response creation.""" # Ensure plan relationship is loaded await test_session.refresh(test_user, ["plan"]) user_response = await auth_service.create_user_response(test_user) assert user_response.id == test_user.id assert user_response.email == test_user.email assert user_response.name == test_user.name assert user_response.role == test_user.role assert user_response.credits == test_user.credits assert user_response.is_active == test_user.is_active assert user_response.plan["id"] == test_user.plan.id assert user_response.plan["code"] == test_user.plan.code