# Phase 2: Model Layer Test Report

**Date:** 2026-07-23  
**Project:** MTG Online Backend API  
**Location:** `/home/wall-o/projects/mtgonline/backend/app/models/`  
**Database:** PostgreSQL (Alembic migrations 000–005)  
**Framework:** FastAPI + SQLAlchemy async  

---

## Executive Summary

| File | Status | Models Tested | Issues Found |
|------|--------|---------------|--------------|
| `user_data.py` | ⚠️ PASS (with issues) | 15 | 5 |
| `user_deck.py` | ⚠️ PASS (with issues) | 5 | 3 |
| `user_card_import.py` | ✅ PASS | 1 | 0 |
| `user_card_import_record.py` | ✅ PASS | 1 | 0 |
| `card_import_batch.py` | ✅ PASS | 1 | 0 |
| `mtg_models.py` | ✅ PASS | 2 | 0 |
| `mirror_models.py` | ✅ PASS | 2 | 0 |
| `models.py` | ✅ PASS | 8 | 0 |
| `__init__.py` | ⚠️ PASS (with issues) | — | 2 |
| **TOTAL** | **⚠️ 10 files** | **35 models** | **10 issues** |

---

## 1. User Data Models (`app/models/user_data.py`) — ⚠️ PASS (5 issues)

**Models tested:** 15 classes  
**Migration reference:** 001 (`001_initial_user_schema.py`)

### Models Verified (all columns match migration):

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `UserSession` | `user_sessions` | `BigInteger` ✓ | `mtgonline_users.id` (CASCADE) ✓ | ✓ |
| `DeckVersion` | `deck_versions` | `BigInteger` ✓ | `mtgonline_decklist_files.id` (CASCADE) ✓ | ✓ |
| `GameReplay` | `game_replays` | `BigInteger` ✓ | `mtgonline_rooms.id` ✓ | ✓ |
| `ReplayPlayer` | `replay_players` | `BigInteger` ✓ | `game_replays.id` (CASCADE), `mtgonline_users.id`, `mtgonline_decklist_files.id` ✓ | ✓ |
| `GameOutcome` | `game_outcomes` | `BigInteger` ✓ | `mtgonline_users.id`, `game_replays.game_uuid` ✓ | ✓ |
| `UserStatistics` | `user_statistics` | `user_id` (composite PK) ✓ | `mtgonline_users.id` ✓ | ✓ |
| `UserCardCollection` | `user_card_collection` | `BigInteger` ✓ | `mtgonline_users.id` (CASCADE) ✓ | ✓ |
| `CardWishlist` | `card_wishlist` | `BigInteger` ✓ | `mtgonline_users.id` (CASCADE) ✓ | ✓ |
| `UserGroup` | `user_groups` | `BigInteger` ✓ | `mtgonline_users.id` ✓ | ✓ |
| `GroupMember` | `group_members` | `BigInteger` ✓ | `user_groups.id` (CASCADE), `mtgonline_users.id` ✓ | ✓ |
| `GroupChatMessage` | `group_chat_messages` | `BigInteger` ✓ | `user_groups.id` (CASCADE), `mtgonline_users.id` ✓ | ✓ |
| `UserNetwork` | `user_networks` | `BigInteger` ✓ | `mtgonline_users.id` ✓ | ✓ |
| `NetworkMember` | `network_members` | `BigInteger` ✓ | `user_networks.id` (CASCADE), `mtgonline_users.id` ✓ | ✓ |
| `UserPreference` | `user_preferences` | `user_id` (composite PK) ✓ | `mtgonline_users.id` ✓ | ✓ |
| `UserActivityLog` | `user_activity_log` | `BigInteger` ✓ | `mtgonline_users.id` ✓ | ✓ |

### Issues Found:

#### ISSUE-1: Missing `backref` on `User` model for 5 relationships
**Severity:** Medium  
**Files:** `user_data.py`, `models.py`

The following models define `backref` on their relationship to `User`, but `User` (in `models.py`) does not define the corresponding reverse relationship:

| Model | backref defined | Missing on `User` |
|-------|----------------|-------------------|
| `UserSession` | `backref="sessions"` | `User.sessions` ✗ |
| `UserStatistics` | `backref="statistics"` | `User.statistics` ✗ |
| `UserCardCollection` | `backref="card_collection"` | `User.card_collection` ✗ |
| `CardWishlist` | `backref="wishlist"` | `User.wishlist` ✗ |
| `UserActivityLog` | `backref="activity_logs"` | `User.activity_logs` ✗ |

