Luigit
repositories / will

will

owned by admin

scripts/boot-test.ts

Raw
/**
 * 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()