"""Integration tests for authentication API endpoints.""" import pytest from unittest.mock import patch, MagicMock, AsyncMock import json class TestRegistration: """Test user registration endpoints.""" @patch("backend.services.email_service.EmailService.send_verification_email") def test_register_success(self, mock_send_email, client, test_user_data): """Test successful user registration.""" # Mock email sending mock_send_email.return_value = AsyncMock() # Register new user response = client.post("/api/auth/register", json=test_user_data) # Check response assert response.status_code == 201 data = response.json() assert data["email"] == test_user_data["email"] assert data["is_verified"] is False assert "id" in data assert "password_hash" not in data def test_register_duplicate_email(self, client, test_user_data): """Test registration with duplicate email.""" # Register first user with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email: mock_email.return_value = AsyncMock() response = client.post("/api/auth/register", json=test_user_data) assert response.status_code == 201 # Try to register with same email response = client.post("/api/auth/register", json=test_user_data) assert response.status_code == 400 assert "already registered" in response.json()["detail"].lower() def test_register_weak_password(self, client): """Test registration with weak password.""" data = { "email": "test@example.com", "password": "weak", "confirm_password": "weak" } response = client.post("/api/auth/register", json=data) assert response.status_code == 422 def test_register_password_mismatch(self, client): """Test registration with mismatched passwords.""" data = { "email": "test@example.com", "password": "SecurePassword123!", "confirm_password": "DifferentPassword123!" } response = client.post("/api/auth/register", json=data) assert response.status_code == 400 assert "do not match" in response.json()["detail"].lower() def test_register_invalid_email(self, client): """Test registration with invalid email format.""" data = { "email": "not-an-email", "password": "SecurePassword123!", "confirm_password": "SecurePassword123!" } response = client.post("/api/auth/register", json=data) assert response.status_code == 422 class TestLogin: """Test login endpoints.""" @pytest.fixture(autouse=True) def setup_user(self, client, test_user_data): """Create a test user before each test.""" with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email: mock_email.return_value = AsyncMock() # Set user as verified for login tests with patch("backend.models.user.User.is_verified", True): response = client.post("/api/auth/register", json=test_user_data) assert response.status_code == 201 self.user_data = test_user_data def test_login_success(self, client): """Test successful login with valid credentials.""" login_data = { "email": self.user_data["email"], "password": self.user_data["password"] } # Mock user verification status with patch("backend.services.auth_service.AuthService.authenticate_user") as mock_auth: from backend.models.user import User mock_user = User( id="test-id", email=self.user_data["email"], password_hash="hashed", is_verified=True, is_active=True ) mock_auth.return_value = mock_user response = client.post("/api/auth/login", json=login_data) assert response.status_code == 200 data = response.json() assert "access_token" in data assert "refresh_token" in data assert data["token_type"] == "bearer" assert data["expires_in"] == 900 # 15 minutes def test_login_invalid_password(self, client): """Test login with wrong password.""" login_data = { "email": self.user_data["email"], "password": "WrongPassword123!" } response = client.post("/api/auth/login", json=login_data) assert response.status_code == 401 assert "Invalid email or password" in response.json()["detail"] def test_login_nonexistent_user(self, client): """Test login with non-existent email.""" login_data = { "email": "nonexistent@example.com", "password": "SomePassword123!" } response = client.post("/api/auth/login", json=login_data) assert response.status_code == 401 assert "Invalid email or password" in response.json()["detail"] def test_login_unverified_user(self, client): """Test login with unverified account.""" # Create unverified user unverified_data = { "email": "unverified@example.com", "password": "SecurePassword123!", "confirm_password": "SecurePassword123!" } with patch("backend.services.email_service.EmailService.send_verification_email") as mock_email: mock_email.return_value = AsyncMock() response = client.post("/api/auth/register", json=unverified_data) assert response.status_code == 201 # Try to login login_data = { "email": unverified_data["email"], "password": unverified_data["password"] } response = client.post("/api/auth/login", json=login_data) assert response.status_code == 403 assert "not verified" in response.json()["detail"].lower() def test_login_inactive_user(self, client): """Test login with inactive account.""" login_data = { "email": self.user_data["email"], "password": self.user_data["password"] } # Mock user as inactive with patch("backend.services.auth_service.AuthService.authenticate_user") as mock_auth: from backend.models.user import User mock_user = User( id="test-id", email=self.user_data["email"], password_hash="hashed", is_verified=True, is_active=False ) mock_auth.return_value = mock_user response = client.post("/api/auth/login", json=login_data) assert response.status_code == 403 assert "deactivated" in response.json()["detail"].lower() class TestTokenRefresh: """Test token refresh endpoints.""" def test_refresh_token_success(self, client): """Test successful token refresh.""" # Mock token validation with patch("backend.services.auth_service.AuthService.refresh_access_token") as mock_refresh: mock_refresh.return_value = { "access_token": "new_access_token", "token_type": "bearer", "expires_in": 900 } response = client.post("/api/auth/refresh", json={ "refresh_token": "valid_refresh_token" }) assert response.status_code == 200 data = response.json() assert data["access_token"] == "new_access_token" assert "refresh_token" in data def test_refresh_invalid_token(self, client): """Test refresh with invalid token.""" response = client.post("/api/auth/refresh", json={ "refresh_token": "invalid_token" }) assert response.status_code == 401 assert "Invalid refresh token" in response.json()["detail"] def test_refresh_expired_token(self, client): """Test refresh with expired token.""" with patch("backend.services.auth_service.AuthService.refresh_access_token") as mock_refresh: mock_refresh.side_effect = Exception("Token expired") response = client.post("/api/auth/refresh", json={ "refresh_token": "expired_token" }) assert response.status_code == 401 class TestLogout: """Test logout endpoints.""" def test_logout_success(self, client): """Test successful logout.""" with patch("backend.services.auth_service.AuthService.revoke_refresh_token") as mock_revoke: mock_revoke.return_value = True response = client.post("/api/auth/logout", params={ "refresh_token": "valid_refresh_token" }) assert response.status_code == 200 assert response.json()["message"] == "Successfully logged out" def test_logout_invalid_token(self, client): """Test logout with invalid token.""" with patch("backend.services.auth_service.AuthService.revoke_refresh_token") as mock_revoke: mock_revoke.return_value = False response = client.post("/api/auth/logout", params={ "refresh_token": "invalid_token" }) # Should still return success for security assert response.status_code == 200 class TestEmailVerification: """Test email verification endpoints.""" def test_verify_email_success(self, client): """Test successful email verification.""" with patch("backend.services.auth_service.AuthService.verify_email") as mock_verify: mock_verify.return_value = True response = client.post("/api/auth/verify-email", params={ "token": "valid_verification_token" }) assert response.status_code == 200 assert "verified successfully" in response.json()["message"].lower() def test_verify_email_invalid_token(self, client): """Test verification with invalid token.""" with patch("backend.services.auth_service.AuthService.verify_email") as mock_verify: mock_verify.side_effect = ValueError("Invalid token") response = client.post("/api/auth/verify-email", params={ "token": "invalid_token" }) assert response.status_code == 400 assert "Invalid" in response.json()["detail"] def test_resend_verification_email(self, client): """Test resending verification email.""" with patch("backend.core.dependencies.get_current_user") as mock_user: mock_user.return_value = MagicMock(id="user-id", email="test@example.com") with patch("backend.services.email_service.EmailService.send_verification_email") as mock_send: mock_send.return_value = AsyncMock() # Mock authorization header response = client.post( "/api/auth/resend-verification", headers={"Authorization": "Bearer fake_token"} ) assert response.status_code == 200 assert "sent successfully" in response.json()["message"].lower() class TestPasswordReset: """Test password reset endpoints.""" def test_request_password_reset(self, client): """Test password reset request.""" with patch("backend.services.auth_service.AuthService.create_password_reset_token") as mock_create: mock_create.return_value = "reset_token" with patch("backend.services.email_service.EmailService.send_password_reset_email") as mock_send: mock_send.return_value = AsyncMock() response = client.post("/api/auth/reset-password", json={ "email": "test@example.com" }) assert response.status_code == 200 assert "sent" in response.json()["message"].lower() def test_confirm_password_reset(self, client): """Test password reset confirmation.""" with patch("backend.services.auth_service.AuthService.reset_password") as mock_reset: mock_reset.return_value = True response = client.post("/api/auth/reset-password/confirm", json={ "token": "valid_reset_token", "new_password": "NewSecurePassword123!", "confirm_password": "NewSecurePassword123!" }) assert response.status_code == 200 assert "reset successfully" in response.json()["message"].lower() def test_reset_password_weak(self, client): """Test reset with weak password.""" response = client.post("/api/auth/reset-password/confirm", json={ "token": "valid_token", "new_password": "weak", "confirm_password": "weak" }) assert response.status_code == 422 def test_reset_password_mismatch(self, client): """Test reset with mismatched passwords.""" response = client.post("/api/auth/reset-password/confirm", json={ "token": "valid_token", "new_password": "NewSecurePassword123!", "confirm_password": "DifferentPassword123!" }) assert response.status_code == 400 assert "do not match" in response.json()["detail"].lower() class TestProtectedEndpoints: """Test protected endpoint authorization.""" def test_get_current_user(self, client): """Test getting current user profile.""" with patch("backend.core.dependencies.get_current_user") as mock_user: from backend.models.user import User mock_user.return_value = User( id="test-id", email="test@example.com", password_hash="hashed", is_verified=True, is_active=True ) response = client.get( "/api/auth/me", headers={"Authorization": "Bearer valid_token"} ) assert response.status_code == 200 data = response.json() assert data["email"] == "test@example.com" assert "password_hash" not in data def test_protected_endpoint_no_token(self, client): """Test accessing protected endpoint without token.""" response = client.get("/api/auth/me") assert response.status_code == 401 assert "Not authenticated" in response.json()["detail"] def test_protected_endpoint_invalid_token(self, client): """Test accessing protected endpoint with invalid token.""" with patch("backend.core.dependencies.get_current_user") as mock_user: mock_user.side_effect = Exception("Invalid token") response = client.get( "/api/auth/me", headers={"Authorization": "Bearer invalid_token"} ) assert response.status_code == 401