Add detailed Phase 3 (Collections + Decks) project plan
14 tasks covering card ownership, deck building, validation, and starter deck selection. 29 estimated hours total. Key components: - CollectionService for card ownership CRUD - DeckService with slot limits and validation - DeckValidator for rule enforcement - 5 starter deck definitions - REST API endpoints for collections and decks - ~80-90 tests planned Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f82bc8aa1f
commit
4859b2a9cb
620
backend/project_plans/PHASE_3_COLLECTION_DECKS.json
Normal file
620
backend/project_plans/PHASE_3_COLLECTION_DECKS.json
Normal file
@ -0,0 +1,620 @@
|
||||
{
|
||||
"meta": {
|
||||
"version": "1.0.0",
|
||||
"created": "2026-01-28",
|
||||
"lastUpdated": "2026-01-28",
|
||||
"planType": "phase",
|
||||
"phaseId": "PHASE_3",
|
||||
"phaseName": "Collections + Decks",
|
||||
"description": "Card ownership (collections), deck building, validation, and starter deck selection",
|
||||
"totalEstimatedHours": 29,
|
||||
"totalTasks": 14,
|
||||
"completedTasks": 0,
|
||||
"status": "not_started",
|
||||
"masterPlan": "../PROJECT_PLAN_MASTER.json"
|
||||
},
|
||||
|
||||
"goals": [
|
||||
"Implement card ownership tracking (CollectionService) with quantity and source tracking",
|
||||
"Create deck building service (DeckService) with CRUD operations",
|
||||
"Implement comprehensive deck validation (card counts, ownership, Basic Pokemon requirement)",
|
||||
"Enforce deck slot limits (5 free, unlimited premium)",
|
||||
"Define 5 starter deck compositions using real card IDs",
|
||||
"Create starter deck selection flow for new users",
|
||||
"Build REST API endpoints for collections and decks"
|
||||
],
|
||||
|
||||
"architectureNotes": {
|
||||
"collectionModel": {
|
||||
"existing": "Collection model in app/db/models/collection.py - already has user_id, card_definition_id, quantity, source (CardSource enum)",
|
||||
"constraints": "Unique constraint on (user_id, card_definition_id) for upsert pattern",
|
||||
"source_tracking": "CardSource enum: STARTER, BOOSTER, REWARD, PURCHASE, TRADE, GIFT"
|
||||
},
|
||||
"deckModel": {
|
||||
"existing": "Deck model in app/db/models/deck.py - has JSONB cards/energy_cards fields",
|
||||
"validation_state": "is_valid boolean and validation_errors JSONB for storing validation results",
|
||||
"starter_flags": "is_starter and starter_type fields for starter deck tracking"
|
||||
},
|
||||
"deckRules": {
|
||||
"mainDeck": "40 cards exactly (from DeckConfig)",
|
||||
"energyDeck": "20 energy cards (Mantimon house rules - separate energy deck)",
|
||||
"maxCopies": "Max 4 copies of any single card",
|
||||
"basicPokemon": "Min 1 Basic Pokemon required",
|
||||
"deckSlots": "5 for free users, 999 (unlimited) for premium (from User.max_decks property)"
|
||||
},
|
||||
"validationModes": {
|
||||
"campaign": "Requires ownership - must own all cards in deck",
|
||||
"freeplay": "Full collection unlocked - skip ownership validation"
|
||||
},
|
||||
"cardIdFormat": {
|
||||
"pattern": "{set}-{number}-{name}",
|
||||
"example": "a1-001-bulbasaur, a1-094-pikachu, a1-036-charizard-ex",
|
||||
"source": "Loaded by CardService from data/definitions/ JSON files"
|
||||
},
|
||||
"starterDecks": {
|
||||
"types": ["grass", "fire", "water", "psychic", "lightning"],
|
||||
"classic": ["grass", "fire", "water"],
|
||||
"rotating": ["psychic", "lightning"],
|
||||
"composition": "40 Pokemon/Trainer cards + 20 energy (type-specific + colorless)"
|
||||
}
|
||||
},
|
||||
|
||||
"directoryStructure": {
|
||||
"schemas": "backend/app/schemas/",
|
||||
"services": "backend/app/services/",
|
||||
"api": "backend/app/api/",
|
||||
"data": "backend/app/data/",
|
||||
"tests": "backend/tests/services/, backend/tests/api/"
|
||||
},
|
||||
|
||||
"tasks": [
|
||||
{
|
||||
"id": "COLL-001",
|
||||
"name": "Create Collection Pydantic schemas",
|
||||
"description": "Define request/response models for collection operations",
|
||||
"category": "critical",
|
||||
"priority": 1,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{"path": "app/schemas/collection.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"CollectionEntryResponse: card_definition_id, quantity, source, obtained_at",
|
||||
"CollectionResponse: total_unique_cards, total_card_count, entries list",
|
||||
"CollectionAddRequest: card_definition_id, quantity (default 1), source",
|
||||
"CollectionCardResponse: card_definition_id, quantity (for single card lookup)",
|
||||
"Use existing CardSource enum from app.db.models.collection"
|
||||
],
|
||||
"estimatedHours": 1,
|
||||
"notes": "Keep schemas focused on what API needs - model has more fields"
|
||||
},
|
||||
{
|
||||
"id": "COLL-002",
|
||||
"name": "Create Deck Pydantic schemas",
|
||||
"description": "Define request/response models for deck operations",
|
||||
"category": "critical",
|
||||
"priority": 2,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{"path": "app/schemas/deck.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"DeckCreateRequest: name, cards (dict[str, int]), energy_cards (dict[str, int])",
|
||||
"DeckUpdateRequest: name (optional), cards (optional), energy_cards (optional)",
|
||||
"DeckResponse: id, name, cards, energy_cards, is_valid, validation_errors, is_starter, starter_type, created_at, updated_at",
|
||||
"DeckListResponse: decks list, deck_count, deck_limit (None for premium = unlimited)",
|
||||
"DeckValidateRequest: cards, energy_cards (validate without saving)",
|
||||
"DeckValidationResponse: is_valid, errors list",
|
||||
"StarterDeckSelectRequest: starter_type (one of grass/fire/water/psychic/lightning)"
|
||||
],
|
||||
"estimatedHours": 1.5,
|
||||
"notes": "Deck response includes validation state from model"
|
||||
},
|
||||
{
|
||||
"id": "COLL-003",
|
||||
"name": "Create DeckValidator service",
|
||||
"description": "Standalone validation logic for deck rules - no DB dependency",
|
||||
"category": "critical",
|
||||
"priority": 3,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{"path": "app/services/deck_validator.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"DeckValidationResult dataclass: is_valid (bool), errors (list[str])",
|
||||
"DeckValidator class with validate_deck() method",
|
||||
"Parameters: cards (dict[str, int]), energy_cards (dict[str, int]), owned_cards (dict[str, int] | None)",
|
||||
"Validation rules:",
|
||||
" 1. Total cards must equal 40 (from DeckConfig.min_size/max_size)",
|
||||
" 2. Total energy must equal 20 (from DeckConfig.energy_deck_size)",
|
||||
" 3. Max 4 copies of any card (from DeckConfig.max_copies_per_card)",
|
||||
" 4. Min 1 Basic Pokemon (from DeckConfig.min_basic_pokemon)",
|
||||
" 5. All card IDs must exist (via CardService.get_card)",
|
||||
" 6. If owned_cards provided: must own enough copies of each card",
|
||||
"Uses CardService singleton to validate card IDs and check Basic Pokemon",
|
||||
"Returns all errors, not just first one (helpful for UI)"
|
||||
],
|
||||
"estimatedHours": 2,
|
||||
"notes": "Keep validator separate from service for testability and reuse"
|
||||
},
|
||||
{
|
||||
"id": "COLL-004",
|
||||
"name": "Create CollectionService",
|
||||
"description": "Service layer for card collection CRUD operations",
|
||||
"category": "critical",
|
||||
"priority": 4,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-001"],
|
||||
"files": [
|
||||
{"path": "app/services/collection_service.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"CollectionService class following UserService pattern",
|
||||
"get_collection(db, user_id) -> list[Collection]",
|
||||
"get_card_quantity(db, user_id, card_definition_id) -> int",
|
||||
"add_cards(db, user_id, card_definition_id, quantity, source) -> Collection",
|
||||
" - Upsert pattern: increment quantity if exists, create if not",
|
||||
" - Validate card_definition_id exists via CardService",
|
||||
"remove_cards(db, user_id, card_definition_id, quantity) -> Collection | None",
|
||||
" - Decrement quantity, delete row if quantity reaches 0",
|
||||
"has_cards(db, user_id, card_requirements: dict[str, int]) -> bool",
|
||||
" - Check if user owns at least the required quantity of each card",
|
||||
"get_owned_cards_dict(db, user_id) -> dict[str, int]",
|
||||
" - Returns {card_id: quantity} for deck validation",
|
||||
"grant_starter_deck(db, user_id, starter_type) -> list[Collection]",
|
||||
" - Add all cards from starter deck definition",
|
||||
" - Use CardSource.STARTER for source",
|
||||
"Global instance: collection_service = CollectionService()"
|
||||
],
|
||||
"estimatedHours": 2.5,
|
||||
"notes": "Upsert pattern: SELECT FOR UPDATE + INSERT ON CONFLICT or merge"
|
||||
},
|
||||
{
|
||||
"id": "COLL-005",
|
||||
"name": "Create DeckService",
|
||||
"description": "Service layer for deck CRUD with validation",
|
||||
"category": "critical",
|
||||
"priority": 5,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-002", "COLL-003", "COLL-004"],
|
||||
"files": [
|
||||
{"path": "app/services/deck_service.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"DeckService class following service pattern",
|
||||
"create_deck(db, user_id, name, cards, energy_cards, validate_ownership=True) -> Deck",
|
||||
" - Check deck slot limit (user.max_decks)",
|
||||
" - Run DeckValidator.validate_deck()",
|
||||
" - If validate_ownership: pass owned_cards from CollectionService",
|
||||
" - Store validation result in is_valid/validation_errors",
|
||||
" - Allow saving invalid decks (with errors) - don't block UI",
|
||||
"update_deck(db, user_id, deck_id, updates: DeckUpdateRequest) -> Deck | None",
|
||||
" - Verify ownership (user_id matches)",
|
||||
" - Re-validate after update",
|
||||
"delete_deck(db, user_id, deck_id) -> bool",
|
||||
" - Verify ownership before delete",
|
||||
"get_deck(db, user_id, deck_id) -> Deck | None",
|
||||
" - Return only if user owns it",
|
||||
"get_user_decks(db, user_id) -> list[Deck]",
|
||||
"can_create_deck(db, user: User) -> bool",
|
||||
" - Check len(user.decks) < user.max_decks",
|
||||
"get_deck_for_game(db, user_id, deck_id) -> list[CardDefinition]",
|
||||
" - Expand deck dict to list of CardDefinition objects",
|
||||
" - For use by GameEngine in future phases",
|
||||
"validate_deck(cards, energy_cards, owned_cards=None) -> DeckValidationResult",
|
||||
" - Wrapper around DeckValidator for API use",
|
||||
"Global instance: deck_service = DeckService()"
|
||||
],
|
||||
"estimatedHours": 3,
|
||||
"notes": "Allow invalid decks to be saved - validation errors shown in UI"
|
||||
},
|
||||
{
|
||||
"id": "COLL-006",
|
||||
"name": "Create starter deck definitions",
|
||||
"description": "Define 5 starter decks with real card IDs from scraped data",
|
||||
"category": "high",
|
||||
"priority": 6,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{"path": "app/data/starter_decks.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"STARTER_DECKS dict mapping type to deck definition",
|
||||
"Each deck: 40 cards + 20 energy",
|
||||
"Structure: {name, cards: {card_id: qty}, energy_cards: {type: qty}}",
|
||||
"",
|
||||
"GRASS starter (using a1 grass cards):",
|
||||
" - Bulbasaur line: a1-001-bulbasaur, a1-002-ivysaur, a1-003-venusaur",
|
||||
" - Caterpie line: a1-005-caterpie, a1-006-metapod, a1-007-butterfree",
|
||||
" - Bellsprout line: a1-018-bellsprout, a1-019-weepinbell, a1-020-victreebel",
|
||||
" - Energy: grass (14), colorless (6)",
|
||||
"",
|
||||
"FIRE starter (using a1 fire cards):",
|
||||
" - Charmander line: a1-033-charmander, a1-034-charmeleon, a1-035-charizard",
|
||||
" - Growlithe line: a1-039-growlithe, a1-040-arcanine",
|
||||
" - Ponyta line: a1-042-ponyta, a1-043-rapidash",
|
||||
" - Energy: fire (14), colorless (6)",
|
||||
"",
|
||||
"WATER starter (using a1 water cards):",
|
||||
" - Squirtle line: a1-053-squirtle, a1-054-wartortle, a1-055-blastoise",
|
||||
" - Poliwag line: a1-059-poliwag, a1-060-poliwhirl, a1-061-poliwrath",
|
||||
" - Horsea line: a1-070-horsea, a1-071-seadra",
|
||||
" - Energy: water (14), colorless (6)",
|
||||
"",
|
||||
"PSYCHIC starter (using a1 psychic cards):",
|
||||
" - Abra line, Gastly line, Drowzee line (IDs TBD from data)",
|
||||
" - Energy: psychic (14), colorless (6)",
|
||||
"",
|
||||
"LIGHTNING starter (using a1 lightning cards):",
|
||||
" - Pikachu line: a1-094-pikachu, a1-095-raichu",
|
||||
" - Magnemite line: a1-097-magnemite, a1-098-magneton",
|
||||
" - Voltorb line: a1-099-voltorb, a1-100-electrode",
|
||||
" - Energy: lightning (14), colorless (6)",
|
||||
"",
|
||||
"get_starter_deck(starter_type) -> dict function",
|
||||
"STARTER_TYPES = ['grass', 'fire', 'water', 'psychic', 'lightning']"
|
||||
],
|
||||
"estimatedHours": 2,
|
||||
"notes": "Must use exact card IDs from data/definitions/ - verify with CardService"
|
||||
},
|
||||
{
|
||||
"id": "COLL-007",
|
||||
"name": "Create collections API router",
|
||||
"description": "REST endpoints for collection management",
|
||||
"category": "high",
|
||||
"priority": 7,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-004"],
|
||||
"files": [
|
||||
{"path": "app/api/collections.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"APIRouter with prefix='/collections', tags=['collections']",
|
||||
"",
|
||||
"GET /collections/me",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: CollectionResponse with all user's cards",
|
||||
"",
|
||||
"GET /collections/me/cards/{card_id}",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: CollectionCardResponse (card_id + quantity)",
|
||||
" - 404 if card not in collection (quantity 0)",
|
||||
"",
|
||||
"POST /collections/admin/{user_id}/add",
|
||||
" - Auth: Admin required (future - stub with flag)",
|
||||
" - Body: CollectionAddRequest",
|
||||
" - Returns: CollectionEntryResponse",
|
||||
" - For testing/admin card grants",
|
||||
"",
|
||||
"All endpoints use DbSession and CurrentUser dependencies from deps.py"
|
||||
],
|
||||
"estimatedHours": 1.5,
|
||||
"notes": "Admin endpoint useful for testing - can add admin role check later"
|
||||
},
|
||||
{
|
||||
"id": "COLL-008",
|
||||
"name": "Create decks API router",
|
||||
"description": "REST endpoints for deck CRUD and validation",
|
||||
"category": "high",
|
||||
"priority": 8,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-005"],
|
||||
"files": [
|
||||
{"path": "app/api/decks.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"APIRouter with prefix='/decks', tags=['decks']",
|
||||
"",
|
||||
"GET /decks",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: DeckListResponse (decks, count, limit)",
|
||||
"",
|
||||
"POST /decks",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Body: DeckCreateRequest",
|
||||
" - Returns: DeckResponse",
|
||||
" - 400 if at deck limit",
|
||||
"",
|
||||
"GET /decks/{deck_id}",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: DeckResponse",
|
||||
" - 404 if not found or not owned",
|
||||
"",
|
||||
"PUT /decks/{deck_id}",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Body: DeckUpdateRequest",
|
||||
" - Returns: DeckResponse",
|
||||
" - 404 if not found or not owned",
|
||||
"",
|
||||
"DELETE /decks/{deck_id}",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: 204 No Content",
|
||||
" - 404 if not found or not owned",
|
||||
"",
|
||||
"POST /decks/validate",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Body: DeckValidateRequest",
|
||||
" - Query param: ?mode=campaign|freeplay (default campaign)",
|
||||
" - Returns: DeckValidationResponse",
|
||||
" - For validating deck before saving"
|
||||
],
|
||||
"estimatedHours": 2.5,
|
||||
"notes": "PUT is full replace for simplicity - PATCH could be added later"
|
||||
},
|
||||
{
|
||||
"id": "COLL-009",
|
||||
"name": "Add starter deck selection endpoint to users API",
|
||||
"description": "Endpoint for new user starter deck selection flow",
|
||||
"category": "high",
|
||||
"priority": 9,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-004", "COLL-005", "COLL-006"],
|
||||
"files": [
|
||||
{"path": "app/api/users.py", "status": "modify"}
|
||||
],
|
||||
"details": [
|
||||
"POST /users/me/starter-deck",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Body: StarterDeckSelectRequest {starter_type: str}",
|
||||
" - Returns: DeckResponse for the created starter deck",
|
||||
" - 400 if starter already selected",
|
||||
" - 400 if invalid starter_type",
|
||||
"",
|
||||
"Logic:",
|
||||
" 1. Check if user already has a starter deck (check collection for STARTER source)",
|
||||
" 2. Validate starter_type is in STARTER_TYPES list",
|
||||
" 3. Grant cards to collection via collection_service.grant_starter_deck()",
|
||||
" 4. Create deck via deck_service.create_deck() with is_starter=True",
|
||||
" 5. Return the created deck",
|
||||
"",
|
||||
"GET /users/me/starter-status",
|
||||
" - Auth: CurrentUser required",
|
||||
" - Returns: {has_starter: bool, starter_type: str | null}"
|
||||
],
|
||||
"estimatedHours": 1.5,
|
||||
"notes": "Check for starter by source in collection OR is_starter flag on deck"
|
||||
},
|
||||
{
|
||||
"id": "COLL-010",
|
||||
"name": "Register API routers in main.py",
|
||||
"description": "Mount collections and decks routers",
|
||||
"category": "high",
|
||||
"priority": 10,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-007", "COLL-008"],
|
||||
"files": [
|
||||
{"path": "app/main.py", "status": "modify"}
|
||||
],
|
||||
"details": [
|
||||
"Import collections and decks routers",
|
||||
"app.include_router(collections.router, prefix='/api')",
|
||||
"app.include_router(decks.router, prefix='/api')"
|
||||
],
|
||||
"estimatedHours": 0.5,
|
||||
"notes": "Following same pattern as auth and users routers"
|
||||
},
|
||||
{
|
||||
"id": "COLL-011",
|
||||
"name": "Create DeckValidator tests",
|
||||
"description": "Unit tests for deck validation logic",
|
||||
"category": "high",
|
||||
"priority": 11,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-003"],
|
||||
"files": [
|
||||
{"path": "tests/services/test_deck_validator.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"Test valid deck passes all checks",
|
||||
"Test deck with wrong card count fails (39, 41 cards)",
|
||||
"Test deck with wrong energy count fails (19, 21 energy)",
|
||||
"Test deck with 5+ copies of same card fails",
|
||||
"Test deck with 0 Basic Pokemon fails",
|
||||
"Test deck with invalid card ID fails",
|
||||
"Test deck with insufficient owned cards fails (ownership mode)",
|
||||
"Test deck validation skips ownership in freeplay mode",
|
||||
"Test multiple errors returned together",
|
||||
"Test validation uses CardService correctly",
|
||||
"~15-20 tests"
|
||||
],
|
||||
"estimatedHours": 2,
|
||||
"notes": "Mock CardService for unit tests"
|
||||
},
|
||||
{
|
||||
"id": "COLL-012",
|
||||
"name": "Create CollectionService tests",
|
||||
"description": "Integration tests for collection operations",
|
||||
"category": "high",
|
||||
"priority": 12,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-004"],
|
||||
"files": [
|
||||
{"path": "tests/services/test_collection_service.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"Test get_collection returns empty for new user",
|
||||
"Test add_cards creates new entry",
|
||||
"Test add_cards increments existing entry quantity",
|
||||
"Test remove_cards decrements quantity",
|
||||
"Test remove_cards deletes entry when quantity reaches 0",
|
||||
"Test remove_cards returns None if card not owned",
|
||||
"Test has_cards returns True when owned",
|
||||
"Test has_cards returns False when insufficient",
|
||||
"Test get_owned_cards_dict returns correct mapping",
|
||||
"Test grant_starter_deck adds all cards with STARTER source",
|
||||
"Test add_cards rejects invalid card_definition_id",
|
||||
"~15-20 tests using real Postgres via testcontainers"
|
||||
],
|
||||
"estimatedHours": 2.5,
|
||||
"notes": "Need CardService mock or loaded for card ID validation"
|
||||
},
|
||||
{
|
||||
"id": "COLL-013",
|
||||
"name": "Create DeckService tests",
|
||||
"description": "Integration tests for deck operations",
|
||||
"category": "high",
|
||||
"priority": 13,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-005"],
|
||||
"files": [
|
||||
{"path": "tests/services/test_deck_service.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"Test create_deck creates valid deck",
|
||||
"Test create_deck stores validation errors for invalid deck",
|
||||
"Test create_deck enforces deck slot limit for free user",
|
||||
"Test create_deck allows unlimited decks for premium user",
|
||||
"Test create_deck validates ownership when flag is True",
|
||||
"Test create_deck skips ownership when flag is False",
|
||||
"Test update_deck changes name only",
|
||||
"Test update_deck changes cards and re-validates",
|
||||
"Test update_deck rejects non-owned deck",
|
||||
"Test delete_deck removes deck",
|
||||
"Test delete_deck rejects non-owned deck",
|
||||
"Test get_deck returns owned deck",
|
||||
"Test get_deck returns None for non-owned deck",
|
||||
"Test get_user_decks returns all user decks",
|
||||
"Test can_create_deck respects limit",
|
||||
"Test get_deck_for_game expands to CardDefinitions",
|
||||
"~20-25 tests using real Postgres via testcontainers"
|
||||
],
|
||||
"estimatedHours": 3,
|
||||
"notes": "Need fixtures for users with different premium status"
|
||||
},
|
||||
{
|
||||
"id": "COLL-014",
|
||||
"name": "Create API integration tests",
|
||||
"description": "Integration tests for collection and deck endpoints",
|
||||
"category": "high",
|
||||
"priority": 14,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"dependencies": ["COLL-007", "COLL-008", "COLL-009"],
|
||||
"files": [
|
||||
{"path": "tests/api/test_collections_api.py", "status": "create"},
|
||||
{"path": "tests/api/test_decks_api.py", "status": "create"}
|
||||
],
|
||||
"details": [
|
||||
"Collections API tests:",
|
||||
" - GET /collections/me returns empty for new user",
|
||||
" - GET /collections/me returns cards after adding",
|
||||
" - GET /collections/me/cards/{id} returns quantity",
|
||||
" - GET /collections/me/cards/{id} returns 404 if not owned",
|
||||
" - POST /collections/admin/{user_id}/add grants cards",
|
||||
" - All endpoints require auth (401 without token)",
|
||||
"",
|
||||
"Decks API tests:",
|
||||
" - GET /decks returns empty list for new user",
|
||||
" - POST /decks creates deck",
|
||||
" - POST /decks returns 400 at deck limit",
|
||||
" - GET /decks/{id} returns deck",
|
||||
" - GET /decks/{id} returns 404 for other user's deck",
|
||||
" - PUT /decks/{id} updates deck",
|
||||
" - DELETE /decks/{id} removes deck",
|
||||
" - POST /decks/validate validates without saving",
|
||||
"",
|
||||
"Starter deck tests:",
|
||||
" - POST /users/me/starter-deck grants cards and creates deck",
|
||||
" - POST /users/me/starter-deck returns 400 if already selected",
|
||||
" - GET /users/me/starter-status returns correct state",
|
||||
"",
|
||||
"~30-35 tests total"
|
||||
],
|
||||
"estimatedHours": 3.5,
|
||||
"notes": "Use TestClient with dependency overrides like auth tests"
|
||||
}
|
||||
],
|
||||
|
||||
"testingStrategy": {
|
||||
"approach": "Unit tests for validator, integration tests for services and API",
|
||||
"mocking": "Mock CardService for unit tests, use loaded cards for integration",
|
||||
"database": "Real Postgres via testcontainers for service/API tests",
|
||||
"fixtures": "Create test users with different premium status, test card data",
|
||||
"coverage": "Target ~80-90 new tests"
|
||||
},
|
||||
|
||||
"acceptanceCriteria": [
|
||||
{"criterion": "User can view their card collection", "met": false},
|
||||
{"criterion": "User can create a deck with name, cards, and energy", "met": false},
|
||||
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": false},
|
||||
{"criterion": "Deck validation reports all errors (not just first)", "met": false},
|
||||
{"criterion": "Free users limited to 5 decks", "met": false},
|
||||
{"criterion": "Premium users have unlimited decks", "met": false},
|
||||
{"criterion": "New user can select a starter deck (one time)", "met": false},
|
||||
{"criterion": "Starter deck selection grants cards and creates deck", "met": false},
|
||||
{"criterion": "Campaign mode validates card ownership", "met": false},
|
||||
{"criterion": "Freeplay mode skips ownership validation", "met": false},
|
||||
{"criterion": "All endpoints require authentication", "met": false},
|
||||
{"criterion": "All tests pass with high coverage", "met": false}
|
||||
],
|
||||
|
||||
"securityConsiderations": [
|
||||
"Deck ownership verified on all operations (no access to other users' decks)",
|
||||
"Collection operations validate card IDs exist (no arbitrary string storage)",
|
||||
"Admin endpoints should have role check (stubbed for now)",
|
||||
"Deck slot limits enforced server-side (not just UI)",
|
||||
"JSONB fields validated before storage"
|
||||
],
|
||||
|
||||
"deferredItems": [
|
||||
{
|
||||
"item": "Card trading between users",
|
||||
"reason": "Separate feature (FUTURE_TRADING in master plan)",
|
||||
"priority": "future"
|
||||
},
|
||||
{
|
||||
"item": "Deck import/export",
|
||||
"reason": "Nice-to-have for sharing decks",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"item": "Deck statistics/analytics",
|
||||
"reason": "Can be added to response later",
|
||||
"priority": "low"
|
||||
},
|
||||
{
|
||||
"item": "Admin role checking",
|
||||
"reason": "Admin system not yet implemented",
|
||||
"priority": "medium"
|
||||
}
|
||||
],
|
||||
|
||||
"dependencies": {
|
||||
"existing": [
|
||||
"Collection model (app/db/models/collection.py)",
|
||||
"Deck model (app/db/models/deck.py)",
|
||||
"CardService singleton (app/services/card_service.py)",
|
||||
"User model with max_decks property",
|
||||
"CurrentUser, DbSession dependencies (app/api/deps.py)"
|
||||
],
|
||||
"added": []
|
||||
},
|
||||
|
||||
"phase2Prerequisites": {
|
||||
"met": [
|
||||
"User authentication (CurrentUser dependency)",
|
||||
"User model with premium status",
|
||||
"Collection model with source tracking",
|
||||
"Deck model with JSONB cards storage",
|
||||
"CardService for card lookup",
|
||||
"DeckConfig for validation rules"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user