Skip to content

Commit 655ab65

Browse files
BridgeARjasnell
authored andcommitted
assert: validate the block return type
This makes sure the returned value when calling `block` is actually of type promise in case `assert.rejects` or `assert.doesNotReject` is called. PR-URL: #19886 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
1 parent 7f3838b commit 655ab65

File tree

5 files changed

+56
-18
lines changed

5 files changed

+56
-18
lines changed

doc/api/assert.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,10 @@ function and awaits the returned promise to complete. It will then check that
387387
the promise is not rejected.
388388

389389
If `block` is a function and it throws an error synchronously,
390-
`assert.doesNotReject()` will return a rejected Promise with that error without
391-
checking the error handler.
390+
`assert.doesNotReject()` will return a rejected Promise with that error. If the
391+
function does not return a promise, `assert.doesNotReject()` will return a
392+
rejected Promise with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases the
393+
error handler is skipped.
392394

393395
Please note: Using `assert.doesNotReject()` is actually not useful because there
394396
is little benefit by catching a rejection and then rejecting it again. Instead,
@@ -929,8 +931,10 @@ function and awaits the returned promise to complete. It will then check that
929931
the promise is rejected.
930932

931933
If `block` is a function and it throws an error synchronously,
932-
`assert.rejects()` will return a rejected Promise with that error without
933-
checking the error handler.
934+
`assert.rejects()` will return a rejected Promise with that error. If the
935+
function does not return a promise, `assert.rejects()` will return a rejected
936+
Promise with an [`ERR_INVALID_RETURN_VALUE`][] error. In both cases the error
937+
handler is skipped.
934938

935939
Besides the async nature to await the completion behaves identically to
936940
[`assert.throws()`][].
@@ -1138,6 +1142,7 @@ assert.throws(throwingFirst, /Second$/);
11381142
Due to the confusing notation, it is recommended not to use a string as the
11391143
second argument. This might lead to difficult-to-spot errors.
11401144

1145+
[`ERR_INVALID_RETURN_VALUE`]: errors.html#errors_err_invalid_return_value
11411146
[`Class`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
11421147
[`Error.captureStackTrace`]: errors.html#errors_error_capturestacktrace_targetobject_constructoropt
11431148
[`Error`]: errors.html#errors_class_error

doc/api/errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,12 @@ An invalid `options.protocol` was passed.
11861186
Both `breakEvalOnSigint` and `eval` options were set in the REPL config, which
11871187
is not supported.
11881188

1189+
<a id="ERR_INVALID_RETURN_VALUE"></a>
1190+
### ERR_INVALID_RETURN_VALUE
1191+
1192+
Thrown in case a function option does not return an expected value on execution.
1193+
For example when a function is expected to return a promise.
1194+
11891195
<a id="ERR_INVALID_SYNC_FORK_INPUT"></a>
11901196
### ERR_INVALID_SYNC_FORK_INPUT
11911197

lib/assert.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const {
3030
errorCache,
3131
codes: {
3232
ERR_AMBIGUOUS_ARGUMENT,
33-
ERR_INVALID_ARG_TYPE
33+
ERR_INVALID_ARG_TYPE,
34+
ERR_INVALID_RETURN_VALUE
3435
}
3536
} = require('internal/errors');
3637
const { openSync, closeSync, readSync } = require('fs');
@@ -455,6 +456,11 @@ async function waitForActual(block) {
455456
if (typeof block === 'function') {
456457
// Return a rejected promise if `block` throws synchronously.
457458
resultPromise = block();
459+
// Fail in case no promise is returned.
460+
if (!checkIsPromise(resultPromise)) {
461+
throw new ERR_INVALID_RETURN_VALUE('instance of Promise',
462+
'block', resultPromise);
463+
}
458464
} else if (checkIsPromise(block)) {
459465
resultPromise = block;
460466
} else {

lib/internal/errors.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,16 @@ E('ERR_INVALID_PROTOCOL',
904904
TypeError);
905905
E('ERR_INVALID_REPL_EVAL_CONFIG',
906906
'Cannot specify both "breakEvalOnSigint" and "eval" for REPL', TypeError);
907+
E('ERR_INVALID_RETURN_VALUE', (input, name, value) => {
908+
let type;
909+
if (value && value.constructor && value.constructor.name) {
910+
type = `instance of ${value.constructor.name}`;
911+
} else {
912+
type = `type ${typeof value}`;
913+
}
914+
return `Expected ${input} to be returned from the "${name}"` +
915+
` function but got ${type}.`;
916+
}, TypeError);
907917
E('ERR_INVALID_SYNC_FORK_INPUT',
908918
'Asynchronous forks do not support Buffer, Uint8Array or string input: %s',
909919
TypeError);

test/parallel/test-assert-async.js

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,25 @@ const promises = [];
2929
`${err.name} is not instance of AssertionError`);
3030
assert.strictEqual(err.code, 'ERR_ASSERTION');
3131
assert.strictEqual(err.message,
32-
'Missing expected rejection (handler).');
32+
'Missing expected rejection (mustNotCall).');
3333
assert.strictEqual(err.operator, 'rejects');
3434
assert.ok(!err.stack.includes('at Function.rejects'));
3535
return true;
3636
};
3737

