"""Tests for OAuth authentication endpoints.""" from typing import Any from unittest.mock import AsyncMock, patch import pytest from httpx import AsyncClient from app.services.oauth import OAuthUserInfo class TestOAuthEndpoints: """Test OAuth API endpoints.""" @pytest.mark.asyncio async def test_get_oauth_providers(self, test_client: AsyncClient) -> None: """Test getting list of OAuth providers.""" response = await test_client.get("/api/v1/oauth/providers") assert response.status_code == 200 data = response.json() assert "providers" in data assert "google" in data["providers"] assert "github" in data["providers"] @pytest.mark.asyncio async def test_oauth_authorize_google(self, test_client: AsyncClient) -> None: """Test OAuth authorization URL generation for Google.""" with patch("app.services.oauth.OAuthService.generate_state") as mock_state: mock_state.return_value = "test_state_123" response = await test_client.get("/api/v1/oauth/google/authorize") assert response.status_code == 200 data = response.json() assert "authorization_url" in data assert "state" in data assert data["state"] == "test_state_123" assert "accounts.google.com" in data["authorization_url"] @pytest.mark.asyncio async def test_oauth_authorize_github(self, test_client: AsyncClient) -> None: """Test OAuth authorization URL generation for GitHub.""" with patch("app.services.oauth.OAuthService.generate_state") as mock_state: mock_state.return_value = "test_state_456" response = await test_client.get("/api/v1/oauth/github/authorize") assert response.status_code == 200 data = response.json() assert "authorization_url" in data assert "state" in data assert data["state"] == "test_state_456" assert "github.com" in data["authorization_url"] @pytest.mark.asyncio async def test_oauth_authorize_invalid_provider( self, test_client: AsyncClient ) -> None: """Test OAuth authorization with invalid provider.""" response = await test_client.get("/api/v1/oauth/invalid/authorize") assert response.status_code == 400 data = response.json() assert "Unsupported OAuth provider" in data["detail"] @pytest.mark.asyncio async def test_oauth_callback_new_user( self, test_client: AsyncClient, ensure_plans: tuple[Any, Any] ) -> None: """Test OAuth callback for new user creation.""" # Mock OAuth user info mock_user_info = OAuthUserInfo( provider="google", provider_user_id="google_123", email="newuser@gmail.com", name="New User", picture="https://example.com/avatar.jpg", ) # Mock the entire handle_callback method to avoid actual OAuth API calls with patch("app.services.oauth.OAuthService.handle_callback") as mock_callback: mock_callback.return_value = mock_user_info response = await test_client.get( "/api/v1/oauth/google/callback", params={"code": "auth_code_123", "state": "test_state"}, follow_redirects=False, ) # OAuth callback should successfully process and redirect to frontend assert response.status_code == 302 assert response.headers["location"] == "http://localhost:8001/?auth=success" # The fact that we get a 302 redirect means the OAuth login was successful # Detailed cookie testing can be done in integration tests @pytest.mark.asyncio async def test_oauth_callback_existing_user_link( self, test_client: AsyncClient, test_user: Any, ensure_plans: tuple[Any, Any] ) -> None: """Test OAuth callback for linking to existing user.""" # Mock OAuth user info with same email as test user mock_user_info = OAuthUserInfo( provider="github", provider_user_id="github_456", email=test_user.email, # Same email as existing user name="Test User", picture="https://github.com/avatar.jpg", ) # Mock the entire handle_callback method to avoid actual OAuth API calls with patch("app.services.oauth.OAuthService.handle_callback") as mock_callback: mock_callback.return_value = mock_user_info response = await test_client.get( "/api/v1/oauth/github/callback", params={"code": "auth_code_456", "state": "test_state"}, follow_redirects=False, ) # OAuth callback should successfully process and redirect to frontend assert response.status_code == 302 assert response.headers["location"] == "http://localhost:8001/?auth=success" # The fact that we get a 302 redirect means the OAuth login was successful # Detailed cookie testing can be done in integration tests @pytest.mark.asyncio async def test_oauth_callback_missing_code(self, test_client: AsyncClient) -> None: """Test OAuth callback with missing authorization code.""" response = await test_client.get( "/api/v1/oauth/google/callback", params={"state": "test_state"}, # Missing code parameter ) assert response.status_code == 422 # Validation error @pytest.mark.asyncio async def test_oauth_callback_invalid_provider( self, test_client: AsyncClient ) -> None: """Test OAuth callback with invalid provider.""" response = await test_client.get( "/api/v1/oauth/invalid/callback", params={"code": "auth_code_123", "state": "test_state"}, ) assert response.status_code == 400 data = response.json() assert "Unsupported OAuth provider" in data["detail"]