Files
sdb2-backend/tests/api/v1/test_oauth_endpoints.py
JSC 51423779a8 feat: Implement OAuth2 authentication with Google and GitHub
- Added OAuth2 endpoints for Google and GitHub authentication.
- Created OAuth service to handle provider interactions and user info retrieval.
- Implemented user OAuth repository for managing user OAuth links in the database.
- Updated auth service to support linking existing users and creating new users via OAuth.
- Added CORS middleware to allow frontend access.
- Created tests for OAuth endpoints and service functionality.
- Introduced environment configuration for OAuth client IDs and secrets.
- Added logging for OAuth operations and error handling.
2025-07-26 14:38:13 +02:00

152 lines
5.9 KiB
Python

"""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"]