38-
let promise = assert.rejects(async () => {}, handler);
39-
promises.push(assert.rejects(promise, handler));
38+
let promise = assert.rejects(async () => {}, common.mustNotCall());
39+
promises.push(assert.rejects(promise, common.mustCall(handler)));
4040

41-
promise = assert.rejects(() => {}, handler);
42-
promises.push(assert.rejects(promise, handler));
41+
promise = assert.rejects(() => {}, common.mustNotCall());
42+
promises.push(assert.rejects(promise, {
43+
name: 'TypeError [ERR_INVALID_RETURN_VALUE]',
44+
code: 'ERR_INVALID_RETURN_VALUE',
45+
message: 'Expected instance of Promise to be returned ' +
46+
'from the "block" function but got type undefined.'
47+
}));
4348

44-
promise = assert.rejects(Promise.resolve(), handler);
45-
promises.push(assert.rejects(promise, handler));
49+
promise = assert.rejects(Promise.resolve(), common.mustNotCall());
50+
promises.push(assert.rejects(promise, common.mustCall(handler)));
4651
}
4752

4853
{
@@ -67,7 +72,13 @@ promises.push(assert.rejects(
6772
// Check `assert.doesNotReject`.
6873
{
6974
// `assert.doesNotReject` accepts a function or a promise as first argument.
70-
promises.push(assert.doesNotReject(() => {}));
75+
const promise = assert.doesNotReject(() => new Map(), common.mustNotCall());
76+
promises.push(assert.rejects(promise, {
77+
message: 'Expected instance of Promise to be returned ' +
78+
'from the "block" function but got instance of Map.',
79+
code: 'ERR_INVALID_RETURN_VALUE',
80+
name: 'TypeError [ERR_INVALID_RETURN_VALUE]'
81+
}));
7182
promises.push(assert.doesNotReject(async () => {}));
7283
promises.push(assert.doesNotReject(Promise.resolve()));
7384
}
@@ -93,14 +104,14 @@ promises.push(assert.rejects(
93104

94105
const rejectingFn = async () => assert.fail();
95106

96-
let promise = assert.doesNotReject(rejectingFn, handler1);
97-
promises.push(assert.rejects(promise, handler2));
107+
let promise = assert.doesNotReject(rejectingFn, common.mustCall(handler1));
108+
promises.push(assert.rejects(promise, common.mustCall(handler2)));
98109

99-
promise = assert.doesNotReject(rejectingFn(), handler1);
100-
promises.push(assert.rejects(promise, handler2));
110+
promise = assert.doesNotReject(rejectingFn(), common.mustCall(handler1));
111+
promises.push(assert.rejects(promise, common.mustCall(handler2)));
101112

102113
promise = assert.doesNotReject(() => assert.fail(), common.mustNotCall());
103-
promises.push(assert.rejects(promise, handler1));
114+
promises.push(assert.rejects(promise, common.mustCall(handler1)));
104115
}
105116

106117
promises.push(assert.rejects(

0 commit comments

Comments
 (0)