83 lines
3.0 KiB
JavaScript
83 lines
3.0 KiB
JavaScript
/**
|
|
* server config — production safety tests (P2-7)
|
|
*
|
|
* P2-7 from the SRS audit: the JWT secret fallback must not leak into
|
|
* production. The contract is:
|
|
* 1. NODE_ENV=production + JWT_SECRET unset → process exits non-zero
|
|
* (so a missing deploy config refuses to start, instead of running
|
|
* with a hardcoded dev secret).
|
|
* 2. NODE_ENV=production + JWT_SECRET set → boots, exports the secret.
|
|
* 3. NODE_ENV=development (or unset) + JWT_SECRET unset → boots with
|
|
* a clearly-marked dev fallback and a loud warning.
|
|
*
|
|
* We spawn child processes to test (1) and (2) because the prod path
|
|
* calls process.exit(1), which would kill the test runner. For (3) we
|
|
* import the module directly in a child NODE_ENV=development so the
|
|
* dev fallback is exercised.
|
|
*/
|
|
|
|
const { spawnSync } = require('child_process');
|
|
const path = require('path');
|
|
|
|
const CONFIG_JS = path.join(__dirname, '..', 'src', 'config', 'index.js');
|
|
|
|
function spawnConfig(env) {
|
|
return spawnSync(process.execPath, ['-e', `require(${JSON.stringify(CONFIG_JS)})`], {
|
|
env: { ...process.env, ...env },
|
|
encoding: 'utf8',
|
|
timeout: 10000,
|
|
});
|
|
}
|
|
|
|
describe('server config — production safety (P2-7)', () => {
|
|
it('crashes on startup when NODE_ENV=production and JWT_SECRET is missing', () => {
|
|
const result = spawnConfig({
|
|
NODE_ENV: 'production',
|
|
ALLOWED_ORIGINS: 'https://example.com',
|
|
// explicitly clear JWT_SECRET
|
|
JWT_SECRET: '',
|
|
});
|
|
expect(result.status).not.toBe(0);
|
|
const combined = (result.stdout || '') + (result.stderr || '');
|
|
expect(combined).toMatch(/FATAL/i);
|
|
expect(combined).toMatch(/JWT_SECRET/);
|
|
});
|
|
|
|
it('crashes on startup when ALLOWED_ORIGINS is missing in production', () => {
|
|
const result = spawnConfig({
|
|
NODE_ENV: 'production',
|
|
JWT_SECRET: 'a-real-secret-with-enough-entropy-for-tests',
|
|
ALLOWED_ORIGINS: '',
|
|
});
|
|
expect(result.status).not.toBe(0);
|
|
const combined = (result.stdout || '') + (result.stderr || '');
|
|
expect(combined).toMatch(/FATAL/i);
|
|
expect(combined).toMatch(/ALLOWED_ORIGINS/);
|
|
});
|
|
|
|
it('boots in production when JWT_SECRET + ALLOWED_ORIGINS are set', () => {
|
|
const result = spawnConfig({
|
|
NODE_ENV: 'production',
|
|
JWT_SECRET: 'a-real-secret-with-enough-entropy-for-tests',
|
|
ALLOWED_ORIGINS: 'https://example.com',
|
|
});
|
|
expect(result.status).toBe(0);
|
|
});
|
|
|
|
it('boots in development with a clearly-marked dev fallback when JWT_SECRET is unset', () => {
|
|
const result = spawnConfig({
|
|
NODE_ENV: 'development',
|
|
// JWT_SECRET deliberately unset
|
|
JWT_SECRET: '',
|
|
ALLOWED_ORIGINS: '',
|
|
});
|
|
// In dev we want the server to start (so contributors can run without
|
|
// a .env). The fallback is a known-insecure string we should never
|
|
// see in production.
|
|
expect(result.status).toBe(0);
|
|
// The config emits a loud WARNING so the dev sees it in logs.
|
|
const combined = (result.stdout || '') + (result.stderr || '');
|
|
expect(combined).toMatch(/WARNING.*JWT_SECRET/);
|
|
});
|
|
});
|