paper-dynasty-discord/tests/players_refactor/test_paperdex.py
2025-10-08 14:45:41 -05:00

523 lines
23 KiB
Python

"""
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_new.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