"""Integration tests for the evolution track catalog API endpoints (WP-06). Tests cover: GET /api/v2/evolution/tracks GET /api/v2/evolution/tracks/{track_id} All tests require a live PostgreSQL connection (POSTGRES_HOST env var) and assume the evolution schema migration (WP-04) has already been applied. Tests auto-skip when POSTGRES_HOST is not set. Test data is inserted via psycopg2 before the test module runs and deleted afterwards so the tests are repeatable. ON CONFLICT keeps the table clean even if a previous run did not complete teardown. """ import os import pytest from fastapi.testclient import TestClient POSTGRES_HOST = os.environ.get("POSTGRES_HOST") _skip_no_pg = pytest.mark.skipif( not POSTGRES_HOST, reason="POSTGRES_HOST not set — integration tests skipped" ) AUTH_HEADER = {"Authorization": f"Bearer {os.environ.get('API_TOKEN', 'test-token')}"} _SEED_TRACKS = [ ("Batter", "batter", "pa+tb*2", 37, 149, 448, 896), ("Starting Pitcher", "sp", "ip+k", 10, 40, 120, 240), ("Relief Pitcher", "rp", "ip+k", 3, 12, 35, 70), ] @pytest.fixture(scope="module") def seeded_tracks(pg_conn): """Insert three canonical evolution tracks; remove them after the module. Uses ON CONFLICT DO UPDATE so the fixture is safe to run even if rows already exist from a prior test run that did not clean up. Returns the list of row IDs that were upserted. """ cur = pg_conn.cursor() ids = [] for name, card_type, formula, t1, t2, t3, t4 in _SEED_TRACKS: cur.execute( """ INSERT INTO evolution_track (name, card_type, formula, t1_threshold, t2_threshold, t3_threshold, t4_threshold) VALUES (%s, %s, %s, %s, %s, %s, %s) ON CONFLICT (card_type) DO UPDATE SET name = EXCLUDED.name, formula = EXCLUDED.formula, t1_threshold = EXCLUDED.t1_threshold, t2_threshold = EXCLUDED.t2_threshold, t3_threshold = EXCLUDED.t3_threshold, t4_threshold = EXCLUDED.t4_threshold RETURNING id """, (name, card_type, formula, t1, t2, t3, t4), ) ids.append(cur.fetchone()[0]) pg_conn.commit() yield ids cur.execute("DELETE FROM evolution_track WHERE id = ANY(%s)", (ids,)) pg_conn.commit() @pytest.fixture(scope="module") def client(): """FastAPI TestClient backed by the real PostgreSQL database.""" from app.main import app with TestClient(app) as c: yield c @_skip_no_pg def test_list_tracks_returns_count_3(client, seeded_tracks): """GET /tracks returns all three tracks with count=3. After seeding batter/sp/rp, the table should have exactly those three rows (no other tracks are inserted by other test modules). """ resp = client.get("/api/v2/evolution/tracks", headers=AUTH_HEADER) assert resp.status_code == 200 data = resp.json() assert data["count"] == 3 assert len(data["items"]) == 3 @_skip_no_pg def test_filter_by_card_type(client, seeded_tracks): """card_type=sp filter returns exactly 1 track with card_type 'sp'.""" resp = client.get("/api/v2/evolution/tracks?card_type=sp", headers=AUTH_HEADER) assert resp.status_code == 200 data = resp.json() assert data["count"] == 1 assert data["items"][0]["card_type"] == "sp" @_skip_no_pg def test_get_single_track_with_thresholds(client, seeded_tracks): """GET /tracks/{id} returns a track dict with formula and t1-t4 thresholds.""" track_id = seeded_tracks[0] # batter resp = client.get(f"/api/v2/evolution/tracks/{track_id}", headers=AUTH_HEADER) assert resp.status_code == 200 data = resp.json() assert data["card_type"] == "batter" assert data["formula"] == "pa+tb*2" for key in ("t1_threshold", "t2_threshold", "t3_threshold", "t4_threshold"): assert key in data, f"Missing field: {key}" assert data["t1_threshold"] == 37 assert data["t4_threshold"] == 896 @_skip_no_pg def test_404_for_nonexistent_track(client, seeded_tracks): """GET /tracks/999999 returns 404 when the track does not exist.""" resp = client.get("/api/v2/evolution/tracks/999999", headers=AUTH_HEADER) assert resp.status_code == 404 @_skip_no_pg def test_auth_required(client, seeded_tracks): """Requests without a Bearer token return 401 for both endpoints.""" resp_list = client.get("/api/v2/evolution/tracks") assert resp_list.status_code == 401 track_id = seeded_tracks[0] resp_single = client.get(f"/api/v2/evolution/tracks/{track_id}") assert resp_single.status_code == 401