## 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>
303 lines
11 KiB
Python
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 |