[object Object]

← back to Robet Site

overnight loop item (i): zero-dep test suite (node --test) — 7 tests covering brand/config/headers/SEO/404/contact-hardening/admin-auth, all green

ba29b4704770ee4d8ba2cea559a516d6878052c3 · 2026-06-29 20:37:40 -0700 · Steve

Files touched

Diff

commit ba29b4704770ee4d8ba2cea559a516d6878052c3
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jun 29 20:37:40 2026 -0700

    overnight loop item (i): zero-dep test suite (node --test) — 7 tests covering brand/config/headers/SEO/404/contact-hardening/admin-auth, all green
---
 package.json       |   3 +-
 tests/api.test.mjs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 542ace3..c670877 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
   "description": "Brutalist marketing site with Smart Scheduling booking + API-key admin",
   "main": "server.js",
   "scripts": {
-    "start": "node server.js"
+    "start": "node server.js",
+    "test": "node --test"
   },
   "dependencies": {
     "express": "^4.19.2"
diff --git a/tests/api.test.mjs b/tests/api.test.mjs
new file mode 100644
index 0000000..5b07318
--- /dev/null
+++ b/tests/api.test.mjs
@@ -0,0 +1,102 @@
+// Zero-dependency API contract tests (node --test). Boots the server on a test
+// port with a known admin password, asserts the contract, cleans up, tears down.
+import { test, before, after } from 'node:test';
+import assert from 'node:assert/strict';
+import { spawn } from 'node:child_process';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const PORT = 9799;
+const BASE = `http://127.0.0.1:${PORT}`;
+const ADMIN = 'Basic ' + Buffer.from('admin:testpass').toString('base64');
+let srv;
+
+before(async () => {
+  srv = spawn('node', ['server.js'], {
+    cwd: ROOT, stdio: 'ignore',
+    env: { ...process.env, PORT: String(PORT), ADMIN_USER: 'admin', ADMIN_PASS: 'testpass' },
+  });
+  for (let i = 0; i < 60; i++) {
+    try { const r = await fetch(`${BASE}/api/config`); if (r.ok) return; } catch {}
+    await new Promise((r) => setTimeout(r, 100));
+  }
+  throw new Error('server did not start');
+});
+
+after(() => {
+  srv?.kill();
+  try { fs.rmSync(path.join(ROOT, 'data', 'contacts.jsonl')); } catch {}
+});
+
+test('home page renders brand + OG meta', async () => {
+  const r = await fetch(`${BASE}/`);
+  assert.equal(r.status, 200);
+  const html = await r.text();
+  assert.match(html, /ROBERT/);
+  assert.match(html, /og:image/);
+});
+
+test('/api/config exposes brand + scheduling, no secrets', async () => {
+  const j = await (await fetch(`${BASE}/api/config`)).json();
+  assert.equal(j.brand, 'ROBERT');
+  assert.ok(j.scheduling?.owner, 'scheduling owner present');
+  assert.equal(JSON.stringify(j).includes('ADMIN_PASS'), false);
+});
+
+test('security headers present', async () => {
+  const r = await fetch(`${BASE}/`);
+  assert.ok(r.headers.get('content-security-policy'), 'CSP set');
+  assert.equal(r.headers.get('x-content-type-options'), 'nosniff');
+  assert.match(r.headers.get('content-security-policy'), /venturacorridor\.com/);
+});
+
+test('static SEO assets serve', async () => {
+  for (const f of ['favicon.svg', 'og.png', 'robots.txt', 'sitemap.xml']) {
+    assert.equal((await fetch(`${BASE}/${f}`)).status, 200, `${f} should be 200`);
+  }
+});
+
+test('unknown route → custom 404', async () => {
+  const r = await fetch(`${BASE}/no-such-page-xyz`);
+  assert.equal(r.status, 404);
+  assert.match(await r.text(), /404/);
+});
+
+test('contact: honeypot + validation + rate limit', async () => {
+  const post = (body) => fetch(`${BASE}/api/contact`, {
+    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
+  });
+  // honeypot → ok but NOT stored (and does not consume a rate hit)
+  const hp = await post({ email: 'bot@x.com', message: 'spam', website: 'http://x' });
+  assert.equal((await hp.json()).ok, true);
+  // invalid email → 400
+  assert.equal((await post({ email: 'bad', message: 'x' })).status, 400);
+  // valid → ok
+  const vr = await post({ email: 'real@example.com', message: 'hi' });
+  assert.equal((await vr.json()).ok, true);
+  // burst → 429 appears
+  let saw429 = false;
+  for (let i = 0; i < 8; i++) {
+    const r = await post({ email: `b${i}@x.com`, message: 'm' });
+    if (r.status === 429) saw429 = true;
+  }
+  assert.ok(saw429, 'rate limit should trigger 429 on burst');
+});
+
+test('admin auth gates + key masking', async () => {
+  assert.equal((await fetch(`${BASE}/admin`)).status, 401);                       // no creds
+  assert.equal((await fetch(`${BASE}/admin`, { headers: { Authorization: ADMIN } })).status, 200);
+  // store a key, confirm only masked value comes back
+  await fetch(`${BASE}/api/keys`, {
+    method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: ADMIN },
+    body: JSON.stringify({ name: 'TEST_KEY', value: 'sk-secret-abcd1234' }),
+  });
+  const list = await (await fetch(`${BASE}/api/keys`, { headers: { Authorization: ADMIN } })).json();
+  const row = list.find((k) => k.name === 'TEST_KEY');
+  assert.ok(row, 'key stored');
+  assert.equal(JSON.stringify(list).includes('sk-secret-abcd1234'), false, 'raw value never returned');
+  assert.match(row.masked, /1234$/);
+  await fetch(`${BASE}/api/keys/TEST_KEY`, { method: 'DELETE', headers: { Authorization: ADMIN } }); // cleanup
+});

← cf3d24b loop ledger: item (g) done + deployed  ·  back to Robet Site  ·  loop ledger: item (i) done + deployed 694340e →