test: update suites locked to the old direct process.env wiring for the adapter (E00-S04-T04)
This commit is contained in:
@@ -6,17 +6,20 @@
|
||||
* - "missing required setting gives a field-specific startup error" → the
|
||||
* `packages/config` package exposes the startup validation entry point
|
||||
* (`assertValidConfig`, building on the E00-S04-T01 TypeBox/Ajv schema)
|
||||
* and the committed `apps/server/src/index.ts` calls it before the server
|
||||
* and the environment adapter (`loadConfigFromEnv`, E00-S04-T04 — the
|
||||
* config package's single owner of `process.env` reads) validates the
|
||||
* mapped environment through it; the committed `apps/server/src/index.ts`
|
||||
* loads its startup configuration through the adapter before the server
|
||||
* binds, so a deployment missing a required setting (the admin-session
|
||||
* secret `EPPP_SESSION_SECRET` — the schema's required field,
|
||||
* Security-and-Operations §32/§26) fails fast at startup instead of
|
||||
* booting with an invalid configuration. Locked in statically (mutation
|
||||
* probes prove non-vacuity: dropping the startup validation call,
|
||||
* moving it after the bind, or dropping the compose/Dockerfile support
|
||||
* all fail) and behaviorally by the deterministic probes (the issue's
|
||||
* test plan: "start with a missing required field and confirm the error
|
||||
* names it" — booting the committed server without `EPPP_SESSION_SECRET`
|
||||
* exits non-zero with the error naming the missing field).
|
||||
* probes prove non-vacuity: dropping the adapter call, moving it after
|
||||
* the bind, or dropping the compose/Dockerfile support all fail) and
|
||||
* behaviorally by the deterministic probes (the issue's test plan: "start
|
||||
* with a missing required field and confirm the error names it" — booting
|
||||
* the committed server without `EPPP_SESSION_SECRET` exits non-zero with
|
||||
* the error naming the missing field).
|
||||
* - "the error names the missing field" → a missing required setting throws
|
||||
* `MissingRequiredSettingError` whose message and `missingField` name the
|
||||
* missing field (e.g. `"missing required setting: sessionSecret"`); other
|
||||
@@ -149,33 +152,35 @@ function assertStartupErrorSource(src) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the committed server validates the required settings at startup:
|
||||
* it imports `assertValidConfig` from `@personal-blog/config` and calls it
|
||||
* with the parsed startup configuration (including the required
|
||||
* `EPPP_SESSION_SECRET`) BEFORE the server binds — so a missing required
|
||||
* setting is a startup error, never a silently-booted invalid configuration.
|
||||
* Asserts the committed server loads its startup configuration through the
|
||||
* environment adapter (E00-S04-T04) before it binds: it imports
|
||||
* `loadConfigFromEnv` from `@personal-blog/config` and calls it (the adapter
|
||||
* validates the mapped environment with `assertValidConfig`, including the
|
||||
* required `EPPP_SESSION_SECRET`) BEFORE `server.listen` — so a missing
|
||||
* required setting is a startup error, never a silently-booted invalid
|
||||
* configuration, and the server itself never reads `process.env` directly.
|
||||
*/
|
||||
function assertServerStartupValidation(src) {
|
||||
assert.match(
|
||||
src,
|
||||
/import \{ assertValidConfig \} from '@personal-blog\/config'/,
|
||||
'the server must import the startup validation entry point from the config package',
|
||||
/import \{ loadConfigFromEnv \} from '@personal-blog\/config'/,
|
||||
'the server must import the environment adapter (loadConfigFromEnv) from the config package',
|
||||
);
|
||||
assert.match(
|
||||
src,
|
||||
/assertValidConfig\(\{/,
|
||||
'the server must call assertValidConfig with its startup configuration',
|
||||
/const config = loadConfigFromEnv\(\);/,
|
||||
'the server must load its startup configuration through the environment adapter (const config = loadConfigFromEnv())',
|
||||
);
|
||||
assert.match(
|
||||
assert.doesNotMatch(
|
||||
src,
|
||||
/sessionSecret: process\.env\.EPPP_SESSION_SECRET/,
|
||||
'the server must feed the required admin-session secret (EPPP_SESSION_SECRET) into the startup validation',
|
||||
/process\.env\.[A-Z_]+/,
|
||||
'the server must not read process.env directly (all settings flow through the config adapter, E00-S04-T04)',
|
||||
);
|
||||
const callIndex = src.indexOf('assertValidConfig({');
|
||||
const callIndex = src.indexOf('loadConfigFromEnv(');
|
||||
const listenIndex = src.indexOf('server.listen(');
|
||||
assert.ok(
|
||||
callIndex !== -1 && listenIndex !== -1 && callIndex < listenIndex,
|
||||
'the startup validation must run before the server binds (server.listen) so a missing required setting is a startup error',
|
||||
'the startup configuration must load through the adapter before the server binds (server.listen) so a missing required setting is a startup error',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,25 +279,35 @@ test('the config-startup-error criterion is enforced in CI', () => {
|
||||
// Mutation probes — the static assertions are non-vacuous
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test('dropping the startup validation call fails the server wiring assertion (mutation probe)', () => {
|
||||
test('dropping the adapter call fails the server wiring assertion (mutation probe)', () => {
|
||||
const src = read(SERVER_SRC);
|
||||
const withoutCall = src.replace('assertValidConfig({\n', 'assertValidConfigx({\n');
|
||||
assert.notEqual(withoutCall, src, 'the mutation must actually replace the assertValidConfig call');
|
||||
assert.throws(() => assertServerStartupValidation(withoutCall), /must call assertValidConfig/);
|
||||
const withoutCall = src.replace('const config = loadConfigFromEnv();', 'const configX = loadConfigFromEnv();');
|
||||
assert.notEqual(withoutCall, src, 'the mutation must actually break the loadConfigFromEnv wiring');
|
||||
assert.throws(() => assertServerStartupValidation(withoutCall), /must load its startup configuration/);
|
||||
});
|
||||
|
||||
test('moving the startup validation after the server binds fails the order assertion (mutation probe)', () => {
|
||||
test('moving the adapter call after the server binds fails the order assertion (mutation probe)', () => {
|
||||
const src = read(SERVER_SRC);
|
||||
const moved = src
|
||||
.replace(/assertValidConfig\(\{\n host: '0\.0\.0\.0',\n port: PORT,\n databaseUrl: process\.env\.DATABASE_URL,\n sessionSecret: process\.env\.EPPP_SESSION_SECRET,\n\}\);\n/, '')
|
||||
.replace('const config = loadConfigFromEnv();\n', '')
|
||||
.replace(
|
||||
'server.listen(PORT, () => {',
|
||||
'server.listen(PORT, () => {\n assertValidConfig({ sessionSecret: process.env.EPPP_SESSION_SECRET });',
|
||||
'server.listen(config.port, () => {',
|
||||
'server.listen(config.port, () => {\n const config = loadConfigFromEnv();',
|
||||
);
|
||||
assert.notEqual(moved, src, 'the mutation must actually move the validation call after the bind');
|
||||
assert.notEqual(moved, src, 'the mutation must actually move the adapter call after the bind');
|
||||
assert.throws(() => assertServerStartupValidation(moved), /before the server binds/);
|
||||
});
|
||||
|
||||
test('the server reading process.env directly fails the no-direct-read assertion (mutation probe)', () => {
|
||||
const src = read(SERVER_SRC);
|
||||
const directRead = src.replace(
|
||||
'const config = loadConfigFromEnv();',
|
||||
'const config = loadConfigFromEnv();\nconst PORT = process.env.PORT;',
|
||||
);
|
||||
assert.notEqual(directRead, src, 'the mutation must actually add a direct process.env read');
|
||||
assert.throws(() => assertServerStartupValidation(directRead), /must not read process\.env/);
|
||||
});
|
||||
|
||||
test('an error message that does not name the missing field fails the naming assertion (mutation probe)', () => {
|
||||
const src = read(STARTUP_SRC);
|
||||
const noField = src.replace(/missing required setting: \$\{missingField\}/g, 'missing required setting');
|
||||
|
||||
Reference in New Issue
Block a user