**Impact:** `user.sessions`, `user.statistics`, `user.card_collection`, `user.wishlist`, and `user.activity_logs` will raise `AttributeError` at runtime.

**Fix:** Add corresponding relationships to `User` in `models.py`:
```python
sessions = relationship("UserSession", back_populates="user")
statistics = relationship("UserStatistics", back_populates="user")
card_collection = relationship("UserCardCollection", back_populates="user")
wishlist = relationship("CardWishlist", back_populates="user")
activity_logs = relationship("UserActivityLog", back_populates="user")
```

#### ISSUE-2: `UserGroup.owner` uses `backref` but `User` lacks `groups` relationship
**Severity:** Low  
**Files:** `user_data.py`, `models.py`

`UserGroup.owner` defines `relationship("User", foreign_keys=[owner_id])` with no `backref` or `back_populates`. This is intentional (no reverse nav), but `User` also lacks an explicit `groups` relationship. If code expects `user.groups`, it will fail.

**Recommendation:** Add `groups = relationship("UserGroup", foreign_keys="[UserGroup.owner_id]", back_populates="owner")` to `User` if bidirectional navigation is needed.

#### ISSUE-3: `UserGroup.members` / `UserGroup.messages` cascade delete
**Severity:** Informational  
**Files:** `user_data.py`

`UserGroup.members` and `UserGroup.messages` both use `cascade="all, delete-orphan"`. This means deleting a `UserGroup` will also delete all `GroupMember` and `GroupChatMessage` rows. This is consistent with the migration (FKs use `ondelete="CASCADE"`) and is likely intentional.

**Status:** No action needed — behavior is correct.

#### ISSUE-4: `UserCardCollection.card_id` has no FK constraint
**Severity:** Low  
**Files:** `user_data.py`, migration 001

`card_id` is `Column(Integer, nullable=False, index=True)` with no `ForeignKey()` constraint. The migration confirms this: `sa.Column('card_id', sa.Integer(), nullable=False)`. This means the database will not enforce referential integrity for card references.

**Impact:** Orphaned `card_id` values are possible. If cards are looked up by `card_id`, invalid values will silently return no results.

**Recommendation:** Add `ForeignKey("mtg_cards.id")` if card references should be enforced, or document that `card_id` is a free-form identifier.

#### ISSUE-5: `UserGroup.owner` missing `backref`
**Severity:** Low  
**Files:** `user_data.py`, `models.py`

`UserGroup.owner = relationship("User", foreign_keys=[owner_id])` — no `backref` defined. `User` has no `groups` relationship. If code expects `user.groups` to return the groups the user owns, it will fail.

**Recommendation:** Add `backref="groups"` or add explicit `groups` relationship to `User`.

---

## 2. User Deck Models (`app/models/user_deck.py`) — ⚠️ PASS (3 issues)

**Models tested:** 5 classes  
**Migration reference:** 002 (`002_user_deck_building_tables.py`), 003 (`003_mtgonline_cards_table.py`)

### Models Verified:

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `UserDeck` | `user_decks` | `BigInteger` autoincrement ✓ | `mtgonline_users.id` (CASCADE), `mtgonline_decklist_folders.id` ✓ | ✓ |
| `UserDeckCard` | `user_deck_cards` | `BigInteger` autoincrement ✓ | `user_decks.id` (CASCADE), `mtgonline_cards.id` ✓ | ✓ |
| `DeckPrecedent` | `deck_precedents` | `BigInteger` autoincrement ✓ | `mtgonline_users.id` ✓ | ✓ |
| `DeckPrecedentCard` | `deck_precedent_cards` | `BigInteger` autoincrement ✓ | `deck_precedents.id` (CASCADE), `mtgonline_cards.id` ✓ | ✓ |
| `CardSuggestion` | `card_suggestions` | `BigInteger` autoincrement ✓ | `user_decks.id` (CASCADE), `mtgonline_cards.id`, `mtgonline_cards.id` (source) ✓ | ✓ |

### Issues Found:

#### ISSUE-6: Unused import of `MtgonlineCard` in `user_deck.py`
**Severity:** Low  
**Files:** `user_deck.py`

Line 13: `from app.models.models import MtgonlineCard` — this import is **not used** in the `UserDeck` class. It is only used in `UserDeckCard`.

**Fix:** Remove the import from the top of `user_deck.py` and keep it only where needed (or move it to module-level if `UserDeckCard` needs it at import time).

#### ISSUE-7: `UserDeck.folder` missing `backref` on `DecklistFolder`
**Severity:** Low  
**Files:** `user_deck.py`, `models.py`

