← 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
M package.jsonA test/smoke.js
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 →