""" Comprehensive tests for the paperdex.py module. Tests collection tracking and statistics functionality including: - Cardset collection checking - Team/franchise collection checking - Collection progress tracking - Missing cards identification - Duplicate cards handling """ import pytest import asyncio from unittest.mock import AsyncMock, Mock, patch, MagicMock from discord.ext import commands from discord import app_commands import discord # Import the module to test (this will need to be updated when modules are moved) try: from cogs.players.paperdex import Paperdex except ImportError: # Create a mock class for testing structure class Paperdex: def __init__(self, bot): self.bot = bot @pytest.mark.asyncio class TestPaperdex: """Test suite for Paperdex cog functionality.""" @pytest.fixture def paperdex_cog(self, mock_bot): """Create Paperdex cog instance for testing.""" return Paperdex(mock_bot) @pytest.fixture def mock_cardset_collection_data(self): """Mock cardset collection data for testing.""" return { 'cardset_id': 1, 'cardset_name': '2024 Season', 'total_cards': 100, 'owned_cards': 75, 'completion_percentage': 75.0, 'missing_cards': [ {'player_id': 10, 'name': 'Mike Trout', 'cost': 500, 'rarity': 'Legendary'}, {'player_id': 20, 'name': 'Mookie Betts', 'cost': 400, 'rarity': 'All-Star'}, {'player_id': 30, 'name': 'Ronald Acuña Jr.', 'cost': 450, 'rarity': 'All-Star'}, ], 'duplicates': [ {'player_id': 1, 'name': 'Common Player 1', 'quantity': 3}, {'player_id': 2, 'name': 'Common Player 2', 'quantity': 2}, ], 'rarity_breakdown': { 'Common': {'owned': 50, 'total': 60, 'percentage': 83.3}, 'All-Star': {'owned': 20, 'total': 30, 'percentage': 66.7}, 'Legendary': {'owned': 5, 'total': 10, 'percentage': 50.0} } } @pytest.fixture def mock_franchise_collection_data(self): """Mock franchise collection data for testing.""" return { 'franchise_name': 'Los Angeles Dodgers', 'total_cards': 15, 'owned_cards': 12, 'completion_percentage': 80.0, 'missing_players': [ {'player_id': 100, 'name': 'Mookie Betts', 'cardset': '2024 Season'}, {'player_id': 101, 'name': 'Freddie Freeman', 'cardset': '2023 Season'}, {'player_id': 102, 'name': 'Walker Buehler', 'cardset': '2022 Season'}, ], 'cardsets_represented': [ {'cardset_id': 1, 'name': '2024 Season', 'owned': 8, 'total': 10}, {'cardset_id': 2, 'name': '2023 Season', 'owned': 3, 'total': 4}, {'cardset_id': 3, 'name': '2022 Season', 'owned': 1, 'total': 1}, ] } async def test_init(self, paperdex_cog, mock_bot): """Test cog initialization.""" assert paperdex_cog.bot == mock_bot @patch('helpers.get_team_by_owner') @patch('api_calls.db_get') @patch('helpers.cardset_search') async def test_paperdex_cardset_success(self, mock_cardset_search, mock_db_get, mock_get_by_owner, paperdex_cog, mock_interaction, sample_team_data, mock_cardset_collection_data, mock_embed): """Test successful cardset collection checking.""" mock_get_by_owner.return_value = sample_team_data mock_cardset_search.return_value = {'id': 1, 'name': '2024 Season'} mock_db_get.return_value = mock_cardset_collection_data async def mock_paperdex_cardset_command(interaction, cardset_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) if not team: await interaction.followup.send('You need a team to check your collection!') return cardset = await mock_cardset_search(cardset_name) if not cardset: await interaction.followup.send(f'Cardset "{cardset_name}" not found.') return collection_data = await mock_db_get(f'paperdex/cardset/{cardset["id"]}', params=[('team_id', team['id'])]) if not collection_data: await interaction.followup.send('Error retrieving collection data.') return # Create embed with collection info embed = mock_embed embed.title = f'Paperdex - {cardset["name"]}' embed.description = f'Collection Progress: {collection_data["owned_cards"]}/{collection_data["total_cards"]} ({collection_data["completion_percentage"]:.1f}%)' # Add missing cards field if collection_data['missing_cards']: missing_list = '\n'.join([f"• {card['name']} (${card['cost']})" for card in collection_data['missing_cards'][:10]]) if len(collection_data['missing_cards']) > 10: missing_list += f"\n... and {len(collection_data['missing_cards']) - 10} more" embed.add_field(name='Missing Cards', value=missing_list, inline=False) # Add duplicates field if collection_data['duplicates']: duplicate_list = '\n'.join([f"• {card['name']} x{card['quantity']}" for card in collection_data['duplicates'][:5]]) if len(collection_data['duplicates']) > 5: duplicate_list += f"\n... and {len(collection_data['duplicates']) - 5} more" embed.add_field(name='Duplicates', value=duplicate_list, inline=False) await interaction.followup.send(embed=embed) await mock_paperdex_cardset_command(mock_interaction, '2024 Season') mock_interaction.response.defer.assert_called_once() mock_get_by_owner.assert_called_once_with(mock_interaction.user.id) mock_cardset_search.assert_called_once_with('2024 Season') mock_db_get.assert_called_once() mock_interaction.followup.send.assert_called_once() @patch('helpers.get_team_by_owner') async def test_paperdex_cardset_no_team(self, mock_get_by_owner, paperdex_cog, mock_interaction): """Test cardset collection check when user has no team.""" mock_get_by_owner.return_value = None async def mock_paperdex_cardset_command(interaction, cardset_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) if not team: await interaction.followup.send('You need a team to check your collection!') return await mock_paperdex_cardset_command(mock_interaction, '2024 Season') mock_interaction.followup.send.assert_called_once_with('You need a team to check your collection!') @patch('helpers.get_team_by_owner') @patch('helpers.cardset_search') async def test_paperdex_cardset_not_found(self, mock_cardset_search, mock_get_by_owner, paperdex_cog, mock_interaction, sample_team_data): """Test cardset collection check when cardset is not found.""" mock_get_by_owner.return_value = sample_team_data mock_cardset_search.return_value = None async def mock_paperdex_cardset_command(interaction, cardset_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) if not team: await interaction.followup.send('You need a team to check your collection!') return cardset = await mock_cardset_search(cardset_name) if not cardset: await interaction.followup.send(f'Cardset "{cardset_name}" not found.') return await mock_paperdex_cardset_command(mock_interaction, 'Nonexistent Set') mock_interaction.followup.send.assert_called_once_with('Cardset "Nonexistent Set" not found.') @patch('helpers.get_team_by_owner') @patch('helpers.cardset_search') @patch('api_calls.db_get') async def test_paperdex_cardset_api_error(self, mock_db_get, mock_cardset_search, mock_get_by_owner, paperdex_cog, mock_interaction, sample_team_data): """Test cardset collection check API error handling.""" mock_get_by_owner.return_value = sample_team_data mock_cardset_search.return_value = {'id': 1, 'name': '2024 Season'} mock_db_get.return_value = None async def mock_paperdex_cardset_command(interaction, cardset_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) cardset = await mock_cardset_search(cardset_name) collection_data = await mock_db_get(f'paperdex/cardset/{cardset["id"]}', params=[('team_id', team['id'])]) if not collection_data: await interaction.followup.send('Error retrieving collection data.') return await mock_paperdex_cardset_command(mock_interaction, '2024 Season') mock_interaction.followup.send.assert_called_once_with('Error retrieving collection data.') @patch('helpers.get_team_by_owner') @patch('api_calls.db_get') @patch('helpers.fuzzy_search') async def test_paperdex_team_success(self, mock_fuzzy_search, mock_db_get, mock_get_by_owner, paperdex_cog, mock_interaction, sample_team_data, mock_franchise_collection_data, mock_embed): """Test successful franchise collection checking.""" mock_get_by_owner.return_value = sample_team_data mock_fuzzy_search.return_value = 'Los Angeles Dodgers' mock_db_get.return_value = mock_franchise_collection_data async def mock_paperdex_team_command(interaction, franchise_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) if not team: await interaction.followup.send('You need a team to check your collection!') return # Fuzzy search for franchise franchise = mock_fuzzy_search(franchise_name, ['Los Angeles Dodgers', 'New York Yankees']) if not franchise: await interaction.followup.send(f'Franchise "{franchise_name}" not found.') return collection_data = await mock_db_get(f'paperdex/franchise/{franchise}', params=[('team_id', team['id'])]) if not collection_data: await interaction.followup.send('Error retrieving collection data.') return # Create embed with collection info embed = mock_embed embed.title = f'Paperdex - {franchise}' embed.description = f'Collection Progress: {collection_data["owned_cards"]}/{collection_data["total_cards"]} ({collection_data["completion_percentage"]:.1f}%)' # Add missing players field if collection_data['missing_players']: missing_list = '\n'.join([f"• {player['name']} ({player['cardset']})" for player in collection_data['missing_players'][:10]]) embed.add_field(name='Missing Players', value=missing_list, inline=False) # Add cardsets breakdown cardset_list = '\n'.join([f"• {cs['name']}: {cs['owned']}/{cs['total']}" for cs in collection_data['cardsets_represented']]) embed.add_field(name='Cardsets', value=cardset_list, inline=False) await interaction.followup.send(embed=embed) await mock_paperdex_team_command(mock_interaction, 'Dodgers') mock_interaction.response.defer.assert_called_once() mock_get_by_owner.assert_called_once() mock_fuzzy_search.assert_called_once() mock_db_get.assert_called_once() mock_interaction.followup.send.assert_called_once() @patch('helpers.get_team_by_owner') async def test_paperdex_team_no_team(self, mock_get_by_owner, paperdex_cog, mock_interaction): """Test franchise collection check when user has no team.""" mock_get_by_owner.return_value = None async def mock_paperdex_team_command(interaction, franchise_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) if not team: await interaction.followup.send('You need a team to check your collection!') return await mock_paperdex_team_command(mock_interaction, 'Dodgers') mock_interaction.followup.send.assert_called_once_with('You need a team to check your collection!') @patch('helpers.get_team_by_owner') @patch('helpers.fuzzy_search') async def test_paperdex_team_not_found(self, mock_fuzzy_search, mock_get_by_owner, paperdex_cog, mock_interaction, sample_team_data): """Test franchise collection check when franchise is not found.""" mock_get_by_owner.return_value = sample_team_data mock_fuzzy_search.return_value = None async def mock_paperdex_team_command(interaction, franchise_name): await interaction.response.defer() team = await mock_get_by_owner(interaction.user.id) franchise = mock_fuzzy_search(franchise_name, []) if not franchise: await interaction.followup.send(f'Franchise "{franchise_name}" not found.') return await mock_paperdex_team_command(mock_interaction, 'Nonexistent Team') mock_interaction.followup.send.assert_called_once_with('Franchise "Nonexistent Team" not found.') def test_collection_percentage_calculation(self, paperdex_cog): """Test collection percentage calculation accuracy.""" test_cases = [ {'owned': 0, 'total': 100, 'expected': 0.0}, {'owned': 50, 'total': 100, 'expected': 50.0}, {'owned': 100, 'total': 100, 'expected': 100.0}, {'owned': 33, 'total': 99, 'expected': 33.33}, {'owned': 1, 'total': 3, 'expected': 33.33} ] def calculate_percentage(owned, total): return round((owned / total) * 100, 2) if total > 0 else 0.0 for case in test_cases: result = calculate_percentage(case['owned'], case['total']) assert abs(result - case['expected']) < 0.01, f"Expected {case['expected']}, got {result}" def test_missing_cards_formatting(self, paperdex_cog, mock_cardset_collection_data): """Test proper formatting of missing cards lists.""" missing_cards = mock_cardset_collection_data['missing_cards'] # Test truncation for long lists def format_missing_cards(cards, limit=10): if not cards: return "None! Collection complete!" formatted_list = '\n'.join([f"• {card['name']} (${card['cost']})" for card in cards[:limit]]) if len(cards) > limit: formatted_list += f"\n... and {len(cards) - limit} more" return formatted_list result = format_missing_cards(missing_cards, 2) lines = result.split('\n') assert len(lines) == 3 # 2 cards + "... and X more" assert "Mike Trout" in lines[0] assert "Mookie Betts" in lines[1] assert "... and 1 more" in lines[2] def test_duplicates_formatting(self, paperdex_cog, mock_cardset_collection_data): """Test proper formatting of duplicate cards lists.""" duplicates = mock_cardset_collection_data['duplicates'] def format_duplicates(cards, limit=5): if not cards: return "No duplicates found." formatted_list = '\n'.join([f"• {card['name']} x{card['quantity']}" for card in cards[:limit]]) if len(cards) > limit: formatted_list += f"\n... and {len(cards) - limit} more" return formatted_list result = format_duplicates(duplicates, 5) lines = result.split('\n') assert len(lines) == 2 # Both cards fit within limit assert "Common Player 1 x3" in lines[0] assert "Common Player 2 x2" in lines[1] def test_rarity_breakdown_formatting(self, paperdex_cog, mock_cardset_collection_data): """Test proper formatting of rarity breakdown information.""" rarity_breakdown = mock_cardset_collection_data['rarity_breakdown'] def format_rarity_breakdown(breakdown): if not breakdown: return "No rarity data available." formatted_list = [] for rarity, data in breakdown.items(): percentage = data['percentage'] formatted_list.append(f"• {rarity}: {data['owned']}/{data['total']} ({percentage:.1f}%)") return '\n'.join(formatted_list) result = format_rarity_breakdown(rarity_breakdown) lines = result.split('\n') assert len(lines) == 3 assert "Common: 50/60 (83.3%)" in lines[0] assert "All-Star: 20/30 (66.7%)" in lines[1] assert "Legendary: 5/10 (50.0%)" in lines[2] def test_cardset_list_formatting(self, paperdex_cog, mock_franchise_collection_data): """Test proper formatting of cardset representation lists.""" cardsets = mock_franchise_collection_data['cardsets_represented'] def format_cardsets(cardsets): if not cardsets: return "No cards owned for this franchise." formatted_list = [] for cardset in cardsets: percentage = (cardset['owned'] / cardset['total']) * 100 formatted_list.append(f"• {cardset['name']}: {cardset['owned']}/{cardset['total']} ({percentage:.1f}%)") return '\n'.join(formatted_list) result = format_cardsets(cardsets) lines = result.split('\n') assert len(lines) == 3 assert "2024 Season: 8/10 (80.0%)" in lines[0] assert "2023 Season: 3/4 (75.0%)" in lines[1] assert "2022 Season: 1/1 (100.0%)" in lines[2] @patch('helpers.ALL_MLB_TEAMS') def test_franchise_fuzzy_search(self, mock_all_teams, paperdex_cog): """Test fuzzy search functionality for MLB franchises.""" mock_all_teams.return_value = [ 'Los Angeles Dodgers', 'New York Yankees', 'Boston Red Sox', 'Chicago Cubs', 'San Francisco Giants' ] with patch('helpers.fuzzy_search') as mock_fuzzy: # Test exact match mock_fuzzy.return_value = 'Los Angeles Dodgers' result = mock_fuzzy('Los Angeles Dodgers', mock_all_teams.return_value) assert result == 'Los Angeles Dodgers' # Test partial match mock_fuzzy.return_value = 'Los Angeles Dodgers' result = mock_fuzzy('Dodgers', mock_all_teams.return_value) assert result == 'Los Angeles Dodgers' # Test abbreviation match mock_fuzzy.return_value = 'New York Yankees' result = mock_fuzzy('NYY', mock_all_teams.return_value) assert result == 'New York Yankees' def test_empty_collection_handling(self, paperdex_cog): """Test handling of completely empty collections.""" empty_collection = { 'cardset_id': 1, 'cardset_name': '2024 Season', 'total_cards': 100, 'owned_cards': 0, 'completion_percentage': 0.0, 'missing_cards': [], 'duplicates': [], 'rarity_breakdown': {} } # Test that zero completion is handled properly assert empty_collection['completion_percentage'] == 0.0 assert empty_collection['owned_cards'] == 0 # Test empty lists assert len(empty_collection['missing_cards']) == 0 assert len(empty_collection['duplicates']) == 0 assert len(empty_collection['rarity_breakdown']) == 0 def test_complete_collection_handling(self, paperdex_cog): """Test handling of complete collections.""" complete_collection = { 'cardset_id': 1, 'cardset_name': '2024 Season', 'total_cards': 100, 'owned_cards': 100, 'completion_percentage': 100.0, 'missing_cards': [], 'duplicates': [ {'player_id': 1, 'name': 'Extra Card', 'quantity': 2} ] } assert complete_collection['completion_percentage'] == 100.0 assert complete_collection['owned_cards'] == complete_collection['total_cards'] assert len(complete_collection['missing_cards']) == 0 # Complete collections can still have duplicates assert len(complete_collection['duplicates']) > 0 @patch('logging.getLogger') async def test_error_logging(self, mock_logger, paperdx_cog): """Test error logging for collection operations.""" mock_logger_instance = Mock() mock_logger.return_value = mock_logger_instance # Test API error logging with patch('api_calls.db_get') as mock_db_get: mock_db_get.side_effect = Exception("Collection API Error") try: await mock_db_get('paperdx/cardset/1') except Exception: # In actual implementation, this would be caught and logged pass def test_permission_checks(self, paperdx_cog, mock_interaction): """Test permission checking for paperdx commands.""" # Test role check mock_member_with_role = Mock() mock_member_with_role.roles = [Mock(name='Paper Dynasty')] mock_interaction.user = mock_member_with_role # Test channel check with patch('helpers.legal_channel') as mock_legal_check: mock_legal_check.return_value = True result = mock_legal_check(mock_interaction.channel) assert result is True