""" Shared test fixtures for the Paper Dynasty database test suite. Uses in-memory SQLite with foreign_keys pragma enabled. Each test gets a fresh set of tables via the setup_test_db fixture (autouse). All models are bound to the in-memory database before table creation so that no connection to the real storage/pd_master.db occurs during tests. """ import os import pytest import psycopg2 from peewee import SqliteDatabase # Set DATABASE_TYPE=postgresql so that the module-level SKIP_TABLE_CREATION # flag is True. This prevents db_engine.py from calling create_tables() # against the real storage/pd_master.db during import — those calls would # fail if indexes already exist and would also contaminate the dev database. # The PooledPostgresqlDatabase object is created but never actually connects # because our fixture rebinds all models to an in-memory SQLite db before # any query is executed. os.environ["DATABASE_TYPE"] = "postgresql" # Provide dummy credentials so PooledPostgresqlDatabase can be instantiated # without raising a configuration error (it will not actually be used). os.environ.setdefault("POSTGRES_PASSWORD", "test-dummy") from app.db_engine import ( Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, Card, Roster, RosterSlot, StratGame, StratPlay, Decision, BattingSeasonStats, PitchingSeasonStats, ProcessedGame, BattingCard, PitchingCard, RefractorTrack, RefractorCardState, RefractorTierBoost, RefractorCosmetic, RefractorBoostAudit, ScoutOpportunity, ScoutClaim, ) _test_db = SqliteDatabase(":memory:", pragmas={"foreign_keys": 1}) # All models in dependency order (parents before children) so that # create_tables and drop_tables work without FK violations. _TEST_MODELS = [ Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, Card, Roster, RosterSlot, StratGame, StratPlay, Decision, BattingSeasonStats, PitchingSeasonStats, ProcessedGame, ScoutOpportunity, ScoutClaim, RefractorTrack, RefractorCardState, RefractorTierBoost, RefractorCosmetic, BattingCard, PitchingCard, RefractorBoostAudit, ] @pytest.fixture(autouse=True) def setup_test_db(): """Bind all models to in-memory SQLite and create tables. The fixture is autouse so every test automatically gets a fresh, isolated database schema without needing to request it explicitly. Tables are dropped in reverse dependency order after each test to keep the teardown clean and to catch any accidental FK reference direction bugs early. """ _test_db.bind(_TEST_MODELS) _test_db.connect() _test_db.create_tables(_TEST_MODELS) yield _test_db _test_db.drop_tables(list(reversed(_TEST_MODELS)), safe=True) _test_db.close() # --------------------------------------------------------------------------- # Minimal shared fixtures — create just enough data for FK dependencies # --------------------------------------------------------------------------- @pytest.fixture def rarity(): """A single Common rarity row used as FK seed for Player rows.""" return Rarity.create(value=1, name="Common", color="#ffffff") @pytest.fixture def player(rarity): """A minimal Player row with all required (non-nullable) columns filled. Player.p_name is the real column name (not 'name'). All FK and non-nullable varchar fields are provided so SQLite's NOT NULL constraints are satisfied even with foreign_keys=ON. """ cardset = Cardset.create( name="Test Set", description="Test cardset", total_cards=100, ) return Player.create( p_name="Test Player", rarity=rarity, cardset=cardset, set_num=1, pos_1="1B", image="https://example.com/image.png", mlbclub="TST", franchise="TST", description="A test player", ) @pytest.fixture def team(): """A minimal Team row. Team uses abbrev/lname/sname/gmid/gmname/gsheet/wallet/team_value/ collection_value — not the 'name'/'user_id' shorthand described in the spec, which referred to the real underlying columns by simplified names. """ return Team.create( abbrev="TST", sname="Test", lname="Test Team", gmid=100000001, gmname="testuser", gsheet="https://docs.google.com/spreadsheets/test", wallet=500, team_value=1000, collection_value=1000, season=11, is_ai=False, ) @pytest.fixture def track(): """A minimal RefractorTrack for batter cards.""" return RefractorTrack.create( name="Batter Track", card_type="batter", formula="pa + tb * 2", t1_threshold=37, t2_threshold=149, t3_threshold=448, t4_threshold=896, ) # --------------------------------------------------------------------------- # PostgreSQL integration fixture (used by test_refractor_*_api.py) # --------------------------------------------------------------------------- @pytest.fixture(scope="session") def pg_conn(): """Open a psycopg2 connection to the PostgreSQL instance for integration tests. Reads connection parameters from the standard POSTGRES_* env vars that the CI workflow injects when a postgres service container is running. Skips the entire session (via pytest.skip) when POSTGRES_HOST is not set, keeping local runs clean. The connection is shared for the whole session (scope="session") because the integration test modules use module-scoped fixtures that rely on it; creating a new connection per test would break those module-scoped fixtures. Teardown: the connection is closed once all tests have finished. """ host = os.environ.get("POSTGRES_HOST") if not host: pytest.skip("POSTGRES_HOST not set — PostgreSQL integration tests skipped") conn = psycopg2.connect( host=host, port=int(os.environ.get("POSTGRES_PORT", "5432")), dbname=os.environ.get("POSTGRES_DB", "paper_dynasty"), user=os.environ.get("POSTGRES_USER", "postgres"), password=os.environ.get("POSTGRES_PASSWORD", ""), ) conn.autocommit = False yield conn conn.close()