import test from "node:test"; import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { mkdtemp, mkdir, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; const execFileAsync = promisify(execFile); const root = path.resolve(import.meta.dirname, ".."); async function dockerAvailable() { try { await execFileAsync("docker", ["info"], { timeout: 15_000 }); return true; } catch { return false; } } test("Docker image starts against PostgreSQL and applies migrations", async (t) => { if (!(await dockerAvailable())) { t.skip("Docker daemon is unavailable"); return; } const temp = await mkdtemp( path.join(os.tmpdir(), "roast-planner-container-"), ); const agentDir = path.join(temp, "pi-agent"); await mkdir(agentDir); const envFile = path.join(temp, "compose.env"); await writeFile( envFile, `POSTGRES_PASSWORD=container-test-password\nBOOTSTRAP_SETUP_TOKEN=\nPI_AGENT_CONFIG_DIR=${agentDir}\nAPP_PORT=18090\n`, ); const project = `roastplanner${Date.now()}`; const compose = (args, options = {}) => execFileAsync( "docker", ["compose", "--project-name", project, "--env-file", envFile, ...args], { cwd: root, timeout: 120_000, ...options }, ); t.after(async () => { try { await compose(["down", "--volumes", "--remove-orphans"]); } catch { // Preserve the startup failure rather than masking it with cleanup. } }); await compose(["up", "--build", "--detach"]); let lastError; for (let attempt = 0; attempt < 30; attempt++) { try { await compose([ "exec", "-T", "app", "node", "--input-type=module", "-e", "const response = await fetch('http://127.0.0.1:8090/'); process.exit(response.ok ? 0 : 1)", ]); lastError = null; break; } catch (error) { lastError = error; await new Promise((resolve) => setTimeout(resolve, 1_000)); } } assert.equal( lastError, null, "application never became ready in its container", ); const migration = await compose([ "exec", "-T", "db", "psql", "-U", "roast", "-d", "roast", "-tAc", "SELECT count(*) FROM schema_migrations WHERE filename = '001_auth.sql'", ]); assert.equal(migration.stdout.trim(), "1"); });