strat-gameplay-webapp/backend/tests/unit/models/test_roster_models.py
Cal Corum 3c5055dbf6 CLAUDE: Implement polymorphic RosterLink for both PD and SBA leagues
Added league-agnostic roster tracking with single-table design:

Database Changes:
- Modified RosterLink model with surrogate primary key (id)
- Added nullable card_id (PD) and player_id (SBA) columns
- Added CHECK constraint ensuring exactly one ID populated (XOR logic)
- Added unique constraints for (game_id, card_id) and (game_id, player_id)
- Imported CheckConstraint and UniqueConstraint from SQLAlchemy

New Files:
- app/models/roster_models.py: Pydantic models for type safety
  - BaseRosterLinkData: Abstract base class
  - PdRosterLinkData: PD league card-based rosters
  - SbaRosterLinkData: SBA league player-based rosters
  - RosterLinkCreate: Request validation model

- tests/unit/models/test_roster_models.py: 24 unit tests (all passing)
  - Tests for PD/SBA roster link creation and validation
  - Tests for RosterLinkCreate XOR validation
  - Tests for polymorphic behavior

Database Operations:
- add_pd_roster_card(): Add PD card to game roster
- add_sba_roster_player(): Add SBA player to game roster
- get_pd_roster(): Get PD cards with optional team filter
- get_sba_roster(): Get SBA players with optional team filter
- remove_roster_entry(): Remove roster entry by ID

Tests:
- Added 12 integration tests for roster operations
- Fixed setup_database fixture scope (module → function)

Documentation:
- Updated backend/CLAUDE.md with RosterLink documentation
- Added usage examples and design rationale
- Updated Game model relationship description

Design Pattern:
Single table with application-layer type safety rather than SQLAlchemy
polymorphic inheritance. Simpler queries, database-enforced integrity,
and Pydantic type safety at application layer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 22:45:44 -05:00

357 lines
9.7 KiB
Python

