""" Unit tests for terminal client shared commands. """ import pytest from uuid import uuid4 from unittest.mock import AsyncMock, MagicMock, patch from terminal_client.commands import GameCommands from app.models.game_models import GameState @pytest.fixture def game_commands(): """Create GameCommands instance with mocked dependencies.""" commands = GameCommands() commands.db_ops = AsyncMock() return commands @pytest.mark.asyncio async def test_create_new_game_success(game_commands): """Test successful game creation.""" game_id = uuid4() with patch('terminal_client.commands.state_manager') as mock_sm: with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.uuid4', return_value=game_id): with patch('terminal_client.commands.Config') as mock_config: # Setup mocks from app.models.game_models import LineupPlayerState mock_batter = LineupPlayerState( lineup_id=1, card_id=100, position='CF', batting_order=1 ) mock_state = GameState( game_id=game_id, league_id='sba', home_team_id=1, away_team_id=2, current_batter=mock_batter, inning=1, half='top' ) mock_sm.create_game = AsyncMock(return_value=mock_state) mock_ge.start_game = AsyncMock(return_value=mock_state) # Execute gid, success = await game_commands.create_new_game() # Verify assert success is True assert gid == game_id mock_sm.create_game.assert_called_once() mock_ge.start_game.assert_called_once() mock_config.set_current_game.assert_called_once_with(game_id) @pytest.mark.asyncio async def test_create_new_game_with_pd_league(game_commands): """Test game creation with PD league.""" game_id = uuid4() with patch('terminal_client.commands.state_manager') as mock_sm: with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.uuid4', return_value=game_id): with patch('terminal_client.commands.Config') as mock_config: # Setup mocks from app.models.game_models import LineupPlayerState mock_batter = LineupPlayerState( lineup_id=1, card_id=200, position='SS', batting_order=1 ) mock_state = GameState( game_id=game_id, league_id='pd', home_team_id=3, away_team_id=5, current_batter=mock_batter, inning=1, half='top' ) mock_sm.create_game = AsyncMock(return_value=mock_state) mock_ge.start_game = AsyncMock(return_value=mock_state) # Execute gid, success = await game_commands.create_new_game( league='pd', home_team=3, away_team=5 ) # Verify assert success is True assert gid == game_id # Check that PD-specific lineup creation was called assert game_commands.db_ops.add_pd_lineup_card.call_count == 18 # 9 per team @pytest.mark.asyncio async def test_create_new_game_failure(game_commands): """Test game creation failure.""" with patch('terminal_client.commands.state_manager') as mock_sm: with patch('terminal_client.commands.uuid4', return_value=uuid4()): # Setup mocks to fail mock_sm.create_game = AsyncMock(side_effect=Exception("Database error")) # Execute gid, success = await game_commands.create_new_game() # Verify assert success is False @pytest.mark.asyncio async def test_submit_defensive_decision_success(game_commands): """Test successful defensive decision submission.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_state = MagicMock() mock_ge.submit_defensive_decision = AsyncMock(return_value=mock_state) success = await game_commands.submit_defensive_decision( game_id=game_id, alignment='shifted_left', hold_runners=[1, 2] ) assert success is True mock_ge.submit_defensive_decision.assert_called_once() @pytest.mark.asyncio async def test_submit_defensive_decision_failure(game_commands): """Test defensive decision submission failure.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.submit_defensive_decision = AsyncMock(side_effect=Exception("Invalid decision")) success = await game_commands.submit_defensive_decision( game_id=game_id, alignment='invalid_alignment' ) assert success is False @pytest.mark.asyncio async def test_submit_offensive_decision_success(game_commands): """Test successful offensive decision submission.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_state = MagicMock() mock_ge.submit_offensive_decision = AsyncMock(return_value=mock_state) success = await game_commands.submit_offensive_decision( game_id=game_id, approach='power', steal_attempts=[2], hit_and_run=True ) assert success is True mock_ge.submit_offensive_decision.assert_called_once() @pytest.mark.asyncio async def test_submit_offensive_decision_failure(game_commands): """Test offensive decision submission failure.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.submit_offensive_decision = AsyncMock(side_effect=Exception("Invalid decision")) success = await game_commands.submit_offensive_decision( game_id=game_id, approach='invalid_approach' ) assert success is False @pytest.mark.asyncio async def test_resolve_play_success(game_commands): """Test successful play resolution.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.display'): # Setup mock result with proper attributes mock_result = MagicMock() mock_result.description = "Single to center field" mock_result.outs_recorded = 0 mock_result.runs_scored = 1 # Setup mock state mock_state = MagicMock() mock_state.away_score = 1 mock_state.home_score = 0 mock_ge.resolve_play = AsyncMock(return_value=mock_result) mock_ge.get_game_state = AsyncMock(return_value=mock_state) success = await game_commands.resolve_play(game_id) assert success is True mock_ge.resolve_play.assert_called_once_with(game_id, None, None, None, None) mock_ge.get_game_state.assert_called_once_with(game_id) @pytest.mark.asyncio async def test_resolve_play_game_not_found(game_commands): """Test play resolution when game is not found.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_result = MagicMock() mock_ge.resolve_play = AsyncMock(return_value=mock_result) mock_ge.get_game_state = AsyncMock(return_value=None) # Game not found success = await game_commands.resolve_play(game_id) assert success is False @pytest.mark.asyncio async def test_resolve_play_failure(game_commands): """Test play resolution failure.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.resolve_play = AsyncMock(side_effect=Exception("Resolution error")) success = await game_commands.resolve_play(game_id) assert success is False @pytest.mark.asyncio async def test_quick_play_rounds_success(game_commands): """Test successful quick play execution.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.asyncio.sleep', new_callable=AsyncMock): with patch('terminal_client.commands.display'): # Setup mocks mock_state = MagicMock() mock_state.status = "active" mock_state.away_score = 0 mock_state.home_score = 0 mock_state.inning = 1 mock_state.half = "top" mock_state.outs = 0 mock_result = MagicMock() mock_result.description = "Groundout" mock_ge.get_game_state = AsyncMock(return_value=mock_state) mock_ge.submit_defensive_decision = AsyncMock() mock_ge.submit_offensive_decision = AsyncMock() mock_ge.resolve_play = AsyncMock(return_value=mock_result) # Execute 3 plays plays_completed = await game_commands.quick_play_rounds(game_id, count=3) # Verify assert plays_completed == 3 assert mock_ge.resolve_play.call_count == 3 @pytest.mark.asyncio async def test_quick_play_rounds_game_ends(game_commands): """Test quick play when game ends mid-execution.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.asyncio.sleep', new_callable=AsyncMock): with patch('terminal_client.commands.display'): # Setup mocks - game becomes completed after 2 plays mock_state_active_1 = MagicMock() mock_state_active_1.status = "active" mock_state_active_1.away_score = 0 mock_state_active_1.home_score = 0 mock_state_active_1.inning = 1 mock_state_active_1.half = "top" mock_state_active_1.outs = 0 mock_state_active_2 = MagicMock() mock_state_active_2.status = "active" mock_state_active_2.away_score = 1 mock_state_active_2.home_score = 0 mock_state_active_2.inning = 1 mock_state_active_2.half = "top" mock_state_active_2.outs = 0 mock_state_completed = MagicMock() mock_state_completed.status = "completed" mock_result = MagicMock() mock_result.description = "Game winning hit" # Setup get_game_state to return: # 1. active (before play 1) # 2. active (after play 1) # 3. active (before play 2) # 4. active (after play 2) # 5. completed (before play 3 - should stop) # 6. completed (final state query) mock_ge.get_game_state = AsyncMock( side_effect=[ mock_state_active_1, # Before play 1 mock_state_active_2, # After play 1 mock_state_active_2, # Before play 2 mock_state_active_2, # After play 2 mock_state_completed, # Before play 3 - stops here mock_state_completed # Final state query ] ) mock_ge.submit_defensive_decision = AsyncMock() mock_ge.submit_offensive_decision = AsyncMock() mock_ge.resolve_play = AsyncMock(return_value=mock_result) # Execute 5 plays but should stop at 2 plays_completed = await game_commands.quick_play_rounds(game_id, count=5) # Verify - should only complete 2 plays assert plays_completed == 2 assert mock_ge.resolve_play.call_count == 2 @pytest.mark.asyncio async def test_show_game_status_success(game_commands): """Test showing game status successfully.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_state = MagicMock() mock_ge.get_game_state = AsyncMock(return_value=mock_state) success = await game_commands.show_game_status(game_id) assert success is True mock_ge.get_game_state.assert_called_once_with(game_id) @pytest.mark.asyncio async def test_show_game_status_not_found(game_commands): """Test showing game status when game not found.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.get_game_state = AsyncMock(return_value=None) success = await game_commands.show_game_status(game_id) assert success is False @pytest.mark.asyncio async def test_show_box_score_success(game_commands): """Test showing box score successfully.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_state = MagicMock() mock_ge.get_game_state = AsyncMock(return_value=mock_state) success = await game_commands.show_box_score(game_id) assert success is True mock_ge.get_game_state.assert_called_once_with(game_id) @pytest.mark.asyncio async def test_show_box_score_not_found(game_commands): """Test showing box score when game not found.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.get_game_state = AsyncMock(return_value=None) success = await game_commands.show_box_score(game_id) assert success is False