test: update suites locked to the old direct process.env wiring for the adapter (E00-S04-T04)

This commit is contained in:
implementer
2026-08-30 04:17:22 +00:00
parent 20173a8241
commit 3214807c9d
4 changed files with 79 additions and 44 deletions
+45 -30
View File
@@ -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');