#!/usr/bin/env node // [unit->REQ-CLI-01] // tools/scripts/lint-vite-env.test.mjs // 6 fixture-driven tests per plan 06-08 Task 1 : // 1. valid staging+prod (required keys + https + non-placeholder prod) → exit 0 // 2. missing VITE_WSS_URL → exit 1 // 3. BETTER_AUTH_SECRET present → exit 1 // 4. ROOM_SIGNING_PRIVATE_KEY present → exit 1 // 5. VITE_HTTP_BASE=http://... (not https) → exit 1 // 6. prod still has PROD_PUBKEY_PENDING_FLY_SSH_PHASE_5_D19_RITUAL → exit 1 import { test } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; const SCRIPT = resolve(process.cwd(), 'tools/scripts/lint-vite-env.mjs'); const isWindows = process.platform === 'win32'; const VALID_STAGING = [ 'VITE_WSS_URL=wss://staging.rebno.decidel.com', 'VITE_HTTP_BASE=https://staging.rebno.decidel.com', 'VITE_ROOM_SIGNING_PUBKEY=VBSr0aj+rbf2vVkXax6vCvWACSv+dmsKdy0g0DsSfQM=', 'VITE_STAGING_MODE=1', ].join('\n'); const VALID_PROD = [ 'VITE_WSS_URL=wss://rebno.decidel.com', 'VITE_HTTP_BASE=https://rebno.decidel.com', 'VITE_ROOM_SIGNING_PUBKEY=ProdPubKeyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=', ].join('\n'); function makeFixtures(stagingBody, prodBody) { const dir = mkdtempSync(join(tmpdir(), 'lint-vite-env-')); const stagingPath = join(dir, '.env.staging'); const prodPath = join(dir, '.env.prod'); writeFileSync(stagingPath, stagingBody); writeFileSync(prodPath, prodBody); return { dir, stagingPath, prodPath }; } function runLint({ stagingPath, prodPath }) { return spawnSync('node', [SCRIPT], { env: { ...process.env, VITE_ENV_STAGING: stagingPath, VITE_ENV_PROD: prodPath, }, encoding: 'utf-8', shell: isWindows, }); } test('1. valid staging+prod → exit 0, OK', () => { const f = makeFixtures(VALID_STAGING, VALID_PROD); try { const r = runLint(f); assert.equal(r.status, 0, `expected exit 0, got ${r.status}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`); assert.match(r.stdout, /lint-vite-env: OK/); } finally { rmSync(f.dir, { recursive: true, force: true }); } }); test('2. missing VITE_WSS_URL → exit 1', () => { const f = makeFixtures( 'VITE_HTTP_BASE=https://staging.rebno.decidel.com\nVITE_ROOM_SIGNING_PUBKEY=abc=\n', VALID_PROD, ); try { const r = runLint(f); assert.equal(r.status, 1); assert.match(r.stderr, /missing required key: VITE_WSS_URL/); } finally { rmSync(f.dir, { recursive: true, force: true }); } }); test('3. BETTER_AUTH_SECRET in staging → exit 1', () => { const f = makeFixtures( `${VALID_STAGING}\nBETTER_AUTH_SECRET=oops`, VALID_PROD, ); try { const r = runLint(f); assert.equal(r.status, 1); assert.match(r.stderr, /secret-leak: .*BETTER_AUTH_SECRET.* MUST NOT appear in client env/); } finally { rmSync(f.dir, { recursive: true, force: true }); } }); test('4. ROOM_SIGNING_PRIVATE_KEY in staging → exit 1', () => { const f = makeFixtures( `${VALID_STAGING}\nROOM_SIGNING_PRIVATE_KEY=oops`, VALID_PROD, ); try { const r = runLint(f); assert.equal(r.status, 1); assert.match(r.stderr, /secret-leak: .*ROOM_SIGNING_PRIVATE_KEY/); } finally { rmSync(f.dir, { recursive: true, force: true }); } }); test('5. VITE_HTTP_BASE=http://... (not https) → exit 1', () => { const badStaging = [ 'VITE_WSS_URL=wss://staging.rebno.decidel.com', 'VITE_HTTP_BASE=http://staging.rebno.decidel.com', 'VITE_ROOM_SIGNING_PUBKEY=abc=', ].join('\n'); const f = makeFixtures(badStaging, VALID_PROD); try { const r = runLint(f); assert.equal(r.status, 1); assert.match(r.stderr, /VITE_HTTP_BASE must be https:\/\//); } finally { rmSync(f.dir, { recursive: true, force: true }); } }); test('6. prod placeholder VITE_ROOM_SIGNING_PUBKEY → exit 1', () => { const placeholderProd = [ 'VITE_WSS_URL=wss://rebno.decidel.com', 'VITE_HTTP_BASE=https://rebno.decidel.com', 'VITE_ROOM_SIGNING_PUBKEY=PROD_PUBKEY_PENDING_FLY_SSH_PHASE_5_D19_RITUAL', ].join('\n'); const f = makeFixtures(VALID_STAGING, placeholderProd); try { const r = runLint(f); assert.equal(r.status, 1); assert.match(r.stderr, /VITE_ROOM_SIGNING_PUBKEY still placeholder/); } finally { rmSync(f.dir, { recursive: true, force: true }); } });