- 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.
69 lines
2.8 KiB
JavaScript
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\)/);
|
|
});
|
|
});
|