From 8bbe60c2f57c1532a6826a47f04a6e96ef007acc Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sat, 9 May 2026 09:52:23 +0200 Subject: [PATCH] =?UTF-8?q?test(voyage):=20add=20tests/integration/observa?= =?UTF-8?q?bility-compose.test.mjs=20=E2=80=94=20SC=20#16=20skip-if-no-doc?= =?UTF-8?q?ker=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 16 of v4.1 — first test in tests/integration/, establishes the skip-on-missing-tool pattern voyage will reuse for environment-dependent integration tests. Two tests: 1. compose config parses and contains expected services 2. compose config pins required image versions Both skip cleanly when 'docker info' fails (no Docker installed). On a machine with Docker, both tests run docker compose config and assert the 4 services + 3 version pins are present. Tests: 468 pass + 2 skipped (Docker not installed in dev env). --- .../observability-compose.test.mjs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 plugins/voyage/tests/integration/observability-compose.test.mjs diff --git a/plugins/voyage/tests/integration/observability-compose.test.mjs b/plugins/voyage/tests/integration/observability-compose.test.mjs new file mode 100644 index 0000000..b698fb1 --- /dev/null +++ b/plugins/voyage/tests/integration/observability-compose.test.mjs @@ -0,0 +1,59 @@ +// 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', + ); + }, +);