Files
mtgonline/backend/test_reports/phase3_schema_layer_report.md

12 KiB

Phase 3: Schema Layer Test Report

Date: 2026-06-25
Scope: All Pydantic schemas in /home/wall-o/projects/mtgonline/backend/app/schemas/
Models Compared Against: All ORM models in /home/wall-o/projects/mtgonline/backend/app/models/


Summary

Schema File Status Critical Warning Info
schemas.py ⚠️ FAIL 1 2 1
user_data_schemas.py ⚠️ FAIL 1 2 3
user_deck_schemas.py ⚠️ FAIL 1 3 2
card_import_schemas.py ⚠️ FAIL 0 2 2
user_card_collection.py ⚠️ FAIL 0 1 2
card_search_schemas.py ⚠️ FAIL 1 3 2
__init__.py ⚠️ FAIL 1 0 0
TOTAL 6 FAIL 5 13 12

1. schemas.py⚠️ FAIL

Models covered: User, DecklistFile, DecklistFolder, Room, Ban, MtgCardMirror, DeckCardLink

Critical Issues

# Schema Issue Detail
C1 UserResponse Missing fields from User model avatar_bmp, salt, ban_ends, vip_expiry are in the User model but absent from the response schema. password_hash is correctly excluded (security), but the others should be present or explicitly excluded.

Warnings

# Schema Issue Detail
W1 schemas.py (all) Pydantic v1 class Config used All schemas use class Config: from_attributes = True (Pydantic v1 style). Other schema files use Pydantic v2 model_config = ConfigDict(from_attributes=True). Inconsistent.
W2 GameResponse Fields not in Room model with_password, max_players, player_count, started are not columns in the Room model. These appear to be computed/derived fields but are not documented. If computed, they should be Field(default=...) with defaults.

Info

# Schema Issue Detail
I1 BanResponse Missing ip_address field The Ban model has ip_address (String(45)) but BanResponse does not include it.

2. user_data_schemas.py⚠️ FAIL

Models covered: UserSession, DeckVersion, GameReplay, ReplayPlayer, GameOutcome, UserStatistics, UserCardCollection, CardWishlist, UserGroup, GroupMember, GroupChatMessage, UserNetwork, NetworkMember, UserPreference, UserActivityLog

Critical Issues

# Schema Issue Detail
C1 GameReplayResponse Missing players field The GameReplay model has a players relationship (List[ReplayPlayer]). The schema has players: List[Dict[str, Any]] = [] which is a generic placeholder, not a typed schema. Should define a ReplayPlayerResponse schema.

Warnings

# Schema Issue Detail
W1 DeckVersionStatus enum Values don't match model Enum has DRAFT, FINAL, ARCHIVED. The DeckVersion model has status column with server_default="DRAFT" and String(20). The user_deck_schemas.py DeckStatus enum has only DRAFT/FINAL. The ARCHIVED value has no model support.
W2 GameReplayCreate / GameReplayUpdate format is a Python keyword Using format as a field name shadows the built-in format() function. Should use game_format or deck_format instead.
W3 GroupMember model Missing joined_at in GroupMemberCreate GroupMemberCreate has user_id and role but the model also has joined_at (auto-set by server_default, so OK for create). Not a bug, but worth noting.

Info

# Schema Issue Detail
I1 UserSessionResponse Missing session_token_hash Model has session_token_hash but it's correctly excluded from response (security). Document this exclusion.
I2 GroupResponse member_count and is_member are computed Not in the UserGroup model. These are computed fields. Should have Field(default=0) / Field(default=False) with documentation.
I3 NetworkResponse Missing updated_at The UserNetwork model does NOT have updated_at, so this is actually correct. No issue.

3. user_deck_schemas.py⚠️ FAIL

Models covered: UserDeck, UserDeckCard, DeckPrecedent, CardSuggestion

Critical Issues

# Schema Issue Detail
C1 UserDeckCreate Missing status field The UserDeck model has status (String(20), default="DRAFT") as a required column. UserDeckCreate does not include status. If the default is relied upon, this is acceptable, but it should be explicit.

Warnings

# Schema Issue Detail
W1 CardSearchResponse Uses List[Dict[str, Any]] instead of typed schema Should use List[CardResponse] (from card_search_schemas.py) for type safety.
W2 PrecedentResponse Missing updated_at field The DeckPrecedent model has updated_at (DateTime) but PrecedentResponse does not include it.
W3 DeckStatus enum Conflicts with user_data_schemas.py DeckVersionStatus Both enums have DRAFT/FINAL values but different names and different files. The DeckVersion model in user_data.py uses DeckVersionStatus from user_data_schemas.py, while UserDeck model uses string values directly. This creates confusion.

Info

# Schema Issue Detail
I1 DeckCardResponse Missing updated_at The UserDeckCard model does NOT have updated_at, so this is correct. No issue.
I2 SuggestionResponse card_name is computed Not in the CardSuggestion model. Should be documented as a computed field with Field(default="").

4. card_import_schemas.py⚠️ FAIL

Models covered: UserCardImport, CardImportBatch

Warnings

# Schema Issue Detail
W1 CardImportStatusResponse Missing fields vs CardImportBatch model Missing batch_id, status, total_cards, matched_cards, unmatched_cards, error_message. The CardImportBatch model has all these columns.
W2 CardImportResponse Missing batch_id and status The CardImportBatch model has batch_id (id) and status fields. Response only has message, card_count, card_names, imported_at.

