CLAUDE: Enhance /dropadd workflow and remove redundant commands
This commit implements significant improvements to the transaction management system: **Major Changes:** 1. Enhanced /dropadd command workflow: - All responses are now ephemeral for better user privacy - Every call now shows the transaction builder (not just first move) - Improved autocomplete with team context prioritization (user's players first) 2. Added team ownership validation: - Prevents adding players from other teams (except Free Agents with abbrev='FA') - Enhanced error messages with specific team information - Changed _add_quick_move return type to tuple[bool, str] for better error handling 3. Removed redundant /transactionstatus command: - Command functionality fully replaced by /dropadd without parameters - Simplified workflow reduces user confusion - All related tests updated accordingly **Technical Improvements:** - Enhanced player autocomplete with team prioritization and better formatting - Improved roster detection logic checking actual roster data vs team_id - Better error handling with specific, actionable error messages - Updated all test cases to handle new tuple return types - Added comprehensive test coverage for team ownership validation **Test Coverage:** - All 30 tests passing (17 dropadd + 13 transaction embed tests) - New tests for team ownership validation scenarios - Updated existing tests for new return type patterns The /dropadd command now serves as the single point of entry for all transaction operations while providing a cleaner, more intuitive user experience. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e20677ec72
commit
25a8b55fca
@ -38,26 +38,69 @@ class DropAddCommands(commands.Cog):
|
|||||||
current: str
|
current: str
|
||||||
) -> List[app_commands.Choice[str]]:
|
) -> List[app_commands.Choice[str]]:
|
||||||
"""
|
"""
|
||||||
Autocomplete for player names.
|
Autocomplete for player names with team context prioritization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction: Discord interaction
|
interaction: Discord interaction
|
||||||
current: Current input from user
|
current: Current input from user
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of player name choices
|
List of player name choices (user's team players first)
|
||||||
"""
|
"""
|
||||||
if len(current) < 2:
|
if len(current) < 2:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
|
||||||
# Search for players using the new dedicated search endpoint
|
|
||||||
players = await player_service.search_players(current, limit=25, season=SBA_CURRENT_SEASON)
|
|
||||||
|
|
||||||
# Format choices for Discord autocomplete
|
try:
|
||||||
choices = []
|
# Get user's team for prioritization
|
||||||
|
user_team = None
|
||||||
|
try:
|
||||||
|
major_league_teams = await team_service.get_teams_by_owner(
|
||||||
|
interaction.user.id,
|
||||||
|
SBA_CURRENT_SEASON,
|
||||||
|
roster_type="ml"
|
||||||
|
)
|
||||||
|
if major_league_teams:
|
||||||
|
user_team = major_league_teams[0]
|
||||||
|
except Exception:
|
||||||
|
# If we can't get user's team, continue without prioritization
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Search for players using the search endpoint
|
||||||
|
players = await player_service.search_players(current, limit=50, season=SBA_CURRENT_SEASON)
|
||||||
|
|
||||||
|
# Separate players by team (user's team vs others)
|
||||||
|
user_team_players = []
|
||||||
|
other_players = []
|
||||||
|
|
||||||
for player in players:
|
for player in players:
|
||||||
# Format: "Player Name (POS - TEAM)"
|
# Check if player belongs to user's team (any roster section)
|
||||||
|
is_users_player = False
|
||||||
|
if user_team and hasattr(player, 'team') and player.team:
|
||||||
|
# Check if player is from user's major league team or has same base team
|
||||||
|
if (player.team.id == user_team.id or
|
||||||
|
(hasattr(player, 'team_id') and player.team_id == user_team.id)):
|
||||||
|
is_users_player = True
|
||||||
|
|
||||||
|
if is_users_player:
|
||||||
|
user_team_players.append(player)
|
||||||
|
else:
|
||||||
|
other_players.append(player)
|
||||||
|
|
||||||
|
# Format choices with team context
|
||||||
|
choices = []
|
||||||
|
|
||||||
|
# Add user's team players first (prioritized)
|
||||||
|
for player in user_team_players[:15]: # Limit user team players
|
||||||
|
team_info = f"{player.primary_position}"
|
||||||
|
if hasattr(player, 'team') and player.team:
|
||||||
|
team_info += f" - {player.team.abbrev}"
|
||||||
|
|
||||||
|
choice_name = f"{player.name} ({team_info})"
|
||||||
|
choices.append(app_commands.Choice(name=choice_name, value=player.name))
|
||||||
|
|
||||||
|
# Add other players (remaining slots)
|
||||||
|
remaining_slots = 25 - len(choices)
|
||||||
|
for player in other_players[:remaining_slots]:
|
||||||
team_info = f"{player.primary_position}"
|
team_info = f"{player.primary_position}"
|
||||||
if hasattr(player, 'team') and player.team:
|
if hasattr(player, 'team') and player.team:
|
||||||
team_info += f" - {player.team.abbrev}"
|
team_info += f" - {player.team.abbrev}"
|
||||||
@ -66,7 +109,7 @@ class DropAddCommands(commands.Cog):
|
|||||||
choices.append(app_commands.Choice(name=choice_name, value=player.name))
|
choices.append(app_commands.Choice(name=choice_name, value=player.name))
|
||||||
|
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error in player autocomplete: {e}")
|
self.logger.error(f"Error in player autocomplete: {e}")
|
||||||
return []
|
return []
|
||||||
@ -76,7 +119,7 @@ class DropAddCommands(commands.Cog):
|
|||||||
description="Interactive transaction builder for player moves"
|
description="Interactive transaction builder for player moves"
|
||||||
)
|
)
|
||||||
@app_commands.describe(
|
@app_commands.describe(
|
||||||
player="Player name (optional - can add later)",
|
player="Player name (use autocomplete for best results)",
|
||||||
destination="Where to move the player: Major League, Minor League, Injured List, or Free Agency"
|
destination="Where to move the player: Major League, Minor League, Injured List, or Free Agency"
|
||||||
)
|
)
|
||||||
@app_commands.autocomplete(player=player_autocomplete)
|
@app_commands.autocomplete(player=player_autocomplete)
|
||||||
@ -94,8 +137,8 @@ class DropAddCommands(commands.Cog):
|
|||||||
destination: Optional[str] = None
|
destination: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""Interactive transaction builder for complex player moves."""
|
"""Interactive transaction builder for complex player moves."""
|
||||||
await interaction.response.defer()
|
await interaction.response.defer(ephemeral=True)
|
||||||
|
|
||||||
# Get user's major league team
|
# Get user's major league team
|
||||||
major_league_teams = await team_service.get_teams_by_owner(
|
major_league_teams = await team_service.get_teams_by_owner(
|
||||||
interaction.user.id,
|
interaction.user.id,
|
||||||
@ -111,47 +154,74 @@ class DropAddCommands(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
team = major_league_teams[0] # Use first major league team
|
team = major_league_teams[0] # Use first major league team
|
||||||
|
|
||||||
# Get or create transaction builder
|
# Get or create transaction builder
|
||||||
builder = get_transaction_builder(interaction.user.id, team)
|
builder = get_transaction_builder(interaction.user.id, team)
|
||||||
|
|
||||||
# If player and destination provided, try to add the move immediately
|
# Handle different scenarios based on builder state and parameters
|
||||||
if player and destination:
|
if player and destination:
|
||||||
success = await self._add_quick_move(builder, player, destination)
|
# User provided both parameters - try to add the move
|
||||||
|
success, error_message = await self._add_quick_move(builder, player, destination)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info(f"Quick move added for {team.abbrev}: {player} → {destination}")
|
# Move added successfully - show updated transaction builder
|
||||||
|
embed = await create_transaction_embed(builder)
|
||||||
|
view = TransactionEmbedView(builder, interaction.user.id)
|
||||||
|
|
||||||
|
success_msg = f"✅ **Added {player} → {destination.upper()}**"
|
||||||
|
if builder.move_count > 1:
|
||||||
|
success_msg += f"\n📊 Transaction now has {builder.move_count} moves"
|
||||||
|
|
||||||
|
await interaction.followup.send(
|
||||||
|
content=success_msg,
|
||||||
|
embed=embed,
|
||||||
|
view=view,
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
self.logger.info(f"Move added for {team.abbrev}: {player} → {destination}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Failed to add quick move: {player} → {destination}")
|
# Failed to add move - still show current transaction state
|
||||||
|
embed = await create_transaction_embed(builder)
|
||||||
# Create and display interactive embed
|
view = TransactionEmbedView(builder, interaction.user.id)
|
||||||
embed = await create_transaction_embed(builder)
|
|
||||||
view = TransactionEmbedView(builder, interaction.user.id)
|
await interaction.followup.send(
|
||||||
|
content=f"❌ **{error_message}**\n"
|
||||||
await interaction.followup.send(embed=embed, view=view)
|
f"💡 Try using autocomplete for player names",
|
||||||
|
embed=embed,
|
||||||
|
view=view,
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
self.logger.warning(f"Failed to add move: {player} → {destination}: {error_message}")
|
||||||
|
else:
|
||||||
|
# No parameters or incomplete parameters - show current transaction state
|
||||||
|
embed = await create_transaction_embed(builder)
|
||||||
|
view = TransactionEmbedView(builder, interaction.user.id)
|
||||||
|
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
|
||||||
|
|
||||||
async def _add_quick_move(
|
async def _add_quick_move(
|
||||||
self,
|
self,
|
||||||
builder: TransactionBuilder,
|
builder: TransactionBuilder,
|
||||||
player_name: str,
|
player_name: str,
|
||||||
destination_str: str
|
destination_str: str
|
||||||
) -> bool:
|
) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Add a move quickly from command parameters by auto-determining the action.
|
Add a move quickly from command parameters by auto-determining the action.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
builder: TransactionBuilder instance
|
builder: TransactionBuilder instance
|
||||||
player_name: Name of player to move
|
player_name: Name of player to move
|
||||||
destination_str: Destination string (ml, mil, fa)
|
destination_str: Destination string (ml, mil, fa)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if move was added successfully
|
Tuple of (success: bool, error_message: str)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Find player using the new search endpoint
|
# Find player using the new search endpoint
|
||||||
players = await player_service.search_players(player_name, limit=10, season=SBA_CURRENT_SEASON)
|
players = await player_service.search_players(player_name, limit=10, season=SBA_CURRENT_SEASON)
|
||||||
if not players:
|
if not players:
|
||||||
self.logger.error(f"Player not found: {player_name}")
|
self.logger.error(f"Player not found: {player_name}")
|
||||||
return False
|
return False, f"Player '{player_name}' not found"
|
||||||
|
|
||||||
# Use exact match if available, otherwise first result
|
# Use exact match if available, otherwise first result
|
||||||
player = None
|
player = None
|
||||||
@ -159,9 +229,20 @@ class DropAddCommands(commands.Cog):
|
|||||||
if p.name.lower() == player_name.lower():
|
if p.name.lower() == player_name.lower():
|
||||||
player = p
|
player = p
|
||||||
break
|
break
|
||||||
|
|
||||||
if not player:
|
if not player:
|
||||||
player = players[0] # Use first match
|
player = players[0] # Use first match
|
||||||
|
|
||||||
|
# Check if player belongs to another team (not user's team and not Free Agency)
|
||||||
|
if player.team and hasattr(player.team, 'abbrev'):
|
||||||
|
# Player belongs to another team if:
|
||||||
|
# 1. They have a team assigned AND
|
||||||
|
# 2. That team is not Free Agency (abbrev != 'FA') AND
|
||||||
|
# 3. That team is not the user's team
|
||||||
|
if (player.team.abbrev != 'FA' and
|
||||||
|
player.team.id != builder.team.id):
|
||||||
|
self.logger.warning(f"Player {player.name} belongs to {player.team.abbrev}, cannot add to {builder.team.abbrev} transaction")
|
||||||
|
return False, f"{player.name} belongs to {player.team.abbrev} and cannot be added to your transaction"
|
||||||
|
|
||||||
# Parse destination
|
# Parse destination
|
||||||
destination_map = {
|
destination_map = {
|
||||||
@ -174,17 +255,36 @@ class DropAddCommands(commands.Cog):
|
|||||||
to_roster = destination_map.get(destination_str.lower())
|
to_roster = destination_map.get(destination_str.lower())
|
||||||
if not to_roster:
|
if not to_roster:
|
||||||
self.logger.error(f"Invalid destination: {destination_str}")
|
self.logger.error(f"Invalid destination: {destination_str}")
|
||||||
return False
|
return False, f"Invalid destination: {destination_str}"
|
||||||
|
|
||||||
# Determine player's current roster status based on their team and roster type
|
# Determine player's current roster status by checking actual roster data
|
||||||
if player.team_id == builder.team.id:
|
# Note: Minor League players have different team_id than Major League team
|
||||||
# Player is on the user's team - need to determine which roster
|
self.logger.debug(f"Player {player.name} team_id: {player.team_id}, Builder team_id: {builder.team.id}")
|
||||||
# This would need to be enhanced to check actual roster data
|
|
||||||
# For now, we'll assume they're coming from Major League
|
await builder.load_roster_data()
|
||||||
from_roster = RosterType.MAJOR_LEAGUE
|
if builder._current_roster:
|
||||||
|
# Check which roster section the player is on (regardless of team_id)
|
||||||
|
player_on_active = any(p.id == player.id for p in builder._current_roster.active_players)
|
||||||
|
player_on_minor = any(p.id == player.id for p in builder._current_roster.minor_league_players)
|
||||||
|
player_on_il = any(p.id == player.id for p in builder._current_roster.il_players)
|
||||||
|
|
||||||
|
if player_on_active:
|
||||||
|
from_roster = RosterType.MAJOR_LEAGUE
|
||||||
|
self.logger.debug(f"Player {player.name} found on active roster (Major League)")
|
||||||
|
elif player_on_minor:
|
||||||
|
from_roster = RosterType.MINOR_LEAGUE
|
||||||
|
self.logger.debug(f"Player {player.name} found on minor league roster")
|
||||||
|
elif player_on_il:
|
||||||
|
from_roster = RosterType.INJURED_LIST
|
||||||
|
self.logger.debug(f"Player {player.name} found on injured list")
|
||||||
|
else:
|
||||||
|
# Player not found on user's roster - they're from another team or free agency
|
||||||
|
from_roster = RosterType.FREE_AGENCY
|
||||||
|
self.logger.debug(f"Player {player.name} not found on user's roster, treating as free agency")
|
||||||
else:
|
else:
|
||||||
# Player is on another team or free agency
|
# Couldn't load roster data, assume free agency as safest fallback
|
||||||
from_roster = RosterType.FREE_AGENCY
|
from_roster = RosterType.FREE_AGENCY
|
||||||
|
self.logger.warning(f"Could not load roster data, assuming {player.name} is free agency")
|
||||||
|
|
||||||
# Create move
|
# Create move
|
||||||
move = TransactionMove(
|
move = TransactionMove(
|
||||||
@ -198,11 +298,12 @@ class DropAddCommands(commands.Cog):
|
|||||||
success, error_message = builder.add_move(move)
|
success, error_message = builder.add_move(move)
|
||||||
if not success:
|
if not success:
|
||||||
self.logger.warning(f"Failed to add quick move: {error_message}")
|
self.logger.warning(f"Failed to add quick move: {error_message}")
|
||||||
return success
|
return False, error_message
|
||||||
|
return True, ""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error adding quick move: {e}")
|
self.logger.error(f"Error adding quick move: {e}")
|
||||||
return False
|
return False, f"Error adding move: {str(e)}"
|
||||||
|
|
||||||
@app_commands.command(
|
@app_commands.command(
|
||||||
name="cleartransaction",
|
name="cleartransaction",
|
||||||
@ -218,56 +319,6 @@ class DropAddCommands(commands.Cog):
|
|||||||
ephemeral=True
|
ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@app_commands.command(
|
|
||||||
name="transactionstatus",
|
|
||||||
description="Show your current transaction builder status"
|
|
||||||
)
|
|
||||||
@logged_command("/transactionstatus")
|
|
||||||
async def transaction_status(self, interaction: discord.Interaction):
|
|
||||||
"""Show the current status of user's transaction builder."""
|
|
||||||
await interaction.response.defer(ephemeral=True)
|
|
||||||
|
|
||||||
# Get user's major league team
|
|
||||||
major_league_teams = await team_service.get_teams_by_owner(
|
|
||||||
interaction.user.id,
|
|
||||||
SBA_CURRENT_SEASON,
|
|
||||||
roster_type="ml"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not major_league_teams:
|
|
||||||
await interaction.followup.send(
|
|
||||||
"❌ You don't appear to own a major league team in the current season.",
|
|
||||||
ephemeral=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
team = major_league_teams[0]
|
|
||||||
builder = get_transaction_builder(interaction.user.id, team)
|
|
||||||
|
|
||||||
if builder.is_empty:
|
|
||||||
await interaction.followup.send(
|
|
||||||
"📋 Your transaction builder is empty. Use `/dropadd` to start building!",
|
|
||||||
ephemeral=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Show current status
|
|
||||||
validation = await builder.validate_transaction()
|
|
||||||
|
|
||||||
status_msg = f"📋 **Transaction Builder Status - {team.abbrev}**\n\n"
|
|
||||||
status_msg += f"**Moves:** {builder.move_count}\n"
|
|
||||||
status_msg += f"**Status:** {'✅ Legal' if validation.is_legal else '❌ Illegal'}\n\n"
|
|
||||||
|
|
||||||
if validation.errors:
|
|
||||||
status_msg += "**Errors:**\n"
|
|
||||||
status_msg += "\n".join([f"• {error}" for error in validation.errors])
|
|
||||||
status_msg += "\n\n"
|
|
||||||
|
|
||||||
if validation.suggestions:
|
|
||||||
status_msg += "**Suggestions:**\n"
|
|
||||||
status_msg += "\n".join([f"💡 {suggestion}" for suggestion in validation.suggestions])
|
|
||||||
|
|
||||||
await interaction.followup.send(status_msg, ephemeral=True)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
|||||||
@ -13,6 +13,7 @@ from services.transaction_builder import TransactionBuilder
|
|||||||
from models.team import RosterType
|
from models.team import RosterType
|
||||||
from models.team import Team
|
from models.team import Team
|
||||||
from models.player import Player
|
from models.player import Player
|
||||||
|
from tests.factories import PlayerFactory, TeamFactory
|
||||||
|
|
||||||
|
|
||||||
class TestDropAddCommands:
|
class TestDropAddCommands:
|
||||||
@ -47,34 +48,23 @@ class TestDropAddCommands:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_team(self):
|
def mock_team(self):
|
||||||
"""Create mock team data."""
|
"""Create mock team data."""
|
||||||
return Team(
|
return TeamFactory.west_virginia()
|
||||||
id=499,
|
|
||||||
abbrev='WV',
|
|
||||||
sname='Black Bears',
|
|
||||||
lname='West Virginia Black Bears',
|
|
||||||
season=12
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_player(self):
|
def mock_player(self):
|
||||||
"""Create mock player data."""
|
"""Create mock player data."""
|
||||||
return Player(
|
return PlayerFactory.mike_trout()
|
||||||
id=12472,
|
|
||||||
name='Mike Trout',
|
|
||||||
season=12,
|
|
||||||
primary_position='CF'
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_player_autocomplete_success(self, commands_cog, mock_interaction):
|
async def test_player_autocomplete_success(self, commands_cog, mock_interaction):
|
||||||
"""Test successful player autocomplete."""
|
"""Test successful player autocomplete."""
|
||||||
mock_players = [
|
mock_players = [
|
||||||
Player(id=1, name='Mike Trout', season=12, primary_position='CF'),
|
PlayerFactory.mike_trout(id=1),
|
||||||
Player(id=2, name='Ronald Acuna Jr.', season=12, primary_position='OF')
|
PlayerFactory.ronald_acuna(id=2)
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_service.get_players_by_name.return_value = mock_players
|
mock_service.search_players = AsyncMock(return_value=mock_players)
|
||||||
|
|
||||||
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
||||||
|
|
||||||
@ -87,18 +77,12 @@ class TestDropAddCommands:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_player_autocomplete_with_team(self, commands_cog, mock_interaction):
|
async def test_player_autocomplete_with_team(self, commands_cog, mock_interaction):
|
||||||
"""Test player autocomplete with team information."""
|
"""Test player autocomplete with team information."""
|
||||||
mock_team = Team(id=499, abbrev='LAA', sname='Angels', lname='Los Angeles Angels', season=12)
|
mock_team = TeamFactory.create(id=499, abbrev='LAA', sname='Angels', lname='Los Angeles Angels')
|
||||||
mock_player = Player(
|
mock_player = PlayerFactory.mike_trout(id=1)
|
||||||
id=1,
|
|
||||||
name='Mike Trout',
|
|
||||||
season=12,
|
|
||||||
primary_position='CF'
|
|
||||||
)
|
|
||||||
mock_player.team = mock_team # Add team info
|
mock_player.team = mock_team # Add team info
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_service.get_players_by_name.return_value = [mock_player]
|
mock_service.search_players = AsyncMock(return_value=[mock_player])
|
||||||
|
|
||||||
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
||||||
|
|
||||||
assert len(choices) == 1
|
assert len(choices) == 1
|
||||||
@ -115,7 +99,7 @@ class TestDropAddCommands:
|
|||||||
async def test_player_autocomplete_error_handling(self, commands_cog, mock_interaction):
|
async def test_player_autocomplete_error_handling(self, commands_cog, mock_interaction):
|
||||||
"""Test player autocomplete error handling."""
|
"""Test player autocomplete error handling."""
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_service.get_players_by_name.side_effect = Exception("API Error")
|
mock_service.search_players.side_effect = Exception("API Error")
|
||||||
|
|
||||||
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
choices = await commands_cog.player_autocomplete(mock_interaction, 'Trout')
|
||||||
assert len(choices) == 0
|
assert len(choices) == 0
|
||||||
@ -124,16 +108,15 @@ class TestDropAddCommands:
|
|||||||
async def test_dropadd_command_no_team(self, commands_cog, mock_interaction):
|
async def test_dropadd_command_no_team(self, commands_cog, mock_interaction):
|
||||||
"""Test /dropadd command when user has no team."""
|
"""Test /dropadd command when user has no team."""
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_service:
|
||||||
mock_service.get_teams_by_owner.return_value = []
|
mock_service.get_teams_by_owner = AsyncMock(return_value=[])
|
||||||
|
await commands_cog.dropadd.callback(commands_cog, mock_interaction)
|
||||||
await commands_cog.dropadd(mock_interaction)
|
|
||||||
|
|
||||||
mock_interaction.response.defer.assert_called_once()
|
mock_interaction.response.defer.assert_called_once()
|
||||||
mock_interaction.followup.send.assert_called_once()
|
mock_interaction.followup.send.assert_called_once()
|
||||||
|
|
||||||
# Check error message
|
# Check error message
|
||||||
call_args = mock_interaction.followup.send.call_args
|
call_args = mock_interaction.followup.send.call_args
|
||||||
assert "don't appear to own a team" in call_args[0][0]
|
assert "don't appear to own" in call_args[0][0]
|
||||||
assert call_args[1]['ephemeral'] is True
|
assert call_args[1]['ephemeral'] is True
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -142,7 +125,7 @@ class TestDropAddCommands:
|
|||||||
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
||||||
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
||||||
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
||||||
mock_team_service.get_teams_by_owner.return_value = [mock_team]
|
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
|
||||||
|
|
||||||
mock_builder = MagicMock()
|
mock_builder = MagicMock()
|
||||||
mock_builder.team = mock_team
|
mock_builder.team = mock_team
|
||||||
@ -151,12 +134,12 @@ class TestDropAddCommands:
|
|||||||
mock_embed = MagicMock()
|
mock_embed = MagicMock()
|
||||||
mock_create_embed.return_value = mock_embed
|
mock_create_embed.return_value = mock_embed
|
||||||
|
|
||||||
await commands_cog.dropadd(mock_interaction)
|
await commands_cog.dropadd.callback(commands_cog, mock_interaction)
|
||||||
|
|
||||||
# Verify flow
|
# Verify flow
|
||||||
mock_interaction.response.defer.assert_called_once()
|
mock_interaction.response.defer.assert_called_once()
|
||||||
mock_team_service.get_teams_by_owner.assert_called_once_with(
|
mock_team_service.get_teams_by_owner.assert_called_once_with(
|
||||||
mock_interaction.user.id, 12
|
mock_interaction.user.id, 12, roster_type='ml'
|
||||||
)
|
)
|
||||||
mock_get_builder.assert_called_once_with(mock_interaction.user.id, mock_team)
|
mock_get_builder.assert_called_once_with(mock_interaction.user.id, mock_team)
|
||||||
mock_create_embed.assert_called_once_with(mock_builder)
|
mock_create_embed.assert_called_once_with(mock_builder)
|
||||||
@ -169,23 +152,23 @@ class TestDropAddCommands:
|
|||||||
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
||||||
with patch.object(commands_cog, '_add_quick_move') as mock_add_quick:
|
with patch.object(commands_cog, '_add_quick_move') as mock_add_quick:
|
||||||
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
||||||
mock_team_service.get_teams_by_owner.return_value = [mock_team]
|
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
|
||||||
|
|
||||||
mock_builder = MagicMock()
|
mock_builder = MagicMock()
|
||||||
|
mock_builder.move_count = 1
|
||||||
mock_get_builder.return_value = mock_builder
|
mock_get_builder.return_value = mock_builder
|
||||||
mock_add_quick.return_value = True
|
mock_add_quick.return_value = (True, "")
|
||||||
mock_create_embed.return_value = MagicMock()
|
mock_create_embed.return_value = MagicMock()
|
||||||
|
|
||||||
await commands_cog.dropadd(
|
await commands_cog.dropadd.callback(commands_cog,
|
||||||
mock_interaction,
|
mock_interaction,
|
||||||
player='Mike Trout',
|
player='Mike Trout',
|
||||||
action='add',
|
|
||||||
destination='ml'
|
destination='ml'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify quick move was attempted
|
# Verify quick move was attempted
|
||||||
mock_add_quick.assert_called_once_with(
|
mock_add_quick.assert_called_once_with(
|
||||||
mock_builder, 'Mike Trout', 'add', 'ml'
|
mock_builder, 'Mike Trout', 'ml'
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -193,17 +176,23 @@ class TestDropAddCommands:
|
|||||||
"""Test successful quick move addition."""
|
"""Test successful quick move addition."""
|
||||||
mock_builder = MagicMock()
|
mock_builder = MagicMock()
|
||||||
mock_builder.team = mock_team
|
mock_builder.team = mock_team
|
||||||
mock_builder.add_move.return_value = True
|
mock_builder.add_move.return_value = (True, "")
|
||||||
|
mock_builder.load_roster_data = AsyncMock()
|
||||||
|
mock_builder._current_roster = MagicMock()
|
||||||
|
mock_builder._current_roster.active_players = []
|
||||||
|
mock_builder._current_roster.minor_league_players = []
|
||||||
|
mock_builder._current_roster.il_players = []
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_service.get_players_by_name.return_value = [mock_player]
|
mock_service.search_players = AsyncMock(return_value=[mock_player])
|
||||||
|
|
||||||
success = await commands_cog._add_quick_move(
|
success, error_message = await commands_cog._add_quick_move(
|
||||||
mock_builder, 'Mike Trout', 'add', 'ml'
|
mock_builder, 'Mike Trout', 'ml'
|
||||||
)
|
)
|
||||||
|
|
||||||
assert success is True
|
assert success is True
|
||||||
mock_service.get_players_by_name.assert_called_once_with('Mike Trout', 12)
|
assert error_message == ""
|
||||||
|
mock_service.search_players.assert_called_once_with('Mike Trout', limit=10, season=12)
|
||||||
mock_builder.add_move.assert_called_once()
|
mock_builder.add_move.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -213,26 +202,86 @@ class TestDropAddCommands:
|
|||||||
mock_builder.team = mock_team
|
mock_builder.team = mock_team
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_service.get_players_by_name.return_value = []
|
mock_service.search_players = AsyncMock(return_value=[])
|
||||||
|
|
||||||
success = await commands_cog._add_quick_move(
|
success, error_message = await commands_cog._add_quick_move(
|
||||||
mock_builder, 'Nonexistent Player', 'add', 'ml'
|
mock_builder, 'Nonexistent Player', 'ml'
|
||||||
)
|
)
|
||||||
|
|
||||||
assert success is False
|
assert success is False
|
||||||
|
assert "not found" in error_message
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_add_quick_move_invalid_action(self, commands_cog, mock_team):
|
async def test_add_quick_move_invalid_action(self, commands_cog, mock_team, mock_player):
|
||||||
"""Test quick move with invalid action."""
|
"""Test quick move with invalid action."""
|
||||||
mock_builder = MagicMock()
|
mock_builder = MagicMock()
|
||||||
mock_builder.team = mock_team
|
mock_builder.team = mock_team
|
||||||
|
|
||||||
success = await commands_cog._add_quick_move(
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
mock_builder, 'Mike Trout', 'invalid_action', 'ml'
|
mock_service.search_players = AsyncMock(return_value=[mock_player])
|
||||||
)
|
|
||||||
|
success, error_message = await commands_cog._add_quick_move(
|
||||||
assert success is False
|
mock_builder, 'Mike Trout', 'invalid_destination'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert success is False
|
||||||
|
assert "Invalid destination" in error_message
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_quick_move_player_from_other_team(self, commands_cog, mock_team):
|
||||||
|
"""Test quick move when player belongs to another team."""
|
||||||
|
mock_builder = MagicMock()
|
||||||
|
mock_builder.team = mock_team # West Virginia (team_id=499)
|
||||||
|
|
||||||
|
# Create a player from another team (not Free Agency)
|
||||||
|
other_team = MagicMock()
|
||||||
|
other_team.id = 100 # Different team
|
||||||
|
other_team.abbrev = "LAA" # Not 'FA'
|
||||||
|
|
||||||
|
mock_player = MagicMock()
|
||||||
|
mock_player.name = "Mike Trout"
|
||||||
|
mock_player.team = other_team
|
||||||
|
|
||||||
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
|
mock_service.search_players = AsyncMock(return_value=[mock_player])
|
||||||
|
|
||||||
|
success, error_message = await commands_cog._add_quick_move(
|
||||||
|
mock_builder, 'Mike Trout', 'ml'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert success is False
|
||||||
|
assert "belongs to LAA" in error_message
|
||||||
|
assert "cannot be added to your transaction" in error_message
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_quick_move_free_agent_allowed(self, commands_cog, mock_team):
|
||||||
|
"""Test quick move when player is a Free Agent (should be allowed)."""
|
||||||
|
from tests.factories import PlayerFactory, TeamFactory
|
||||||
|
|
||||||
|
mock_builder = MagicMock()
|
||||||
|
mock_builder.team = mock_team
|
||||||
|
mock_builder.add_move.return_value = (True, "")
|
||||||
|
mock_builder.load_roster_data = AsyncMock()
|
||||||
|
mock_builder._current_roster = MagicMock()
|
||||||
|
mock_builder._current_roster.active_players = []
|
||||||
|
mock_builder._current_roster.minor_league_players = []
|
||||||
|
mock_builder._current_roster.il_players = []
|
||||||
|
|
||||||
|
# Create a Free Agent team and player
|
||||||
|
fa_team = TeamFactory.create(id=1, abbrev="FA", sname="Free Agency", lname="Free Agency")
|
||||||
|
fa_player = PlayerFactory.create(id=12472, name="Mike Trout", team_id=1)
|
||||||
|
fa_player.team = fa_team
|
||||||
|
|
||||||
|
with patch('commands.transactions.dropadd.player_service') as mock_service:
|
||||||
|
mock_service.search_players = AsyncMock(return_value=[fa_player])
|
||||||
|
|
||||||
|
success, error_message = await commands_cog._add_quick_move(
|
||||||
|
mock_builder, 'Mike Trout', 'ml'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert success is True
|
||||||
|
assert error_message == ""
|
||||||
|
|
||||||
# TODO: These tests are for obsolete MoveAction-based functionality
|
# TODO: These tests are for obsolete MoveAction-based functionality
|
||||||
# The transaction system now uses from_roster/to_roster directly
|
# The transaction system now uses from_roster/to_roster directly
|
||||||
# def test_determine_roster_types_add(self, commands_cog):
|
# def test_determine_roster_types_add(self, commands_cog):
|
||||||
@ -245,7 +294,7 @@ class TestDropAddCommands:
|
|||||||
async def test_clear_transaction_command(self, commands_cog, mock_interaction):
|
async def test_clear_transaction_command(self, commands_cog, mock_interaction):
|
||||||
"""Test /cleartransaction command."""
|
"""Test /cleartransaction command."""
|
||||||
with patch('commands.transactions.dropadd.clear_transaction_builder') as mock_clear:
|
with patch('commands.transactions.dropadd.clear_transaction_builder') as mock_clear:
|
||||||
await commands_cog.clear_transaction(mock_interaction)
|
await commands_cog.clear_transaction.callback(commands_cog, mock_interaction)
|
||||||
|
|
||||||
mock_clear.assert_called_once_with(mock_interaction.user.id)
|
mock_clear.assert_called_once_with(mock_interaction.user.id)
|
||||||
mock_interaction.response.send_message.assert_called_once()
|
mock_interaction.response.send_message.assert_called_once()
|
||||||
@ -255,64 +304,79 @@ class TestDropAddCommands:
|
|||||||
assert "transaction builder has been cleared" in call_args[0][0]
|
assert "transaction builder has been cleared" in call_args[0][0]
|
||||||
assert call_args[1]['ephemeral'] is True
|
assert call_args[1]['ephemeral'] is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_transaction_status_no_team(self, commands_cog, mock_interaction):
|
async def test_dropadd_first_move_shows_full_embed(self, commands_cog, mock_interaction, mock_team):
|
||||||
"""Test /transactionstatus when user has no team."""
|
"""Test /dropadd command with first move shows full interactive embed."""
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_service:
|
|
||||||
mock_service.get_teams_by_owner.return_value = []
|
|
||||||
|
|
||||||
await commands_cog.transaction_status(mock_interaction)
|
|
||||||
|
|
||||||
mock_interaction.response.defer.assert_called_once_with(ephemeral=True)
|
|
||||||
mock_interaction.followup.send.assert_called_once()
|
|
||||||
|
|
||||||
call_args = mock_interaction.followup.send.call_args
|
|
||||||
assert "don't appear to own a team" in call_args[0][0]
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_transaction_status_empty_builder(self, commands_cog, mock_interaction, mock_team):
|
|
||||||
"""Test /transactionstatus with empty builder."""
|
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
||||||
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
||||||
mock_team_service.get_teams_by_owner.return_value = [mock_team]
|
with patch.object(commands_cog, '_add_quick_move') as mock_add_quick:
|
||||||
|
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
||||||
mock_builder = MagicMock()
|
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
|
||||||
mock_builder.is_empty = True
|
|
||||||
mock_get_builder.return_value = mock_builder
|
# Create empty builder (first move)
|
||||||
|
mock_builder = MagicMock()
|
||||||
await commands_cog.transaction_status(mock_interaction)
|
mock_builder.is_empty = True
|
||||||
|
mock_builder.move_count = 1
|
||||||
call_args = mock_interaction.followup.send.call_args
|
mock_get_builder.return_value = mock_builder
|
||||||
assert "transaction builder is empty" in call_args[0][0]
|
mock_add_quick.return_value = (True, "")
|
||||||
|
mock_create_embed.return_value = MagicMock()
|
||||||
|
|
||||||
|
await commands_cog.dropadd.callback(commands_cog,
|
||||||
|
mock_interaction,
|
||||||
|
player='Mike Trout',
|
||||||
|
destination='ml'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should show full embed with view (now ephemeral)
|
||||||
|
mock_interaction.followup.send.assert_called_once()
|
||||||
|
call_args = mock_interaction.followup.send.call_args
|
||||||
|
assert call_args[1]['ephemeral'] is True
|
||||||
|
assert 'embed' in call_args[1]
|
||||||
|
assert 'view' in call_args[1]
|
||||||
|
assert 'content' in call_args[1]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_transaction_status_with_moves(self, commands_cog, mock_interaction, mock_team):
|
async def test_dropadd_append_mode_shows_confirmation(self, commands_cog, mock_interaction, mock_team):
|
||||||
"""Test /transactionstatus with moves in builder."""
|
"""Test /dropadd command in append mode shows ephemeral confirmation."""
|
||||||
from services.transaction_builder import RosterValidationResult
|
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
||||||
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
||||||
mock_team_service.get_teams_by_owner.return_value = [mock_team]
|
with patch.object(commands_cog, '_add_quick_move') as mock_add_quick:
|
||||||
|
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
|
||||||
mock_builder = MagicMock()
|
|
||||||
mock_builder.is_empty = False
|
# Create builder with existing moves (append mode)
|
||||||
mock_builder.move_count = 2
|
mock_builder = MagicMock()
|
||||||
mock_builder.validate_transaction = AsyncMock(return_value=RosterValidationResult(
|
mock_builder.is_empty = False
|
||||||
is_legal=True,
|
mock_builder.move_count = 2
|
||||||
major_league_count=25,
|
mock_builder.validate_transaction = AsyncMock(return_value=MagicMock(
|
||||||
minor_league_count=10,
|
is_legal=True,
|
||||||
warnings=[],
|
major_league_count=25,
|
||||||
errors=[],
|
minor_league_count=10,
|
||||||
suggestions=[]
|
warnings=[],
|
||||||
))
|
errors=[],
|
||||||
mock_get_builder.return_value = mock_builder
|
suggestions=[]
|
||||||
|
))
|
||||||
await commands_cog.transaction_status(mock_interaction)
|
mock_get_builder.return_value = mock_builder
|
||||||
|
mock_add_quick.return_value = (True, "")
|
||||||
call_args = mock_interaction.followup.send.call_args
|
|
||||||
status_msg = call_args[0][0]
|
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
||||||
assert "Moves:** 2" in status_msg
|
mock_create_embed.return_value = MagicMock()
|
||||||
assert "✅ Legal" in status_msg
|
|
||||||
|
await commands_cog.dropadd.callback(commands_cog,
|
||||||
|
mock_interaction,
|
||||||
|
player='Kevin Ginkel',
|
||||||
|
destination='ml'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should show embed with ephemeral confirmation
|
||||||
|
mock_interaction.followup.send.assert_called_once()
|
||||||
|
call_args = mock_interaction.followup.send.call_args
|
||||||
|
assert call_args[1]['ephemeral'] is True
|
||||||
|
assert 'embed' in call_args[1]
|
||||||
|
assert 'view' in call_args[1]
|
||||||
|
content = call_args[1]['content']
|
||||||
|
assert "Added Kevin Ginkel → ML" in content
|
||||||
|
assert "Transaction now has 2 moves" in content
|
||||||
|
|
||||||
|
|
||||||
class TestDropAddCommandsIntegration:
|
class TestDropAddCommandsIntegration:
|
||||||
@ -334,38 +398,30 @@ class TestDropAddCommandsIntegration:
|
|||||||
mock_interaction = AsyncMock()
|
mock_interaction = AsyncMock()
|
||||||
mock_interaction.user.id = 123456789
|
mock_interaction.user.id = 123456789
|
||||||
|
|
||||||
mock_team = Team(
|
mock_team = TeamFactory.west_virginia()
|
||||||
id=499,
|
|
||||||
abbrev='WV',
|
|
||||||
sname='Black Bears',
|
|
||||||
lname='West Virginia Black Bears',
|
|
||||||
season=12
|
|
||||||
)
|
|
||||||
|
|
||||||
mock_player = Player(
|
mock_player = PlayerFactory.mike_trout(id=12472)
|
||||||
id=12472,
|
|
||||||
name='Mike Trout',
|
|
||||||
season=12,
|
|
||||||
primary_position='CF'
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_team_service:
|
||||||
with patch('commands.transactions.dropadd.player_service') as mock_player_service:
|
with patch('commands.transactions.dropadd.player_service') as mock_player_service:
|
||||||
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
with patch('commands.transactions.dropadd.get_transaction_builder') as mock_get_builder:
|
||||||
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
with patch('commands.transactions.dropadd.create_transaction_embed') as mock_create_embed:
|
||||||
# Setup mocks
|
# Setup mocks
|
||||||
mock_team_service.get_teams_by_owner.return_value = [mock_team]
|
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
|
||||||
mock_player_service.get_players_by_name.return_value = [mock_player]
|
mock_player_service.search_players = AsyncMock(return_value=[mock_player])
|
||||||
|
|
||||||
mock_builder = TransactionBuilder(mock_team, 123456789, 12)
|
mock_builder = TransactionBuilder(mock_team, 123456789, 12)
|
||||||
mock_get_builder.return_value = mock_builder
|
mock_get_builder.return_value = mock_builder
|
||||||
mock_create_embed.return_value = MagicMock()
|
|
||||||
|
# Mock the async function
|
||||||
|
async def mock_create_embed_func(builder):
|
||||||
|
return MagicMock()
|
||||||
|
mock_create_embed.side_effect = mock_create_embed_func
|
||||||
|
|
||||||
# Execute command with parameters
|
# Execute command with parameters
|
||||||
await commands_cog.dropadd(
|
await commands_cog.dropadd.callback(commands_cog,
|
||||||
mock_interaction,
|
mock_interaction,
|
||||||
player='Mike Trout',
|
player='Mike Trout',
|
||||||
action='add',
|
|
||||||
destination='ml'
|
destination='ml'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -384,10 +440,11 @@ class TestDropAddCommandsIntegration:
|
|||||||
|
|
||||||
with patch('commands.transactions.dropadd.team_service') as mock_service:
|
with patch('commands.transactions.dropadd.team_service') as mock_service:
|
||||||
# Simulate API error
|
# Simulate API error
|
||||||
mock_service.get_teams_by_owner.side_effect = Exception("API Error")
|
mock_service.get_teams_by_owner = AsyncMock(side_effect=Exception("API Error"))
|
||||||
|
|
||||||
# Should not raise exception, should handle gracefully
|
# Exception should be raised (logged_command decorator re-raises)
|
||||||
await commands_cog.dropadd(mock_interaction)
|
with pytest.raises(Exception, match="API Error"):
|
||||||
|
await commands_cog.dropadd.callback(commands_cog, mock_interaction)
|
||||||
# Should have deferred and attempted to send error (which will also fail gracefully)
|
|
||||||
|
# Should have deferred before the error occurred
|
||||||
mock_interaction.response.defer.assert_called_once()
|
mock_interaction.response.defer.assert_called_once()
|
||||||
Loading…
Reference in New Issue
Block a user