[object Object]

← back to build-pages

smoke: 23-assertion harness covering healthz, /api/health, /api/version, /api/projects, butlr page, 404

6e352302a165873b1d303f6e3c95210efd6c690b · 2026-05-13 12:36:28 -0700 · SteveStudio2

Files touched

Diff

commit 6e352302a165873b1d303f6e3c95210efd6c690b
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 12:36:28 2026 -0700

    smoke: 23-assertion harness covering healthz, /api/health, /api/version, /api/projects, butlr page, 404
---
 test/smoke.js | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 98 insertions(+)

diff --git a/test/smoke.js b/test/smoke.js
new file mode 100644
index 0000000..3770c97
--- /dev/null
+++ b/test/smoke.js
@@ -0,0 +1,98 @@
+// build-pages smoke — same shape as asim/sod harnesses. Run via:
+//   node test/smoke.js              (verbose)
+//   node test/smoke.js --quiet      (failures + tally only)
+//   node test/smoke.js --json       (single JSON object)
+const http = require('http');
+const https = require('https');
+const { URL } = require('url');
+
+const BASE = process.env.BP_URL || 'http://127.0.0.1:9700';
+const TIMEOUT_MS = 8_000;
+
+function fetch(path, opts = {}) {
+  const u = new URL(path, BASE);
+  const lib = u.protocol === 'https:' ? https : http;
+  const method = opts.method || 'GET';
+  return new Promise((resolve, reject) => {
+    const req = lib.request(u, { method, timeout: TIMEOUT_MS, headers: opts.headers || {} }, (res) => {
+      let body = '';
+      res.on('data', (c) => (body += c));
+      res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body }));
+    });
+    req.on('error', reject);
+    req.on('timeout', () => { req.destroy(new Error('timeout')); });
+    req.end();
+  });
+}
+
+const checks = [];
+let pass = 0, fail = 0;
+function check(name, cond, hint = '') {
+  if (cond) { pass++; checks.push({ name, ok: true }); }
+  else      { fail++; checks.push({ name, ok: false, hint }); }
+}
+
+(async () => {
+  if (!process.argv.includes('--json')) console.log(`build-pages smoke — ${BASE}\n`);
+
+  // 1. healthz + HEAD
+  let r = await fetch('/healthz');
+  check('healthz: 200',            r.status === 200);
+  check('healthz: ok JSON',        /"ok":true/.test(r.body));
+  check('healthz: no-store',       (r.headers['cache-control'] || '').includes('no-store'));
+  r = await fetch('/healthz', { method: 'HEAD' });
+  check('healthz: HEAD 200',       r.status === 200);
+
+  // 2. /api/health
+  r = await fetch('/api/health');
+  check('api/health: 200',         r.status === 200);
+  check('api/health: ok',          /"ok":true/.test(r.body));
+  check('api/health: uptime_s',    /"uptime_s":\d+/.test(r.body));
+  check('api/health: projects',    /"projects":\d+/.test(r.body));
+
+  // 3. /api/version
+  r = await fetch('/api/version');
+  check('api/version: 200',        r.status === 200);
+  check('api/version: name',       /"name":"build-pages"/.test(r.body));
+  check('api/version: git sha',    /"git":"[a-f0-9]{4,}/.test(r.body));
+  check('api/version: git resolved', !/"git":"unknown"/.test(r.body));
+  check('api/version: no-store',   (r.headers['cache-control'] || '').includes('no-store'));
+
+  // 4. /api/projects discovery
+  r = await fetch('/api/projects');
+  check('api/projects: 200',       r.status === 200);
+  check('api/projects: butlr in list', /"slug":"butlr"/.test(r.body));
+
+  // 5. index page
+  r = await fetch('/');
+  check('home: 200',               r.status === 200);
+  check('home: title',             /build-pages/.test(r.body));
+  check('home: X-Content-Type-Options', (r.headers['x-content-type-options'] || '') === 'nosniff');
+
+  // 6. butlr project page renders + has commit count
+  r = await fetch('/p/butlr');
+  check('butlr: 200',              r.status === 200);
+  check('butlr: title',            /Butlr/.test(r.body));
+  check('butlr: commit list',      /commits indexed/.test(r.body));
+  check('butlr: search input',     /id="q"/.test(r.body));
+
+  // 7. unknown project → 404
+  r = await fetch('/p/nope-not-a-project');
+  check('unknown project: 404',    r.status === 404);
+
+  // Report
+  if (process.argv.includes('--json')) {
+    console.log(JSON.stringify({ base: BASE, pass, fail, total: pass + fail, checks, as_of: new Date().toISOString() }));
+    process.exit(fail === 0 ? 0 : 1);
+  }
+  const quiet = process.argv.includes('--quiet');
+  const lines = quiet
+    ? checks.filter(c => !c.ok).map(c => '✗ ' + c.name + (c.hint ? '  (' + c.hint + ')' : ''))
+    : checks.map(c => (c.ok ? '✓' : '✗') + ' ' + c.name + (c.hint ? '  (' + c.hint + ')' : ''));
+  if (lines.length) console.log(lines.join('\n'));
+  console.log(`${quiet && fail === 0 ? '' : '\n'}${pass} passed, ${fail} failed`);
+  process.exit(fail === 0 ? 0 : 1);
+})().catch((e) => {
+  console.error('smoke harness error:', e.message);
+  process.exit(2);
+});

← 27818e5 api: add /api/health + /api/version (joins fleet-status dige  ·  back to build-pages  ·  Makefile + .deploy.conf + /robots.txt + 2 smoke (parity with 7ebd20c →