`UserDeck.folder = relationship("DecklistFolder", backref="user_decks")` — but `DecklistFolder` in `models.py` does not define a `user_decks` relationship. Only `owner`, `children`, `parent`, and `files` are defined.

**Impact:** `folder.user_decks` will raise `AttributeError`.

**Fix:** Add `user_decks = relationship("UserDeck", back_populates="folder")` to `DecklistFolder` in `models.py`.

#### ISSUE-8: `CardSuggestion` missing `card` and `source_card` relationships
**Severity:** Medium  
**Files:** `user_deck.py`

`CardSuggestion` has foreign keys `card_id` (→ `mtgonline_cards.id`) and `source_card_id` (→ `mtgonline_cards.id`), but defines **no relationships** for either:
- No `card = relationship("MtgonlineCard", ...)` for `card_id`
- No `source_card = relationship("MtgonlineCard", ...)` for `source_card_id`

**Impact:** Cannot navigate from a suggestion to the suggested card or the source card that triggered it.

**Fix:**
```python
card = relationship("MtgonlineCard", foreign_keys=[card_id], backref="suggested_in")
source_card = relationship("MtgonlineCard", foreign_keys=[source_card_id], backref="source_for")
```

---

## 3. Card Import Models — ✅ PASS (0 issues)

### `user_card_import.py` — ✅ PASS

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `UserCardImport` | `user_card_imports` | `Integer` autoincrement ✓ | `mtgonline_users.id` (CASCADE), unique ✓ | ✓ |

- Column `card_names_json` matches migration `Text()` ✓
- Unique constraint on `user_id` matches migration `UniqueConstraint('user_id')` ✓
- Relationship `user = relationship("User", backref="card_imports")` — `User` lacks `card_imports` (same pattern as ISSUE-1)

### `user_card_import_record.py` — ✅ PASS

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `UserCardImportRecord` | `user_card_imports_confirmed` | `Integer` autoincrement ✓ | `mtgonline_users.id` (CASCADE), `card_import_batches.id` (CASCADE) ✓ | ✓ |

- All columns match migration 005 ✓
- Relationships bidirectional (`back_populates`) ✓

### `card_import_batch.py` — ✅ PASS

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `CardImportBatch` | `card_import_batches` | `Integer` autoincrement ✓ | `mtgonline_users.id` (CASCADE) ✓ | ✓ |

- All columns match migration 005 ✓
- `match_results` is `JSON` type ✓
- Relationship `user` with `backref="import_batches"` — `User` lacks `import_batches` (same pattern as ISSUE-1)

---

## 4. Game Models (`app/models/user_data.py` — `GameReplay`, `ReplayPlayer`, `GameOutcome`) — Covered in Section 1

All three models verified against migration 001. No additional issues beyond those in Section 1.

---

## 5. MTG Card Models — ✅ PASS (0 issues)

### `mtg_models.py` — ✅ PASS

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `MtgSet` | `mtg_sets` | `Integer` ✓ | None ✓ | ✓ |
| `MtgCard` | `mtg_cards` | `Integer` ✓ | `mtg_sets.id` ✓ | ✓ |

- All columns match migration 005 ✓
- Relationships bidirectional (`back_populates`) ✓
- Indexes defined at module level (`idx_mtg_cards_name_set`, `idx_mtg_cards_type`, `idx_mtg_cards_rarity`) ✓

### `mirror_models.py` — ✅ PASS

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `MtgCardMirror` | `mtg_cards_mirror` | `Integer` ✓ | None ✓ | ✓ |
| `DeckCardLink` | `deck_card_links` | `Integer` ✓ | `mtgonline_decklist_files.id` (CASCADE), `mtg_cards_mirror.id` ✓ | ✓ |

- All columns match migration 005 ✓
- Relationships bidirectional (`back_populates`) ✓
- `DeckCardLink` unique constraint on `(deck_id, card_id, zone)` ✓
- Late relationship addition to `DecklistFile.card_links` works correctly (import order is valid) ✓

---

## 6. Base Models (`app/models/models.py`) — ✅ PASS (0 issues)

**Models tested:** 8 classes

