mantimon-tcg/backend/tests/api/conftest.py
Cal Corum 996c43fbd9 Implement Phase 2: Authentication system
Complete OAuth-based authentication with JWT session management:

Core Services:
- JWT service for access/refresh token creation and verification
- Token store with Redis-backed refresh token revocation
- User service for CRUD operations and OAuth-based creation
- Google and Discord OAuth services with full flow support

API Endpoints:
- GET /api/auth/{google,discord} - Start OAuth flows
- GET /api/auth/{google,discord}/callback - Handle OAuth callbacks
- POST /api/auth/refresh - Exchange refresh token for new access token
- POST /api/auth/logout - Revoke single refresh token
- POST /api/auth/logout-all - Revoke all user sessions
- GET/PATCH /api/users/me - User profile management
- GET /api/users/me/linked-accounts - List OAuth providers
- GET /api/users/me/sessions - Count active sessions

Infrastructure:
- Pydantic schemas for auth/user request/response models
- FastAPI dependencies (get_current_user, get_current_premium_user)
- OAuthLinkedAccount model for multi-provider support
- Alembic migration for oauth_linked_accounts table

Dependencies added: email-validator, fakeredis (dev), respx (dev)

84 new tests, 1058 total passing
2026-01-27 21:49:59 -06:00

134 lines
3.6 KiB
Python

"""Test fixtures for API endpoint tests.
Provides fixtures for testing FastAPI endpoints with mocked dependencies.
"""
from contextlib import asynccontextmanager
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch
from uuid import uuid4
import fakeredis.aioredis
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.api import deps as api_deps
from app.api.auth import router as auth_router
from app.api.users import router as users_router
from app.db.models import User
from app.services.jwt_service import create_access_token, create_refresh_token
@pytest.fixture
def fake_redis():
"""Provide a fake Redis instance for testing."""
return fakeredis.aioredis.FakeRedis(decode_responses=True)
@pytest.fixture
def mock_get_redis(fake_redis):
"""Mock the get_redis context manager to use fake Redis."""
@asynccontextmanager
async def _mock_get_redis():
yield fake_redis
return _mock_get_redis
@pytest.fixture
def test_user():
"""Create a test user object.
Returns a User model instance that can be used in tests.
The user is not persisted to database.
"""
user = User(
email="test@example.com",
display_name="Test User",
avatar_url="https://example.com/avatar.jpg",
oauth_provider="google",
oauth_id="google-123",
is_premium=False,
premium_until=None,
)
# Manually set the ID since we're not using database
user.id = str(uuid4())
user.created_at = datetime.now(UTC)
user.updated_at = datetime.now(UTC)
user.last_login = None
user.linked_accounts = []
return user
@pytest.fixture
def premium_user(test_user):
"""Create a premium test user."""
from datetime import timedelta
test_user.is_premium = True
test_user.premium_until = datetime.now(UTC) + timedelta(days=30)
return test_user
@pytest.fixture
def access_token(test_user):
"""Create a valid access token for the test user."""
from uuid import UUID
user_id = UUID(test_user.id) if isinstance(test_user.id, str) else test_user.id
return create_access_token(user_id)
@pytest.fixture
def refresh_token_data(test_user):
"""Create a valid refresh token and JTI for the test user."""
from uuid import UUID
user_id = UUID(test_user.id) if isinstance(test_user.id, str) else test_user.id
token, jti = create_refresh_token(user_id)
return {"token": token, "jti": jti, "user_id": user_id}
@pytest.fixture
def mock_db_session():
"""Create a mock database session."""
return MagicMock(spec=AsyncSession)
@pytest.fixture
def app(mock_get_redis, mock_db_session):
"""Create a test FastAPI app with mocked Redis and DB.
This creates a minimal app with just the auth and users routers,
with Redis and database mocked.
"""
# Create test app (no lifespan since we're mocking everything)
test_app = FastAPI()
test_app.include_router(auth_router, prefix="/api")
test_app.include_router(users_router, prefix="/api")
# Override get_db dependency to return mock session
async def override_get_db():
yield mock_db_session
test_app.dependency_overrides[api_deps.get_db] = override_get_db
# Patch get_redis globally for this app
with (
patch("app.api.auth.get_redis", mock_get_redis),
patch("app.services.token_store.get_redis", mock_get_redis),
):
yield test_app
# Clean up overrides
test_app.dependency_overrides.clear()
@pytest.fixture
def client(app):
"""Create a test client for the app."""
return TestClient(app)