major-domo-v2/tests/test_utils_autocomplete.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

257 lines
11 KiB
Python

"""
Tests for shared autocomplete utility functions.
Validates the shared autocomplete functions used across multiple command modules.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from utils.autocomplete import player_autocomplete, team_autocomplete, major_league_team_autocomplete
from tests.factories import PlayerFactory, TeamFactory
from models.team import RosterType
class TestPlayerAutocomplete:
"""Test player autocomplete functionality."""
@pytest.fixture
def mock_interaction(self):
"""Create a mock Discord interaction."""
interaction = MagicMock()
interaction.user.id = 12345
return interaction
@pytest.mark.asyncio
async def test_player_autocomplete_success(self, mock_interaction):
"""Test successful player autocomplete."""
mock_players = [
PlayerFactory.mike_trout(id=1),
PlayerFactory.ronald_acuna(id=2)
]
with patch('utils.autocomplete.player_service') as mock_service:
mock_service.search_players = AsyncMock(return_value=mock_players)
choices = await player_autocomplete(mock_interaction, 'Trout')
assert len(choices) == 2
assert choices[0].name == 'Mike Trout (CF)'
assert choices[0].value == 'Mike Trout'
assert choices[1].name == 'Ronald Acuna Jr. (OF)'
assert choices[1].value == 'Ronald Acuna Jr.'
@pytest.mark.asyncio
async def test_player_autocomplete_with_team_info(self, mock_interaction):
"""Test player autocomplete with team information."""
mock_team = TeamFactory.create(id=499, abbrev='LAA', sname='Angels', lname='Los Angeles Angels')
mock_player = PlayerFactory.mike_trout(id=1)
mock_player.team = mock_team
with patch('utils.autocomplete.player_service') as mock_service:
mock_service.search_players = AsyncMock(return_value=[mock_player])
choices = await player_autocomplete(mock_interaction, 'Trout')
assert len(choices) == 1
assert choices[0].name == 'Mike Trout (CF - LAA)'
assert choices[0].value == 'Mike Trout'
@pytest.mark.asyncio
async def test_player_autocomplete_prioritizes_user_team(self, mock_interaction):
"""Test that user's team players are prioritized in autocomplete."""
user_team = TeamFactory.create(id=1, abbrev='POR', sname='Loggers')
other_team = TeamFactory.create(id=2, abbrev='LAA', sname='Angels')
# Create players - one from user's team, one from other team
user_player = PlayerFactory.mike_trout(id=1)
user_player.team = user_team
user_player.team_id = user_team.id
other_player = PlayerFactory.ronald_acuna(id=2)
other_player.team = other_team
other_player.team_id = other_team.id
with patch('utils.autocomplete.player_service') as mock_service, \
patch('utils.autocomplete.get_user_major_league_team') as mock_get_team:
mock_service.search_players = AsyncMock(return_value=[other_player, user_player])
mock_get_team.return_value = user_team
choices = await player_autocomplete(mock_interaction, 'player')
assert len(choices) == 2
# User's team player should be first
assert choices[0].name == 'Mike Trout (CF - POR)'
assert choices[1].name == 'Ronald Acuna Jr. (OF - LAA)'
@pytest.mark.asyncio
async def test_player_autocomplete_short_input(self, mock_interaction):
"""Test player autocomplete with short input returns empty."""
choices = await player_autocomplete(mock_interaction, 'T')
assert len(choices) == 0
@pytest.mark.asyncio
async def test_player_autocomplete_error_handling(self, mock_interaction):
"""Test player autocomplete error handling."""
with patch('utils.autocomplete.player_service') as mock_service:
mock_service.search_players.side_effect = Exception("API Error")
choices = await player_autocomplete(mock_interaction, 'Trout')
assert len(choices) == 0
class TestTeamAutocomplete:
"""Test team autocomplete functionality."""
@pytest.fixture
def mock_interaction(self):
"""Create a mock Discord interaction."""
interaction = MagicMock()
interaction.user.id = 12345
return interaction
@pytest.mark.asyncio
async def test_team_autocomplete_success(self, mock_interaction):
"""Test successful team autocomplete."""
mock_teams = [
TeamFactory.create(id=1, abbrev='LAA', sname='Angels'),
TeamFactory.create(id=2, abbrev='LAAMIL', sname='Salt Lake Bees'),
TeamFactory.create(id=3, abbrev='LAAAIL', sname='Angels IL'),
TeamFactory.create(id=4, abbrev='POR', sname='Loggers')
]
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season = AsyncMock(return_value=mock_teams)
choices = await team_autocomplete(mock_interaction, 'la')
assert len(choices) == 3 # All teams with 'la' in abbrev or sname
assert any('LAA' in choice.name for choice in choices)
assert any('LAAMIL' in choice.name for choice in choices)
assert any('LAAAIL' in choice.name for choice in choices)
@pytest.mark.asyncio
async def test_team_autocomplete_short_input(self, mock_interaction):
"""Test team autocomplete with very short input."""
choices = await team_autocomplete(mock_interaction, '')
assert len(choices) == 0
@pytest.mark.asyncio
async def test_team_autocomplete_error_handling(self, mock_interaction):
"""Test team autocomplete error handling."""
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season.side_effect = Exception("API Error")
choices = await team_autocomplete(mock_interaction, 'LAA')
assert len(choices) == 0
class TestMajorLeagueTeamAutocomplete:
"""Test major league team autocomplete functionality."""
@pytest.fixture
def mock_interaction(self):
"""Create a mock Discord interaction."""
interaction = MagicMock()
interaction.user.id = 12345
return interaction
@pytest.mark.asyncio
async def test_major_league_team_autocomplete_filters_correctly(self, mock_interaction):
"""Test that only major league teams are returned."""
# Create teams with different roster types
mock_teams = [
TeamFactory.create(id=1, abbrev='LAA', sname='Angels'), # ML
TeamFactory.create(id=2, abbrev='LAAMIL', sname='Salt Lake Bees'), # MiL
TeamFactory.create(id=3, abbrev='LAAAIL', sname='Angels IL'), # IL
TeamFactory.create(id=4, abbrev='FA', sname='Free Agents'), # FA
TeamFactory.create(id=5, abbrev='POR', sname='Loggers'), # ML
TeamFactory.create(id=6, abbrev='PORMIL', sname='Portland MiL'), # MiL
]
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season = AsyncMock(return_value=mock_teams)
choices = await major_league_team_autocomplete(mock_interaction, 'l')
# Should only return major league teams that match 'l' (LAA, POR)
choice_values = [choice.value for choice in choices]
assert 'LAA' in choice_values
assert 'POR' in choice_values
assert len(choice_values) == 2
# Should NOT include MiL, IL, or FA teams
assert 'LAAMIL' not in choice_values
assert 'LAAAIL' not in choice_values
assert 'FA' not in choice_values
assert 'PORMIL' not in choice_values
@pytest.mark.asyncio
async def test_major_league_team_autocomplete_matching(self, mock_interaction):
"""Test search matching on abbreviation and short name."""
mock_teams = [
TeamFactory.create(id=1, abbrev='LAA', sname='Angels'),
TeamFactory.create(id=2, abbrev='LAD', sname='Dodgers'),
TeamFactory.create(id=3, abbrev='POR', sname='Loggers'),
TeamFactory.create(id=4, abbrev='BOS', sname='Red Sox'),
]
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season = AsyncMock(return_value=mock_teams)
# Test abbreviation matching
choices = await major_league_team_autocomplete(mock_interaction, 'la')
assert len(choices) == 2 # LAA and LAD
choice_values = [choice.value for choice in choices]
assert 'LAA' in choice_values
assert 'LAD' in choice_values
# Test short name matching
choices = await major_league_team_autocomplete(mock_interaction, 'red')
assert len(choices) == 1
assert choices[0].value == 'BOS'
@pytest.mark.asyncio
async def test_major_league_team_autocomplete_short_input(self, mock_interaction):
"""Test major league team autocomplete with very short input."""
choices = await major_league_team_autocomplete(mock_interaction, '')
assert len(choices) == 0
@pytest.mark.asyncio
async def test_major_league_team_autocomplete_error_handling(self, mock_interaction):
"""Test major league team autocomplete error handling."""
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season.side_effect = Exception("API Error")
choices = await major_league_team_autocomplete(mock_interaction, 'LAA')
assert len(choices) == 0
@pytest.mark.asyncio
async def test_major_league_team_autocomplete_roster_type_detection(self, mock_interaction):
"""Test that roster type detection works correctly for edge cases."""
# Test edge cases like teams whose abbreviation ends in 'M' + 'IL'
mock_teams = [
TeamFactory.create(id=1, abbrev='BHM', sname='Iron'), # ML team ending in 'M'
TeamFactory.create(id=2, abbrev='BHMIL', sname='Iron IL'), # IL team (BHM + IL)
TeamFactory.create(id=3, abbrev='NYYMIL', sname='Staten Island RailRiders'), # MiL team (NYY + MIL)
TeamFactory.create(id=4, abbrev='NYY', sname='Yankees'), # ML team
]
with patch('utils.autocomplete.team_service') as mock_service:
mock_service.get_teams_by_season = AsyncMock(return_value=mock_teams)
choices = await major_league_team_autocomplete(mock_interaction, 'b')
# Should only return major league teams
choice_values = [choice.value for choice in choices]
assert 'BHM' in choice_values # Major league team
assert 'BHMIL' not in choice_values # Should be detected as IL, not MiL
assert 'NYYMIL' not in choice_values # Minor league team
# Verify the roster type detection is working
bhm_team = next(t for t in mock_teams if t.abbrev == 'BHM')
bhmil_team = next(t for t in mock_teams if t.abbrev == 'BHMIL')
nyymil_team = next(t for t in mock_teams if t.abbrev == 'NYYMIL')
assert bhm_team.roster_type() == RosterType.MAJOR_LEAGUE
assert bhmil_team.roster_type() == RosterType.INJURED_LIST
assert nyymil_team.roster_type() == RosterType.MINOR_LEAGUE