← 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 →