""" Integration tests for DatabaseOperations. Tests actual database operations using the test database. These tests are slower than unit tests but verify real DB interactions. Author: Claude Date: 2025-10-22 """ import pytest from uuid import uuid4 from app.database.operations import DatabaseOperations from app.database.session import init_db, engine # Mark all tests in this module as integration tests pytestmark = pytest.mark.integration @pytest.fixture(scope="module") async def setup_database(): """ Set up test database schema. Runs once per test module. """ # Create all tables await init_db() yield # Teardown if needed (tables persist between test runs) @pytest.fixture async def db_ops(): """Create DatabaseOperations instance for each test""" return DatabaseOperations() @pytest.fixture def sample_game_id(): """Generate a unique game ID for each test""" return uuid4() class TestDatabaseOperationsGame: """Tests for game CRUD operations""" @pytest.mark.asyncio async def test_create_game(self, setup_database, db_ops, sample_game_id): """Test creating a game in database""" game = await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) assert game.id == sample_game_id assert game.league_id == "sba" assert game.status == "pending" assert game.home_team_id == 1 assert game.away_team_id == 2 @pytest.mark.asyncio async def test_create_game_with_ai(self, setup_database, db_ops, sample_game_id): """Test creating a game with AI opponent""" game = await db_ops.create_game( game_id=sample_game_id, league_id="pd", home_team_id=10, away_team_id=20, game_mode="practice", visibility="private", home_team_is_ai=False, away_team_is_ai=True, ai_difficulty="balanced" ) assert game.away_team_is_ai is True assert game.home_team_is_ai is False assert game.ai_difficulty == "balanced" @pytest.mark.asyncio async def test_get_game(self, setup_database, db_ops, sample_game_id): """Test retrieving a game from database""" # Create game await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Retrieve game retrieved = await db_ops.get_game(sample_game_id) assert retrieved is not None assert retrieved.id == sample_game_id assert retrieved.league_id == "sba" @pytest.mark.asyncio async def test_get_game_nonexistent(self, setup_database, db_ops): """Test retrieving nonexistent game returns None""" fake_id = uuid4() game = await db_ops.get_game(fake_id) assert game is None @pytest.mark.asyncio async def test_update_game_state(self, setup_database, db_ops, sample_game_id): """Test updating game state""" # Create game await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Update state await db_ops.update_game_state( game_id=sample_game_id, inning=5, half="bottom", home_score=3, away_score=2, status="active" ) # Verify update game = await db_ops.get_game(sample_game_id) assert game.current_inning == 5 assert game.current_half == "bottom" assert game.home_score == 3 assert game.away_score == 2 assert game.status == "active" @pytest.mark.asyncio async def test_update_game_state_nonexistent_raises_error(self, setup_database, db_ops): """Test updating nonexistent game raises error""" fake_id = uuid4() with pytest.raises(ValueError, match="not found"): await db_ops.update_game_state( game_id=fake_id, inning=1, half="top", home_score=0, away_score=0 ) class TestDatabaseOperationsLineup: """Tests for lineup operations""" @pytest.mark.asyncio async def test_create_lineup_entry(self, setup_database, db_ops, sample_game_id): """Test creating a lineup entry""" # Create game first await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Create lineup entry lineup = await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=101, position="CF", batting_order=1, is_starter=True ) assert lineup.game_id == sample_game_id assert lineup.team_id == 1 assert lineup.card_id == 101 assert lineup.position == "CF" assert lineup.batting_order == 1 assert lineup.is_active is True @pytest.mark.asyncio async def test_create_pitcher_no_batting_order(self, setup_database, db_ops, sample_game_id): """Test creating pitcher without batting order""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) lineup = await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=200, position="P", batting_order=None, # Pitcher, no batting order (AL rules) is_starter=True ) assert lineup.position == "P" assert lineup.batting_order is None @pytest.mark.asyncio async def test_get_active_lineup(self, setup_database, db_ops, sample_game_id): """Test retrieving active lineup for a team""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Create multiple lineup entries await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=103, position="1B", batting_order=3 ) await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=101, position="CF", batting_order=1 ) await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=102, position="SS", batting_order=2 ) # Retrieve lineup lineup = await db_ops.get_active_lineup(sample_game_id, team_id=1) assert len(lineup) == 3 # Should be sorted by batting order assert lineup[0].batting_order == 1 assert lineup[1].batting_order == 2 assert lineup[2].batting_order == 3 @pytest.mark.asyncio async def test_get_active_lineup_empty(self, setup_database, db_ops, sample_game_id): """Test retrieving lineup for team with no entries""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) lineup = await db_ops.get_active_lineup(sample_game_id, team_id=1) assert lineup == [] class TestDatabaseOperationsPlays: """Tests for play operations""" @pytest.mark.asyncio async def test_save_play(self, setup_database, db_ops, sample_game_id): """Test saving a play""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) play_data = { "game_id": sample_game_id, "play_number": 1, "inning": 1, "half": "top", "outs_before": 0, "batting_order": 1, "result_description": "Single to left field", "pa": 1, "ab": 1, "hit": 1 } play = await db_ops.save_play(play_data) assert play.game_id == sample_game_id assert play.play_number == 1 assert play.result_description == "Single to left field" @pytest.mark.asyncio async def test_get_plays(self, setup_database, db_ops, sample_game_id): """Test retrieving plays for a game""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Save multiple plays for i in range(3): await db_ops.save_play({ "game_id": sample_game_id, "play_number": i + 1, "inning": 1, "half": "top", "outs_before": i, "batting_order": i + 1, "result_description": f"Play {i+1}", "pa": 1 }) # Retrieve plays plays = await db_ops.get_plays(sample_game_id) assert len(plays) == 3 # Should be ordered by play_number assert plays[0].play_number == 1 assert plays[1].play_number == 2 assert plays[2].play_number == 3 @pytest.mark.asyncio async def test_get_plays_empty(self, setup_database, db_ops, sample_game_id): """Test retrieving plays for game with no plays""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) plays = await db_ops.get_plays(sample_game_id) assert plays == [] class TestDatabaseOperationsRecovery: """Tests for game state recovery""" @pytest.mark.asyncio async def test_load_game_state_complete(self, setup_database, db_ops, sample_game_id): """Test loading complete game state""" # Create game await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) # Add lineups await db_ops.create_lineup_entry( game_id=sample_game_id, team_id=1, card_id=101, position="CF", batting_order=1 ) # Add play await db_ops.save_play({ "game_id": sample_game_id, "play_number": 1, "inning": 1, "half": "top", "outs_before": 0, "batting_order": 1, "result_description": "Single", "pa": 1 }) # Update game state await db_ops.update_game_state( game_id=sample_game_id, inning=2, half="bottom", home_score=1, away_score=0 ) # Load complete state state = await db_ops.load_game_state(sample_game_id) assert state is not None assert state["game"]["id"] == sample_game_id assert state["game"]["current_inning"] == 2 assert state["game"]["current_half"] == "bottom" assert len(state["lineups"]) == 1 assert len(state["plays"]) == 1 @pytest.mark.asyncio async def test_load_game_state_nonexistent(self, setup_database, db_ops): """Test loading nonexistent game returns None""" fake_id = uuid4() state = await db_ops.load_game_state(fake_id) assert state is None class TestDatabaseOperationsGameSession: """Tests for game session operations""" @pytest.mark.asyncio async def test_create_game_session(self, setup_database, db_ops, sample_game_id): """Test creating a game session""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) session = await db_ops.create_game_session(sample_game_id) assert session.game_id == sample_game_id assert session.state_snapshot is None # Initially null @pytest.mark.asyncio async def test_update_session_snapshot(self, setup_database, db_ops, sample_game_id): """Test updating session snapshot""" await db_ops.create_game( game_id=sample_game_id, league_id="sba", home_team_id=1, away_team_id=2, game_mode="friendly", visibility="public" ) await db_ops.create_game_session(sample_game_id) snapshot = { "inning": 3, "outs": 2, "runners": [1, 3] } await db_ops.update_session_snapshot(sample_game_id, snapshot) # Note: Would need to query session to verify, but this tests no errors @pytest.mark.asyncio async def test_update_session_snapshot_nonexistent_raises_error(self, setup_database, db_ops): """Test updating nonexistent session raises error""" fake_id = uuid4() with pytest.raises(ValueError, match="not found"): await db_ops.update_session_snapshot(fake_id, {})