# tests/test_auth_service.py import pytest from app.exceptions.auth import ( InvalidCredentialsException, UserAlreadyExistsException, UserNotActiveException, ) from app.exceptions.base import ValidationException from app.services.auth_service import AuthService from models.schema.auth import UserLogin, UserRegister @pytest.mark.unit @pytest.mark.auth class TestAuthService: """Test suite for AuthService following the application's testing patterns""" def setup_method(self): """Setup method following the same pattern as admin service tests""" self.service = AuthService() def test_register_user_success(self, db): """Test successful user registration""" user_data = UserRegister( email="newuser@example.com", username="newuser123", password="securepass123" ) user = self.service.register_user(db, user_data) assert user is not None assert user.email == "newuser@example.com" assert user.username == "newuser123" assert user.role == "user" assert user.is_active is True assert user.hashed_password != "securepass123" # Should be hashed def test_register_user_email_already_exists(self, db, test_user): """Test registration fails when email already exists""" user_data = UserRegister( email=test_user.email, # Use existing email username="differentuser", password="securepass123", ) with pytest.raises(UserAlreadyExistsException) as exc_info: self.service.register_user(db, user_data) exception = exc_info.value assert exception.error_code == "USER_ALREADY_EXISTS" assert exception.status_code == 409 assert "Email already registered" in exception.message assert exception.details["field"] == "email" def test_register_user_username_already_exists(self, db, test_user): """Test registration fails when username already exists""" user_data = UserRegister( email="different@example.com", username=test_user.username, # Use existing username password="securepass123", ) with pytest.raises(UserAlreadyExistsException) as exc_info: self.service.register_user(db, user_data) exception = exc_info.value assert exception.error_code == "USER_ALREADY_EXISTS" assert exception.status_code == 409 assert "Username already taken" in exception.message assert exception.details["field"] == "username" def test_login_user_success(self, db, test_user): """Test successful user login""" user_credentials = UserLogin( username=test_user.username, password="testpass123" ) result = self.service.login_user(db, user_credentials) assert "token_data" in result assert "user" in result assert result["user"].id == test_user.id assert result["user"].username == test_user.username assert "access_token" in result["token_data"] assert "token_type" in result["token_data"] assert "expires_in" in result["token_data"] def test_login_user_wrong_username(self, db): """Test login fails with wrong username""" user_credentials = UserLogin(username="nonexistentuser", password="testpass123") with pytest.raises(InvalidCredentialsException) as exc_info: self.service.login_user(db, user_credentials) exception = exc_info.value assert exception.error_code == "INVALID_CREDENTIALS" assert exception.status_code == 401 assert "Incorrect username or password" in exception.message def test_login_user_wrong_password(self, db, test_user): """Test login fails with wrong password""" user_credentials = UserLogin( username=test_user.username, password="wrongpassword" ) with pytest.raises(InvalidCredentialsException) as exc_info: self.service.login_user(db, user_credentials) exception = exc_info.value assert exception.error_code == "INVALID_CREDENTIALS" assert exception.status_code == 401 assert "Incorrect username or password" in exception.message def test_login_user_inactive_user(self, db, test_user): """Test login fails for inactive user""" # Deactivate user test_user.is_active = False db.commit() user_credentials = UserLogin( username=test_user.username, password="testpass123" ) with pytest.raises(UserNotActiveException) as exc_info: self.service.login_user(db, user_credentials) exception = exc_info.value assert exception.error_code == "USER_NOT_ACTIVE" assert exception.status_code == 403 assert "User account is not active" in exception.message # Reactivate for cleanup test_user.is_active = True db.commit() def test_get_user_by_email(self, db, test_user): """Test getting user by email""" user = self.service.get_user_by_email(db, test_user.email) assert user is not None assert user.id == test_user.id assert user.email == test_user.email def test_get_user_by_email_not_found(self, db): """Test getting user by email when user doesn't exist""" user = self.service.get_user_by_email(db, "nonexistent@example.com") assert user is None def test_get_user_by_username(self, db, test_user): """Test getting user by username""" user = self.service.get_user_by_username(db, test_user.username) assert user is not None assert user.id == test_user.id assert user.username == test_user.username def test_get_user_by_username_not_found(self, db): """Test getting user by username when user doesn't exist""" user = self.service.get_user_by_username(db, "nonexistentuser") assert user is None def test_email_exists_true(self, db, test_user): """Test email_exists returns True when email exists""" exists = self.service._email_exists(db, test_user.email) assert exists is True def test_email_exists_false(self, db): """Test email_exists returns False when email doesn't exist""" exists = self.service._email_exists(db, "nonexistent@example.com") assert exists is False def test_username_exists_true(self, db, test_user): """Test username_exists returns True when username exists""" exists = self.service._username_exists(db, test_user.username) assert exists is True def test_username_exists_false(self, db): """Test username_exists returns False when username doesn't exist""" exists = self.service._username_exists(db, "nonexistentuser") assert exists is False def test_authenticate_user_success(self, db, test_user): """Test successful user authentication""" user = self.service.authenticate_user(db, test_user.username, "testpass123") assert user is not None assert user.id == test_user.id assert user.username == test_user.username def test_authenticate_user_wrong_password(self, db, test_user): """Test authentication fails with wrong password""" user = self.service.authenticate_user(db, test_user.username, "wrongpassword") assert user is None def test_authenticate_user_nonexistent(self, db): """Test authentication fails with nonexistent user""" user = self.service.authenticate_user(db, "nonexistentuser", "password") assert user is None def test_create_access_token(self, test_user): """Test creating access token for user""" token_data = self.service.create_access_token(test_user) assert "access_token" in token_data assert "token_type" in token_data assert "expires_in" in token_data assert token_data["token_type"] == "bearer" assert isinstance(token_data["expires_in"], int) assert token_data["expires_in"] > 0 def test_create_access_token_failure(self, test_user, monkeypatch): """Test creating access token handles failures""" # Mock the auth_manager to raise an exception def mock_create_token(*args, **kwargs): raise Exception("Token creation failed") monkeypatch.setattr( self.service.auth_manager, "create_access_token", mock_create_token ) with pytest.raises(ValidationException) as exc_info: self.service.create_access_token(test_user) exception = exc_info.value assert exception.error_code == "VALIDATION_ERROR" assert "Failed to create access token" in exception.message def test_hash_password(self): """Test password hashing""" password = "testpassword123" hashed = self.service.hash_password(password) assert hashed != password assert len(hashed) > len(password) assert hashed.startswith("$") # bcrypt hash format def test_hash_password_different_results(self): """Test that hashing same password produces different hashes (salt)""" password = "testpassword123" hash1 = self.service.hash_password(password) hash2 = self.service.hash_password(password) assert hash1 != hash2 # Should be different due to salt def test_hash_password_failure(self, monkeypatch): """Test password hashing handles failures""" # Mock the auth_manager to raise an exception def mock_hash_password(*args, **kwargs): raise Exception("Hashing failed") monkeypatch.setattr( self.service.auth_manager, "hash_password", mock_hash_password ) with pytest.raises(ValidationException) as exc_info: self.service.hash_password("testpassword") exception = exc_info.value assert exception.error_code == "VALIDATION_ERROR" assert "Failed to hash password" in exception.message # Test database error handling def test_register_user_database_error(self, db_with_error): """Test user registration handles database errors""" user_data = UserRegister( email="test@example.com", username="testuser", password="password123" ) with pytest.raises(ValidationException) as exc_info: self.service.register_user(db_with_error, user_data) exception = exc_info.value assert exception.error_code == "VALIDATION_ERROR" assert "Registration failed" in exception.message def test_login_user_database_error(self, db_with_error): """Test user login handles database errors""" user_credentials = UserLogin(username="testuser", password="password123") with pytest.raises(InvalidCredentialsException): self.service.login_user(db_with_error, user_credentials)