Closes #122 Both prod and dev environments use PostgreSQL. Removes all SQLite compatibility code that was never exercised in practice. Changes: - db_engine.py: replace SQLite/PostgreSQL branching with direct PooledPostgresqlDatabase init; remove DATABASE_TYPE, SKIP_TABLE_CREATION, all db.create_tables() calls, and commented-out SQLite scout_db code - db_helpers.py: remove DATABASE_TYPE var and SQLite on_conflict_replace branch from upsert_many(); PostgreSQL ON CONFLICT is now the only path - players.py: update stale comment - tests/conftest.py: remove DATABASE_TYPE env var (no longer needed); keep POSTGRES_PASSWORD dummy for instantiation - CLAUDE.md: update SQLite references to PostgreSQL Note: unit tests in test_evolution_seed.py and test_season_stats_model.py use SqliteDatabase(':memory:') for test isolation — this is legitimate test infrastructure, not production SQLite compatibility code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
204 lines
5.6 KiB
Python
204 lines
5.6 KiB
Python
"""
|
|
Shared test fixtures for the Paper Dynasty database test suite.
|
|
|
|
Provides dummy PostgreSQL credentials so PooledPostgresqlDatabase can be
|
|
instantiated during test collection without a real database connection.
|
|
Each test module is responsible for binding models to its own test database.
|
|
"""
|
|
|
|
import os
|
|
import pytest
|
|
import psycopg2
|
|
from peewee import SqliteDatabase
|
|
|
|
# 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,
|
|
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,
|
|
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()
|