major-domo-v2/tests/test_services_team_service.py
Cal Corum 758be0f166 CLAUDE: Fix trade system issues and enhance documentation
Major fixes and improvements:

Trade System Fixes:
- Fix duplicate player moves in trade embed Player Exchanges section
- Resolve "WVMiL not participating" error for Minor League destinations
- Implement organizational authority model for ML/MiL/IL team relationships
- Update Trade.cross_team_moves to deduplicate using moves_giving only

Team Model Enhancements:
- Rewrite roster_type() method using sname as definitive source per spec
- Fix edge cases like "BHMIL" (Birmingham IL) vs "BHMMIL"
- Update _get_base_abbrev() to use consistent sname-based logic
- Add organizational lookup support in trade participation

Autocomplete System:
- Fix major_league_team_autocomplete invalid roster_type parameter
- Implement client-side filtering using Team.roster_type() method
- Add comprehensive test coverage for all autocomplete functions
- Centralize autocomplete logic to shared utils functions

Test Infrastructure:
- Add 25 new tests for trade models and trade builder
- Add 13 autocomplete function tests with error handling
- Fix existing test failures with proper mocking patterns
- Update dropadd tests to use shared autocomplete functions

Documentation Updates:
- Document trade model enhancements and deduplication fix
- Add autocomplete function documentation with usage examples
- Document organizational authority model and edge case handling
- Update README files with recent fixes and implementation notes

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-06 16:10:13 -05:00

332 lines
13 KiB
Python

