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>
119 lines
3.7 KiB
Python
119 lines
3.7 KiB
Python
"""Unit tests for User model properties.
|
|
|
|
Tests the computed properties on the User model that don't require
|
|
database access.
|
|
"""
|
|
|
|
from datetime import UTC, datetime
|
|
from unittest.mock import MagicMock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.db.models.user import User
|
|
|
|
|
|
@pytest.fixture
|
|
def user():
|
|
"""Create a test user with minimal attributes.
|
|
|
|
Returns a User model instance without database persistence.
|
|
"""
|
|
user = User(
|
|
email="test@example.com",
|
|
display_name="Test User",
|
|
avatar_url=None,
|
|
oauth_provider="google",
|
|
oauth_id="google-123",
|
|
is_premium=False,
|
|
premium_until=None,
|
|
)
|
|
user.id = uuid4()
|
|
user.created_at = datetime.now(UTC)
|
|
user.updated_at = datetime.now(UTC)
|
|
user.decks = []
|
|
user.linked_accounts = []
|
|
return user
|
|
|
|
|
|
class TestHasStarterDeck:
|
|
"""Tests for the has_starter_deck computed property."""
|
|
|
|
def test_returns_false_when_no_decks(self, user):
|
|
"""Test has_starter_deck is False when user has no decks.
|
|
|
|
New users start with no decks, so has_starter_deck should be False.
|
|
This is used by the frontend to redirect to starter selection.
|
|
"""
|
|
user.decks = []
|
|
assert user.has_starter_deck is False
|
|
|
|
def test_returns_false_when_only_regular_decks(self, user):
|
|
"""Test has_starter_deck is False when user only has regular decks.
|
|
|
|
Users can create custom decks without selecting a starter.
|
|
has_starter_deck should only be True for actual starter decks.
|
|
"""
|
|
regular_deck = MagicMock()
|
|
regular_deck.is_starter = False
|
|
user.decks = [regular_deck]
|
|
assert user.has_starter_deck is False
|
|
|
|
def test_returns_true_when_has_starter_deck(self, user):
|
|
"""Test has_starter_deck is True when user has selected a starter.
|
|
|
|
After selecting a starter deck, the property should return True.
|
|
This allows the frontend to skip the starter selection page.
|
|
"""
|
|
starter_deck = MagicMock()
|
|
starter_deck.is_starter = True
|
|
user.decks = [starter_deck]
|
|
assert user.has_starter_deck is True
|
|
|
|
def test_returns_true_when_starter_among_multiple_decks(self, user):
|
|
"""Test has_starter_deck is True even with mixed deck types.
|
|
|
|
Users can have both starter and custom decks. As long as one
|
|
starter deck exists, the property should return True.
|
|
"""
|
|
regular_deck = MagicMock()
|
|
regular_deck.is_starter = False
|
|
starter_deck = MagicMock()
|
|
starter_deck.is_starter = True
|
|
user.decks = [regular_deck, starter_deck]
|
|
assert user.has_starter_deck is True
|
|
|
|
|
|
class TestMaxDecks:
|
|
"""Tests for the max_decks computed property."""
|
|
|
|
def test_returns_5_for_free_users(self, user):
|
|
"""Test free users have 5 deck slots.
|
|
|
|
Free users should be limited to 5 decks to encourage premium upgrades.
|
|
"""
|
|
user.is_premium = False
|
|
assert user.max_decks == 5
|
|
|
|
def test_returns_999_for_premium_users(self, user):
|
|
"""Test premium users have unlimited deck slots.
|
|
|
|
Premium users get 999 slots (effectively unlimited) as a benefit.
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
user.is_premium = True
|
|
user.premium_until = datetime.now(UTC) + timedelta(days=30)
|
|
assert user.max_decks == 999
|
|
|
|
def test_returns_5_for_expired_premium(self, user):
|
|
"""Test expired premium users revert to free limits.
|
|
|
|
When premium expires, users should be treated as free users.
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
user.is_premium = True
|
|
user.premium_until = datetime.now(UTC) - timedelta(days=1) # Expired
|
|
assert user.max_decks == 5
|