From 3c66ada99d4ea39b8e28071c69ee1cdaf85f6931 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 14 Oct 2025 16:51:24 -0500 Subject: [PATCH] CLAUDE: Add pagination and move ID display to /mymoves command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix transaction count bug by filtering transactions >= current week - Add TransactionPaginationView with "Show Move IDs" button - Implement intelligent message chunking for long transaction lists - Remove emojis from individual transaction lines (kept in headers) - Display 10 transactions per page with navigation buttons - Show move IDs on demand via ephemeral button (stays under 2000 char limit) - Update all tests to validate pagination and chunking behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- commands/transactions/management.py | 325 ++++++++++++++++++++-------- services/transaction_service.py | 31 ++- tests/test_commands_transactions.py | 252 +++++++++++++++------ 3 files changed, 453 insertions(+), 155 deletions(-) diff --git a/commands/transactions/management.py b/commands/transactions/management.py index 1851baf..3aac3fc 100644 --- a/commands/transactions/management.py +++ b/commands/transactions/management.py @@ -12,7 +12,9 @@ from discord import app_commands from utils.logging import get_contextual_logger from utils.decorators import logged_command +from utils.team_utils import get_user_major_league_team from views.embeds import EmbedColors, EmbedTemplate +from views.base import PaginationView from constants import SBA_CURRENT_SEASON from services.transaction_service import transaction_service @@ -21,6 +23,81 @@ from services.team_service import team_service # No longer need TransactionStatus enum +class TransactionPaginationView(PaginationView): + """Custom pagination view with Show Move IDs button.""" + + def __init__( + self, + *, + pages: list[discord.Embed], + all_transactions: list, + user_id: int, + timeout: float = 300.0, + show_page_numbers: bool = True + ): + super().__init__( + pages=pages, + user_id=user_id, + timeout=timeout, + show_page_numbers=show_page_numbers + ) + self.all_transactions = all_transactions + + @discord.ui.button(label="Show Move IDs", style=discord.ButtonStyle.secondary, emoji="🔍", row=1) + async def show_move_ids(self, interaction: discord.Interaction, button: discord.ui.Button): + """Show all move IDs in an ephemeral message.""" + self.increment_interaction_count() + + if not self.all_transactions: + await interaction.response.send_message( + "No transactions to show.", + ephemeral=True + ) + return + + # Build the move ID list + header = "📋 **Move IDs for Your Transactions**\n" + lines = [] + + for transaction in self.all_transactions: + lines.append( + f"• Week {transaction.week}: {transaction.player.name} → `{transaction.moveid}`" + ) + + # Discord has a 2000 character limit for messages + # Chunk messages to stay under the limit + messages = [] + current_message = header + + for line in lines: + # Check if adding this line would exceed limit (leave 50 char buffer) + if len(current_message) + len(line) + 1 > 1950: + messages.append(current_message) + current_message = line + "\n" + else: + current_message += line + "\n" + + # Add the last message if it has content beyond the header + if current_message.strip() != header.strip(): + messages.append(current_message) + + # Send the messages + if not messages: + await interaction.response.send_message( + "No transactions to display.", + ephemeral=True + ) + return + + # Send first message as response + await interaction.response.send_message(messages[0], ephemeral=True) + + # Send remaining messages as followups + if len(messages) > 1: + for msg in messages[1:]: + await interaction.followup.send(msg, ephemeral=True) + + class TransactionCommands(commands.Cog): """Transaction command handlers for roster management.""" @@ -45,17 +122,15 @@ class TransactionCommands(commands.Cog): await interaction.response.defer() # Get user's team - user_teams = await team_service.get_teams_by_owner(interaction.user.id, SBA_CURRENT_SEASON) + team = await get_user_major_league_team(interaction.user.id, SBA_CURRENT_SEASON) - if not user_teams: + if not team: await interaction.followup.send( "❌ You don't appear to own a team in the current season.", ephemeral=True ) return - team = user_teams[0] # Use first team if multiple - # Get transactions in parallel pending_task = transaction_service.get_pending_transactions(team.abbrev, SBA_CURRENT_SEASON) frozen_task = transaction_service.get_frozen_transactions(team.abbrev, SBA_CURRENT_SEASON) @@ -69,20 +144,40 @@ class TransactionCommands(commands.Cog): cancelled_transactions = [] if show_cancelled: cancelled_transactions = await transaction_service.get_team_transactions( - team.abbrev, + team.abbrev, SBA_CURRENT_SEASON, cancelled=True ) - - embed = await self._create_my_moves_embed( - team, - pending_transactions, - frozen_transactions, + + pages = self._create_my_moves_pages( + team, + pending_transactions, + frozen_transactions, processed_transactions, cancelled_transactions ) - - await interaction.followup.send(embed=embed) + + # Collect all transactions for the "Show Move IDs" button + all_transactions = ( + pending_transactions + + frozen_transactions + + processed_transactions + + cancelled_transactions + ) + + # If only one page and no transactions, send without any buttons + if len(pages) == 1 and not all_transactions: + await interaction.followup.send(embed=pages[0]) + else: + # Use custom pagination view with "Show Move IDs" button + view = TransactionPaginationView( + pages=pages, + all_transactions=all_transactions, + user_id=interaction.user.id, + timeout=300.0, + show_page_numbers=True + ) + await interaction.followup.send(embed=view.get_current_embed(), view=view) @app_commands.command( name="legal", @@ -159,106 +254,166 @@ class TransactionCommands(commands.Cog): await interaction.followup.send(embed=embed) - async def _create_my_moves_embed( + def _create_my_moves_pages( self, team, pending_transactions, frozen_transactions, processed_transactions, cancelled_transactions - ) -> discord.Embed: - """Create embed showing user's transaction status.""" - - embed = EmbedTemplate.create_base_embed( - title=f"📋 Transaction Status - {team.abbrev}", - description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", - color=EmbedColors.INFO - ) - - # Add team thumbnail if available - if hasattr(team, 'thumbnail') and team.thumbnail: - embed.set_thumbnail(url=team.thumbnail) - - # Pending transactions + ) -> list[discord.Embed]: + """Create paginated embeds showing user's transaction status.""" + + pages = [] + transactions_per_page = 10 + + # Helper function to create transaction lines without emojis + def format_transaction(transaction): + return f"Week {transaction.week}: {transaction.move_description}" + + # Page 1: Summary + Pending Transactions if pending_transactions: - pending_lines = [] - for transaction in pending_transactions[-5:]: # Show last 5 - pending_lines.append( - f"{transaction.status_emoji} Week {transaction.week}: {transaction.move_description}" + total_pending = len(pending_transactions) + total_pages = (total_pending + transactions_per_page - 1) // transactions_per_page + + for page_num in range(total_pages): + start_idx = page_num * transactions_per_page + end_idx = min(start_idx + transactions_per_page, total_pending) + page_transactions = pending_transactions[start_idx:end_idx] + + embed = EmbedTemplate.create_base_embed( + title=f"📋 Transaction Status - {team.abbrev}", + description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", + color=EmbedColors.INFO ) - + + # Add team thumbnail if available + if hasattr(team, 'thumbnail') and team.thumbnail: + embed.set_thumbnail(url=team.thumbnail) + + # Pending transactions for this page + pending_lines = [format_transaction(tx) for tx in page_transactions] + + embed.add_field( + name=f"⏳ Pending Transactions ({total_pending} total)", + value="\n".join(pending_lines), + inline=False + ) + + # Add summary only on first page + if page_num == 0: + total_frozen = len(frozen_transactions) + status_text = [] + if total_pending > 0: + status_text.append(f"{total_pending} pending") + if total_frozen > 0: + status_text.append(f"{total_frozen} scheduled") + + embed.add_field( + name="Summary", + value=", ".join(status_text) if status_text else "No active transactions", + inline=True + ) + + pages.append(embed) + else: + # No pending transactions - create single page + embed = EmbedTemplate.create_base_embed( + title=f"📋 Transaction Status - {team.abbrev}", + description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", + color=EmbedColors.INFO + ) + + if hasattr(team, 'thumbnail') and team.thumbnail: + embed.set_thumbnail(url=team.thumbnail) + embed.add_field( name="⏳ Pending Transactions", - value="\n".join(pending_lines), - inline=False - ) - else: - embed.add_field( - name="⏳ Pending Transactions", value="No pending transactions", inline=False ) - - # Frozen transactions (scheduled for processing) - if frozen_transactions: - frozen_lines = [] - for transaction in frozen_transactions[-3:]: # Show last 3 - frozen_lines.append( - f"{transaction.status_emoji} Week {transaction.week}: {transaction.move_description}" - ) - + + total_frozen = len(frozen_transactions) + status_text = [] + if total_frozen > 0: + status_text.append(f"{total_frozen} scheduled") + embed.add_field( - name="❄️ Scheduled for Processing", + name="Summary", + value=", ".join(status_text) if status_text else "No active transactions", + inline=True + ) + + pages.append(embed) + + # Additional page: Frozen transactions + if frozen_transactions: + embed = EmbedTemplate.create_base_embed( + title=f"📋 Transaction Status - {team.abbrev}", + description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", + color=EmbedColors.INFO + ) + + if hasattr(team, 'thumbnail') and team.thumbnail: + embed.set_thumbnail(url=team.thumbnail) + + frozen_lines = [format_transaction(tx) for tx in frozen_transactions] + + embed.add_field( + name=f"❄️ Scheduled for Processing ({len(frozen_transactions)} total)", value="\n".join(frozen_lines), inline=False ) - - # Recent processed transactions + + pages.append(embed) + + # Additional page: Recently processed transactions if processed_transactions: - processed_lines = [] - for transaction in processed_transactions[-3:]: # Show last 3 - processed_lines.append( - f"{transaction.status_emoji} Week {transaction.week}: {transaction.move_description}" - ) - + embed = EmbedTemplate.create_base_embed( + title=f"📋 Transaction Status - {team.abbrev}", + description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", + color=EmbedColors.INFO + ) + + if hasattr(team, 'thumbnail') and team.thumbnail: + embed.set_thumbnail(url=team.thumbnail) + + processed_lines = [format_transaction(tx) for tx in processed_transactions[-20:]] # Last 20 + embed.add_field( - name="✅ Recently Processed", + name=f"✅ Recently Processed ({len(processed_transactions[-20:])} shown)", value="\n".join(processed_lines), inline=False ) - - # Cancelled transactions (if requested) + + pages.append(embed) + + # Additional page: Cancelled transactions (if requested) if cancelled_transactions: - cancelled_lines = [] - for transaction in cancelled_transactions[-2:]: # Show last 2 - cancelled_lines.append( - f"{transaction.status_emoji} Week {transaction.week}: {transaction.move_description}" - ) - + embed = EmbedTemplate.create_base_embed( + title=f"📋 Transaction Status - {team.abbrev}", + description=f"{team.lname} • Season {SBA_CURRENT_SEASON}", + color=EmbedColors.INFO + ) + + if hasattr(team, 'thumbnail') and team.thumbnail: + embed.set_thumbnail(url=team.thumbnail) + + cancelled_lines = [format_transaction(tx) for tx in cancelled_transactions[-20:]] # Last 20 + embed.add_field( - name="❌ Cancelled Transactions", + name=f"❌ Cancelled Transactions ({len(cancelled_transactions[-20:])} shown)", value="\n".join(cancelled_lines), inline=False ) - - # Transaction summary - total_pending = len(pending_transactions) - total_frozen = len(frozen_transactions) - - status_text = [] - if total_pending > 0: - status_text.append(f"{total_pending} pending") - if total_frozen > 0: - status_text.append(f"{total_frozen} scheduled") - - embed.add_field( - name="Summary", - value=", ".join(status_text) if status_text else "No active transactions", - inline=True - ) - - embed.set_footer(text="Use /legal to check roster legality") - return embed + + pages.append(embed) + + # Add footer to all pages + for page in pages: + page.set_footer(text="Use /legal to check roster legality") + + return pages async def _create_legal_embed( self, diff --git a/services/transaction_service.py b/services/transaction_service.py index 5efbedb..6104812 100644 --- a/services/transaction_service.py +++ b/services/transaction_service.py @@ -77,13 +77,30 @@ class TransactionService(BaseService[Transaction]): raise APIException(f"Failed to retrieve transactions: {e}") async def get_pending_transactions(self, team_abbrev: str, season: int) -> List[Transaction]: - """Get pending transactions for a team.""" - return await self.get_team_transactions( - team_abbrev, - season, - cancelled=False, - frozen=False - ) + """Get pending (future) transactions for a team.""" + try: + # Get current week to filter future transactions + current_data = await self.get_client() + current_response = await current_data.get('current') + current_week = current_response.get('week', 0) if current_response else 0 + + # Get transactions from current week onward + return await self.get_team_transactions( + team_abbrev, + season, + cancelled=False, + frozen=False, + week_start=current_week + ) + except Exception as e: + logger.warning(f"Could not get current week, returning all non-cancelled/non-frozen transactions: {e}") + # Fallback to all non-cancelled/non-frozen if we can't get current week + return await self.get_team_transactions( + team_abbrev, + season, + cancelled=False, + frozen=False + ) async def get_frozen_transactions(self, team_abbrev: str, season: int) -> List[Transaction]: """Get frozen (scheduled for processing) transactions for a team.""" diff --git a/tests/test_commands_transactions.py b/tests/test_commands_transactions.py index cdc6e49..263f05d 100644 --- a/tests/test_commands_transactions.py +++ b/tests/test_commands_transactions.py @@ -113,31 +113,28 @@ class TestTransactionCommands: pending_tx = [tx for tx in mock_transactions if tx.is_pending] frozen_tx = [tx for tx in mock_transactions if tx.is_frozen] cancelled_tx = [tx for tx in mock_transactions if tx.is_cancelled] - - with patch('commands.transactions.management.team_service') as mock_team_service: + + with patch('utils.team_utils.team_service') as mock_team_utils_service: with patch('commands.transactions.management.transaction_service') as mock_tx_service: - + # Mock service responses - mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) + mock_team_utils_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) mock_tx_service.get_pending_transactions = AsyncMock(return_value=pending_tx) mock_tx_service.get_frozen_transactions = AsyncMock(return_value=frozen_tx) mock_tx_service.get_processed_transactions = AsyncMock(return_value=[]) - + # Execute command await commands_cog.my_moves.callback(commands_cog, mock_interaction, show_cancelled=False) - + # Verify interaction flow mock_interaction.response.defer.assert_called_once() mock_interaction.followup.send.assert_called_once() - + # Verify service calls - mock_team_service.get_teams_by_owner.assert_called_once_with( - mock_interaction.user.id, 12 - ) mock_tx_service.get_pending_transactions.assert_called_once_with('WV', 12) mock_tx_service.get_frozen_transactions.assert_called_once_with('WV', 12) mock_tx_service.get_processed_transactions.assert_called_once_with('WV', 12) - + # Check embed was sent embed_call = mock_interaction.followup.send.call_args assert 'embed' in embed_call.kwargs @@ -146,18 +143,18 @@ class TestTransactionCommands: async def test_my_moves_with_cancelled(self, commands_cog, mock_interaction, mock_team, mock_transactions): """Test /mymoves command with cancelled transactions shown.""" cancelled_tx = [tx for tx in mock_transactions if tx.is_cancelled] - - with patch('commands.transactions.management.team_service') as mock_team_service: + + with patch('utils.team_utils.team_service') as mock_team_service: with patch('commands.transactions.management.transaction_service') as mock_tx_service: - + mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) mock_tx_service.get_pending_transactions = AsyncMock(return_value=[]) mock_tx_service.get_frozen_transactions = AsyncMock(return_value=[]) mock_tx_service.get_processed_transactions = AsyncMock(return_value=[]) mock_tx_service.get_team_transactions = AsyncMock(return_value=cancelled_tx) - + await commands_cog.my_moves.callback(commands_cog, mock_interaction, show_cancelled=True) - + # Verify cancelled transactions were requested mock_tx_service.get_team_transactions.assert_called_once_with( 'WV', 12, cancelled=True @@ -166,11 +163,11 @@ class TestTransactionCommands: @pytest.mark.asyncio async def test_my_moves_no_team(self, commands_cog, mock_interaction): """Test /mymoves command when user has no team.""" - with patch('commands.transactions.management.team_service') as mock_team_service: + with patch('utils.team_utils.team_service') as mock_team_service: mock_team_service.get_teams_by_owner = AsyncMock(return_value=[]) - + await commands_cog.my_moves.callback(commands_cog, mock_interaction) - + # Should send error message mock_interaction.followup.send.assert_called_once() call_args = mock_interaction.followup.send.call_args @@ -180,12 +177,12 @@ class TestTransactionCommands: @pytest.mark.asyncio async def test_my_moves_api_error(self, commands_cog, mock_interaction, mock_team): """Test /mymoves command with API error.""" - with patch('commands.transactions.management.team_service') as mock_team_service: + with patch('utils.team_utils.team_service') as mock_team_service: with patch('commands.transactions.management.transaction_service') as mock_tx_service: - + mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) mock_tx_service.get_pending_transactions.side_effect = APIException("API Error") - + # Should raise the exception (logged_command decorator handles it) with pytest.raises(APIException): await commands_cog.my_moves.callback(commands_cog, mock_interaction) @@ -308,45 +305,167 @@ class TestTransactionCommands: assert "Could not retrieve roster data" in call_args.args[0] @pytest.mark.asyncio - async def test_create_my_moves_embed(self, commands_cog, mock_team, mock_transactions): - """Test embed creation for /mymoves command.""" + async def test_create_my_moves_pages(self, commands_cog, mock_team, mock_transactions): + """Test paginated embed creation for /mymoves command.""" pending_tx = [tx for tx in mock_transactions if tx.is_pending] frozen_tx = [tx for tx in mock_transactions if tx.is_frozen] processed_tx = [] cancelled_tx = [tx for tx in mock_transactions if tx.is_cancelled] - - embed = await commands_cog._create_my_moves_embed( + + pages = commands_cog._create_my_moves_pages( mock_team, pending_tx, frozen_tx, processed_tx, cancelled_tx ) - - assert isinstance(embed, discord.Embed) - assert embed.title == "📋 Transaction Status - WV" - assert "West Virginia Black Bears • Season 12" in embed.description - - # Check that fields are created for each transaction type - field_names = [field.name for field in embed.fields] - assert "⏳ Pending Transactions" in field_names - assert "❄️ Scheduled for Processing" in field_names - assert "❌ Cancelled Transactions" in field_names - assert "Summary" in field_names - + + assert len(pages) > 0 + first_page = pages[0] + assert isinstance(first_page, discord.Embed) + assert first_page.title == "📋 Transaction Status - WV" + assert "West Virginia Black Bears • Season 12" in first_page.description + + # Check that fields are created for transaction types + field_names = [field.name for field in first_page.fields] + assert any("Pending Transactions" in name for name in field_names) + # Verify thumbnail is set - assert embed.thumbnail.url == mock_team.thumbnail + assert first_page.thumbnail.url == mock_team.thumbnail + + # Verify emoji is NOT in individual transaction lines + for page in pages: + for field in page.fields: + if "Pending" in field.name or "Scheduled" in field.name or "Cancelled" in field.name: + # Check that emojis (⏳, ❄️, ❌) are NOT in the field value + assert "⏳" not in field.value + assert "❄️" not in field.value + assert "✅" not in field.value @pytest.mark.asyncio - async def test_create_my_moves_embed_no_transactions(self, commands_cog, mock_team): - """Test embed creation with no transactions.""" - embed = await commands_cog._create_my_moves_embed( + async def test_create_my_moves_pages_no_transactions(self, commands_cog, mock_team): + """Test paginated embed creation with no transactions.""" + pages = commands_cog._create_my_moves_pages( mock_team, [], [], [], [] ) - + + assert len(pages) == 1 # Should have single page + embed = pages[0] + # Find the pending transactions field pending_field = next(f for f in embed.fields if "Pending" in f.name) assert pending_field.value == "No pending transactions" - + # Summary should show no active transactions summary_field = next(f for f in embed.fields if f.name == "Summary") assert summary_field.value == "No active transactions" + + @pytest.mark.asyncio + async def test_transaction_pagination_view_with_move_ids(self, commands_cog, mock_interaction, mock_team, mock_transactions): + """Test that TransactionPaginationView is created with move IDs button.""" + from commands.transactions.management import TransactionPaginationView + + pending_tx = [tx for tx in mock_transactions if tx.is_pending] + + with patch('utils.team_utils.team_service') as mock_team_service: + with patch('commands.transactions.management.transaction_service') as mock_tx_service: + + mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) + mock_tx_service.get_pending_transactions = AsyncMock(return_value=pending_tx) + mock_tx_service.get_frozen_transactions = AsyncMock(return_value=[]) + mock_tx_service.get_processed_transactions = AsyncMock(return_value=[]) + + await commands_cog.my_moves.callback(commands_cog, mock_interaction, show_cancelled=False) + + # Verify TransactionPaginationView was created + mock_interaction.followup.send.assert_called_once() + call_args = mock_interaction.followup.send.call_args + view = call_args.kwargs.get('view') + + assert view is not None + assert isinstance(view, TransactionPaginationView) + assert len(view.all_transactions) == len(pending_tx) + + @pytest.mark.asyncio + async def test_show_move_ids_handles_long_lists(self, mock_team, mock_transactions): + """Test that Show Move IDs button properly chunks very long transaction lists.""" + from commands.transactions.management import TransactionPaginationView + + # Create 100 transactions to simulate a very long list + base_data = { + 'season': 12, + 'player': { + 'id': 12472, + 'name': 'Very Long Player Name That Takes Up Space', + 'wara': 2.47, + 'season': 12, + 'pos_1': 'LF' + }, + 'oldteam': { + 'id': 508, + 'abbrev': 'NYD', + 'sname': 'Diamonds', + 'lname': 'New York Diamonds', + 'season': 12 + }, + 'newteam': { + 'id': 499, + 'abbrev': 'WV', + 'sname': 'Black Bears', + 'lname': 'West Virginia Black Bears', + 'season': 12 + } + } + + many_transactions = [] + for i in range(100): + tx_data = { + **base_data, + 'id': i, + 'week': 10 + (i % 5), + 'moveid': f'Season-012-Week-{10 + (i % 5)}-Move-{i:03d}', + 'cancelled': False, + 'frozen': False + } + many_transactions.append(Transaction.from_api_data(tx_data)) + + # Create view with many transactions + pages = [discord.Embed(title="Test")] + view = TransactionPaginationView( + pages=pages, + all_transactions=many_transactions, + user_id=258104532423147520, + timeout=300.0, + show_page_numbers=True + ) + + # Create mock interaction + mock_interaction = AsyncMock() + mock_button = MagicMock() + + # Find the show_move_ids button and call its callback directly + show_move_ids_button = None + for item in view.children: + if hasattr(item, 'label') and item.label == "Show Move IDs": + show_move_ids_button = item + break + + assert show_move_ids_button is not None, "Show Move IDs button not found" + + # Call the button's callback + await show_move_ids_button.callback(mock_interaction) + + # Verify response was sent + mock_interaction.response.send_message.assert_called_once() + + # Get the message that was sent + call_args = mock_interaction.response.send_message.call_args + first_message = call_args.args[0] + + # Verify first message is under 2000 characters + assert len(first_message) < 2000, f"First message is {len(first_message)} characters (exceeds 2000 limit)" + + # If there were followup messages, verify they're also under 2000 chars + if mock_interaction.followup.send.called: + for call in mock_interaction.followup.send.call_args_list: + followup_message = call.args[0] + assert len(followup_message) < 2000, f"Followup message is {len(followup_message)} characters (exceeds 2000 limit)" @pytest.mark.asyncio async def test_create_legal_embed_all_legal(self, commands_cog, mock_team): @@ -470,7 +589,7 @@ class TestTransactionCommandsIntegration: """Test complete /mymoves workflow with realistic data volumes.""" mock_interaction = AsyncMock() mock_interaction.user.id = 258104532423147520 - + # Create realistic transaction volumes pending_transactions = [] for i in range(15): # 15 pending transactions @@ -486,7 +605,7 @@ class TestTransactionCommandsIntegration: 'frozen': False } pending_transactions.append(Transaction.from_api_data(tx_data)) - + mock_team = Team.from_api_data({ 'id': 499, 'abbrev': 'WV', @@ -494,43 +613,50 @@ class TestTransactionCommandsIntegration: 'lname': 'West Virginia Black Bears', 'season': 12 }) - - with patch('commands.transactions.management.team_service') as mock_team_service: + + with patch('utils.team_utils.team_service') as mock_team_service: with patch('commands.transactions.management.transaction_service') as mock_tx_service: - + mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) mock_tx_service.get_pending_transactions = AsyncMock(return_value=pending_transactions) mock_tx_service.get_frozen_transactions = AsyncMock(return_value=[]) mock_tx_service.get_processed_transactions = AsyncMock(return_value=[]) - + await commands_cog.my_moves.callback(commands_cog, mock_interaction, show_cancelled=False) - + # Verify embed was created and sent mock_interaction.followup.send.assert_called_once() embed_call = mock_interaction.followup.send.call_args embed = embed_call.kwargs['embed'] - - # Check that only last 5 pending transactions are shown + + # With 15 transactions, should show 10 per page pending_field = next(f for f in embed.fields if "Pending" in f.name) lines = pending_field.value.split('\n') - assert len(lines) == 5 # Should show only last 5 - + assert len(lines) == 10 # Should show 10 per page + # Verify summary shows correct count summary_field = next(f for f in embed.fields if f.name == "Summary") assert "15 pending" in summary_field.value + + # Verify pagination view was created + from commands.transactions.management import TransactionPaginationView + view = embed_call.kwargs.get('view') + assert view is not None + assert isinstance(view, TransactionPaginationView) + assert len(view.all_transactions) == 15 @pytest.mark.asyncio async def test_concurrent_command_execution(self, commands_cog): """Test that commands can handle concurrent execution.""" import asyncio - + # Create multiple mock interactions interactions = [] for i in range(5): mock_interaction = AsyncMock() mock_interaction.user.id = 258104532423147520 + i interactions.append(mock_interaction) - + mock_team = Team.from_api_data({ 'id': 499, 'abbrev': 'WV', @@ -538,22 +664,22 @@ class TestTransactionCommandsIntegration: 'lname': 'West Virginia Black Bears', 'season': 12 }) - - with patch('commands.transactions.management.team_service') as mock_team_service: + + with patch('utils.team_utils.team_service') as mock_team_service: with patch('commands.transactions.management.transaction_service') as mock_tx_service: - + mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team]) mock_tx_service.get_pending_transactions = AsyncMock(return_value=[]) mock_tx_service.get_frozen_transactions = AsyncMock(return_value=[]) mock_tx_service.get_processed_transactions = AsyncMock(return_value=[]) - + # Execute commands concurrently tasks = [commands_cog.my_moves.callback(commands_cog, interaction) for interaction in interactions] results = await asyncio.gather(*tasks, return_exceptions=True) - + # All should complete successfully assert len([r for r in results if not isinstance(r, Exception)]) == 5 - + # All interactions should have received responses for interaction in interactions: interaction.followup.send.assert_called_once() \ No newline at end of file