## Player Model Migration - Migrate Player model from Discord app following Model/Service Architecture pattern - Extract all business logic from Player model to PlayerService - Create pure data model with PostgreSQL relationships (Cardset, PositionRating) - Implement comprehensive PlayerFactory with specialized methods for test data ## PlayerService Implementation - Extract 5 business logic methods from original Player model: - get_batter_card_url() - batting card URL retrieval - get_pitcher_card_url() - pitching card URL retrieval - generate_name_card_link() - markdown link generation - get_formatted_name_with_description() - name formatting - get_player_description() - description from object or dict - Follow BaseService pattern with dependency injection and logging ## Comprehensive Testing - 35 passing Player tests (14 model + 21 service tests) - PlayerFactory with specialized methods (batting/pitching cards, positions) - Test isolation following factory pattern and db_session guidelines - Fix PostgreSQL integer overflow in test ID generation ## Integration Test Infrastructure - Create integration test framework for improving service coverage - Design AIService integration tests targeting uncovered branches - Demonstrate real database query testing with proper isolation - Establish patterns for testing complex game scenarios ## Service Coverage Analysis - Current service coverage: 61% overall - PlayerService: 100% coverage (excellent migration example) - AIService: 60% coverage (improvement opportunities identified) - Integration test strategy designed to achieve 90%+ coverage ## Database Integration - Update Cardset model to include players relationship - Update PositionRating model with proper Player foreign key - Maintain all existing relationships and constraints - Demonstrate data isolation and automatic cleanup in tests ## Test Suite Status - 137 tests passing, 0 failures (maintained 100% pass rate) - Added 35 new tests while preserving all existing functionality - Integration test infrastructure ready for coverage improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| cardset_factory.py | ||
| manager_ai_factory.py | ||
| player_factory.py | ||
| README.md | ||
| team_factory.py | ||
Test Factories
This directory contains factory classes for generating unique, valid test data. Factories are essential for maintaining test isolation and preventing data conflicts between tests.
🚨 CRITICAL: Test Isolation Requirements
ALL tests must use these factories instead of manual model creation to ensure:
- ✅ Unique IDs prevent primary key conflicts
- ✅ Consistent test data structure
- ✅ Isolated test execution
- ✅ Deterministic test results
Factory Pattern Overview
Each model has a corresponding factory that follows this pattern:
class ModelFactory:
@staticmethod
def build(**kwargs):
"""Build model instance without saving to database."""
defaults = {
'id': generate_unique_id(),
'field1': 'default_value',
'field2': generate_unique_name('Prefix')
}
defaults.update(kwargs)
return Model(**defaults)
@staticmethod
def create(session, **kwargs):
"""Create and save model instance to database."""
instance = ModelFactory.build(**kwargs)
session.add(instance)
session.commit()
session.refresh(instance)
return instance
Available Factories
TeamFactory (team_factory.py)
Purpose: Generate Team instances with unique IDs and team data.
Basic Usage:
from tests.factories.team_factory import TeamFactory
# Build without saving
team = TeamFactory.build(abbrev="LAD", lname="Los Angeles Dodgers")
# Create and save to database
team = TeamFactory.create(db_session, abbrev="LAD", wallet=50000)
Specialized Methods:
# Create AI team
ai_team = TeamFactory.build_ai_team(abbrev="AI1")
# Create human team
human_team = TeamFactory.build_human_team(abbrev="HUM1")
# Create multiple teams with unique IDs
teams = TeamFactory.build_multiple(3, season=9)
Default Values:
id: Unique generated IDabbrev: "TST"lname: "Test Team"wallet: 25000is_ai: False- All other required fields have sensible defaults
CardsetFactory (cardset_factory.py)
Purpose: Generate Cardset instances for testing card sets and game configurations.
Basic Usage:
from tests.factories.cardset_factory import CardsetFactory
# Build without saving
cardset = CardsetFactory.build(name="2024 Season", ranked_legal=True)
# Create and save to database
cardset = CardsetFactory.create(db_session, name="Test Set")
Specialized Methods:
# Create ranked legal cardset
ranked_set = CardsetFactory.create_ranked(db_session, name="Ranked Set")
# Create multiple cardsets
cardsets = CardsetFactory.create_batch(db_session, 3, ranked_legal=True)
Default Values:
id: Unique generated IDname: "Test Cardset [unique]"ranked_legal: False
ManagerAiFactory (manager_ai_factory.py)
Purpose: Generate ManagerAi instances for testing AI decision-making.
Basic Usage:
from tests.factories.manager_ai_factory import ManagerAiFactory
# Build AI with default settings
ai = ManagerAiFactory.build_balanced()
# Create aggressive AI
ai = ManagerAiFactory.create_aggressive(db_session)
Specialized Methods:
# Predefined AI types
balanced_ai = ManagerAiFactory.build_balanced()
aggressive_ai = ManagerAiFactory.build_aggressive()
conservative_ai = ManagerAiFactory.build_conservative()
# Custom AI settings
custom_ai = ManagerAiFactory.build(steal=10, running=8, hold=3)
Factory Usage Patterns
✅ Correct Usage Patterns
Basic Model Creation:
def test_team_creation(db_session):
team = TeamFactory.create(db_session, abbrev="BOS")
assert team.abbrev == "BOS"
assert team.id is not None
Custom Field Values:
def test_ai_team_behavior(db_session):
ai_team = TeamFactory.create(
db_session,
is_ai=True,
abbrev="AI1",
wallet=100000
)
assert ai_team.is_ai is True
Multiple Related Objects:
def test_game_creation(db_session):
home_team = TeamFactory.create(db_session, abbrev="HOME")
away_team = TeamFactory.create(db_session, abbrev="AWAY")
cardset = CardsetFactory.create(db_session, ranked_legal=True)
# Test game creation with related objects
# Game logic here...
Batch Creation:
def test_multiple_teams(db_session):
teams = TeamFactory.build_multiple(5)
for team in teams:
db_session.add(team)
db_session.commit()
# All teams have unique IDs
ids = [team.id for team in teams]
assert len(set(ids)) == 5
❌ Anti-Patterns to Avoid
Manual Model Creation:
# DON'T DO THIS - hardcoded IDs cause conflicts
def test_bad_pattern(db_session):
team = Team(
id=1, # ❌ Hardcoded ID
abbrev="TST",
lname="Test Team",
# ... many required fields
)
Shared Mutable State:
# DON'T DO THIS - shared state between tests
SHARED_TEAM = Team(id=999, abbrev="SHARED")
def test_bad_shared_state(db_session):
db_session.add(SHARED_TEAM) # ❌ Modifies shared state
Non-unique Values:
# DON'T DO THIS - non-unique values cause conflicts
def test_bad_non_unique(db_session):
team1 = Team(id=1, abbrev="SAME") # ❌ Same ID
team2 = Team(id=1, abbrev="SAME") # ❌ Same ID
Creating New Factories
When adding new models, create corresponding factories following this template:
1. Create Factory File
Create tests/factories/model_name_factory.py:
"""
ModelName factory for generating test data.
Provides methods to create unique, valid ModelName instances for testing
without conflicts between test runs.
"""
from app.models.model_name import ModelName
from tests.conftest import generate_unique_id, generate_unique_name
class ModelNameFactory:
"""Factory for creating ModelName test instances."""
@staticmethod
def build(**kwargs):
"""
Build a ModelName instance without saving to database.
Args:
**kwargs: Override default field values
Returns:
ModelName: Configured model instance
Example:
model = ModelNameFactory.build(field="custom_value")
"""
defaults = {
'id': generate_unique_id(),
'name': generate_unique_name('ModelName'),
# Add all required fields with sensible defaults
}
# Override defaults with provided kwargs
defaults.update(kwargs)
return ModelName(**defaults)
@staticmethod
def create(session, **kwargs):
"""
Create and save a ModelName instance to the database.
Args:
session: Database session
**kwargs: Override default field values
Returns:
ModelName: Saved model instance
Example:
model = ModelNameFactory.create(session, field="custom_value")
"""
instance = ModelNameFactory.build(**kwargs)
session.add(instance)
session.commit()
session.refresh(instance)
return instance
# Add specialized factory methods as needed
@staticmethod
def build_special_type(**kwargs):
"""Build specialized variant of model."""
special_defaults = {
'special_field': 'special_value'
}
special_defaults.update(kwargs)
return ModelNameFactory.build(**special_defaults)
2. Update Factory init.py
Add your factory to tests/factories/__init__.py:
from .model_name_factory import ModelNameFactory
__all__ = [
# ... existing factories
"ModelNameFactory",
]
3. Add Factory Tests
Create tests for your factory in tests/unit/factories/test_model_name_factory.py:
def test_build_creates_valid_instance():
model = ModelNameFactory.build()
assert model.id is not None
assert model.name is not None
def test_create_saves_to_database(db_session):
model = ModelNameFactory.create(db_session)
retrieved = db_session.get(ModelName, model.id)
assert retrieved is not None
def test_unique_ids_generated():
models = [ModelNameFactory.build() for _ in range(5)]
ids = [model.id for model in models]
assert len(set(ids)) == 5 # All unique
Helper Functions
generate_unique_id()
Generates unique integer IDs using UUID hex conversion:
id = generate_unique_id() # Returns: 3847291847
generate_unique_name(prefix="Test")
Generates unique names with UUID suffix:
name = generate_unique_name("Team") # Returns: "Team a3b4c5d6"
Best Practices
- Always use factories for test data creation
- Never hardcode IDs - use
generate_unique_id() - Provide sensible defaults for all required fields
- Override only what you need in tests
- Use specialized methods for common patterns
- Test your factories to ensure they work correctly
- Keep factories simple - complex logic belongs in services
Integration with Tests
Required imports for new test files:
from tests.factories.team_factory import TeamFactory
from tests.factories.cardset_factory import CardsetFactory
# Import other factories as needed
Required fixture usage:
def test_something(db_session): # Use db_session from conftest.py
team = TeamFactory.create(db_session, abbrev="TEST")
# Test logic here
This pattern ensures consistent, isolated, and reliable tests across the entire project.