[object Object]

← back to AsSeenInMovies

asseeninmovies: smoke test harness — test/smoke.js (zero deps, pure node) hits every public route and asserts 200/3xx + key DOM markers (canonical, JSON-LD type, OG tags, ItemList, BreadcrumbList). 50 checks across home, movies, people, spotted, queue, detail pages, search, api/health, api/spotted/stats, random redirects, sitemap, robots, favicon, about/privacy/terms, and the 404 paths. `npm test` wired up. 50/50 pass on initial run.

2d5ec296c3a9fc83fd5545936b8bf2eed0ece55a · 2026-05-12 19:46:25 -0700 · SteveStudio2

Files touched

Diff

commit 2d5ec296c3a9fc83fd5545936b8bf2eed0ece55a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 19:46:25 2026 -0700

    asseeninmovies: smoke test harness — test/smoke.js (zero deps, pure node) hits every public route and asserts 200/3xx + key DOM markers (canonical, JSON-LD type, OG tags, ItemList, BreadcrumbList). 50 checks across home, movies, people, spotted, queue, detail pages, search, api/health, api/spotted/stats, random redirects, sitemap, robots, favicon, about/privacy/terms, and the 404 paths. `npm test` wired up. 50/50 pass on initial run.
---
 package.json  |   2 +
 test/smoke.js | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 157 insertions(+)

diff --git a/package.json b/package.json
index 8ac524a..c445c9b 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
   "scripts": {
     "start": "node server.js",
     "dev": "PORT=9742 node server.js",
+    "test": "node test/smoke.js",
+    "smoke": "node test/smoke.js",
     "ingest:tmdb": "node scrapers/tmdb-ingest.js",
     "ingest:wikidata": "node scrapers/wikidata-enrich.js",
     "ingest:thesetdecorator": "node scrapers/import-thesetdecorator-roster.js",
diff --git a/test/smoke.js b/test/smoke.js
new file mode 100644
index 0000000..d80d976
--- /dev/null
+++ b/test/smoke.js
@@ -0,0 +1,155 @@
+#!/usr/bin/env node
+'use strict';
+// Smoke test for asseeninmovies. Hits every public route and verifies:
+//   1. HTTP 200/3xx where expected
+//   2. Key DOM markers (title, canonical, JSON-LD type, OG tag)
+//
+// Usage:
+//   node test/smoke.js               # against http://127.0.0.1:9742
+//   ASIM_URL=https://… node test/smoke.js
+//
+// Exit 0 on all-pass, 1 if any check fails. No framework deps — just node.
+
+const http = require('http');
+const https = require('https');
+const { URL } = require('url');
+
+const BASE = process.env.ASIM_URL || 'http://127.0.0.1:9742';
+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(`asseeninmovies smoke — ${BASE}\n`);
+
+  // 1. home
+  let r = await fetch('/');
+  check('home: 200',           r.status === 200);
+  check('home: title',         /AsSeenInMovies/.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 About',  /\/about/.test(r.body));
+
+  // 2. /movies
+  r = await fetch('/movies');
+  check('movies: 200',         r.status === 200);
+  check('movies: canonical',   /rel="canonical"/.test(r.body));
+  check('movies: sort select', /name="sort"/.test(r.body));
+  check('movies: density',     /type="range"/.test(r.body));
+
+  // 3. /people
+  r = await fetch('/people');
+  check('people: 200',         r.status === 200);
+  check('people: canonical',   /rel="canonical"/.test(r.body));
+
+  // 4. /spotted
+  r = await fetch('/spotted');
+  check('spotted: 200',        r.status === 200);
+  check('spotted: stats',      /sp-stats/.test(r.body));
+  check('spotted: ItemList',   /"@type":"ItemList"/.test(r.body));
+  check('spotted: submit cta', /\/spotted\/submit/.test(r.body));
+
+  // 5. /spotted/submit
+  r = await fetch('/spotted/submit');
+  check('submit: 200',         r.status === 200);
+  check('submit: form',        /<form/.test(r.body));
+
+  // 6. /spotted/queue (admin, no token → 401)
+  r = await fetch('/spotted/queue');
+  check('queue: 401 no token', r.status === 401);
+
+  // 7. /movie/:id (use a known credited movie)
+  r = await fetch('/movie/34029');
+  check('movie/:id: 200',         r.status === 200);
+  check('movie/:id: Movie type',  /"@type":"Movie"/.test(r.body));
+  check('movie/:id: Breadcrumb',  /"@type":"BreadcrumbList"/.test(r.body));
+  check('movie/:id: og:image',    /property="og:image"/.test(r.body));
+
+  // 8. /person/:id
+  r = await fetch('/person/36');
+  check('person/:id: 200',        r.status === 200);
+  check('person/:id: Person type',/"@type":"Person"/.test(r.body));
+  check('person/:id: Breadcrumb', /"@type":"BreadcrumbList"/.test(r.body));
+
+  // 9. /search
+  r = await fetch('/search');
+  check('search: 200',         r.status === 200);
+  r = await fetch('/search?q=hotel');
+  check('search?q: 200',       r.status === 200);
+
+  // 10. /api/health
+  r = await fetch('/api/health');
+  check('api/health: 200',     r.status === 200);
+  check('api/health: ok',      /"ok":true/.test(r.body));
+
+  // 11. /api/spotted/stats
+  r = await fetch('/api/spotted/stats');
+  check('api/spotted/stats: 200', r.status === 200);
+  check('api/spotted/stats: live_total field', /"live_total":/.test(r.body));
+
+  // 12. /random/movie + /random/person
+  r = await fetch('/random/movie');
+  check('random/movie: 302', r.status === 302);
+  check('random/movie: -> /movie/', /^\/movie\/\d+/.test(r.headers.location || ''));
+  r = await fetch('/random/person');
+  check('random/person: 302', r.status === 302);
+
+  // 13. /sitemap.xml + /robots.txt
+  r = await fetch('/sitemap.xml');
+  check('sitemap: 200',        r.status === 200);
+  check('sitemap: urlset',     /<urlset/.test(r.body));
+  check('sitemap: has /about', /<loc>[^<]*\/about<\/loc>/.test(r.body));
+  r = await fetch('/robots.txt');
+  check('robots: 200',         r.status === 200);
+  check('robots: disallow queue', /\/spotted\/queue/.test(r.body));
+
+  // 14. /favicon.ico
+  r = await fetch('/favicon.ico');
+  check('favicon: 200',        r.status === 200);
+  check('favicon: image/svg',  /image\/svg/.test(r.headers['content-type'] || ''));
+
+  // 15. /about + /privacy + /terms
+  r = await fetch('/about');
+  check('about: 200',          r.status === 200);
+  check('about: h1',           /About AsSeenInMovies/.test(r.body));
+  r = await fetch('/privacy');
+  check('privacy: 200',        r.status === 200);
+  r = await fetch('/terms');
+  check('terms: 200',          r.status === 200);
+
+  // 16. 404 — branded HTML
+  r = await fetch('/zzz-not-a-real-page');
+  check('404 path: 404',       r.status === 404);
+  check('404 path: branded',   /class="title"/.test(r.body));
+  r = await fetch('/api/zzz-not-a-real');
+  check('404 api: JSON 404',   r.status === 404 && /"error":"not-found"/.test(r.body));
+
+  // 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);
+});

← 210edd9 asseeninmovies: GET /privacy + /terms — minimal legal pages  ·  back to AsSeenInMovies  ·  asseeninmovies: ItemList JSON-LD on /movies + /people index 4648ead →