From 662637d1ac10bd99af4b4c84f0875ae88bb20a0b Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 May 2026 14:26:19 +0100 Subject: [PATCH] test(backend): fix Windows ENOENT in backend-tests-glob regression check (#7794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(backend): make the glob regression check Windows-safe The previous version called execFileSync('npx', ...). On Windows runners (and any Windows host where npx is installed as npx.cmd), node's child_process.spawn does not auto-pick the .cmd shim, so the call fails with spawnSync npx ENOENT. CI on develop went red for "Windows without plugins" because of this — Linux passed. Resolve mocha's JS entry directly via require.resolve and run it under the current node process. No shell, no .cmd resolution, identical behaviour on every platform. Also normalise the absolute paths mocha prints to POSIX-relative form so the toContain() assertions match on both Linux (which emits forward slashes) and Windows (backslashes). Verified locally that the test still fails when the package.json glob is reverted to the broken tests/backend/specs/**.ts pattern. Co-Authored-By: Claude Opus 4.7 (1M context) * test(backend): use path.relative for cross-platform glob normalization Qodo flagged the prefix-strip + sep-split approach as brittle. On Windows runners mocha can emit paths with mixed separators or different drive-letter casing, in which case startsWith() misses the prefix and the assertions fail against absolute paths. path.relative(srcRoot, abs) handles drive-letter casing and mixed separators consistently, then a final replace([\\/]) -> '/' yields POSIX-relative paths regardless of how mocha printed them. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../specs/backend-tests-glob.test.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/tests/backend-new/specs/backend-tests-glob.test.ts b/src/tests/backend-new/specs/backend-tests-glob.test.ts index 97ce7bd48..41a3ccfcc 100644 --- a/src/tests/backend-new/specs/backend-tests-glob.test.ts +++ b/src/tests/backend-new/specs/backend-tests-glob.test.ts @@ -13,21 +13,20 @@ import {execFileSync} from 'child_process'; import {readFileSync} from 'fs'; -import {join} from 'path'; +import {isAbsolute, join, relative} from 'path'; import {describe, it, expect} from 'vitest'; const srcRoot = join(__dirname, '..', '..', '..'); const pkg = JSON.parse(readFileSync(join(srcRoot, 'package.json'), 'utf8')); // Strip `cross-env NAME=value` prefixes and the leading binary name so we -// can pass the remaining tokens straight to npx mocha --dry-run. +// invoke mocha directly with the rest of the script's arguments. const tokens = String(pkg.scripts.test).split(/\s+/); while (tokens[0] && /^[A-Z_][A-Z0-9_]*=/.test(tokens[0])) tokens.shift(); if (tokens[0] === 'cross-env') { tokens.shift(); while (tokens[0] && /^[A-Z_][A-Z0-9_]*=/.test(tokens[0])) tokens.shift(); } -// Drop the binary itself (mocha) — npx will re-add it. if (tokens[0] === 'mocha') tokens.shift(); const REQUIRED = [ @@ -38,15 +37,26 @@ const REQUIRED = [ describe('backend test glob', () => { it('discovers nested specs under tests/backend/specs/{api,admin}/', () => { + // Resolve mocha's JS entry directly and run it under the current node. + // Going through `npx` (or even via the package.json bin shim) breaks on + // Windows runners where the resolver doesn't auto-pick `.cmd`/`.bat`. + const mochaBin = require.resolve('mocha/bin/mocha.js'); const out = execFileSync( - 'npx', ['mocha', '--dry-run', '--list-files', ...tokens], + process.execPath, [mochaBin, '--dry-run', '--list-files', ...tokens], {cwd: srcRoot, encoding: 'utf8', env: {...process.env, NODE_ENV: 'production'}}, ); - // mocha --list-files prints absolute paths. Normalise to repo-relative. - const seen = out.split('\n') + // mocha --list-files prints absolute paths with platform separators. + // Normalise to repo-relative POSIX paths so the assertions match on + // both Linux and Windows runners. path.relative handles drive-letter + // casing and mixed separators consistently; absolute lines that fall + // outside srcRoot (shouldn't happen with --recursive on srcRoot, but + // be defensive) are passed through untouched and would fail the + // toContain() check loudly rather than silently. + const seen = out.split(/\r?\n/) .map((l) => l.trim()) .filter(Boolean) - .map((l) => l.replace(`${srcRoot}/`, '')); + .map((l) => (isAbsolute(l) ? relative(srcRoot, l) : l)) + .map((l) => l.split(/[\\/]/).join('/')); for (const required of REQUIRED) { expect(seen, `mocha test glob missed ${required}`).toContain(required); }