Files
msd-core/tests/no-path-literal-in-assert.rule.test.cjs
2026-06-25 14:37:10 -04:00

403 lines
13 KiB
JavaScript

'use strict';
/**
* no-path-literal-in-assert.rule.test.cjs
*
* RuleTester unit tests for the local/no-path-literal-in-assert ESLint rule.
* Mirrors the style of tests/eslint-rules.test.cjs.
*
* Rule: report when a path-returning call (path.join, getGlobalConfigDir, …)
* is compared to a hardcoded POSIX-slash literal in an assert.*() or
* expect(…).<matcher>() assertion — a DEFECT that fails on Windows.
*
* VALID (no report) when:
* - the path operand is wrapped by a POSIX normalizer (.replace, .replaceAll, toPosixPath, etc.)
* - the assertion is inside a Windows-excluded block (process.platform !== 'win32' guard,
* early-return guard, hoisted isWindows guard)
* - both operands are path calls (no slash literal involved)
* - the string literal has no slash (file-name only)
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { RuleTester } = require('eslint');
const noPathLiteralInAssert = require('../eslint-rules/no-path-literal-in-assert.cjs');
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'commonjs',
},
});
// ─── module shape ─────────────────────────────────────────────────────────────
describe('no-path-literal-in-assert rule module', () => {
test('exports meta and create', () => {
assert.strictEqual(typeof noPathLiteralInAssert.meta, 'object');
assert.strictEqual(typeof noPathLiteralInAssert.create, 'function');
assert.strictEqual(noPathLiteralInAssert.meta.type, 'problem');
assert.ok(noPathLiteralInAssert.meta.messages.pathLiteral);
});
});
// ─── INVALID cases (violation expected) ───────────────────────────────────────
describe('no-path-literal-in-assert invalid cases', () => {
test('invalid: assert.strictEqual(path.join(a,b), "/x/y")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.strictEqual(path.join(a, b), '/x/y');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: assert.equal(getGlobalConfigDir("claude"), "/custom/claude")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal(getGlobalConfigDir('claude'), '/custom/claude');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: assert.deepStrictEqual(path.resolve(x), "/a/b")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.deepStrictEqual(path.resolve(x), '/a/b');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: reversed operands — assert.equal("/x/y", path.join(a,b))', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal('/x/y', path.join(a, b));`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: expect(path.resolve(x)).toBe("/a/b")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `expect(path.resolve(x)).toBe('/a/b');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: multi-line assert.strictEqual', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `
assert.strictEqual(
path.join(base, 'dir'),
'/home/user/dir'
);
`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: assert.equal(os.homedir(), "/home/user")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal(os.homedir(), '/home/user');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: assert.equal(os.tmpdir(), "/tmp")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal(os.tmpdir(), '/tmp');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: expect(getGlobalConfigDir("claude")).toEqual("/custom/claude")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `expect(getGlobalConfigDir('claude')).toEqual('/custom/claude');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('invalid: assert.deepEqual(path.normalize(x), "/a/b/c")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.deepEqual(path.normalize(x), '/a/b/c');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
});
// ─── VALID cases (no violation expected) ─────────────────────────────────────
describe('no-path-literal-in-assert valid cases', () => {
test('valid: normalized with String(path.join).replace(/\\\\/g, "/")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(String(path.join(a, b)).replace(/\\\\/g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: normalized with String(path.join).replace(/[\\\\/]/g, "/")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(String(path.join(a, b)).replace(/[\\\\/]/g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: normalized with path.join().replaceAll(path.sep, "/")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(path.join(a, b).replaceAll(path.sep, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: assert.equal("foo", "foo") — no path call, no slash difference', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal('foo', 'foo');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: assert.equal(path.basename(p), "file.txt") — string has no slash', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(path.basename(p), 'file.txt');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: both operands are path calls — no slash literal', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(path.join(a, b), path.join(c, d));`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: guarded by if (process.platform !== "win32") { ... }', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `
if (process.platform !== 'win32') {
assert.equal(path.join(a, b), '/x/y');
}
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: early-return guard — if (process.platform === "win32") return; assert.equal(path.join(a,b), "/x/y")', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `
if (process.platform === 'win32') return;
assert.equal(path.join(a, b), '/x/y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: hoisted isWindows guard — const isWindows = process.platform === "win32"; if (!isWindows) assert.equal(...)', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `
const isWindows = process.platform === 'win32';
if (!isWindows) assert.equal(path.join(a, b), '/x/y');
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: assert.ok(path.join(a,b)) — not an equality assert', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.ok(path.join(a, b));`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: URL string starting with https:// is not flagged', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(getUrl(), 'https://example.com/path');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: normalized with toPosixPath(path.join(a,b))', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(toPosixPath(path.join(a, b)), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});
// ─── C1: isPosixNormalizerCall must require backslash-targeting regex ─────────
// The fix: .replace(/foo/g,'/') and .replace(/\//g,'/') must NOT suppress the rule.
// Before the fix, ANY <x>.replace(/<anything>/g, '/') was accepted as a normalizer
// and would suppress the violation even when the regex did not target backslashes.
describe('C1 — isPosixNormalizerCall backslash-targeting requirement', () => {
test('C1 INVALID: path.join().replace(/foo/g, "/") is NOT a POSIX normalizer — flagged', () => {
// Before C1 fix: was NOT flagged (any regex with g flag was accepted as normalizer).
// After C1 fix: IS flagged (/foo/g does not target backslashes → not a normalizer;
// rule now peels the non-normalizer method chain and finds path.join inside).
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal(path.join(a,b).replace(/foo/g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('C1 INVALID: path.join().replace(/\\//g, "/") targets forward-slash only — flagged', () => {
// Before C1 fix: was NOT flagged.
// After C1 fix: IS flagged (/\//g matches forward-slash only, not backslash → not a normalizer).
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [],
invalid: [
{
code: `assert.equal(path.join(a,b).replace(/\\//g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
errors: [{ messageId: 'pathLiteral' }],
},
],
});
});
test('C1 VALID: path.join().replace(/\\\\/g, "/") IS a proper POSIX normalizer — NOT flagged', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(path.join(a,b).replace(/\\\\/g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('C1 VALID: path.join().replace(/[\\\\/]/g, "/") IS a proper POSIX normalizer — NOT flagged', () => {
ruleTester.run('no-path-literal-in-assert', noPathLiteralInAssert, {
valid: [
{
code: `assert.equal(path.join(a,b).replace(/[\\\\/]/g, '/'), '/x/y');`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
});