Phase 7: End-to-End API Testing - Database setup, migrations, and application fixes
- Switched from psycopg2 to asyncpg for async SQLAlchemy support - Fixed router registration in main.py - removed duplicate prefixes - Added user_data export to routers/__init__.py - Refactored decks router to use DeckManager service layer - Integrated FuzzyCardMatcher into card_router search endpoints - Made WishlistCreate.card_id optional for proper schema validation - Set PostgreSQL password and configured scram-sha-256 auth - Updated alembic.ini to use local PostgreSQL instead of Docker hostname - Created generic_schemas.py for reusable schema patterns - Added test_routers.py and test_schema_validation.py test files - All 6 Alembic migrations applied successfully (37 tables created) - Application running on port 8000 with all services connected
This commit is contained in:
@@ -3,7 +3,7 @@ Pydantic schemas for user data features.
|
||||
|
||||
Covers sessions, decks, replays, cards, groups, networks, preferences, and activity logs.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -72,8 +72,7 @@ class SessionResponse(BaseModel):
|
||||
expires_at: datetime
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SessionCleanupResponse(BaseModel):
|
||||
@@ -108,8 +107,7 @@ class DeckVersionResponse(BaseModel):
|
||||
comment: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckVersionListResponse(BaseModel):
|
||||
@@ -160,8 +158,7 @@ class GameReplayResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
players: List[Dict[str, Any]] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GameReplayListResponse(BaseModel):
|
||||
@@ -199,8 +196,7 @@ class GameOutcomeResponse(BaseModel):
|
||||
rating_change: Optional[int]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GameOutcomeListResponse(BaseModel):
|
||||
@@ -225,8 +221,7 @@ class UserStatisticsResponse(BaseModel):
|
||||
last_game_date: Optional[datetime]
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class StatisticsUpdateResponse(BaseModel):
|
||||
@@ -240,94 +235,12 @@ class StatisticsUpdateResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
# ===== Card Collection Schemas =====
|
||||
|
||||
class CardCollectionCreate(BaseModel):
|
||||
"""Card collection item creation request."""
|
||||
card_id: int
|
||||
quantity: int = Field(1, ge=1)
|
||||
condition: str = Field("NEAR_MINT", max_length=20)
|
||||
language: str = Field("EN", max_length=5)
|
||||
is_foil: bool = False
|
||||
is_alt_art: bool = False
|
||||
acquired_date: Optional[datetime] = None
|
||||
acquisition_method: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
# ===== Card Collection Schemas (moved to user_card_collection.py) =====
|
||||
# These are kept here for backward compatibility but should be imported from user_card_collection.py
|
||||
|
||||
|
||||
class CardCollectionUpdate(BaseModel):
|
||||
"""Card collection item update request."""
|
||||
quantity: Optional[int] = None
|
||||
condition: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
is_foil: Optional[bool] = None
|
||||
is_alt_art: Optional[bool] = None
|
||||
acquired_date: Optional[datetime] = None
|
||||
acquisition_method: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class CardCollectionResponse(BaseModel):
|
||||
"""Card collection item response."""
|
||||
id: int
|
||||
user_id: int
|
||||
card_id: int
|
||||
quantity: int
|
||||
condition: str
|
||||
language: str
|
||||
is_foil: bool
|
||||
is_alt_art: bool
|
||||
acquired_date: Optional[datetime]
|
||||
acquisition_method: Optional[str]
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CardCollectionListResponse(BaseModel):
|
||||
"""List of user card collection."""
|
||||
cards: List[CardCollectionResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
total_pages: int
|
||||
|
||||
|
||||
# ===== Wishlist Schemas =====
|
||||
|
||||
class WishlistCreate(BaseModel):
|
||||
"""Wishlist item creation request."""
|
||||
card_id: int
|
||||
max_price: Optional[float] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class WishlistUpdate(BaseModel):
|
||||
"""Wishlist item update request."""
|
||||
max_price: Optional[float] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class WishlistResponse(BaseModel):
|
||||
"""Wishlist item response."""
|
||||
id: int
|
||||
user_id: int
|
||||
card_id: int
|
||||
max_price: Optional[float]
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class WishlistListResponse(BaseModel):
|
||||
"""List of wishlist items."""
|
||||
items: List[WishlistResponse]
|
||||
total: int
|
||||
# ===== Wishlist Schemas (moved to user_card_collection.py) =====
|
||||
# These are kept here for backward compatibility but should be imported from user_card_collection.py
|
||||
|
||||
|
||||
# ===== Group Schemas =====
|
||||
@@ -377,8 +290,7 @@ class GroupResponse(BaseModel):
|
||||
member_count: int = 0
|
||||
is_member: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupListResponse(BaseModel):
|
||||
@@ -401,8 +313,7 @@ class GroupChatMessageResponse(BaseModel):
|
||||
message: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupChatMessageListResponse(BaseModel):
|
||||
@@ -447,8 +358,7 @@ class NetworkResponse(BaseModel):
|
||||
member_count: int = 0
|
||||
is_member: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class NetworkListResponse(BaseModel):
|
||||
@@ -480,8 +390,7 @@ class UserPreferenceResponse(BaseModel):
|
||||
language: str
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Activity Log Schemas =====
|
||||
@@ -495,8 +404,7 @@ class ActivityLogEntry(BaseModel):
|
||||
ip_address: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class ActivityLogListResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user