diff --git a/tests/docker-compose.test.yml b/tests/docker-compose.test.yml index abd25221..a155dc9d 100644 --- a/tests/docker-compose.test.yml +++ b/tests/docker-compose.test.yml @@ -1,7 +1,7 @@ services: web: environment: - SESSION_SECRET: "test-session-secret-32chars!xxx" + SESSION_SECRET: "test-session-secret-32chars!xxxY" ADMIN_USERNAME: testadmin ADMIN_PASSWORD: "TestPassword2026!" BASE_URL: http://localhost:3000 diff --git a/tests/global-setup.ts b/tests/global-setup.ts index 782a7bc1..bc160d1b 100644 --- a/tests/global-setup.ts +++ b/tests/global-setup.ts @@ -1,4 +1,4 @@ -import { execFileSync } from 'node:child_process'; +import { execFileSync, execFileSync as runCmd } from 'node:child_process'; import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -10,41 +10,53 @@ const COMPOSE_ARGS = [ const HEALTH_URL = 'http://localhost:3000/api/health'; const AUTH_DIR = resolve(process.cwd(), 'tests/.auth'); const AUTH_FILE = resolve(AUTH_DIR, 'admin.json'); -const MAX_WAIT_MS = 120_000; -const POLL_INTERVAL_MS = 2_000; +const MAX_WAIT_MS = 180_000; +const POLL_INTERVAL_MS = 3_000; async function waitForHealth(): Promise { const start = Date.now(); + let attempt = 0; while (Date.now() - start < MAX_WAIT_MS) { + attempt++; try { const res = await fetch(HEALTH_URL); if (res.status === 200) { - console.log('[global-setup] App is healthy'); + console.log(`[global-setup] App is healthy (attempt ${attempt})`); return; } - } catch { - // not ready yet + console.log(`[global-setup] Health check attempt ${attempt}: HTTP ${res.status}, retrying...`); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + console.log(`[global-setup] Health check attempt ${attempt}: ${msg}, retrying in ${POLL_INTERVAL_MS / 1000}s...`); } await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); } + + // Dump container logs to help diagnose the failure + console.error('[global-setup] Health check timed out. Container logs:'); + try { + execFileSync('docker', [...COMPOSE_ARGS, 'logs', '--tail=50'], { stdio: 'inherit', cwd: process.cwd() }); + } catch { /* ignore */ } + throw new Error(`App did not become healthy within ${MAX_WAIT_MS}ms`); } async function seedAuthState(): Promise { - // Navigate via the web login form to get a real session cookie. - // The app uses credentials-based NextAuth signin. - // We POST to the credentials callback directly. - const callbackUrl = 'http://localhost:3000'; + console.log('[global-setup] Seeding auth state...'); - // First, get CSRF token from NextAuth + // Get CSRF token from NextAuth const csrfRes = await fetch('http://localhost:3000/api/auth/csrf'); + if (!csrfRes.ok) { + throw new Error(`CSRF request failed: ${csrfRes.status}`); + } const csrfData = await csrfRes.json() as { csrfToken: string }; + console.log('[global-setup] Got CSRF token'); const params = new URLSearchParams({ csrfToken: csrfData.csrfToken, username: 'testadmin', - password: 'TestPassword2026!', - callbackUrl, + password: 'TestPassword2026!', // matches ADMIN_PASSWORD in docker-compose.test.yml + callbackUrl: 'http://localhost:3000', json: 'true', }); @@ -58,7 +70,9 @@ async function seedAuthState(): Promise { redirect: 'manual', }); - // Collect all cookies from both responses + console.log(`[global-setup] Sign-in response: HTTP ${signinRes.status}`); + + // Collect cookies from both responses const allCookieHeaders: string[] = []; for (const [k, v] of csrfRes.headers.entries()) { if (k === 'set-cookie') allCookieHeaders.push(v); @@ -94,23 +108,26 @@ async function seedAuthState(): Promise { }).filter(Boolean) ); - const storageState = { - cookies, - origins: [], - }; + console.log(`[global-setup] Collected ${cookies.length} cookies`); + const storageState = { cookies, origins: [] }; mkdirSync(AUTH_DIR, { recursive: true }); writeFileSync(AUTH_FILE, JSON.stringify(storageState, null, 2)); - console.log('[global-setup] Auth state seeded at', AUTH_FILE); + console.log('[global-setup] Auth state saved to', AUTH_FILE); } export default async function globalSetup() { console.log('[global-setup] Starting Docker Compose test stack...'); - execFileSync('docker', [...COMPOSE_ARGS, 'up', '-d', '--build'], { + execFileSync('docker', [ + ...COMPOSE_ARGS, + 'up', '-d', '--build', + '--wait', '--wait-timeout', '120', + ], { stdio: 'inherit', cwd: process.cwd(), }); + console.log('[global-setup] Containers up. Waiting for /api/health...'); await waitForHealth(); await seedAuthState();