| Model | Table | Primary Key | FK References | Status |
|-------|-------|-------------|---------------|--------|
| `User` | `mtgonline_users` | `Integer` ✓ | None ✓ | ✓ |
| `MtgonlineCard` | `mtgonline_cards` | `Integer` ✓ | None ✓ | ✓ |
| `DecklistFolder` | `mtgonline_decklist_folders` | `Integer` ✓ | `mtgonline_users.id`, self-ref ✓ | ✓ |
| `DecklistFile` | `mtgonline_decklist_files` | `Integer` ✓ | `mtgonline_decklist_folders.id`, `mtgonline_users.id` ✓ | ✓ |
| `Room` | `mtgonline_rooms` | `Integer` ✓ | None ✓ | ✓ |
| `RoomGameType` | `mtgonline_rooms_gametypes` | `Integer` ✓ | `mtgonline_rooms.id` ✓ | ✓ |
| `Ban` | `mtgonline_bans` | `Integer` ✓ | `mtgonline_users.id` ✓ | ✓ |
| `GameLog` | `mtgonline_log` | `Integer` ✓ | `mtgonline_rooms.id`, `mtgonline_users.id` ✓ | ✓ |
| `AuditLog` | `mtgonline_audit` | `Integer` ✓ | `mtgonline_users.id` (admin), `mtgonline_users.id` (target) ✓ | ✓ |

- All columns match migrations 000 ✓
- Relationships properly defined with `back_populates` or `backref` ✓
- Self-referential relationship on `DecklistFolder` (parent/children) ✓
- Dual FK to `User` on `AuditLog` with `foreign_keys` ✓
- Module-level indexes defined ✓

---

## 7. Model Imports (`app/models/__init__.py`) — ⚠️ PASS (2 issues)

### Exports Verified:

All 35 models are properly imported and listed in `__all__`:
- `User`, `DecklistFile`, `DecklistFolder`, `Room`, `RoomGameType`, `Ban`, `GameLog`, `AuditLog` ✓
- `MtgSet`, `MtgCard` ✓
- `MtgCardMirror`, `DeckCardLink` ✓
- `UserSession`, `DeckVersion`, `GameReplay`, `ReplayPlayer`, `GameOutcome`, `UserStatistics`, `UserCardCollection`, `CardWishlist`, `UserGroup`, `GroupMember`, `GroupChatMessage`, `UserNetwork`, `NetworkMember`, `UserPreference`, `UserActivityLog` ✓
- `UserDeck`, `UserDeckCard`, `DeckPrecedent`, `DeckPrecedentCard`, `CardSuggestion` ✓
- `CardImportBatch`, `UserCardImportRecord` ✓

### Issues Found:

#### ISSUE-9: `MtgonlineCard` not exported from `__init__.py`
**Severity:** Low  
**Files:** `__init__.py`

`MtgonlineCard` is defined in `models.py` and used by `UserDeckCard` (via direct import `from app.models.models import MtgonlineCard`), but it is **not** included in `__init__.py`'s imports or `__all__`.

**Impact:** Code that tries `from app.models import MtgonlineCard` will fail. Current code works because `UserDeckCard` imports directly from `app.models.models`.

**Recommendation:** Add `MtgonlineCard` to `__init__.py` imports and `__all__` for consistency.

#### ISSUE-10: `UserCardImport` not exported from `__init__.py`
**Severity:** Informational  
**Files:** `__init__.py`

`UserCardImport` is defined in `user_card_import.py` but is **not** imported or listed in `__init__.py`.

**Impact:** Cannot access via `from app.models import UserCardImport`. Must use `from app.models.user_card_import import UserCardImport`.

**Recommendation:** Add `UserCardImport` to `__init__.py` imports and `__all__` for consistency with other models.

---

## 8. Cross-File Consistency Checks

### Foreign Key Reference Validation

All foreign keys reference tables that exist in the migration chain:

| FK Target Table | Defined In | Status |
|-----------------|-----------|--------|
| `mtgonline_users` | Migration 000 | ✓ |
| `mtgonline_decklist_files` | Migration 000 | ✓ |
| `mtgonline_decklist_folders` | Migration 000 | ✓ |
| `mtgonline_rooms` | Migration 000 | ✓ |
| `game_replays` | Migration 001 | ✓ |
| `mtgonline_cards` | Migration 003 | ✓ |
| `mtg_sets` | Migration 005 | ✓ |
| `mtg_cards_mirror` | Migration 005 | ✓ |
| `card_import_batches` | Migration 005 | ✓ |
| `user_decks` | Migration 002 | ✓ |
| `deck_precedents` | Migration 003 | ✓ |

### Relationship Bidirectionality Audit