Info

# Schema Issue Detail
I1 CardMatchResult.match_type Inconsistent with card_search_schemas.py This file uses 'exact', 'fuzzy', 'partial'. The card_search_schemas.py version uses 'exact', 'high_confidence', 'low_confidence'.
I2 ErrorResponse Duplicate of generic schemas Same as MessageResponse/CountResponse duplicates found in other files.

5. user_card_collection.py⚠️ FAIL

Models covered: UserCardCollection, CardWishlist

Warnings

# Schema Issue Detail
W1 CardCollectionListResponse Missing pagination fields Has cards, total, page, page_size, total_pages — this is actually correct and matches the pattern. No issue here. The warning is that WishlistListResponse is missing page/page_size/total_pages while CardCollectionListResponse has them. Inconsistent pagination.

Info

# Schema Issue Detail
I1 CardCondition / AcquisitionMethod enums Not validated against model The model stores these as String(20) and String(50) respectively. The enums provide validation at the schema layer, which is good. However, the CardCollectionResponse serializes them as plain str, losing the enum type info.
I2 WishlistResponse Missing updated_at The CardWishlist model does NOT have updated_at, so this is correct. No issue.

6. card_search_schemas.py⚠️ FAIL

Models covered: MtgCard, MtgSet, CardImportBatch (partial)

Critical Issues

# Schema Issue Detail
C1 CardResponse Missing 8 fields from MtgCard model Missing: artist, flavor_text, numbers, image, card_parts, keywords, legalities, identifiers (as typed). The MtgCard model has all these columns.
C2 SetResponse Missing 10 fields from MtgSet model Missing: type, base_set_size, total_size, is_foil_only, is_non_foil_only, digital, icon_svg_url, parent_code, mtgo_code, image. The MtgSet model has all these columns.

Warnings

# Schema Issue Detail
W1 CardImportStatusResponse Missing fields vs CardImportBatch Missing batch_id, status, total_cards, matched_cards, unmatched_cards, error_message.
W2 CardMatchResult.match_type Inconsistent with card_import_schemas.py Uses 'exact', 'high_confidence', 'low_confidence' vs 'exact', 'fuzzy', 'partial' in card_import_schemas.py.
W3 CardImportResponse Missing file_type and file_size The CardImportBatch model has file_type and file_size columns not reflected in this response.

Info

# Schema Issue Detail
I1 CardSearchResponse Uses List[Dict[str, Any]] Should use List[CardResponse] for type safety.
I2 CardTypeResponse Trivial schema Only has type: str. May be unnecessary or could be merged.

7. __init__.py⚠️ FAIL

Critical Issues

# Schema Issue Detail
C1 __init__.py No imports — schemas not exported The file only contains # Schemas package comment. No schemas are imported or re-exported. Consumers must know the exact file path for each schema. Should at minimum export commonly used schemas.

Cross-File Issues

Duplicate Generic Schemas (Found in 5 files)

The following generic schemas are defined identically in multiple files:

Schema Files
MessageResponse user_data_schemas.py, user_deck_schemas.py, card_import_schemas.py, user_card_collection.py, card_search_schemas.py
CountResponse user_data_schemas.py, user_deck_schemas.py, card_import_schemas.py, user_card_collection.py, card_search_schemas.py
ErrorResponse card_import_schemas.py, card_search_schemas.py
ErrorDetail user_data_schemas.py, user_card_collection.py

Recommendation: Create a app/schemas/common.py with these shared schemas and import from there.

Models Without Any Schema Coverage

Model File
UserCardImport user_card_import.py
UserCardImportRecord user_card_import_record.py
ReplayPlayer user_data.py
GroupMember (response) user_data.py
NetworkMember (response) user_data.py
DeckPrecedentCard user_deck.py

Pydantic v1 vs v2 Inconsistency

  • schemas.py: Uses class Config: from_attributes = True (v1 style)
  • All other files: Use Pydantic v2 BaseModel (no explicit model_config, relying on defaults or not setting from_attributes)

Recommendation: Standardize on Pydantic v2 model_config = ConfigDict(from_attributes=True) in a common base or in each file.


Recommendations (Priority Order)

  1. Fix __init__.py — Add schema exports so consumers can import from app.schemas
  2. Create app/schemas/common.py — Extract duplicate MessageResponse, CountResponse, ErrorResponse, ErrorDetail
  3. Fix CardResponse and SetResponse in card_search_schemas.py — Add all missing model fields
  4. Fix CardImportStatusResponse in both card_import_schemas.py and card_search_schemas.py — Add missing fields
  5. Standardize Pydantic v2 — Use model_config = ConfigDict(from_attributes=True) consistently
  6. Resolve enum conflictsDeckVersionStatus vs DeckStatus, CardMatchResult.match_type values
  7. Add ReplayPlayerResponse — Replace List[Dict[str, Any]] in GameReplayResponse
  8. Add missing schemasUserCardImport, UserCardImportRecord, DeckPrecedentCard
  9. Fix format field name in GameReplayCreate/Update — Rename to game_format
  10. Add updated_at to PrecedentResponse and UserDeckCreate (status field)