Files
msd-core/tests/gsd-check-update-worker-platform-gate.test.cjs

87 lines
3.3 KiB
JavaScript

/**
* Tests for the Windows npm resolution platform gate.
*
* Background (issue #3103, PR #3102):
* On Windows, `npm` ships as `npm.cmd`. Node's spawn does not apply PATHEXT
* resolution and fails with ENOENT. The fix is to spawn through a shell on
* Windows (cmd.exe resolves npm.cmd via PATHEXT). On POSIX, `npm` resolves
* without a shell, so spawning `/bin/sh -c` is pure overhead and changes
* signal / exit-code semantics — undesirable.
*
* Relocation (#498): the SessionStart worker no longer spawns npm itself. It
* delegates the latest-version lookup to check-latest-version's
* `checkLatestVersion()`, which routes through `execNpm` in the shell-command
* projection seam. The PR #3102 contract therefore now lives on `execNpm`.
* This test locks it there, and additionally locks that the worker does NOT
* re-introduce a direct npm spawn (which would re-open the gate question in a
* second place).
*
* Source-grep policy: these structural assertions read source via readFileSync.
* The behavior (Windows-only shell resolution) is platform-gated at runtime and
* cannot be reached on POSIX CI without a Windows lane; a structural assertion
* is the minimum-cost contract.
*/
// allow-test-rule: structural assertion on spawn-options shape; the behavior
// (Windows-only shell resolution) is platform-gated at runtime and cannot be
// reached on POSIX CI without a Windows lane.
'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const WORKER_PATH = path.join(__dirname, '..', 'hooks', 'gsd-check-update-worker.js');
const PROJECTION_PATH = path.join(
__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs',
);
function codeOnly(file) {
return fs.readFileSync(file, 'utf8')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/[^\r\n]*/g, '$1');
}
describe('execNpm: Windows npm spawn platform gate (PR #3102, relocated #498)', () => {
test('projection seam exists', () => {
assert.ok(fs.existsSync(PROJECTION_PATH), `not found at ${PROJECTION_PATH}`);
});
test('execNpm gates shell to process.platform === "win32"', () => {
assert.match(
codeOnly(PROJECTION_PATH),
/shell:\s*process\.platform\s*===\s*['"]win32['"]/,
[
'execNpm must gate shell to `process.platform === "win32"`.',
'A regression to `shell: true` would spawn /bin/sh -c on POSIX',
'(adds shell overhead, changes signal/exit semantics). See PR #3102.',
].join(' '),
);
});
test('no unconditional shell: true on the npm spawn', () => {
assert.doesNotMatch(
codeOnly(PROJECTION_PATH),
/shell\s*:\s*true\s*[,\s}]/,
'shell: true is forbidden — use the `process.platform === "win32"` gate.',
);
});
});
describe('worker delegates the npm spawn (does not re-open the gate, #498)', () => {
test('worker does NOT spawn npm directly', () => {
const code = codeOnly(WORKER_PATH);
assert.doesNotMatch(
code,
/(execFileSync|spawnSync|execSync|exec)\s*\(\s*['"]npm['"]/,
'Worker must delegate to checkLatestVersion(), not spawn npm itself.',
);
});
test('worker requires check-latest-version for the lookup', () => {
assert.match(codeOnly(WORKER_PATH), /check-latest-version/);
});
});