paper-dynasty-gameplay-webapp/tests/unit/services/test_player_service.py
Cal Corum 559fe73f07 CLAUDE: Complete Player model migration with service layer and integration test infrastructure
## 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>
2025-09-29 16:20:29 -05:00

303 lines
11 KiB
Python

"""
Unit tests for PlayerService.
Tests business logic methods extracted from Player model,
following test isolation guidelines with mocked dependencies.
"""
import pytest
from unittest.mock import Mock
from app.services.player_service import PlayerService
from tests.factories.player_factory import PlayerFactory
class TestPlayerServiceCardUrls:
"""Test card URL retrieval methods."""
def test_get_batter_card_url_from_primary_image(self, db_session):
"""Test getting batter card URL from primary image."""
player = PlayerFactory.create_with_batting_card(db_session)
service = PlayerService(db_session)
result = service.get_batter_card_url(player)
assert result == player.image
assert "batting" in result
def test_get_batter_card_url_from_secondary_image(self, db_session):
"""Test getting batter card URL from secondary image."""
player = PlayerFactory.create(
db_session,
image="https://example.com/player_pitching.jpg",
image2="https://example.com/player_batting.jpg"
)
service = PlayerService(db_session)
result = service.get_batter_card_url(player)
assert result == player.image2
assert "batting" in result
def test_get_batter_card_url_none_available(self, db_session):
"""Test getting batter card URL when none available."""
player = PlayerFactory.create(
db_session,
image="https://example.com/player_pitching.jpg",
image2="https://example.com/player_fielding.jpg"
)
service = PlayerService(db_session)
result = service.get_batter_card_url(player)
assert result is None
def test_get_pitcher_card_url_from_primary_image(self, db_session):
"""Test getting pitcher card URL from primary image."""
player = PlayerFactory.create_with_pitching_card(db_session)
service = PlayerService(db_session)
result = service.get_pitcher_card_url(player)
assert result == player.image
assert "pitching" in result
def test_get_pitcher_card_url_from_secondary_image(self, db_session):
"""Test getting pitcher card URL from secondary image."""
player = PlayerFactory.create(
db_session,
image="https://example.com/player_batting.jpg",
image2="https://example.com/player_pitching.jpg"
)
service = PlayerService(db_session)
result = service.get_pitcher_card_url(player)
assert result == player.image2
assert "pitching" in result
def test_get_pitcher_card_url_none_available(self, db_session):
"""Test getting pitcher card URL when none available."""
player = PlayerFactory.create(
db_session,
image="https://example.com/player_batting.jpg",
image2="https://example.com/player_fielding.jpg"
)
service = PlayerService(db_session)
result = service.get_pitcher_card_url(player)
assert result is None
class TestPlayerServiceNameCardLink:
"""Test name card link generation."""
def test_generate_batting_card_link(self, db_session):
"""Test generating batting card markdown link."""
player = PlayerFactory.create_with_batting_card(
db_session,
name="Test Batter"
)
service = PlayerService(db_session)
result = service.generate_name_card_link(player, 'batting')
expected = f"[Test Batter]({player.image})"
assert result == expected
def test_generate_pitching_card_link(self, db_session):
"""Test generating pitching card markdown link."""
player = PlayerFactory.create_with_pitching_card(
db_session,
name="Test Pitcher"
)
service = PlayerService(db_session)
result = service.generate_name_card_link(player, 'pitching')
expected = f"[Test Pitcher]({player.image})"
assert result == expected
def test_generate_card_link_no_url_available(self, db_session):
"""Test generating card link when URL not available."""
player = PlayerFactory.create(
db_session,
name="No Card Player",
image="https://example.com/fielding_card.jpg", # Contains neither 'batting' nor 'pitching'
image2=None
)
service = PlayerService(db_session)
with pytest.raises(ValueError, match="No batting card URL available"):
service.generate_name_card_link(player, 'batting')
with pytest.raises(ValueError, match="No pitching card URL available"):
service.generate_name_card_link(player, 'pitching')
def test_generate_card_link_both_types_available(self, db_session):
"""Test generating links when both card types are available."""
player = PlayerFactory.create_with_both_cards(
db_session,
name="Multi Card Player"
)
service = PlayerService(db_session)
batting_link = service.generate_name_card_link(player, 'batting')
pitching_link = service.generate_name_card_link(player, 'pitching')
assert f"[Multi Card Player]({player.image})" == batting_link
assert f"[Multi Card Player]({player.image2})" == pitching_link
class TestPlayerServiceFormatting:
"""Test player formatting methods."""
def test_get_formatted_name_with_description(self, db_session):
"""Test getting formatted name with description."""
player = PlayerFactory.create(
db_session,
name="John Smith",
description="2023 Rookie"
)
service = PlayerService(db_session)
result = service.get_formatted_name_with_description(player)
assert result == "2023 Rookie John Smith"
def test_get_player_description_from_player_object(self, db_session):
"""Test getting description from Player object."""
player = PlayerFactory.create(
db_session,
name="Test Player",
description="Prime"
)
service = PlayerService(db_session)
result = service.get_player_description(player=player)
assert result == "Prime Test Player"
def test_get_player_description_from_dict_with_name(self, db_session):
"""Test getting description from dictionary with 'name' key."""
player_dict = {
"description": "Veteran",
"name": "Dict Player"
}
service = PlayerService(db_session)
result = service.get_player_description(player_dict=player_dict)
assert result == "Veteran Dict Player"
def test_get_player_description_from_dict_with_p_name(self, db_session):
"""Test getting description from dictionary with 'p_name' key."""
player_dict = {
"description": "Legend",
"p_name": "P Name Player"
}
service = PlayerService(db_session)
result = service.get_player_description(player_dict=player_dict)
assert result == "Legend P Name Player"
def test_get_player_description_from_dict_description_only(self, db_session):
"""Test getting description from dictionary with only description."""
player_dict = {
"description": "No Name Era"
}
service = PlayerService(db_session)
result = service.get_player_description(player_dict=player_dict)
assert result == "No Name Era"
def test_get_player_description_no_parameters(self, db_session):
"""Test getting description with no parameters raises error."""
service = PlayerService(db_session)
with pytest.raises(TypeError, match="One of \"player\" or \"player_dict\" must be included"):
service.get_player_description()
def test_get_player_description_missing_description_key(self, db_session):
"""Test getting description with missing description key."""
player_dict = {
"name": "Missing Description Player"
}
service = PlayerService(db_session)
with pytest.raises(KeyError, match="player_dict must contain \"description\" key"):
service.get_player_description(player_dict=player_dict)
class TestPlayerServiceIntegration:
"""Test PlayerService integration and edge cases."""
def test_service_inherits_from_base_service(self, db_session):
"""Test that PlayerService properly inherits from BaseService."""
service = PlayerService(db_session)
# Should have BaseService methods
assert hasattr(service, '_log_operation')
assert hasattr(service, '_log_error')
assert hasattr(service, 'session')
assert hasattr(service, 'logger')
def test_service_logging(self, db_session):
"""Test that service methods perform logging."""
player = PlayerFactory.create_with_batting_card(db_session, name="Log Test")
service = PlayerService(db_session)
# These methods should not raise exceptions and should log
result = service.get_batter_card_url(player)
assert result is not None
result = service.get_formatted_name_with_description(player)
assert "Log Test" in result
def test_service_with_complex_player_data(self, db_session):
"""Test service methods with complex player data."""
player = PlayerFactory.create(
db_session,
name="Complex Player",
description="2023 All-Star Rookie",
image="https://example.com/complex_batting_card.jpg",
image2="https://example.com/complex_pitching_card.jpg",
pos_1="C",
pos_2="1B",
mlbclub="LAD",
cost=45,
headshot="https://example.com/headshot.jpg"
)
service = PlayerService(db_session)
# Test all service methods work with complex data
batting_url = service.get_batter_card_url(player)
pitching_url = service.get_pitcher_card_url(player)
batting_link = service.generate_name_card_link(player, 'batting')
pitching_link = service.generate_name_card_link(player, 'pitching')
formatted_name = service.get_formatted_name_with_description(player)
assert batting_url == player.image
assert pitching_url == player.image2
assert "Complex Player" in batting_link
assert "Complex Player" in pitching_link
assert formatted_name == "2023 All-Star Rookie Complex Player"
def test_service_methods_with_none_values(self, db_session):
"""Test service methods handle None values gracefully."""
player = PlayerFactory.create(
db_session,
image2=None,
headshot=None
)
service = PlayerService(db_session)
# Methods should handle None values without errors
pitcher_url = service.get_pitcher_card_url(player)
formatted_name = service.get_formatted_name_with_description(player)
# These should work even with None values
assert formatted_name is not None
assert player.name in formatted_name