"""
Unit tests for roster models (Pydantic).
Tests polymorphic roster link models for type safety across leagues.
Author: Claude
Date: 2025-10-22
"""
import pytest
from uuid import uuid4
from pydantic import ValidationError
from app.models.roster_models import (
PdRosterLinkData,
SbaRosterLinkData,
RosterLinkCreate,
)
class TestPdRosterLinkData:
"""Test PD league roster link data model"""
def test_create_valid_pd_roster_link(self):
"""Test creating valid PD roster link"""
game_id = uuid4()
data = PdRosterLinkData(
game_id=game_id,
card_id=123,
team_id=1
)
assert data.game_id == game_id
assert data.card_id == 123
assert data.team_id == 1
assert data.id is None # Not yet saved to DB
def test_pd_roster_link_with_id(self):
"""Test PD roster link with database ID"""
game_id = uuid4()
data = PdRosterLinkData(
id=42,
game_id=game_id,
card_id=123,
team_id=1
)
assert data.id == 42
def test_pd_roster_link_invalid_card_id(self):
"""Test PD roster link rejects negative card_id"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
PdRosterLinkData(
game_id=game_id,
card_id=-1,
team_id=1
)
assert "card_id must be positive" in str(exc_info.value)
def test_pd_roster_link_zero_card_id(self):
"""Test PD roster link rejects zero card_id"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
PdRosterLinkData(
game_id=game_id,
card_id=0,
team_id=1
)
assert "card_id must be positive" in str(exc_info.value)
def test_pd_get_entity_id(self):
"""Test get_entity_id returns card_id"""
data = PdRosterLinkData(
game_id=uuid4(),
card_id=123,
team_id=1
)
assert data.get_entity_id() == 123
def test_pd_get_entity_type(self):
"""Test get_entity_type returns 'card'"""
data = PdRosterLinkData(
game_id=uuid4(),
card_id=123,
team_id=1
)
assert data.get_entity_type() == "card"
class TestSbaRosterLinkData:
"""Test SBA league roster link data model"""
def test_create_valid_sba_roster_link(self):
"""Test creating valid SBA roster link"""
game_id = uuid4()
data = SbaRosterLinkData(
game_id=game_id,
player_id=456,
team_id=2
)
assert data.game_id == game_id
assert data.player_id == 456
assert data.team_id == 2
assert data.id is None
def test_sba_roster_link_with_id(self):
"""Test SBA roster link with database ID"""
game_id = uuid4()
data = SbaRosterLinkData(
id=99,
game_id=game_id,
player_id=456,
team_id=2
)
assert data.id == 99
def test_sba_roster_link_invalid_player_id(self):
"""Test SBA roster link rejects negative player_id"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
SbaRosterLinkData(
game_id=game_id,
player_id=-5,
team_id=2
)
assert "player_id must be positive" in str(exc_info.value)
def test_sba_roster_link_zero_player_id(self):
"""Test SBA roster link rejects zero player_id"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
SbaRosterLinkData(
game_id=game_id,
player_id=0,
team_id=2
)
assert "player_id must be positive" in str(exc_info.value)
def test_sba_get_entity_id(self):
"""Test get_entity_id returns player_id"""
data = SbaRosterLinkData(
game_id=uuid4(),
player_id=456,
team_id=2
)
assert data.get_entity_id() == 456
def test_sba_get_entity_type(self):
"""Test get_entity_type returns 'player'"""
data = SbaRosterLinkData(
game_id=uuid4(),
player_id=456,
team_id=2
)
assert data.get_entity_type() == "player"
class TestRosterLinkCreate:
"""Test RosterLinkCreate request model"""
def test_create_with_card_id(self):
"""Test creating request with card_id only"""
game_id = uuid4()
data = RosterLinkCreate(
game_id=game_id,
team_id=1,
card_id=123
)
assert data.game_id == game_id
assert data.team_id == 1
assert data.card_id == 123
assert data.player_id is None
def test_create_with_player_id(self):
"""Test creating request with player_id only"""
game_id = uuid4()
data = RosterLinkCreate(
game_id=game_id,
team_id=2,
player_id=456
)
assert data.game_id == game_id
assert data.team_id == 2
assert data.player_id == 456
assert data.card_id is None
def test_create_with_both_ids_fails(self):
"""Test that providing both IDs raises error"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
RosterLinkCreate(
game_id=game_id,
team_id=1,
card_id=123,
player_id=456
)
assert "Exactly one of card_id or player_id must be provided" in str(exc_info.value)
def test_create_with_neither_id_fails(self):
"""Test that providing neither ID raises error"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
RosterLinkCreate(
game_id=game_id,
team_id=1
)
assert "Exactly one of card_id or player_id must be provided" in str(exc_info.value)
def test_invalid_team_id(self):
"""Test that negative team_id raises error"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
RosterLinkCreate(
game_id=game_id,
team_id=-1,
card_id=123
)
assert "team_id must be positive" in str(exc_info.value)
def test_zero_team_id(self):
"""Test that zero team_id raises error"""
game_id = uuid4()
with pytest.raises(ValidationError) as exc_info:
RosterLinkCreate(
game_id=game_id,
team_id=0,
player_id=456
)
assert "team_id must be positive" in str(exc_info.value)
def test_to_pd_data_success(self):
"""Test converting to PdRosterLinkData"""
game_id = uuid4()
create_data = RosterLinkCreate(
game_id=game_id,
team_id=1,
card_id=123
)
pd_data = create_data.to_pd_data()
assert isinstance(pd_data, PdRosterLinkData)
assert pd_data.game_id == game_id
assert pd_data.team_id == 1
assert pd_data.card_id == 123
def test_to_pd_data_fails_without_card_id(self):
"""Test converting to PdRosterLinkData fails without card_id"""
game_id = uuid4()
create_data = RosterLinkCreate(
game_id=game_id,
team_id=1,
player_id=456
)
with pytest.raises(ValueError) as exc_info:
create_data.to_pd_data()
assert "card_id required for PD roster" in str(exc_info.value)
def test_to_sba_data_success(self):
"""Test converting to SbaRosterLinkData"""
game_id = uuid4()
create_data = RosterLinkCreate(
game_id=game_id,
team_id=2,
player_id=456
)
sba_data = create_data.to_sba_data()
assert isinstance(sba_data, SbaRosterLinkData)
assert sba_data.game_id == game_id
assert sba_data.team_id == 2
assert sba_data.player_id == 456
def test_to_sba_data_fails_without_player_id(self):
"""Test converting to SbaRosterLinkData fails without player_id"""
game_id = uuid4()
create_data = RosterLinkCreate(
game_id=game_id,
team_id=1,
card_id=123
)
with pytest.raises(ValueError) as exc_info:
create_data.to_sba_data()
assert "player_id required for SBA roster" in str(exc_info.value)
class TestPolymorphicBehavior:
"""Test polymorphic patterns across roster types"""
def test_both_types_have_get_entity_id(self):
"""Test both types implement get_entity_id"""
pd_data = PdRosterLinkData(
game_id=uuid4(),
card_id=123,
team_id=1
)
sba_data = SbaRosterLinkData(
game_id=uuid4(),
player_id=456,
team_id=2
)
# Both should have the method
assert callable(pd_data.get_entity_id)
assert callable(sba_data.get_entity_id)
# Different return values
assert pd_data.get_entity_id() == 123
assert sba_data.get_entity_id() == 456
def test_both_types_have_get_entity_type(self):
"""Test both types implement get_entity_type"""
pd_data = PdRosterLinkData(
game_id=uuid4(),
card_id=123,
team_id=1
)
sba_data = SbaRosterLinkData(
game_id=uuid4(),
player_id=456,
team_id=2
)
# Different return values
assert pd_data.get_entity_type() == "card"
assert sba_data.get_entity_type() == "player"