- Add @logged_command decorator in utils/decorators.py to eliminate try/catch/finally boilerplate - Migrate all Discord commands to use new decorator pattern: * commands/league/info.py - /league command * commands/players/info.py - /player command * commands/teams/info.py - /team and /teams commands * commands/teams/roster.py - /roster command - Fix PyLance type issues by making model IDs required for database entities - Update Player and Team models to require id field since they come from database - Fix test cases to provide required id values - Add comprehensive test coverage for decorator functionality - Add migration guide for applying decorator to additional commands - Reduce codebase by ~100 lines of repetitive logging boilerplate 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
356 lines
13 KiB
Python
356 lines
13 KiB
Python
"""
|
|
Tests for league service functionality
|
|
|
|
Comprehensive testing of league-related operations including current state,
|
|
standings, division standings, and league leaders.
|
|
"""
|
|
import pytest
|
|
from unittest.mock import AsyncMock, patch
|
|
from typing import Dict, Any, List
|
|
|
|
from services.league_service import LeagueService, league_service
|
|
from models.current import Current
|
|
from exceptions import APIException
|
|
|
|
|
|
class TestLeagueService:
|
|
"""Test league service functionality."""
|
|
|
|
@pytest.fixture
|
|
def mock_current_data(self) -> Dict[str, Any]:
|
|
"""Mock current league state data."""
|
|
return {
|
|
'week': 10,
|
|
'season': 12,
|
|
'freeze': False,
|
|
'bet_week': 'sheets',
|
|
'trade_deadline': 14,
|
|
'pick_trade_start': 15,
|
|
'pick_trade_end': 18,
|
|
'playoffs_begin': 19
|
|
}
|
|
|
|
@pytest.fixture
|
|
def mock_standings_data(self) -> List[Dict[str, Any]]:
|
|
"""Mock standings data."""
|
|
return [
|
|
{
|
|
'abbrev': 'NYY',
|
|
'wins': 85,
|
|
'losses': 45,
|
|
'pct': 0.654,
|
|
'gb': 0,
|
|
'division_id': 1
|
|
},
|
|
{
|
|
'abbrev': 'BOS',
|
|
'wins': 80,
|
|
'losses': 50,
|
|
'pct': 0.615,
|
|
'gb': 5,
|
|
'division_id': 1
|
|
},
|
|
{
|
|
'abbrev': 'LAD',
|
|
'wins': 88,
|
|
'losses': 42,
|
|
'pct': 0.677,
|
|
'gb': 0,
|
|
'division_id': 2
|
|
}
|
|
]
|
|
|
|
@pytest.fixture
|
|
def mock_leaders_data(self) -> List[Dict[str, Any]]:
|
|
"""Mock league leaders data."""
|
|
return [
|
|
{
|
|
'name': 'Mike Trout',
|
|
'avg': 0.325,
|
|
'war': 8.5,
|
|
'ops': 1.050
|
|
},
|
|
{
|
|
'name': 'Mookie Betts',
|
|
'avg': 0.318,
|
|
'war': 7.8,
|
|
'ops': 1.025
|
|
},
|
|
{
|
|
'name': 'Aaron Judge',
|
|
'avg': 0.305,
|
|
'war': 7.2,
|
|
'ops': 1.015
|
|
}
|
|
]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_state_success(self, mock_current_data):
|
|
"""Test successful retrieval of current league state."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = mock_current_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_current_state()
|
|
|
|
assert result is not None
|
|
assert isinstance(result, Current)
|
|
assert result.week == 10
|
|
assert result.season == 12
|
|
assert result.freeze is False
|
|
assert result.trade_deadline == 14
|
|
|
|
mock_api.get.assert_called_once_with('current')
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_state_no_data(self):
|
|
"""Test get_current_state when no data is returned."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = None
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_current_state()
|
|
|
|
assert result is None
|
|
mock_api.get.assert_called_once_with('current')
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_current_state_exception(self):
|
|
"""Test get_current_state exception handling."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.side_effect = Exception("API Error")
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_current_state()
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_standings_success_list(self, mock_standings_data):
|
|
"""Test successful retrieval of standings as list."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = mock_standings_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_standings(12)
|
|
|
|
assert result is not None
|
|
assert len(result) == 3
|
|
assert result[0]['abbrev'] == 'NYY'
|
|
assert result[0]['wins'] == 85
|
|
|
|
mock_api.get.assert_called_once_with('standings', params=[('season', '12')])
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_standings_success_dict(self, mock_standings_data):
|
|
"""Test successful retrieval of standings wrapped in dict."""
|
|
service = LeagueService()
|
|
wrapped_data = {'standings': mock_standings_data}
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = wrapped_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_standings()
|
|
|
|
assert result is not None
|
|
assert len(result) == 3
|
|
assert result[0]['abbrev'] == 'NYY'
|
|
|
|
mock_api.get.assert_called_once_with('standings', params=[('season', '12')])
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_standings_no_data(self):
|
|
"""Test get_standings when no data is returned."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = None
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_standings()
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_standings_exception(self):
|
|
"""Test get_standings exception handling."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.side_effect = Exception("API Error")
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_standings()
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_division_standings_success(self):
|
|
"""Test successful retrieval of division standings."""
|
|
service = LeagueService()
|
|
division_data = [
|
|
{'abbrev': 'NYY', 'wins': 85, 'losses': 45, 'division_id': 1},
|
|
{'abbrev': 'BOS', 'wins': 80, 'losses': 50, 'division_id': 1}
|
|
]
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = division_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_division_standings(1, 12)
|
|
|
|
assert result is not None
|
|
assert len(result) == 2
|
|
assert all(team['division_id'] == 1 for team in result)
|
|
|
|
mock_api.get.assert_called_once_with('standings/division/1', params=[('season', '12')])
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_division_standings_no_data(self):
|
|
"""Test get_division_standings when no data is returned."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = None
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_division_standings(1)
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_division_standings_exception(self):
|
|
"""Test get_division_standings exception handling."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.side_effect = Exception("API Error")
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_division_standings(1, 12)
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_success_list(self, mock_leaders_data):
|
|
"""Test successful retrieval of league leaders as list."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = mock_leaders_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders('batting', 12, 10)
|
|
|
|
assert result is not None
|
|
assert len(result) == 3
|
|
assert result[0]['name'] == 'Mike Trout'
|
|
assert result[0]['avg'] == 0.325
|
|
|
|
expected_params = [('season', '12'), ('limit', '10')]
|
|
mock_api.get.assert_called_once_with('leaders/batting', params=expected_params)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_success_dict(self, mock_leaders_data):
|
|
"""Test successful retrieval of league leaders wrapped in dict."""
|
|
service = LeagueService()
|
|
wrapped_data = {'leaders': mock_leaders_data}
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = wrapped_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders('pitching', 12, 5)
|
|
|
|
assert result is not None
|
|
assert len(result) == 3
|
|
assert result[0]['name'] == 'Mike Trout'
|
|
|
|
expected_params = [('season', '12'), ('limit', '5')]
|
|
mock_api.get.assert_called_once_with('leaders/pitching', params=expected_params)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_limit_enforcement(self, mock_leaders_data):
|
|
"""Test that league leaders respects the limit parameter."""
|
|
service = LeagueService()
|
|
long_list = mock_leaders_data * 5 # 15 items
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = long_list
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders('batting', 12, 5)
|
|
|
|
assert result is not None
|
|
assert len(result) == 5 # Should be limited to 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_default_params(self, mock_leaders_data):
|
|
"""Test league leaders with default parameters."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = mock_leaders_data
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders()
|
|
|
|
assert result is not None
|
|
expected_params = [('season', '12'), ('limit', '10')]
|
|
mock_api.get.assert_called_once_with('leaders/batting', params=expected_params)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_no_data(self):
|
|
"""Test get_league_leaders when no data is returned."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.return_value = None
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders()
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_league_leaders_exception(self):
|
|
"""Test get_league_leaders exception handling."""
|
|
service = LeagueService()
|
|
|
|
with patch.object(service, 'get_client') as mock_client:
|
|
mock_api = AsyncMock()
|
|
mock_api.get.side_effect = Exception("API Error")
|
|
mock_client.return_value = mock_api
|
|
|
|
result = await service.get_league_leaders('batting', 12)
|
|
|
|
assert result is None
|
|
|
|
def test_league_service_global_instance(self):
|
|
"""Test that global league_service instance exists."""
|
|
assert league_service is not None
|
|
assert isinstance(league_service, LeagueService) |