Files
msd-core/tests/no-unguarded-nonportable-exec.rule.test.cjs

301 lines
8.5 KiB
JavaScript

'use strict';
/**
* no-unguarded-nonportable-exec.rule.test.cjs
*
* RuleTester unit tests for the local/no-unguarded-nonportable-exec ESLint rule.
*
* Rule: at Program:exit, if the file contains any chmod call with an exec-bit
* octal (0oNNN & 0o111 !== 0), report each sh/bash -c invocation
* (execFileSync/spawnSync/spawn/exec/execSync) that is NOT inside a Windows
* platform guard.
*
* DEFECT category: DEFECT.WINDOWS-TEST-PORTABILITY
*
* Test cases mirror those in the retired regex-based script
* scripts/lint-windows-test-portability.cjs.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { RuleTester } = require('eslint');
const rule = require('../eslint-rules/no-unguarded-nonportable-exec.cjs');
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'commonjs',
},
});
// ─── module shape ─────────────────────────────────────────────────────────────
describe('no-unguarded-nonportable-exec rule module', () => {
test('exports meta and create', () => {
assert.strictEqual(typeof rule.meta, 'object');
assert.strictEqual(typeof rule.create, 'function');
assert.strictEqual(rule.meta.type, 'problem');
assert.ok(rule.meta.messages.nonportableExec);
});
});
// ─── INVALID cases (violation expected) ───────────────────────────────────────
describe('no-unguarded-nonportable-exec invalid cases', () => {
test('invalid: chmod 0o755 + execFileSync bash -c with no guard', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [],
invalid: [
{
code: `
fs.chmodSync(fixture, 0o755);
execFileSync('bash', ['-c', 'echo hi']);
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'nonportableExec' }],
},
],
});
});
test('invalid: chmod 0o111 (pure exec bits) + spawnSync sh -c', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [],
invalid: [
{
code: `
fs.chmodSync(f, 0o111);
spawnSync('sh', ['-c', 'x']);
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'nonportableExec' }],
},
],
});
});
test('invalid: string-form exec(sh -c) + chmod 0o755', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [],
invalid: [
{
code: `
fs.chmodSync(f, 0o755);
exec('sh -c "run.sh"');
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'nonportableExec' }],
},
],
});
});
test('invalid: /bin/bash prefix in array form + chmod exec bit', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [],
invalid: [
{
code: `
fs.chmodSync(f, 0o755);
execFileSync('/bin/bash', ['-c', 'run']);
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'nonportableExec' }],
},
],
});
});
});
// ─── VALID cases (no violation expected) ──────────────────────────────────────
describe('no-unguarded-nonportable-exec valid cases', () => {
test('valid: chmod 0o755 + execFileSync bash -c with process.platform guard', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
if (process.platform !== 'win32') {
execFileSync('bash', ['-c', 'echo hi']);
}
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: chmod 0o644 (no exec bit) + bash -c does not trigger', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o644);
execFileSync('bash', ['-c', 'cat file']);
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: chmod 0o755 + execFileSync(sh, [path]) no -c flag', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
execFileSync('sh', [fixturePath]);
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: no chmod at all — bash -c alone is fine', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `execFileSync('bash', ['-c', 'echo hi']);`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: chmod 0o444 (read-only, no exec bits) + bash -c', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(f, 0o444);
execFileSync('bash', ['-c', 'x']);
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: early-return windows guard before sh -c', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(f, 0o755);
if (process.platform === 'win32') return;
execFileSync('sh', ['-c', 'run']);
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: hoisted isWindows guard', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
const isWindows = process.platform === 'win32';
fs.chmodSync(f, 0o755);
if (!isWindows) {
execFileSync('bash', ['-c', 'run']);
}
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: chmod 0o755 + exec("finish -c something") — "sh" in "finish" is not a shell invocation (W1 word-boundary)', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
exec('finish -c something');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: chmod 0o755 + exec("publish -c") — "sh" in "publish" is not a shell invocation (W1 word-boundary)', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
exec('publish -c');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
// C1 fix: '-c' must be FIRST element; a script receiving '-c' as a later arg
// is not a shell -c invocation.
test('valid (C1): chmod 0o755 + execFileSync(sh, [fixturePath, "-c"]) — script arg, not shell -c', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixturePath, 0o755);
execFileSync('sh', [fixturePath, '-c']);
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
// C2 fix: 'sh -c' embedded mid-string in data (as arg to printf) must NOT flag.
test('valid (C2): chmod 0o755 + exec("printf \\"sh -c\\"") — sh -c as data, not a shell invocation', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
exec('printf "sh -c"');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
// C2 fix: 'sh -c' embedded after other words must NOT flag.
test('valid (C2): chmod 0o755 + exec("echo run sh -c later") — sh -c mid-string as data', () => {
ruleTester.run('no-unguarded-nonportable-exec', rule, {
valid: [
{
code: `
fs.chmodSync(fixture, 0o755);
exec('echo run sh -c later');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});