Files
houseplan-card/test/windows-portability.test.mjs
Claude eb3abe2053 test: spawn CLI scripts via fileURLToPath, not URL.pathname
Owner's Windows run after #496 (2338 pass, 2 fail): both remaining failures
are tests building the script path as `new URL(...).pathname`, which is
`/C:/Users/...` on Windows and makes Node look for `C:\C:\Users\...`.
check-inputs and classify-changes CLI tests now use fileURLToPath; the
portability test forbids `import.meta.url).pathname` in test/**.

Issue: #496
User-Visible: no
2026-09-09 02:18:58 +03:00

63 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { isMainModule, portableCommand } from '../scripts/spawn-portable.mjs';
// #496: на машине владельца (Windows) три инфраструктурных теста падали при
// зелёном Linux CI. Причины — не логика, а переносимость: разбор argv[1] через
// `file://`, запуск команд через оболочку, CRLF из глобального git config.
// Здесь закреплены правила, чтобы они не вернулись с очередным скриптом.
const SCRIPTS = fileURLToPath(new URL('../scripts/', import.meta.url));
const scriptSources = () => readdirSync(SCRIPTS)
.filter((name) => name.endsWith('.mjs'))
.map((name) => ({ name, text: readFileSync(new URL(name, `file://${SCRIPTS}`), 'utf8') }));
test('скрипты не собирают file-URL конкатенацией с argv (#496)', () => {
const offenders = scriptSources()
.filter(({ text }) => /new URL\(`file:\/\/\$\{process\.argv/.test(text))
.map(({ name }) => name);
assert.deepEqual(offenders, [], 'на Windows это даёт file:///C:/C:/… и CLI молчит; использовать isMainModule');
});
test('скрипты не включают оболочку по платформе — только portableCommand (#496)', () => {
const offenders = scriptSources()
.filter(({ name, text }) => name !== 'spawn-portable.mjs' && /shell:\s*process\.platform\s*===\s*'win32'/.test(text))
.map(({ name }) => name);
assert.deepEqual(offenders, [], 'оболочка нужна только npm.cmd; node/git через shell теряют кавычки');
});
test('portableCommand: оболочка только для npm/npx/.cmd на Windows (#496)', () => {
assert.deepEqual(portableCommand('npm', 'win32'), { cmd: 'npm.cmd', shell: true });
assert.deepEqual(portableCommand('npx', 'win32'), { cmd: 'npx.cmd', shell: true });
assert.deepEqual(portableCommand('tool.CMD', 'win32'), { cmd: 'tool.CMD', shell: true });
assert.deepEqual(portableCommand(process.execPath, 'win32'), { cmd: process.execPath, shell: false });
assert.deepEqual(portableCommand('git', 'win32'), { cmd: 'git', shell: false });
assert.deepEqual(portableCommand('npm', 'linux'), { cmd: 'npm', shell: false });
assert.deepEqual(portableCommand('npm', 'darwin'), { cmd: 'npm', shell: false });
});
test('isMainModule сравнивает через pathToFileURL и терпит мусор в argv (#496)', () => {
const here = fileURLToPath(import.meta.url);
assert.equal(isMainModule(import.meta.url, here), true);
assert.equal(isMainModule(pathToFileURL(here).href, here), true);
assert.equal(isMainModule(import.meta.url, `${here}.other`), false);
assert.equal(isMainModule(import.meta.url, null), false);
assert.equal(isMainModule(import.meta.url, ''), false);
});
test('тесты с временным git-репозиторием изолируют переводы строк от глобального конфига (#496)', () => {
const text = readFileSync(new URL('./rebase-on-dev.test.mjs', import.meta.url), 'utf8');
assert.match(text, /GIT_CONFIG_KEY_0: 'core\.autocrlf', GIT_CONFIG_VALUE_0: 'false'/);
});
test('тесты не запускают скрипты по URL.pathname — на Windows это /C:/… → C:\\C:\\… (#496)', () => {
const TESTS = fileURLToPath(new URL('./', import.meta.url));
const offenders = readdirSync(TESTS)
.filter((name) => name.endsWith('.test.mjs'))
.filter((name) => /import\.meta\.url\)\.pathname/.test(readFileSync(new URL(name, `file://${TESTS}`), 'utf8')))
.filter((name) => name !== 'windows-portability.test.mjs');
assert.deepEqual(offenders, [], 'путь для execSync/spawn — только через fileURLToPath');
});