node/test/parallel/test-eslint-must-call-assert.js
Antoine du Hamel e50cbc1abd
test: enforce better never-settling-promise detection
Tests should be explicit regarding whether a promise is expected to
settle, and the test should fail when the behavior does not meet
expectations.

PR-URL: https://github.com/nodejs/node/pull/60976
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Erick Wendel <erick.workspace@gmail.com>
2025-12-10 23:55:36 +00:00

257 lines
8.7 KiB
JavaScript

'use strict';
const common = require('../common');
if ((!common.hasCrypto) || (!common.hasIntl)) {
common.skip('ESLint tests require crypto and Intl');
}
common.skipIfEslintMissing();
const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester;
const rule = require('../../tools/eslint-rules/must-call-assert');
const message = 'Assertions must be wrapped into `common.mustSucceed`, `common.mustCall` or `common.mustCallAtLeast`';
const tester = new RuleTester();
tester.run('must-call-assert', rule, {
valid: [
'assert.strictEqual(2+2, 4)',
'process.on("message", common.mustCallAtLeast((code) => {assert.strictEqual(code, 0)}));',
'process.once("message", common.mustCall((code) => {assert.strictEqual(code, 0)}));',
'process.once("message", common.mustCall((code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} }));',
'process.once("message", common.mustCall((code) => { (() => assert.strictEqual(code, 0))(); }));',
'someAsyncTask(common.mustSucceed((code) => { (() => assert.strictEqual(code, 0))(); }));',
'someAsyncTask(mustSucceed((code) => { (() => assert.strictEqual(code, 0))(); }));',
'(async () => {await assert.rejects(fun())})().then(common.mustCall())',
'[1, true].forEach((val) => assert.strictEqual(fun(val), 0));',
'const assert = require("node:assert")',
'const assert = require("assert")',
'const assert = require("assert/strict")',
'const assert = require("node:assert/strict")',
'import assert from "assert"',
'import * as assert from "assert"',
'import assert from "assert/strict"',
'import * as assert from "assert/strict"',
'import assert from "node:assert"',
'import * as assert from "node:assert"',
'import assert from "node:assert/strict"',
'import * as assert from "node:assert/strict"',
`
assert.throws(() => {}, (err) => {
assert.strictEqual(err, 5);
});
process.on('exit', () => {
assert.ok();
});
process.once('exit', () => {
assert.ok();
});
process.on('message', () => {
assert.fail('error message');
});
Promise.resolve().then((arg) => {
assert.ok(arg);
}).then(common.mustCall());
new Promise(() => {
assert.ok(global.prop);
}).then(common.mustCall());
process.nextTick(() => {
assert.ok(String);
});
`,
`
import test from 'node:test';
import assert from 'node:assert';
test("whatever", () => {
assert.strictEqual(2+2, 5);
});
`,
`
import test from 'node:test';
import assert from 'node:assert';
describe("whatever", () => {
it("should not be reported", async (t) => {
assert.strictEqual(2+2, 5);
await t.test("name", () => {
assert.ok(global.test);
});
});
});
`,
`
process.on("message", common.mustCall(() => {
Promise.all([].map(async (val) => {
val = await asyncTask(val);
assert.strictEqual(val, 3);
})).then(common.mustCall());
Promise.all(Object.keys(keys).map(async (val) => {
val = await asyncTask(val);
assert.strictEqual(val, 3);
})).then(common.mustCall());
}));
`,
`
spawnSyncAndAssert(
outputFile,
{
env: {
NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT',
...process.env,
},
},
{
trim: true,
stdout: 'Hello from snapshot',
stderr(output) {
assert.doesNotMatch(
output,
/Single executable application is an experimental feature/);
},
},
);
`,
'eval("(" + function() {} + ")()")',
// eslint-disable-next-line no-template-curly-in-string
'eval(`(${function() {}})()`)',
'promise.then(mustCall())',
'promise.then(mustNotCall("expected a never settling promise"))',
'promise.then(common.mustCall((val) => { assert.ok(val); }))',
'await promise.then((val) => { assert.ok(val); })',
'checkSupportReusePort().then(test, () => common.skip("reuse port is not supported"))',
],
invalid: [
{
code: 'process.on("message", (code) => assert.strictEqual(code, 0))',
errors: [{ message }],
},
{
code: `
process.once("message", () => {
process.once("message", common.mustCall((code) => {
assert.strictEqual(code, 0);
}));
});
`,
errors: [{ message }],
},
{
code: 'function test() { process.on("message", (code) => assert.strictEqual(code, 0)) }',
errors: [{ message }],
},
{
code: 'process.once("message", (code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} });',
errors: [{ message }],
},
{
code: 'process.once("message", (code) => { assert(code) });',
errors: [{ message }],
},
{
code: 'process.once("message", (code) => { setTimeout(mustCall()); });',
errors: [{ message }],
},
{
code: 'process.once("message", (code) => { (() => { assert.strictEqual(code, 0)})(); });',
errors: [{ message }],
},
{
code: 'child.once("exit", common.mustCall((code) => {setImmediate(() => { assert.strictEqual(code, 0)}); }));',
errors: [{ message }],
},
{
code: 'require("node:assert").strictEqual(2+2, 5)',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'const { strictEqual } = require("node:assert")',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'const { strictEqual } = require("node:assert/strict")',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'const { strictEqual } = require("assert")',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'const { strictEqual } = require("assert/strict")',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'const someOtherName = require("assert")',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import assert, { strictEqual } from "assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import * as someOtherName from "assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import someOtherName from "assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import "assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import { strictEqual } from "node:assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import assert, { strictEqual } from "node:assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import * as someOtherName from "node:assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import someOtherName from "node:assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'import "node:assert"',
errors: [{ message: 'Only assign `node:assert` to `assert`' }],
},
{
code: 'promise.then(common.mustCall((val) => assert.ok(val)))',
errors: [{ message: 'Add an additional `.then(common.mustCall())` to detect if resulting promise settles' }],
},
{
code: 'promise.then(common.mustCall(async (val) => { assert.ok(val); }))',
errors: [{ message: 'Add an additional `.then(common.mustCall())` to detect if resulting promise settles' }],
},
{
code: 'promise.then(common.mustCall((val) => { assert.ok(val); return; }))',
errors: [{ message: 'Cannot mix `common.mustCall` and return statement inside a `.then` chain' }],
},
{
code: 'promise.then((val) => val).then(common.mustCall((val) => { assert.ok(val); })).then(common.mustCall())',
errors: [{ message: 'in a `.then` chain, only use mustCall as the last node' }],
},
{
code: 'promise.then((val) => val).then((val) => { assert.ok(val); })',
errors: [{
message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
}],
},
{
code: 'promise.then((val) => { assert.ok(val); })',
errors: [{
message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
}],
},
{
code: 'promise.then((val) => { assert.ok(val); }).then(common.mustCallAtLeast())',
errors: [{
message: 'Last item of `.then` list must use `mustCall()` or `mustNotCall("expected never settling promise")`',
}],
},
]
});