Files
dashcaddy/dashcaddy-api/__tests__/shield-dc.routes.test.js
T
Hermes Agent 8744609887 [pending-judge] fix(integration): catalog detail reads app.docker.* (DC-160); Shield committed; composition-root e2e
- DC-160: /catalog/:appId read app.image/ports/env/volumes which live under
  app.docker.* — every detail response was empty. Now reads docker.* and
  surfaces defaultPort/subdomain/healthCheck/mediaMount/claimToken/difficulty/
  setupInstructions for the deploy form. List/search use the templates own
  category field (old hand-rolled CATEGORY_MAP mis-categorized apps).
- Shield (routes/shield.js + 5-line app.js mount) committed to source after
  weeks as prod-only drift; regression test asserts the mount exists.
- New composition-root e2e suite: boots REAL createApp() on an ephemeral
  port, probes the historically-broken routes through the REAL mount table
  (wizard wiring, shield, catalog detail, deploys bridge) — the class of bug
  all 3 P7 dogfood findings belonged to. 5 tests.
- Full suite 3499/3499 (155 suites); table-driven DC-160 test covers all 73
  image templates.
2026-10-07 00:58:03 -07:00

69 lines
2.8 KiB
JavaScript

/**
* DC-Shield route tests: /security/shield endpoint.
*
* The route reads the sec-dashboard snapshot from an absolute path
* (/var/lib/sec-dashboard/snapshot.json) and services.json. Those constants
* live inside the module, so these tests exercise the REAL module against
* the REAL host files — same approach as app-startup-smoke — plus
* behavior-level assertions for the 503 no-snapshot case using a bound
* copy of the handler with a stubbed fs (dependency seam via require cache).
*
* On a dev host without the snapshot the 503 path is the testable truth; on
* DNS2 the snapshot exists, so both shapes are covered.
*/
const path = require('path');
const express = require('express');
const request = require('supertest');
const buildShield = require(path.resolve(__dirname, '..', 'routes', 'shield'));
function buildApp() {
const e = express();
e.use('/api/v1/security', buildShield({ log: { info() {}, error() {}, warn() {} } }));
return e;
}
describe('DC-Shield: GET /api/v1/security/shield', () => {
it('mounts and responds without crashing (200 with snapshot, else 503)', async () => {
const res = await request(buildApp()).get('/api/v1/security/shield');
expect([200, 503]).toContain(res.status);
});
it('response shape matches the frontend contract on 200', async () => {
const res = await request(buildApp()).get('/api/v1/security/shield');
if (res.status === 503) return; // no snapshot on this host — covered below
const b = res.body.data || res.body;
expect(b).toHaveProperty('generated_utc');
expect(b.fleet).toHaveProperty('verdict');
expect(['secure', 'watch', 'incident']).toContain(b.fleet.verdict);
expect(b.services).toBeDefined();
expect(Array.isArray(b.unmapped_hosts)).toBe(true);
});
it('503 + SNAPSHOT_UNAVAILABLE when snapshot missing (fs stubbed)', async () => {
// Stub fs.readFileSync for the snapshot path only, via the module cache.
const fsPath = require.resolve('fs');
const realFs = require('fs');
const orig = realFs.readFileSync;
realFs.readFileSync = function (p, enc) {
if (String(p).includes('sec-dashboard')) {
throw new Error('ENOENT (test stub)');
}
return orig.call(realFs, p, enc);
};
try {
const res = await request(buildApp()).get('/api/v1/security/shield');
expect(res.status).toBe(503);
expect(JSON.stringify(res.body)).toContain('SNAPSHOT_UNAVAILABLE');
} finally {
realFs.readFileSync = orig;
}
});
it('route module loads with the composition-root mount (regression: prod-only drift)', () => {
// src/app.js must mount routes/shield (was prod-only for weeks).
const appjs = require('fs').readFileSync(path.resolve(__dirname, '..', 'src', 'app.js'), 'utf8');
expect(appjs).toMatch(/apiRouter\.use\(\x27\/security\x27, require\(\x27\.\.\/routes\/shield\x27\)/);
});
});