[object Object]

← back to New Items Dashboard

test: auth gate smoke test (boots server, asserts 401/200 matrix)

ed3ed848da9a5c3c69b12d747cd4062e307e5a62 · 2026-05-19 22:13:46 -0700 · Steve Abrams

7 assertions covering unauth-401 on /, /api/new-wines, /api/new-handbags,
/api/stats, WWW-Authenticate present, valid-creds non-401, wrong-creds 401.
Standalone (no framework dep); wired to npm test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit ed3ed848da9a5c3c69b12d747cd4062e307e5a62
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 19 22:13:46 2026 -0700

    test: auth gate smoke test (boots server, asserts 401/200 matrix)
    
    7 assertions covering unauth-401 on /, /api/new-wines, /api/new-handbags,
    /api/stats, WWW-Authenticate present, valid-creds non-401, wrong-creds 401.
    Standalone (no framework dep); wired to npm test.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 package.json      |   3 +-
 test/auth.test.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index fc6d43f..d6a1e12 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
   "description": "Dashboard for NEW wines and handbags",
   "main": "server.js",
   "scripts": {
-    "start": "node server.js"
+    "start": "node server.js",
+    "test": "node test/auth.test.js"
   },
   "dependencies": {
     "express": "^4.18.2",
diff --git a/test/auth.test.js b/test/auth.test.js
new file mode 100644
index 0000000..9556634
--- /dev/null
+++ b/test/auth.test.js
@@ -0,0 +1,129 @@
+// Smoke test: basic-auth gate on new-items-dashboard.
+//
+// Boots server.js in a child process with a deterministic BASIC_AUTH +
+// NODE_ENV=production, hits a few routes via plain http, asserts:
+//   (a) GET /                  unauth -> 401
+//   (b) GET /api/new-wines     unauth -> 401
+//   (c) GET / with valid creds -> 200 (or any non-401; the gate is what matters)
+//
+// Runs standalone — node test/auth.test.js. No test framework dep.
+
+const { spawn } = require('child_process');
+const http = require('http');
+const path = require('path');
+
+const PORT = process.env.TEST_PORT || 7299;
+const CREDS = 'admin:DW2025Secure!';
+const SERVER = path.join(__dirname, '..', 'server.js');
+
+function get(opts) {
+  return new Promise((resolve, reject) => {
+    const req = http.request({
+      hostname: '127.0.0.1',
+      port: PORT,
+      method: 'GET',
+      ...opts,
+    }, (res) => {
+      let body = '';
+      res.on('data', (c) => { body += c; });
+      res.on('end', () => resolve({ status: res.statusCode, body, headers: res.headers }));
+    });
+    req.on('error', reject);
+    req.end();
+  });
+}
+
+function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
+
+async function waitForServer(timeoutMs = 5000) {
+  const deadline = Date.now() + timeoutMs;
+  while (Date.now() < deadline) {
+    try {
+      await get({ path: '/' });
+      return true;
+    } catch (e) {
+      await sleep(100);
+    }
+  }
+  return false;
+}
+
+(async () => {
+  const child = spawn('node', [SERVER], {
+    env: { ...process.env, PORT: String(PORT), NODE_ENV: 'production', BASIC_AUTH: CREDS },
+    stdio: ['ignore', 'pipe', 'pipe'],
+  });
+
+  let serverErr = '';
+  child.stderr.on('data', (c) => { serverErr += c; });
+
+  const failures = [];
+  const ok = (msg) => console.log('  ok  -', msg);
+  const fail = (msg) => { failures.push(msg); console.log('  FAIL -', msg); };
+
+  try {
+    const up = await waitForServer();
+    if (!up) {
+      console.error('server failed to come up. stderr:\n', serverErr);
+      process.exit(1);
+    }
+
+    // (a) unauth on /
+    {
+      const r = await get({ path: '/' });
+      if (r.status === 401) ok('GET / unauth -> 401');
+      else fail(`GET / unauth -> ${r.status} (expected 401)`);
+      if (/^Basic/i.test(r.headers['www-authenticate'] || '')) ok('WWW-Authenticate present');
+      else fail('WWW-Authenticate header missing on /');
+    }
+
+    // (b) unauth on /api/new-wines
+    {
+      const r = await get({ path: '/api/new-wines' });
+      if (r.status === 401) ok('GET /api/new-wines unauth -> 401');
+      else fail(`GET /api/new-wines unauth -> ${r.status} (expected 401)`);
+    }
+
+    // (b2) unauth on /api/new-handbags
+    {
+      const r = await get({ path: '/api/new-handbags' });
+      if (r.status === 401) ok('GET /api/new-handbags unauth -> 401');
+      else fail(`GET /api/new-handbags unauth -> ${r.status} (expected 401)`);
+    }
+
+    // (b3) unauth on /api/stats
+    {
+      const r = await get({ path: '/api/stats' });
+      if (r.status === 401) ok('GET /api/stats unauth -> 401');
+      else fail(`GET /api/stats unauth -> ${r.status} (expected 401)`);
+    }
+
+    // (c) authed on /
+    {
+      const auth = 'Basic ' + Buffer.from(CREDS).toString('base64');
+      const r = await get({ path: '/', headers: { Authorization: auth } });
+      // 200 if index.html exists; or 404 if not present — but NEVER 401
+      if (r.status !== 401) ok(`GET / with creds -> ${r.status} (not 401)`);
+      else fail(`GET / with valid creds -> 401 (gate rejecting good creds)`);
+    }
+
+    // (d) bad creds -> 401
+    {
+      const auth = 'Basic ' + Buffer.from('admin:wrong').toString('base64');
+      const r = await get({ path: '/', headers: { Authorization: auth } });
+      if (r.status === 401) ok('GET / wrong creds -> 401');
+      else fail(`GET / wrong creds -> ${r.status} (expected 401)`);
+    }
+
+  } finally {
+    child.kill('SIGTERM');
+  }
+
+  if (failures.length) {
+    console.error(`\n${failures.length} failure(s):`);
+    failures.forEach((f) => console.error('  - ' + f));
+    process.exit(1);
+  }
+  console.log('\nall auth smoke tests passed.');
+  process.exit(0);
+})();

← b447df1 secure: basic-auth gate on all routes (server was public on  ·  back to New Items Dashboard  ·  add: pre-stage Meta Pixel snippet (placeholder; flip via _dw 03f8a8c →