import { spawnSync } from 'child_process'; import * as fs from 'fs'; import * as http from 'http'; import * as path from 'path'; const ROOT_DIR = path.resolve(__dirname, '../../../'); const MAX_STARTUP_TIME = 180000; // 3 minutes async function waitForUrl(url: string, timeout: number = MAX_STARTUP_TIME): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { const httpUrl = new URL(url); const response = await new Promise((resolve, reject) => { const req = http.get(httpUrl, (res) => { resolve(res); }); req.on('error', reject); req.setTimeout(5000); }); if (response.statusCode && response.statusCode < 500) { console.log(`✓ Service ready at ${url}`); return; } } catch (e) { // Service not ready, continue waiting } await new Promise((resolve) => setTimeout(resolve, 2000)); } throw new Error(`Timeout waiting for service at ${url}`); } function runComposeCommand(args: string[]): void { try { // Use docker compose (modern Docker CLI syntax) const result = spawnSync('docker', ['compose', ...args], { cwd: ROOT_DIR, stdio: 'inherit', shell: false }); if (result.error) { throw result.error; } } catch (e) { throw new Error(`Failed to run docker compose: ${(e as Error).message}`); } } export default async function globalSetup(): Promise { console.log('🚀 Starting global test setup...'); // Load environment const envFile = process.env.ENV_FILE || '.env'; const envPath = path.join(ROOT_DIR, envFile); if (fs.existsSync(envPath)) { require('dotenv').config({ path: envPath }); } else { console.warn(`⚠ ENV file not found at ${envPath}, using defaults`); } // Check if we should use real stack or mocks const useMocks = process.env.FELLOWSHIP_USE_MOCKS?.toLowerCase() === 'true'; if (useMocks) { console.log('✓ Using mocks for tests (FELLOWSHIP_USE_MOCKS=true)'); return; } console.log('🐳 Starting Fellowship SUT stack via docker compose...'); // Start (or restart) containers without recreating — images are rebuilt only when // Dockerfiles/sources change. Per-test DB state is cleaned up via the // /api/shop/test-reset endpoint called in each test's beforeEach hook. runComposeCommand(['up', '-d', '--build']); console.log('📍 Waiting for services to be ready...'); await waitForUrl('http://localhost/api/health', MAX_STARTUP_TIME); await waitForUrl('http://localhost/login', MAX_STARTUP_TIME); await waitForUrl('http://localhost:3000', MAX_STARTUP_TIME); console.log('✓ All services are ready!'); }