← back to Tk 10965 Zero Price Analysis

test/cli-preflight.test.mjs

65 lines

import test from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { parseArgs } from '../cli-args.mjs';
const entry = fileURLToPath(new URL('../apply-fix.mjs', import.meta.url));
const parser = fileURLToPath(new URL('../cli-args.mjs', import.meta.url));
const preload = fileURLToPath(new URL('./deny-side-effects.cjs', import.meta.url));
function run(args, target = entry) {
  const result = spawnSync(process.execPath, ['--require', preload, target, ...args], {
    encoding: 'utf8', timeout: 5000,
    env: { GUARD_ALLOWED_READS: JSON.stringify([target, parser]) }
  });
  assert.ifError(result.error);
  assert.equal(result.signal, null);
  return { ...result, attempts: [...result.stderr.matchAll(/^GUARD_ATTEMPT:(.+)$/gm)].map(m => m[1]) };
}
const invalid = [[], ['--help', '--all'], ['--wat'], ['--canery'], ['--all', '--all'], ['--enumerate', '--all'], ['--all', 'extra'], ['--canary', '--all'], ['--canary', '50', '--all'], ['--rollback'], ['--rollback', ''], ['--rollback', ' '], ['--rollback', '--all'], ['--rollback', 'file.json', 'extra'], ...['0','-1','1.5','nope','9007199254740992','Infinity','NaN','',' ','1e2','0x10'].map(n => ['--canary', n])];
for (const args of invalid) test('reject before initialization: ' + JSON.stringify(args), () => {
  assert.throws(() => parseArgs(args));
  const result = run(args);
  assert.equal(result.status, 2, result.stderr);
  assert.match(result.stderr, /Usage: node apply-fix.mjs/);
  assert.deepEqual(result.attempts, [], result.stderr);
});
test('--help is safe at actual entry point', () => {
  assert.deepEqual(parseArgs(['--help']), {mode:'--help'});
  const result = run(['--help']);
  assert.equal(result.status, 0, result.stderr);
  assert.match(result.stdout, /Usage: node apply-fix.mjs/);
  assert.deepEqual(result.attempts, []);
});
// Historical accepted write syntax now fails closed without a frozen manifest.
const recognized = [['--enumerate'],['--all'],['--canary'],['--canary','1'],['--canary','9007199254740991'],['--rollback','file.json'],['--rollback','./--file.json']];
for (const args of recognized) test('legacy mode requires frozen inputs: ' + JSON.stringify(args), () => {
  assert.throws(() => parseArgs(args));
  const result = run(args);
  assert.equal(result.status, 2, result.stderr);
  assert.deepEqual(result.attempts, [], result.stderr);
});
if (process.env.CLI_BASELINE_ENTRY) for (const args of [[], ['--help'], ['--typo']]) {
  test('prepatch entry exposes missing guard: ' + JSON.stringify(args), () => {
    const result = run(args, process.env.CLI_BASELINE_ENTRY);
    assert.notEqual(result.status, 0);
    assert.deepEqual(result.attempts, ['fs.mkdirSync'], result.stderr);
  });
}

const deniedProbes = [
  ["require('node:fs').readFileSync('/guard-denied-read')", 'fs.readFileSync'],
  ["require('node:fs').writeFileSync('/guard-denied-write', 'x')", 'fs.writeFileSync'],
  ["require('node:fs').promises.readFile('/guard-denied-read')", 'fs.promises.readFile'],
  ["fetch('https://guard.invalid')", 'fetch'],
  ["require('node:https').get('https://guard.invalid')", 'node:https.get'],
  ["require('node:child_process').spawn('guard-must-not-launch')", 'node:child_process.spawn']
];
for (const [code, expected] of deniedProbes) test('boundary hook calibration: ' + expected, () => {
  const result = spawnSync(process.execPath, ['--require', preload, '--eval', code], {
    encoding:'utf8', timeout:5000, env:{GUARD_ALLOWED_READS:'[]'}
  });
  assert.ifError(result.error);
  assert.notEqual(result.status, 0);
  assert.deepEqual([...result.stderr.matchAll(/^GUARD_ATTEMPT:(.+)$/gm)].map(m => m[1]), [expected]);
});