major-domo-v2/tests/test_transactions_integration.py
Cal Corum c01f88e7e3 CLAUDE: Comprehensive bot improvements and test infrastructure
This commit includes various enhancements across the bot architecture:

**New Infrastructure:**
- Added tests/factories.py - Factory classes for creating test data objects
- Added PRE_LAUNCH_ROADMAP.md - Project planning and roadmap documentation

**Model Enhancements:**
- Updated models/roster.py - Enhanced roster data structures
- Updated models/team.py - Improved team model definitions

**Service Layer Improvements:**
- Enhanced services/player_service.py - Better player data handling
- Updated services/roster_service.py - Roster management improvements
- Enhanced services/team_service.py - Team data service refinements
- Updated services/transaction_service.py - Transaction processing enhancements

**Command Updates:**
- Updated commands/teams/info.py - Team information command improvements
- Enhanced commands/voice/tracker.py - Voice channel tracking refinements

**Background Tasks:**
- Updated tasks/custom_command_cleanup.py - Automated cleanup improvements

**View Components:**
- Enhanced views/transaction_embed.py - Transaction embed UI improvements

**Test Coverage Enhancements:**
- Updated tests/test_commands_voice.py - Voice command test improvements
- Enhanced tests/test_dropadd_integration.py - Integration test coverage
- Updated tests/test_services_player_service.py - Player service test coverage
- Enhanced tests/test_services_transaction_builder.py - Transaction builder tests
- Updated tests/test_transactions_integration.py - Transaction integration tests
- Enhanced tests/test_views_transaction_embed.py - UI component test coverage

These changes collectively improve the bot's reliability, maintainability, and test coverage while adding essential infrastructure for continued development.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-02 11:35:26 -05:00

461 lines
19 KiB
Python

