/** * Two-container boot test (spec AC4/AC23): build images if needed, run the * pod with fake providers, and assert both containers become ready within * the 60-second startup deadline. Requires podman; exits 77 when unavailable * so CI can distinguish "skipped" from "failed". */ import { execFile } from 'node:child_process' import { promisify } from 'node:util' const exec = promisify(execFile) const podman = async (...args: string[]) => exec('podman', args, { timeout: 300_000 }) async function main() { try { await podman('--version') } catch { console.error('boot-test: podman not available, skipping (77)') process.exit(77) } const fakeEnv = [ '-e', 'WILL_SESSION_MODE=fake', '-e', 'WILL_TELEGRAM_TOKEN=boot-test', '-e', 'WILL_TELEGRAM_CHAT_ID=-100', '-e', 'WILL_TELEGRAM_OLIVER_ID=1', ] console.log('boot-test: creating pod') await podman('pod', 'rm', '-f', 'will-boot-test').catch(() => undefined) await podman('pod', 'create', '--name', 'will-boot-test', '-p', '33100:3000') console.log('boot-test: starting service container') await podman( 'run', '-d', '--name', 'will-boot-test-service', '--pod', 'will-boot-test', '-e', 'WILL_SERVICE_DATA_DIR=/srv/will-data', '-v', 'will-boot-test-data:/srv/will-data', 'ghcr.io/bugabinga/will-service:dev', ).catch((err) => { console.error(`boot-test: service image missing, build first (mise run images): ${err.message}`) process.exit(1) }) console.log('boot-test: starting agent container') await podman( 'run', '-d', '--name', 'will-boot-test-agent', '--pod', 'will-boot-test', ...fakeEnv, '-v', 'will-boot-test-agent:/srv/will-agent', 'ghcr.io/bugabinga/will-agent:dev', ) const deadline = Date.now() + 60_000 let serviceOk = false let agentOk = false while (Date.now() < deadline && !(serviceOk && agentOk)) { if (!serviceOk) { serviceOk = await fetch('http://127.0.0.1:33100/healthz') .then((r) => r.ok) .catch(() => false) } if (!agentOk) { agentOk = await exec('podman', [ 'exec', 'will-boot-test-agent', 'node', '-e', "fetch('http://127.0.0.1:8081/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", ]) .then(() => true) .catch(() => false) } if (!(serviceOk && agentOk)) await new Promise((r) => setTimeout(r, 2000)) } const teardown = async () => { await podman('rm', '-f', 'will-boot-test-service', 'will-boot-test-agent').catch( () => undefined, ) await podman('pod', 'rm', '-f', 'will-boot-test').catch(() => undefined) } if (serviceOk && agentOk) { console.log('boot-test: both containers ready within deadline') await teardown() process.exit(0) } console.error(`boot-test: FAILED (service=${serviceOk}, agent=${agentOk})`) await podman('logs', 'will-boot-test-service') .then((r) => console.error(r.stdout)) .catch(() => undefined) await podman('logs', 'will-boot-test-agent') .then((r) => console.error(r.stdout)) .catch(() => undefined) await teardown() process.exit(1) } void main()