"""
Tests for TeamService functionality
"""
import pytest
from unittest.mock import AsyncMock
from services.team_service import TeamService, team_service
from models.team import Team
from constants import SBA_CURRENT_SEASON
from exceptions import APIException
class TestTeamService:
"""Test TeamService functionality."""
@pytest.fixture
def mock_client(self):
"""Mock API client."""
client = AsyncMock()
return client
@pytest.fixture
def team_service_instance(self, mock_client):
"""Create TeamService instance with mocked client."""
service = TeamService()
service._client = mock_client
return service
def create_team_data(self, team_id: int, abbrev: str, season: int = 12, **kwargs):
"""Create complete team data for testing."""
base_data = {
'id': team_id,
'abbrev': abbrev,
'sname': f'{abbrev} Team',
'lname': f'{abbrev} Long Team Name',
'season': season,
'gmid': 101,
'division_id': 1,
}
base_data.update(kwargs)
return base_data
@pytest.mark.asyncio
async def test_get_team_success(self, team_service_instance, mock_client):
"""Test successful team retrieval."""
mock_data = self.create_team_data(1, 'TST', stadium='Test Stadium')
mock_client.get.return_value = mock_data
result = await team_service_instance.get_team(1)
assert isinstance(result, Team)
assert result.abbrev == 'TST'
assert result.sname == 'TST Team'
assert result.season == 12
mock_client.get.assert_called_once_with('teams', object_id=1)
@pytest.mark.asyncio
async def test_get_team_by_abbrev_success(self, team_service_instance, mock_client):
"""Test getting team by abbreviation."""
mock_data = {
'count': 1,
'teams': [self.create_team_data(1, 'NYY', season=12)]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_team_by_abbrev('nyy', season=12)
assert isinstance(result, Team)
assert result.abbrev == 'NYY'
mock_client.get.assert_called_once_with('teams', params=[('team_abbrev', 'NYY'), ('season', '12')])
@pytest.mark.asyncio
async def test_get_team_by_abbrev_not_found(self, team_service_instance, mock_client):
"""Test getting team by abbreviation when not found."""
mock_data = {'count': 0, 'teams': []}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_team_by_abbrev('XXX', season=12)
assert result is None
@pytest.mark.asyncio
async def test_get_teams_by_season(self, team_service_instance, mock_client):
"""Test getting all teams for a season."""
mock_data = {
'count': 3,
'teams': [
self.create_team_data(1, 'TEA', season=12),
self.create_team_data(2, 'TEB', season=12),
self.create_team_data(3, 'TEC', season=12)
]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_teams_by_season(12)
assert len(result) == 3
assert all(isinstance(team, Team) for team in result)
assert all(team.season == 12 for team in result)
mock_client.get.assert_called_once_with('teams', params=[('season', '12')])
@pytest.mark.asyncio
async def test_get_teams_by_manager(self, team_service_instance, mock_client):
"""Test getting teams by manager."""
mock_data = {
'count': 2,
'teams': [
self.create_team_data(1, 'TEA', manager1_id=101, season=12),
self.create_team_data(2, 'TEB', manager2_id=101, season=12)
]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_teams_by_manager(101, season=12)
assert len(result) == 2
assert all(isinstance(team, Team) for team in result)
mock_client.get.assert_called_once_with('teams', params=[('manager_id', '101'), ('season', '12')])
@pytest.mark.asyncio
async def test_get_teams_by_division(self, team_service_instance, mock_client):
"""Test getting teams by division."""
mock_data = {
'count': 4,
'teams': [
self.create_team_data(1, 'TEA', division_id=1, season=12),
self.create_team_data(2, 'TEB', division_id=1, season=12),
self.create_team_data(3, 'TEC', division_id=1, season=12),
self.create_team_data(4, 'TED', division_id=1, season=12)
]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_teams_by_division(1, season=12)
assert len(result) == 4
assert all(isinstance(team, Team) for team in result)
mock_client.get.assert_called_once_with('teams', params=[('division_id', '1'), ('season', '12')])
@pytest.mark.asyncio
async def test_get_team_roster(self, team_service_instance, mock_client):
"""Test getting team roster with position counts."""
mock_roster_data = {
'active': {
'C': 2, '1B': 1, '2B': 1, '3B': 1, 'SS': 1, 'LF': 1, 'CF': 1, 'RF': 1, 'DH': 1,
'SP': 5, 'RP': 8, 'CP': 2, 'WARa': 45.2,
'players': [
{'id': 1, 'name': 'Player 1', 'wara': 5.2},
{'id': 2, 'name': 'Player 2', 'wara': 4.8}
]
},
'shortil': {
'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 2, 'RP': 3, 'CP': 0, 'WARa': 8.5,
'players': [
{'id': 3, 'name': 'Minor Player 1', 'wara': 2.1}
]
},
'longil': {
'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0,
'SP': 0, 'RP': 1, 'CP': 0, 'WARa': 1.2,
'players': [
{'id': 4, 'name': 'Injured Player', 'wara': 1.2}
]
}
}
mock_client.get.return_value = mock_roster_data
result = await team_service_instance.get_team_roster(1, 'current')
assert result is not None
assert 'active' in result
assert 'shortil' in result
assert 'longil' in result
assert result['active']['C'] == 2
assert result['active']['SP'] == 5
assert len(result['active']['players']) == 2
mock_client.get.assert_called_once_with('teams/1/roster/current')
@pytest.mark.asyncio
async def test_get_team_roster_next_week(self, team_service_instance, mock_client):
"""Test getting next week's roster."""
mock_roster_data = {
'active': {'C': 1, 'SP': 5, 'players': []},
'shortil': {'C': 0, 'SP': 0, 'players': []},
'longil': {'C': 0, 'SP': 0, 'players': []}
}
mock_client.get.return_value = mock_roster_data
result = await team_service_instance.get_team_roster(1, 'next')
assert result is not None
mock_client.get.assert_called_once_with('teams/1/roster/next')
@pytest.mark.asyncio
async def test_get_team_standings_position(self, team_service_instance, mock_client):
"""Test getting team standings information."""
mock_standings_data = {
'id': 1,
'team_id': 1,
'wins': 45,
'losses': 27,
'run_diff': 125,
'div_gb': None,
'wc_gb': None,
'home_wins': 25,
'home_losses': 11,
'away_wins': 20,
'away_losses': 16
}
mock_client.get.return_value = mock_standings_data
result = await team_service_instance.get_team_standings_position(1, season=12)
assert result is not None
assert result['wins'] == 45
assert result['losses'] == 27
assert result['run_diff'] == 125
mock_client.get.assert_called_once_with('standings/team/1', params=[('season', '12')])
@pytest.mark.asyncio
async def test_update_team(self, team_service_instance, mock_client):
"""Test team update functionality."""
update_data = {'stadium': 'New Stadium', 'color': '#FF0000'}
response_data = self.create_team_data(1, 'TST', stadium='New Stadium', color='#FF0000')
mock_client.put.return_value = response_data
result = await team_service_instance.update_team(1, update_data)
assert isinstance(result, Team)
assert result.stadium == 'New Stadium'
assert result.color == '#FF0000'
mock_client.put.assert_called_once_with('teams', update_data, object_id=1)
@pytest.mark.asyncio
async def test_is_valid_team_abbrev(self, team_service_instance, mock_client):
"""Test team abbreviation validation."""
# Mock successful lookup
mock_data = {
'count': 1,
'teams': [self.create_team_data(1, 'TST', season=12)]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.is_valid_team_abbrev('TST', season=12)
assert result is True
# Mock failed lookup
mock_client.get.return_value = {'count': 0, 'teams': []}
result = await team_service_instance.is_valid_team_abbrev('XXX', season=12)
assert result is False
@pytest.mark.asyncio
async def test_get_current_season_teams(self, team_service_instance, mock_client):
"""Test getting current season teams."""
mock_data = {
'count': 2,
'teams': [
self.create_team_data(1, 'TEA', season=SBA_CURRENT_SEASON),
self.create_team_data(2, 'TEB', season=SBA_CURRENT_SEASON)
]
}
mock_client.get.return_value = mock_data
result = await team_service_instance.get_current_season_teams()
assert len(result) == 2
assert all(team.season == SBA_CURRENT_SEASON for team in result)
mock_client.get.assert_called_once_with('teams', params=[('season', str(SBA_CURRENT_SEASON))])
@pytest.mark.asyncio
async def test_error_handling(self, team_service_instance, mock_client):
"""Test error handling in team service methods."""
mock_client.get.side_effect = APIException("API Error")
# Should return None/empty list on errors, not raise
result = await team_service_instance.get_team(1)
assert result is None
result = await team_service_instance.get_teams_by_season(12)
assert result == []
result = await team_service_instance.get_teams_by_manager(101)
assert result == []
result = await team_service_instance.get_team_roster(1)
assert result is None
result = await team_service_instance.get_team_standings_position(1, 12)
assert result is None
@pytest.mark.asyncio
async def test_abbrev_case_insensitive(self, team_service_instance, mock_client):
"""Test that abbreviation lookup is case insensitive."""
mock_data = {
'count': 1,
'teams': [self.create_team_data(1, 'NYY', season=12)]
}
mock_client.get.return_value = mock_data
# Test with lowercase input
result = await team_service_instance.get_team_by_abbrev('nyy', season=12)
assert result is not None
assert result.abbrev == 'NYY'
# Should call with uppercase
mock_client.get.assert_called_once_with('teams', params=[('team_abbrev', 'NYY'), ('season', '12')])
class TestGlobalTeamServiceInstance:
"""Test global team service instance."""
def test_team_service_global(self):
"""Test global team service instance."""
assert isinstance(team_service, TeamService)
assert team_service.model_class == Team
assert team_service.endpoint == 'teams'
@pytest.mark.asyncio
async def test_service_independence(self):
"""Test that service instances are independent."""
service1 = TeamService()
service2 = TeamService()
# Should be different instances
assert service1 is not service2
# But same configuration
assert service1.model_class == service2.model_class
assert service1.endpoint == service2.endpoint