""" Simple AIService Integration Test Example Demonstrates what AIService integration tests would look like with proper setup. This shows the testing approach for improving coverage from 60% to 90%+. """ import pytest from unittest.mock import Mock from app.models.ai_responses import JumpResponse from app.models.position_rating import PositionRating from app.services.ai_service import AIService from tests.factories.manager_ai_factory import ManagerAiFactory from tests.factories.player_factory import PlayerFactory class TestAIServiceIntegrationExample: """Example integration tests that would increase AIService coverage.""" def test_steal_with_real_database_catcher_query(self, db_session): """Example: Test steal decision with actual database query for catcher defense.""" # Create real catcher with position rating catcher = PlayerFactory.create_catcher(db_session, name="Elite Catcher") catcher_defense = PositionRating( player_id=catcher.id, variant=0, position='C', arm=9, # Excellent arm range=7, error=1 ) db_session.add(catcher_defense) db_session.commit() # Create AI and properly mocked game aggressive_ai = ManagerAiFactory.create(db_session, steal=9, running=8) mock_game = self._create_proper_game_mock(catcher, db_session) ai_service = AIService(db_session) # This executes real database query: session.exec(select(PositionRating)...) result = ai_service.check_steal_opportunity(aggressive_ai, mock_game, 2) assert isinstance(result, JumpResponse) assert result.min_safe is not None # Real catcher arm (9) affects battery_hold calculation def test_steal_edge_case_branches(self, db_session): """Example: Test uncovered conditional branches in steal logic.""" # Test case that hits line 91: steal > 8 and run_diff <= 5 high_steal_ai = ManagerAiFactory.create(db_session, steal=9) catcher = PlayerFactory.create_catcher(db_session) self._create_catcher_defense(catcher, db_session, arm=5) mock_game = self._create_proper_game_mock(catcher, db_session) # Set specific game state to hit uncovered branch mock_game.current_play_or_none.return_value.ai_run_diff = 3 # <= 5 mock_game.current_play_or_none.return_value.starting_outs = 1 ai_service = AIService(db_session) result = ai_service.check_steal_opportunity(high_steal_ai, mock_game, 2) # This should hit the uncovered line 91: min_safe = 13 + num_outs = 14 assert result.min_safe == 14 def test_steal_missing_runner_error_handling(self, db_session): """Example: Test error handling when runner is missing.""" ai = ManagerAiFactory.create(db_session, steal=5) catcher = PlayerFactory.create_catcher(db_session) self._create_catcher_defense(catcher, db_session) mock_game = self._create_proper_game_mock(catcher, db_session) # Remove runner to trigger error path mock_game.current_play_or_none.return_value.on_first = None ai_service = AIService(db_session) # This tests the uncovered error handling branch with pytest.raises(ValueError, match="no runner found on first"): ai_service.check_steal_opportunity(ai, mock_game, 2) def _create_proper_game_mock(self, catcher, db_session): """Create properly configured game mock with realistic values.""" mock_game = Mock() mock_play = Mock() # Create realistic runner runner = PlayerFactory.create(db_session, name="Fast Runner") mock_runner = Mock() mock_runner.player.name = runner.name mock_runner.card.batterscouting.battingcard.steal_auto = False mock_runner.card.batterscouting.battingcard.steal_high = 15 mock_runner.card.batterscouting.battingcard.steal_low = 12 # Setup play with proper numeric values (not Mocks) mock_play.on_first = mock_runner mock_play.away_score = 4 # Actual number mock_play.home_score = 2 # Actual number mock_play.ai_run_diff = 2 # AI is home team, so 2-4 = -2, but we set directly mock_play.starting_outs = 1 mock_play.outs = 1 # Setup catcher for database query mock_play.catcher = Mock() mock_play.catcher.player_id = catcher.id mock_play.catcher.card.variant = 0 # Setup pitcher mock_play.pitcher.card.pitcherscouting.pitchingcard.hold = 5 mock_game.current_play_or_none.return_value = mock_play mock_game.id = 12345 return mock_game def _create_catcher_defense(self, catcher, db_session, arm=7): """Helper to create catcher defensive rating.""" defense = PositionRating( player_id=catcher.id, variant=0, position='C', arm=arm, range=6, error=2 ) db_session.add(defense) db_session.commit() return defense class TestAIServiceCoverageImprovements: """Examples of specific tests that would improve coverage metrics.""" def test_steal_to_home_scenario(self, db_session): """Test steal to home (base 4) - covers lines 151-171.""" ai = ManagerAiFactory.create(db_session, steal=6) catcher = PlayerFactory.create_catcher(db_session) self._create_catcher_defense(catcher, db_session) mock_game = Mock() mock_play = Mock() # Setup for steal to home runner = PlayerFactory.create(db_session) mock_runner = Mock() mock_runner.player.name = runner.name mock_runner.card.batterscouting.battingcard.steal_low = 10 mock_play.on_third = mock_runner # Runner on third for steal home mock_play.away_score = 3 mock_play.home_score = 3 # Tied game mock_play.ai_run_diff = 0 # This triggers home steal logic mock_play.starting_outs = 1 mock_play.inning_num = 8 # Late inning mock_play.catcher = Mock() mock_play.catcher.player_id = catcher.id mock_play.catcher.card.variant = 0 mock_play.pitcher.card.pitcherscouting.pitchingcard.hold = 4 mock_game.current_play_or_none.return_value = mock_play mock_game.id = 67890 ai_service = AIService(db_session) result = ai_service.check_steal_opportunity(ai, mock_game, 4) # Steal home # This covers the steal > 5 branch in home steal logic (line 160) assert isinstance(result, JumpResponse) assert result.min_safe == 7 # steal > 5 → min_safe = 7 def test_default_case_branch(self, db_session): """Test default case in steal logic - covers lines 98-99.""" very_low_steal_ai = ManagerAiFactory.create(db_session, steal=1) # Very low steal catcher = PlayerFactory.create_catcher(db_session) self._create_catcher_defense(catcher, db_session) mock_game = self._create_basic_mock(catcher, db_session) # Set conditions that hit default case mock_game.current_play_or_none.return_value.ai_run_diff = 3 # <= 5 mock_game.current_play_or_none.return_value.starting_outs = 0 ai_service = AIService(db_session) result = ai_service.check_steal_opportunity(very_low_steal_ai, mock_game, 2) # Should hit default case: min_safe = 17 + num_outs = 17 assert result.min_safe == 17 def test_auto_jump_conditions(self, db_session): """Test auto jump logic - covers lines 107-108.""" high_steal_ai = ManagerAiFactory.create(db_session, steal=8) # > 7 catcher = PlayerFactory.create_catcher(db_session) self._create_catcher_defense(catcher, db_session) mock_game = self._create_basic_mock(catcher, db_session) mock_play = mock_game.current_play_or_none.return_value mock_play.ai_run_diff = 2 # <= 5 mock_play.starting_outs = 1 # < 2 # Set runner to have steal_auto = True mock_play.on_first.card.batterscouting.battingcard.steal_auto = True ai_service = AIService(db_session) result = ai_service.check_steal_opportunity(high_steal_ai, mock_game, 2) # Should hit run_if_auto_jump = True and steal_auto = True branch assert result.run_if_auto_jump is True assert "WILL SEND" in result.ai_note def _create_basic_mock(self, catcher, db_session): """Create basic game mock for testing.""" mock_game = Mock() mock_play = Mock() runner = PlayerFactory.create(db_session) mock_runner = Mock() mock_runner.player.name = runner.name mock_runner.card.batterscouting.battingcard.steal_auto = False mock_runner.card.batterscouting.battingcard.steal_high = 14 mock_play.on_first = mock_runner mock_play.away_score = 5 mock_play.home_score = 3 mock_play.ai_run_diff = 2 mock_play.starting_outs = 0 mock_play.catcher = Mock() mock_play.catcher.player_id = catcher.id mock_play.catcher.card.variant = 0 mock_play.pitcher.card.pitcherscouting.pitchingcard.hold = 5 mock_game.current_play_or_none.return_value = mock_play mock_game.id = 99999 return mock_game def _create_catcher_defense(self, catcher, db_session, arm=6): """Create catcher defensive rating.""" defense = PositionRating( player_id=catcher.id, variant=0, position='C', arm=arm, range=5, error=2 ) db_session.add(defense) db_session.commit() return defense