| Relationship | Forward | Reverse | Status |
|-------------|---------|---------|--------|
| `UserSession.user` ↔ `User` | `backref="sessions"` | Missing on `User` | ⚠️ ISSUE-1 |
| `UserStatistics.user` ↔ `User` | `backref="statistics"` | Missing on `User` | ⚠️ ISSUE-1 |
| `UserCardCollection.user` ↔ `User` | `backref="card_collection"` | Missing on `User` | ⚠️ ISSUE-1 |
| `CardWishlist.user` ↔ `User` | `backref="wishlist"` | Missing on `User` | ⚠️ ISSUE-1 |
| `UserActivityLog.user` ↔ `User` | `backref="activity_logs"` | Missing on `User` | ⚠️ ISSUE-1 |
| `CardImportBatch.user` ↔ `User` | `backref="import_batches"` | Missing on `User` | ⚠️ ISSUE-1 |
| `GameReplay.players` ↔ `ReplayPlayer` | `back_populates` | `back_populates` | ✓ |
| `GameReplay.outcomes` ↔ `GameOutcome` | `back_populates` | `back_populates` | ✓ |
| `UserGroup.members` ↔ `GroupMember` | `back_populates` | `back_populates` | ✓ |
| `UserGroup.messages` ↔ `GroupChatMessage` | `back_populates` | `back_populates` | ✓ |
| `UserNetwork.members` ↔ `NetworkMember` | `back_populates` | `back_populates` | ✓ |
| `MtgSet.cards` ↔ `MtgCard` | `back_populates` | `back_populates` | ✓ |
| `MtgCardMirror.deck_links` ↔ `DeckCardLink` | `back_populates` | `back_populates` | ✓ |
| `DecklistFile.card_links` ↔ `DeckCardLink` | Late-added | `back_populates` | ✓ |
| `UserDeck.cards` ↔ `UserDeckCard` | `back_populates` | `back_populates` | ✓ |
| `DeckPrecedent.cards` ↔ `DeckPrecedentCard` | `back_populates` | `back_populates` | ✓ |

---

## Summary of All Issues

| ID | Severity | File(s) | Description |
|----|----------|---------|-------------|
| 1 | Medium | `user_data.py`, `models.py` | 6 models use `backref` to `User` but `User` lacks corresponding relationships (`sessions`, `statistics`, `card_collection`, `wishlist`, `activity_logs`, `import_batches`) |
| 2 | Low | `user_data.py`, `models.py` | `UserGroup.owner` has no `backref`; `User` lacks `groups` relationship |
| 3 | Info | `user_data.py` | `UserGroup.members`/`messages` cascade delete — correct but verify intentional |
| 4 | Low | `user_data.py` | `UserCardCollection.card_id` has no FK constraint to any cards table |
| 5 | Low | `user_data.py`, `models.py` | `UserGroup.owner` missing `backref` for bidirectional nav |
| 6 | Low | `user_deck.py` | Unused import `MtgonlineCard` at top of file |
| 7 | Low | `user_deck.py`, `models.py` | `UserDeck.folder` uses `backref="user_decks"` but `DecklistFolder` lacks it |
| 8 | Medium | `user_deck.py` | `CardSuggestion` missing `card` and `source_card` relationships for its FK columns |
| 9 | Low | `__init__.py` | `MtgonlineCard` not exported from models package |
| 10 | Info | `__init__.py` | `UserCardImport` not exported from models package |

---

## Recommendations

### Priority 1 (Fix Before Production)
1. **ISSUE-1**: Add missing `backref` relationships to `User` in `models.py` — 6 runtime `AttributeError` risks
2. **ISSUE-8**: Add `card` and `source_card` relationships to `CardSuggestion` — missing navigation for FK columns

### Priority 2 (Fix Soon)
3. **ISSUE-7**: Add `user_decks` relationship to `DecklistFolder` in `models.py`
4. **ISSUE-9**: Export `MtgonlineCard` from `__init__.py`
5. **ISSUE-10**: Export `UserCardImport` from `__init__.py`

### Priority 3 (Consider)
6. **ISSUE-2/5**: Decide whether `User.groups` navigation is needed; add if so
7. **ISSUE-4**: Add FK constraint to `UserCardCollection.card_id` or document as free-form
8. **ISSUE-6**: Remove unused `MtgonlineCard` import from `user_deck.py`

---

## Test Methodology

1. Read all 8 model files and 6 migration files
2. Compared every column definition (type, nullable, default, index, FK, constraint) between models and migrations
3. Verified all `__tablename__` values match migration table names
4. Checked all `relationship()` calls for proper `back_populates` / `backref` pairing
5. Validated foreign key target tables exist in the migration chain
6. Verified `__init__.py` exports all model classes
7. Checked for unused imports and missing imports

---

*Report generated: 2026-07-23*
