[object Object]

← back to Stars of Design

starsofdesign: smoke test harness — test/smoke.js (zero deps, pure node) hits every public route and asserts 200/3xx + key DOM markers (canonical, WebSite/Person/Organization/BreadcrumbList JSON-LD, footer privacy/terms links). 40 checks. npm test wired. 40/40 pass on initial run. Mirrors asim smoke harness.

9d16cb331467f398573121f8e51d7dc3990655cb · 2026-05-12 19:50:16 -0700 · Steve Abrams

Files touched

Diff

commit 9d16cb331467f398573121f8e51d7dc3990655cb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 12 19:50:16 2026 -0700

    starsofdesign: smoke test harness — test/smoke.js (zero deps, pure node) hits every public route and asserts 200/3xx + key DOM markers (canonical, WebSite/Person/Organization/BreadcrumbList JSON-LD, footer privacy/terms links). 40 checks. npm test wired. 40/40 pass on initial run. Mirrors asim smoke harness.
---
 package.json  |   4 +-
 test/smoke.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 132 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index afb1fff..5894655 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,9 @@
   "main": "server.js",
   "scripts": {
     "start": "node server.js",
-    "dev": "node server.js"
+    "dev": "node server.js",
+    "test": "node test/smoke.js",
+    "smoke": "node test/smoke.js"
   },
   "dependencies": {
     "cookie-parser": "^1.4.7",
diff --git a/test/smoke.js b/test/smoke.js
new file mode 100644
index 0000000..21fba75
--- /dev/null
+++ b/test/smoke.js
@@ -0,0 +1,129 @@
+#!/usr/bin/env node
+'use strict';
+// Smoke test for starsofdesign. Hits every public route + verifies key DOM
+// markers (canonical, JSON-LD type, BreadcrumbList, footer links). Zero deps.
+//
+// Usage:
+//   node test/smoke.js
+//   SOD_URL=https://… node test/smoke.js
+
+const http = require('http');
+const https = require('https');
+const { URL } = require('url');
+
+const BASE = process.env.SOD_URL || 'http://127.0.0.1:9928';
+const TIMEOUT_MS = 8_000;
+
+function fetch(path) {
+  const u = new URL(path, BASE);
+  const lib = u.protocol === 'https:' ? https : http;
+  return new Promise((resolve, reject) => {
+    const req = lib.get(u, { timeout: TIMEOUT_MS }, (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')); });
+  });
+}
+
+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 () => {
+  console.log(`starsofdesign smoke — ${BASE}\n`);
+
+  // 1. home
+  let r = await fetch('/');
+  check('home: 200',           r.status === 200);
+  check('home: title',         /Stars of Design/.test(r.body));
+  check('home: canonical',     /rel="canonical"/.test(r.body));
+  check('home: WebSite schema',/"@type":"WebSite"/.test(r.body));
+  check('home: SearchAction',  /SearchAction/.test(r.body));
+  check('home: favicon',       /rel="icon"/.test(r.body));
+  check('home: footer Privacy',/\/privacy/.test(r.body));
+  check('home: footer Terms',  /\/terms/.test(r.body));
+
+  // 2. /designers
+  r = await fetch('/designers');
+  check('designers: 200',      r.status === 200);
+  check('designers: canonical',/rel="canonical"/.test(r.body));
+
+  // 3. /firms
+  r = await fetch('/firms');
+  check('firms: 200',          r.status === 200);
+  check('firms: canonical',    /rel="canonical"/.test(r.body));
+
+  // 4. /clients
+  r = await fetch('/clients');
+  check('clients: 200',        r.status === 200);
+
+  // 5. /feed
+  r = await fetch('/feed');
+  check('feed: 200',           r.status === 200);
+
+  // 6. /videos
+  r = await fetch('/videos');
+  check('videos: 200',         r.status === 200);
+
+  // 7. detail pages
+  r = await fetch('/designers/dorothy-draper');
+  check('designer/:slug: 200',         r.status === 200);
+  check('designer/:slug: Person',      /"@type":"Person"/.test(r.body));
+  check('designer/:slug: Breadcrumb',  /"@type":"BreadcrumbList"/.test(r.body));
+
+  r = await fetch('/firms/nick-olsen-style');
+  check('firm/:slug: 200',             r.status === 200);
+  check('firm/:slug: Organization',    /"@type":"Organization"/.test(r.body));
+  check('firm/:slug: Breadcrumb',      /"@type":"BreadcrumbList"/.test(r.body));
+
+  r = await fetch('/clients/nick-olsen-style-firm');
+  check('client/:slug: 200',           r.status === 200);
+  check('client/:slug: Person',        /"@type":"Person"/.test(r.body));
+  check('client/:slug: Breadcrumb',    /"@type":"BreadcrumbList"/.test(r.body));
+
+  // 8. /api/stats
+  r = await fetch('/api/stats');
+  check('api/stats: 200',              r.status === 200);
+  check('api/stats: designers field',  /"designers":/.test(r.body));
+
+  // 9. /sitemap.xml + /robots.txt + /favicon.ico
+  r = await fetch('/sitemap.xml');
+  check('sitemap: 200',                r.status === 200);
+  check('sitemap: urlset',             /<urlset/.test(r.body));
+  check('sitemap: has /privacy',       /<loc>[^<]*\/privacy<\/loc>/.test(r.body));
+  check('sitemap: has /terms',         /<loc>[^<]*\/terms<\/loc>/.test(r.body));
+  r = await fetch('/robots.txt');
+  check('robots: 200',                 r.status === 200);
+  r = await fetch('/favicon.ico');
+  check('favicon: 200',                r.status === 200);
+  check('favicon: image/svg',          /image\/svg/.test(r.headers['content-type'] || ''));
+
+  // 10. legal pages
+  r = await fetch('/about');
+  check('about: 200',                  r.status === 200);
+  check('about: Stars of Design h1',   /About Stars of Design/.test(r.body));
+  r = await fetch('/privacy');
+  check('privacy: 200',                r.status === 200);
+  check('privacy: h1',                 /<h1[^>]*>Privacy<\/h1>/.test(r.body));
+  r = await fetch('/terms');
+  check('terms: 200',                  r.status === 200);
+  check('terms: h1',                   /<h1[^>]*>Terms of use<\/h1>/.test(r.body));
+
+  // 11. /submit
+  r = await fetch('/submit');
+  check('submit: 200',                 r.status === 200);
+
+  // Report
+  console.log(checks.map(c => (c.ok ? '✓' : '✗') + ' ' + c.name + (c.hint ? '  (' + c.hint + ')' : '')).join('\n'));
+  console.log(`\n${pass} passed, ${fail} failed`);
+  process.exit(fail === 0 ? 0 : 1);
+})().catch((e) => {
+  console.error('smoke harness error:', e.message);
+  process.exit(2);
+});

← 00a2a60 starsofdesign: GET /privacy + /terms — minimal legal pages (  ·  back to Stars of Design  ·  starsofdesign: GET /api — JSON discovery doc for public API 6e94cda →