paper-dynasty-gameplay-webapp/tests/factories
Cal Corum 1c24161e76 CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing
- Fix TypeError in check_steal_opportunity by properly mocking catcher defense
- Correct tag_from_third test calculation to account for all adjustment conditions
- Fix pitcher replacement test by setting appropriate allowed runners threshold
- Add comprehensive test coverage for AI service business logic
- Implement VS Code testing panel configuration with pytest integration
- Create pytest.ini for consistent test execution and warning management
- Add test isolation guidelines and factory pattern implementation
- Establish 102 passing tests with zero failures

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 17:55:34 -05:00
..
__init__.py CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing 2025-09-28 17:55:34 -05:00
cardset_factory.py CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing 2025-09-28 17:55:34 -05:00
manager_ai_factory.py CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing 2025-09-28 17:55:34 -05:00
README.md CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing 2025-09-28 17:55:34 -05:00
team_factory.py CLAUDE: Achieve 100% test pass rate with comprehensive AI service testing 2025-09-28 17:55:34 -05:00

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 ID
  • abbrev: "TST"
  • lname: "Test Team"
  • wallet: 25000
  • is_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 ID
  • name: "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

  1. Always use factories for test data creation
  2. Never hardcode IDs - use generate_unique_id()
  3. Provide sensible defaults for all required fields
  4. Override only what you need in tests
  5. Use specialized methods for common patterns
  6. Test your factories to ensure they work correctly
  7. 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.