The frontend routing guard checks has_starter_deck to decide whether to redirect users to starter selection. The field was missing from the API response, causing authenticated users with a starter deck to be incorrectly redirected to /starter on page refresh. - Add has_starter_deck computed property to User model - Add has_starter_deck field to UserResponse schema - Add unit tests for User model properties - Add API tests for has_starter_deck in profile response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
135 lines
3.7 KiB
Python
135 lines
3.7 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 = []
|
|
user.decks = [] # No decks by default (no starter selected)
|
|
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)
|