"""
Integration tests for Transaction functionality
Tests the complete flow from API through services to Discord commands.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import asyncio
from models.transaction import Transaction, RosterValidation
from models.team import Team
from models.roster import TeamRoster
from services.transaction_service import transaction_service
from commands.transactions.management import TransactionCommands
from tests.factories import TeamFactory
class TestTransactionIntegration:
"""Integration tests for the complete transaction system."""
@pytest.fixture
def realistic_api_data(self):
"""Create realistic API response data based on actual structure."""
return [
{
'id': 27787,
'week': 10,
'player': {
'id': 12472,
'name': 'Yordan Alvarez',
'wara': 2.47,
'image': 'https://sba-cards-2024.s3.us-east-1.amazonaws.com/2024-cards/yordan-alvarez.png',
'image2': None,
'team': {
'id': 508,
'abbrev': 'NYD',
'sname': 'Diamonds',
'lname': 'New York Diamonds',
'manager_legacy': None,
'division_legacy': None,
'gmid': '143034072787058688',
'gmid2': None,
'season': 12
},
'season': 12,
'pitcher_injury': None,
'pos_1': 'LF',
'pos_2': None,
'last_game': None,
'il_return': None,
'demotion_week': 1,
'headshot': None,
'strat_code': 'Alvarez,Y',
'bbref_id': 'alvaryo01',
'injury_rating': '1p65'
},
'oldteam': {
'id': 508,
'abbrev': 'NYD',
'sname': 'Diamonds',
'lname': 'New York Diamonds',
'manager_legacy': None,
'division_legacy': None,
'gmid': '143034072787058688',
'gmid2': None,
'season': 12
},
'newteam': {
'id': 499,
'abbrev': 'WV',
'sname': 'Black Bears',
'lname': 'West Virginia Black Bears',
'manager_legacy': None,
'division_legacy': None,
'gmid': '258104532423147520',
'gmid2': None,
'season': 12
},
'season': 12,
'moveid': 'Season-012-Week-10-19-13:04:41',
'cancelled': False,
'frozen': False
},
{
'id': 27788,
'week': 10,
'player': {
'id': 12473,
'name': 'Ronald Acuna Jr.',
'wara': 3.12,
'season': 12,
'pos_1': 'OF'
},
'oldteam': {
'id': 499,
'abbrev': 'WV',
'sname': 'Black Bears',
'lname': 'West Virginia Black Bears',
'season': 12
},
'newteam': {
'id': 501,
'abbrev': 'ATL',
'sname': 'Braves',
'lname': 'Atlanta Braves',
'season': 12
},
'season': 12,
'moveid': 'Season-012-Week-10-20-14:22:15',
'cancelled': False,
'frozen': True
},
{
'id': 27789,
'week': 9,
'player': {
'id': 12474,
'name': 'Mike Trout',
'wara': 2.89,
'season': 12,
'pos_1': 'CF'
},
'oldteam': {
'id': 502,
'abbrev': 'LAA',
'sname': 'Angels',
'lname': 'Los Angeles Angels',
'season': 12
},
'newteam': {
'id': 503,
'abbrev': 'FA',
'sname': 'Free Agents',
'lname': 'Free Agency',
'season': 12
},
'season': 12,
'moveid': 'Season-012-Week-09-18-11:45:33',
'cancelled': True,
'frozen': False
}
]
@pytest.mark.asyncio
async def test_api_to_model_conversion(self, realistic_api_data):
"""Test that realistic API data converts correctly to Transaction models."""
transactions = [Transaction.from_api_data(data) for data in realistic_api_data]
assert len(transactions) == 3
# Test first transaction (pending)
tx1 = transactions[0]
assert tx1.id == 27787
assert tx1.player.name == 'Yordan Alvarez'
assert tx1.player.wara == 2.47
assert tx1.player.bbref_id == 'alvaryo01'
assert tx1.oldteam.abbrev == 'NYD'
assert tx1.newteam.abbrev == 'WV'
assert tx1.is_pending is True
assert tx1.is_major_league_move is True
# Test second transaction (frozen)
tx2 = transactions[1]
assert tx2.id == 27788
assert tx2.is_frozen is True
assert tx2.is_pending is False
# Test third transaction (cancelled)
tx3 = transactions[2]
assert tx3.id == 27789
assert tx3.is_cancelled is True
assert tx3.newteam.abbrev == 'FA' # Move to free agency
@pytest.mark.asyncio
async def test_service_layer_integration(self, realistic_api_data):
"""Test service layer with realistic data processing."""
service = transaction_service
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
# Mock API returns realistic data
mock_get.return_value = [Transaction.from_api_data(data) for data in realistic_api_data]
# Test team transactions
result = await service.get_team_transactions('WV', 12)
# Should sort by week, then moveid
assert result[0].week == 9 # Week 9 first
assert result[1].week == 10 # Then week 10 transactions
assert result[2].week == 10
# Test filtering
pending = await service.get_pending_transactions('WV', 12)
frozen = await service.get_frozen_transactions('WV', 12)
# Verify filtering works correctly
with patch.object(service, 'get_team_transactions', new_callable=AsyncMock) as mock_team_tx:
mock_team_tx.return_value = [tx for tx in result if tx.is_pending]
pending_filtered = await service.get_pending_transactions('WV', 12)
mock_team_tx.assert_called_with('WV', 12, cancelled=False, frozen=False)
@pytest.mark.asyncio
async def test_command_layer_integration(self, realistic_api_data):
"""Test Discord command layer with realistic transaction data."""
mock_bot = MagicMock()
commands_cog = TransactionCommands(mock_bot)
mock_interaction = AsyncMock()
mock_interaction.user.id = 258104532423147520 # WV owner ID from API data
mock_team = Team.from_api_data({
'id': 499,
'abbrev': 'WV',
'sname': 'Black Bears',
'lname': 'West Virginia Black Bears',
'season': 12,
'thumbnail': 'https://example.com/wv.png'
})
transactions = [Transaction.from_api_data(data) for data in realistic_api_data]
with patch('commands.transactions.management.team_service') as mock_team_service:
with patch('commands.transactions.management.transaction_service') as mock_tx_service:
# Setup service mocks
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
# Filter transactions by status
pending_tx = [tx for tx in transactions if tx.is_pending]
frozen_tx = [tx for tx in transactions if tx.is_frozen]
cancelled_tx = [tx for tx in transactions if tx.is_cancelled]
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 embed creation
embed_call = mock_interaction.followup.send.call_args
embed = embed_call.kwargs['embed']
# Check embed contains realistic data
assert 'WV' in embed.title
assert 'West Virginia Black Bears' in embed.description
# Check transaction descriptions in fields
pending_field = next(f for f in embed.fields if 'Pending' in f.name)
assert 'Yordan Alvarez: NYD → WV' in pending_field.value
@pytest.mark.asyncio
async def test_error_propagation_integration(self):
"""Test that errors propagate correctly through all layers."""
service = transaction_service
mock_bot = MagicMock()
commands_cog = TransactionCommands(mock_bot)
mock_interaction = AsyncMock()
mock_interaction.user.id = 258104532423147520
# Test API error propagation
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
# Mock API failure
mock_get.side_effect = Exception("Database connection failed")
with patch('commands.transactions.management.team_service') as mock_team_service:
mock_team = Team.from_api_data({
'id': 499,
'abbrev': 'WV',
'sname': 'Black Bears',
'lname': 'West Virginia Black Bears',
'season': 12
})
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
# Should propagate exception
with pytest.raises(Exception) as exc_info:
await commands_cog.my_moves.callback(commands_cog, mock_interaction)
assert "Database connection failed" in str(exc_info.value)
@pytest.mark.asyncio
async def test_performance_integration(self, realistic_api_data):
"""Test system performance with realistic data volumes."""
# Scale up the data to simulate production load
large_dataset = []
for week in range(1, 19): # 18 weeks
for i in range(20): # 20 transactions per week
tx_data = {
**realistic_api_data[0],
'id': (week * 100) + i,
'week': week,
'moveid': f'Season-012-Week-{week:02d}-{i:02d}',
'player': {
**realistic_api_data[0]['player'],
'id': (week * 100) + i,
'name': f'Player {(week * 100) + i}'
},
'cancelled': i % 10 == 0, # 10% cancelled
'frozen': i % 7 == 0 # ~14% frozen
}
large_dataset.append(tx_data)
service = transaction_service
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
mock_get.return_value = [Transaction.from_api_data(data) for data in large_dataset]
import time
start_time = time.time()
# Test various service operations
all_transactions = await service.get_team_transactions('WV', 12)
pending = await service.get_pending_transactions('WV', 12)
frozen = await service.get_frozen_transactions('WV', 12)
end_time = time.time()
processing_time = end_time - start_time
# Performance assertions
assert len(all_transactions) == 360 # 18 weeks * 20 transactions
assert len(pending) > 0
assert len(frozen) > 0
assert processing_time < 0.5 # Should process quickly
# Verify sorting performance
for i in range(len(all_transactions) - 1):
current_tx = all_transactions[i]
next_tx = all_transactions[i + 1]
assert current_tx.week <= next_tx.week
@pytest.mark.asyncio
async def test_concurrent_operations_integration(self, realistic_api_data):
"""Test concurrent operations across the entire system."""
service = transaction_service
mock_bot = MagicMock()
# Create multiple command instances (simulating multiple users)
command_instances = [TransactionCommands(mock_bot) for _ in range(5)]
mock_interactions = []
for i in range(5):
interaction = AsyncMock()
interaction.user.id = 258104532423147520 + i
mock_interactions.append(interaction)
transactions = [Transaction.from_api_data(data) for data in realistic_api_data]
# Prepare test data
pending_tx = [tx for tx in transactions if tx.is_pending]
frozen_tx = [tx for tx in transactions if tx.is_frozen]
mock_team = TeamFactory.west_virginia()
with patch('commands.transactions.management.team_service') as mock_team_service:
with patch('commands.transactions.management.transaction_service') as mock_tx_service:
# Mock team service
mock_team_service.get_teams_by_owner = AsyncMock(return_value=[mock_team])
# Mock transaction service methods completely
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=[])
mock_tx_service.get_team_transactions = AsyncMock(return_value=[]) # No cancelled transactions
# Execute concurrent operations
tasks = []
for i, (cmd, interaction) in enumerate(zip(command_instances, mock_interactions)):
tasks.append(cmd.my_moves.callback(cmd, interaction, show_cancelled=(i % 2 == 0)))
# Wait for all operations to complete
results = await asyncio.gather(*tasks, return_exceptions=True)
# All should complete successfully
successful_results = [r for r in results if not isinstance(r, Exception)]
assert len(successful_results) == 5
# All interactions should have received responses
for interaction in mock_interactions:
interaction.followup.send.assert_called_once()
@pytest.mark.asyncio
async def test_data_consistency_integration(self, realistic_api_data):
"""Test data consistency across service operations."""
transactions = [Transaction.from_api_data(data) for data in realistic_api_data]
# Separate transactions by status for consistent mocking
all_tx = transactions
pending_tx = [tx for tx in transactions if tx.is_pending]
frozen_tx = [tx for tx in transactions if tx.is_frozen]
# Mock ALL service methods consistently
with patch('services.transaction_service.transaction_service') as mock_service:
mock_service.get_team_transactions = AsyncMock(return_value=all_tx)
mock_service.get_pending_transactions = AsyncMock(return_value=pending_tx)
mock_service.get_frozen_transactions = AsyncMock(return_value=frozen_tx)
# Get transactions through different service methods
all_tx_result = await mock_service.get_team_transactions('WV', 12)
pending_tx_result = await mock_service.get_pending_transactions('WV', 12)
frozen_tx_result = await mock_service.get_frozen_transactions('WV', 12)
# Verify data consistency
total_by_status = len(pending_tx_result) + len(frozen_tx_result)
# Count cancelled transactions separately
cancelled_count = len([tx for tx in all_tx_result if tx.is_cancelled])
# Total should match when accounting for all statuses
assert len(all_tx_result) == total_by_status + cancelled_count
# Verify no transaction appears in multiple status lists
pending_ids = {tx.id for tx in pending_tx_result}
frozen_ids = {tx.id for tx in frozen_tx_result}
assert len(pending_ids.intersection(frozen_ids)) == 0 # No overlap
# Verify transaction properties match their categorization
for tx in pending_tx_result:
assert tx.is_pending is True
assert tx.is_frozen is False
assert tx.is_cancelled is False
for tx in frozen_tx_result:
assert tx.is_frozen is True
assert tx.is_pending is False
assert tx.is_cancelled is False
@pytest.mark.asyncio
async def test_validation_integration(self, realistic_api_data):
"""Test transaction validation integration."""
service = transaction_service
transactions = [Transaction.from_api_data(data) for data in realistic_api_data]
# Test validation for each transaction
for tx in transactions:
validation = await service.validate_transaction(tx)
assert isinstance(validation, RosterValidation)
# Basic validation should pass for well-formed transactions
assert validation.is_legal is True
assert len(validation.errors) == 0
# Test validation with problematic transaction (simulated)
problematic_tx = transactions[0]
# Mock validation failure
with patch.object(service, 'validate_transaction') as mock_validate:
mock_validate.return_value = RosterValidation(
is_legal=False,
errors=['Player not eligible for move', 'Roster size violation'],
warnings=['Team WARA below threshold']
)
validation = await service.validate_transaction(problematic_tx)
assert validation.is_legal is False
assert len(validation.errors) == 2
assert len(validation.warnings) == 1
assert validation.status_emoji == ''