// SC #16 — skip-if-no-docker compose-config validation. // First test in tests/integration/ — establishes the skip-on-missing-tool // pattern voyage uses for environment-dependent integration tests. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { execFileSync, spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = resolve(__dirname, '../..'); const COMPOSE_FILE = resolve(REPO_ROOT, 'examples/observability/docker-compose.yml'); const dockerAvailable = (() => { try { execFileSync('docker', ['info'], { stdio: 'ignore' }); return true; } catch { return false; } })(); test( 'compose config parses and contains expected services', { skip: !dockerAvailable && 'Docker not installed' }, () => { const r = spawnSync( 'docker', ['compose', '-f', COMPOSE_FILE, 'config'], { encoding: 'utf8' }, ); assert.equal(r.status, 0, `docker compose config exited ${r.status}: ${r.stderr}`); assert.match(r.stdout, /otel-collector/, 'otel-collector service missing'); assert.match(r.stdout, /prometheus/, 'prometheus service missing'); assert.match(r.stdout, /grafana/, 'grafana service missing'); assert.match(r.stdout, /node-exporter/, 'node-exporter service missing'); }, ); test( 'compose config pins required image versions', { skip: !dockerAvailable && 'Docker not installed' }, () => { const r = spawnSync( 'docker', ['compose', '-f', COMPOSE_FILE, 'config'], { encoding: 'utf8' }, ); assert.equal(r.status, 0); assert.match(r.stdout, /prom\/prometheus:v3\.0\.1/, 'prometheus pin missing'); assert.match(r.stdout, /grafana\/grafana:11\.4\.0/, 'grafana pin missing'); assert.match( r.stdout, /otel\/opentelemetry-collector-contrib:0\.115\.0/, 'otel-collector pin missing', ); }, );