import { execSync } from 'node:child_process' import { writeFileSync } from 'node:fs' import { resolve } from 'node:path' const API_BASE = 'http://localhost:8000/api/v1' const COMPOSE_FILE = resolve(__dirname, '../../docker-compose.test.yml') const FIXTURES_PATH = resolve(__dirname, '.fixtures.json') function run(cmd: string): string { console.log(`[setup] ${cmd}`) return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'inherit'] }) } async function api( path: string, options?: RequestInit, ): Promise { const res = await fetch(`${API_BASE}${path}`, { headers: { 'Content-Type': 'application/json' }, ...options, }) if (!res.ok) { const body = await res.text() throw new Error(`API ${options?.method ?? 'GET'} ${path} → ${res.status}: ${body}`) } return res.json() as Promise } export default async function globalSetup() { // 1. Start test DB + API run(`docker compose -f ${COMPOSE_FILE} up -d --wait`) // 2. Run migrations run( `docker compose -f ${COMPOSE_FILE} exec -T test-api alembic -c /app/alembic.ini upgrade head`, ) // 3. Seed reference data (run from /app/src where the app package lives) run( `docker compose -f ${COMPOSE_FILE} exec -T -w /app/src test-api python -m app.seeds`, ) // 4. Create test fixtures via API const games = await api>('/games') const game = games[0] if (!game) throw new Error('No games found after seeding') const routes = await api>( `/games/${game.id}/routes?flat=true`, ) // Pick leaf routes (no children — a route is a leaf if no other route has it as parent) const parentIds = new Set(routes.map((r) => r.parentRouteId).filter(Boolean)) const leafRoutes = routes.filter((r) => !parentIds.has(r.id)) if (leafRoutes.length < 3) throw new Error(`Need ≥3 leaf routes, found ${leafRoutes.length}`) const pokemonRes = await api<{ items: Array<{ id: number; name: string }> }>( '/pokemon?limit=10', ) const pokemon = pokemonRes.items if (pokemon.length < 3) throw new Error(`Need ≥3 pokemon, found ${pokemon.length}`) // Create a test run const testRun = await api<{ id: number }>('/runs', { method: 'POST', body: JSON.stringify({ gameId: game.id, name: 'E2E Test Run', rules: { duplicatesClause: true, shinyClause: true }, }), }) // Create encounters: caught, fainted, missed const statuses = ['caught', 'fainted', 'missed'] as const for (let i = 0; i < 3; i++) { await api(`/runs/${testRun.id}/encounters`, { method: 'POST', body: JSON.stringify({ routeId: leafRoutes[i]!.id, pokemonId: pokemon[i]!.id, nickname: `Test ${statuses[i]}`, status: statuses[i], catchLevel: statuses[i] === 'missed' ? null : 5 + i * 10, }), }) } // Create a genlocke with 2 game legs const secondGame = games[1] ?? game const genlocke = await api<{ id: number }>('/genlockes', { method: 'POST', body: JSON.stringify({ name: 'E2E Test Genlocke', gameIds: [game.id, secondGame.id], genlockeRules: {}, nuzlockeRules: { duplicatesClause: true }, }), }) // 5. Write fixtures file const fixtures = { gameId: game.id, runId: testRun.id, genlockeId: genlocke.id, } writeFileSync(FIXTURES_PATH, JSON.stringify(fixtures, null, 2)) console.log('[setup] Fixtures written:', fixtures) }