← 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
M package.jsonA tests/api.test.mjs
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 →