diff --git a/README.md b/README.md index b51c380..4992c2b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ engine.push(function (req, res, end) { }); ``` +`end()` **must** be called once during middleware processing, or an internal error will be returned. + Middleware functions can be `async`: ```js @@ -70,6 +72,7 @@ Middleware functions **must** return a falsy value or a function. If anything else is returned, the request will end with an error. If a middleware calls `end()`, its return value will be ignored. +The `end()` callback **must not** be passed a value. Engines can be nested by converting them to middleware using `JsonRpcEngine.asMiddleware()`: @@ -81,34 +84,14 @@ engine.push(subengine.asMiddleware()); ### Error Handling -Errors should be handled by throwing inside middleware functions. - -For backwards compatibility, you can also pass an error to the `end` callback, -or set the error on the response object, and then return or call `end`. - -Errors always take precedent over results. -If an error is detected, the response's `result` property will be deleted. +Errors must be handled by throwing them inside middleware functions. +A thrown error will immediately end middleware processing, +and return a response object with an `error` but no `result`. -All of the following examples are equivalent. -It does not matter of the middleware function is synchronous or asynchronous. +Errors assigned directly to `response.error` will be overwritten. +Non-`Error` values thrown inside middleware will be added under the `data` property of a new `Error`, +which will be used as the response `error`. -```js -// Throwing is preferred. -engine.push(function (req, res, end) { - throw new Error(); -}); - -// For backwards compatibility, you can also do this: -engine.push(function (req, res, end) { - end(new Error()); -}); - -engine.push(function (req, res, end) { - res.error = new Error(); - end(); -}); - -engine.push(function (req, res, end) { - res.error = new Error(); -}); -``` +The `error` property of a response object returned by this package will always +be a valid [JSON-RPC error](https://www.jsonrpc.org/specification#error_object), if present. +These error values are plain objects, without `stack` properties. diff --git a/src/JsonRpcEngine.ts b/src/JsonRpcEngine.ts index b41c483..de7e3e7 100644 --- a/src/JsonRpcEngine.ts +++ b/src/JsonRpcEngine.ts @@ -1,5 +1,6 @@ import SafeEventEmitter from '@metamask/safe-event-emitter'; import { errorCodes, EthereumRpcError, serializeError } from 'eth-rpc-errors'; +import { isValidCode } from 'eth-rpc-errors/dist/utils'; type Maybe = Partial | null | undefined; @@ -71,9 +72,7 @@ type MaybePromise = Promise | T; export type JsonRpcEngineReturnHandler = () => MaybePromise; -export type JsonRpcEngineEndCallback = ( - error?: JsonRpcEngineCallbackError -) => void; +export type JsonRpcEngineEndCallback = () => void; export type JsonRpcMiddleware = ( req: JsonRpcRequest, @@ -81,6 +80,18 @@ export type JsonRpcMiddleware = ( end: JsonRpcEngineEndCallback ) => MaybePromise; +const errorMessages = { + invalidReturnHandler: (value: unknown) => `JsonRpcEngine: Return handlers must be functions. Received: ${typeof value}`, + noAssignmentToResponse: `JsonRpcEngine: The response "error" property must not be directly assigned. Throw errors instead.`, + noErrorOrResult: `JsonRpcEngine: Response has no error or result`, + noErrorsToEnd: `JsonRpcEngine: "end" callback must not be passed any values. Received an error. Throw errors instead.`, + nonObjectRequest: (request: unknown) => `Requests must be plain objects. Received: ${typeof request}`, + nonStringMethod: (method: unknown) => `Must specify a string method. Received: ${typeof method}`, + noValuesToEnd: (value: unknown) => `JsonRpcEngine: "end" callback must not be passed any values. Received: ${typeof value}`, + nothingEndedRequest: `JsonRpcEngine: Nothing ended request.`, + threwNonError: `JsonRpcEngine: Middleware threw non-Error value.`, +}; + /** * A JSON-RPC request and response processor. * Give it a stack of middleware, pass it requests, and get back responses. @@ -169,24 +180,23 @@ export class JsonRpcEngine extends SafeEventEmitter { */ asMiddleware(): JsonRpcMiddleware { return async (req, res, end) => { - try { - const [ - middlewareError, - isComplete, - returnHandlers, - ] = await JsonRpcEngine._runAllMiddleware(req, res, this._middleware); - - if (isComplete) { - await JsonRpcEngine._runReturnHandlers(returnHandlers); - return end(middlewareError as JsonRpcEngineCallbackError); - } + const [ + middlewareError, + isComplete, + returnHandlers, + ] = await JsonRpcEngine._runAllMiddleware(req, res, this._middleware); - return async () => { - await JsonRpcEngine._runReturnHandlers(returnHandlers); - }; - } catch (error) { - return end(error); + if (isComplete) { + await JsonRpcEngine._runReturnHandlers(returnHandlers); + if (middlewareError) { + throw middlewareError; + } + return end(); } + + return async () => { + await JsonRpcEngine._runReturnHandlers(returnHandlers); + }; }; } @@ -264,7 +274,7 @@ export class JsonRpcEngine extends SafeEventEmitter { ) { const error = new EthereumRpcError( errorCodes.rpc.invalidRequest, - `Requests must be plain objects. Received: ${typeof callerReq}`, + errorMessages.nonObjectRequest(callerReq), { request: callerReq }, ); return cb(error, { id: undefined, jsonrpc: '2.0', error }); @@ -273,7 +283,7 @@ export class JsonRpcEngine extends SafeEventEmitter { if (typeof callerReq.method !== 'string') { const error = new EthereumRpcError( errorCodes.rpc.invalidRequest, - `Must specify a string method. Received: ${typeof callerReq.method}`, + errorMessages.nonStringMethod(callerReq.method), { request: callerReq }, ); return cb(error, { id: callerReq.id, jsonrpc: '2.0', error }); @@ -389,48 +399,44 @@ export class JsonRpcEngine extends SafeEventEmitter { resolve, ] = getDeferredPromise<[unknown, boolean]>(); - let ended = false; - const end: JsonRpcEngineEndCallback = (err?: unknown) => { - const error = err || res.error; - if (error) { - res.error = serializeError(error); - } - - // True indicates that the request should end - ended = true; - resolve([error, true]); + let endCalled = false; + const end: JsonRpcEngineEndCallback = (arg?: unknown) => { + JsonRpcEngine._validateEndState(req, res, arg); + endCalled = true; + resolve([null, true]); }; try { const returnHandler = await middleware(req, res, end); - // If the request is already ended, there's nothing to do. - if (!ended) { + // If "end" was not called, validate the response state, collect the + // middleware's return handler (if any), and indicate that the next + // middleware should be called. + if (!endCalled) { if (res.error) { - end(res.error); + throw new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.noAssignmentToResponse, + { request: req, responseError: res.error }, + ); } else { if (returnHandler) { if (typeof returnHandler !== 'function') { - end( - new EthereumRpcError( - errorCodes.rpc.internal, - `JsonRpcEngine: return handlers must be functions. ` + - `Received "${typeof returnHandler}" for request:\n${jsonify( - req, - )}`, - { request: req }, - ), + throw new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.invalidReturnHandler(returnHandler), + { request: req }, ); } returnHandlers.push(returnHandler); } - // False indicates that the request should not end resolve([null, false]); } } } catch (error) { - end(error); + JsonRpcEngine._processMiddlewareError(req, res, error); + resolve([res.error, true]); } return middlewareCallbackPromise; } @@ -459,24 +465,85 @@ export class JsonRpcEngine extends SafeEventEmitter { if (!('result' in res) && !('error' in res)) { throw new EthereumRpcError( errorCodes.rpc.internal, - `JsonRpcEngine: Response has no error or result for request:\n${jsonify( - req, - )}`, + errorMessages.noErrorOrResult, { request: req }, ); } + if (!isComplete) { throw new EthereumRpcError( errorCodes.rpc.internal, - `JsonRpcEngine: Nothing ended request:\n${jsonify(req)}`, + errorMessages.nothingEndedRequest, { request: req }, ); } } -} -function jsonify(request: JsonRpcRequest): string { - return JSON.stringify(request, null, 2); + /** + * Throws an appropriate error if the given response has its error property + * set, or if the given argument to an "end" callback is truthy. + * + * Must only be called in the internal implementation of an "end" callback. + */ + private static _validateEndState( + req: JsonRpcRequest, + res: PendingJsonRpcResponse, + endArg: unknown, + ): void { + if (endArg instanceof Error) { + throw new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.noErrorsToEnd, + { request: req, endCallbackCalledWith: endArg }, + ); + } else if (endArg) { + throw new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.noValuesToEnd(endArg), + { request: req, endCallbackCalledWith: endArg }, + ); + } + + if (res.error) { + throw new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.noAssignmentToResponse, + { request: req, responseError: res.error }, + ); + } + } + + /** + * Processes an error thrown during middleware processing, coerces it into + * a valid JSON-RPC error, and assigns it to the response. + * Attempts to preserve as many properties of the original error as possible. + * + * Must only be called in response to an error thrown by a consumer middleware. + */ + private static _processMiddlewareError( + req: JsonRpcRequest, + res: PendingJsonRpcResponse, + error: unknown, + ): void { + if (error instanceof Error) { + if (error instanceof EthereumRpcError) { + res.error = error; + } else { + const { code } = error as any; + res.error = new EthereumRpcError( + isValidCode(code) ? code : errorCodes.rpc.internal, + error.message, + { request: req, originalError: error }, + ); + } + } else { + res.error = new EthereumRpcError( + errorCodes.rpc.internal, + errorMessages.threwNonError, + { request: req, thrownValue: error }, + ); + } + } } function getDeferredPromise(): [ Promise, (value: T) => void] { diff --git a/test/createScaffoldMiddleware.spec.js b/test/createScaffoldMiddleware.spec.js index a350095..d5346d6 100644 --- a/test/createScaffoldMiddleware.spec.js +++ b/test/createScaffoldMiddleware.spec.js @@ -14,9 +14,8 @@ describe('createScaffoldMiddleware', function () { res.result = 42; end(); }, - 'method3': (_req, res, end) => { - res.error = new Error('method3'); - end(); + 'method3': (_req, _res, _end) => { + throw new Error('method3'); }, }; diff --git a/test/engine.spec.js b/test/engine.spec.js index a49778c..3a850f8 100644 --- a/test/engine.spec.js +++ b/test/engine.spec.js @@ -18,23 +18,23 @@ describe('JsonRpcEngine', function () { it('handle: returns error for invalid request parameter', async function () { const engine = new JsonRpcEngine(); let response = await engine.handle(null); - assert.equal(response.error.code, -32600, 'should have expected error'); - assert.equal(response.result, undefined, 'should have no results'); + assert.strictEqual(response.error.code, -32600, 'should have expected error'); + assert.strictEqual(response.result, undefined, 'should have no results'); response = await engine.handle(true); - assert.equal(response.error.code, -32600, 'should have expected error'); - assert.equal(response.result, undefined, 'should have no results'); + assert.strictEqual(response.error.code, -32600, 'should have expected error'); + assert.strictEqual(response.result, undefined, 'should have no results'); }); it('handle: returns error for invalid request method', async function () { const engine = new JsonRpcEngine(); let response = await engine.handle({ method: null }); - assert.equal(response.error.code, -32600, 'should have expected error'); - assert.equal(response.result, undefined, 'should have no results'); + assert.strictEqual(response.error.code, -32600, 'should have expected error'); + assert.strictEqual(response.result, undefined, 'should have no results'); response = await engine.handle({ method: true }); - assert.equal(response.error.code, -32600, 'should have expected error'); - assert.equal(response.result, undefined, 'should have no results'); + assert.strictEqual(response.error.code, -32600, 'should have expected error'); + assert.strictEqual(response.result, undefined, 'should have no results'); }); it('handle: basic middleware test 1', function (done) { @@ -50,7 +50,7 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, res) { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); - assert.equal(res.result, 42, 'has expected result'); + assert.strictEqual(res.result, 42, 'has expected result'); done(); }); }); @@ -69,8 +69,8 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, res) { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); - assert.equal(res.result, 42, 'has expected result'); - assert.equal(payload.method, 'hello', 'original request object is not mutated by middleware'); + assert.strictEqual(res.result, 42, 'has expected result'); + assert.strictEqual(payload.method, 'hello', 'original request object is not mutated by middleware'); done(); }); }); @@ -87,7 +87,7 @@ describe('JsonRpcEngine', function () { const res = await engine.handle(payload); assert.ok(res, 'has res'); - assert.equal(res.result, 42, 'has expected result'); + assert.strictEqual(res.result, 42, 'has expected result'); }); it('allow null result', function (done) { @@ -103,7 +103,7 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, res) { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); - assert.equal(res.result, null, 'has expected result'); + assert.strictEqual(res.result, null, 'has expected result'); done(); }); }); @@ -125,7 +125,7 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, res) { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); - assert.equal(res.result, 42, 'has expected result'); + assert.strictEqual(res.result, 42, 'has expected result'); done(); }); }); @@ -147,82 +147,182 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, res) { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); - assert.equal(res.result, 42, 'has expected result'); + assert.strictEqual(res.result, 42, 'has expected result'); done(); }); }); - it('erroring middleware test: end(error)', function (done) { + it('handles error thrown in synchronous middleware', function (done) { + const errorMessage = 'foo'; const engine = new JsonRpcEngine(); - engine.push(function (_req, _res, end) { - end(new Error('no bueno')); + engine.push((_req, _res, _end) => { + throw new Error(errorMessage); }); const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; - engine.handle(payload, function (err, res) { - assert.ok(err, 'did error'); - assert.ok(res, 'does have response'); - assert.ok(res.error, 'does have error on response'); + engine.handle(payload, (err, res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.message, errorMessage, 'should have correct error'); + assert.ok(res, 'should have response'); + assert.ok(res.error, 'should have error on response'); + assert.strictEqual(res.error.message, errorMessage, 'should have correct error'); assert.ok(!res.result, 'does not have result on response'); done(); }); }); - it('erroring middleware test: res.error -> return', function (done) { + it('handles error thrown in asynchronous middleware', function (done) { + const errorMessage = 'foo'; const engine = new JsonRpcEngine(); - engine.push(function (_req, res, _end) { - res.error = new Error('no bueno'); + engine.push(async (_req, _res, _end) => { + throw new Error(errorMessage); }); const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; - engine.handle(payload, function (err, res) { - assert.ok(err, 'did error'); - assert.ok(res, 'does have response'); - assert.ok(res.error, 'does have error on response'); + engine.handle(payload, (err, res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.message, errorMessage, 'should have correct error'); + assert.ok(res, 'should have response'); + assert.ok(res.error, 'should have error on response'); + assert.strictEqual(res.error.message, errorMessage, 'should have correct error'); assert.ok(!res.result, 'does not have result on response'); done(); }); }); - it('erroring middleware test: res.error -> end()', function (done) { + it('preserves valid error code of error thrown in middleware', function (done) { + const errorMessage = 'foo'; + const errorCode = -32005; const engine = new JsonRpcEngine(); - engine.push(function (_req, res, end) { - res.error = new Error('no bueno'); - end(); + engine.push((_req, _res, _end) => { + const err = new Error(errorMessage); + err.code = errorCode; + throw err; }); const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; - engine.handle(payload, function (err, res) { - assert.ok(err, 'did error'); - assert.ok(res, 'does have response'); - assert.ok(res.error, 'does have error on response'); + engine.handle(payload, (err, res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.message, errorMessage, 'should have correct error'); + assert.ok(res, 'should have response'); + assert.ok(res.error, 'should have error on response'); + assert.strictEqual(res.error.message, errorMessage, 'should have correct error message'); + assert.strictEqual(res.error.code, errorCode, 'should have correct error code'); assert.ok(!res.result, 'does not have result on response'); done(); }); }); - it('erroring middleware test: returning truthy non-function', function (done) { + it('handles non-Error value thrown in middleware', function (done) { + const errorMessage = 'foo'; const engine = new JsonRpcEngine(); - engine.push(function (_req, _res, _end) { + engine.push((_req, _res, _end) => { + throw errorMessage; + }); + + const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; + + engine.handle(payload, (err, _res) => { + assert.ok(err, 'should error'); + assert.notStrictEqual(err.message, errorMessage, 'should have different error'); + assert.strictEqual(err.data.thrownValue, errorMessage, 'should have preserved thrown value'); + done(); + }); + }); + + it('overwrites error passed to end callback', function (done) { + const errorMessage = 'foo'; + const engine = new JsonRpcEngine(); + + engine.push((_req, _res, end) => { + end(new Error(errorMessage)); + }); + + const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; + + engine.handle(payload, (err, _res) => { + assert.ok(err, 'should error'); + assert.notStrictEqual(err.message, errorMessage, 'should have different error'); + assert.strictEqual(err.data.endCallbackCalledWith.message, errorMessage, 'should have preserved original error'); + done(); + }); + }); + + it('throws error if truthy, non-Error passed to end callback', function (done) { + const truthyValue = 'foo'; + const engine = new JsonRpcEngine(); + + engine.push((_req, _res, end) => { + end(truthyValue); + }); + + const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; + + engine.handle(payload, (err, _res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.data.endCallbackCalledWith, truthyValue, 'should have preserved original error'); + done(); + }); + }); + + it('overwrites error set on response in middleware, without calling end', function (done) { + const errorMessage = 'foo'; + const engine = new JsonRpcEngine(); + + engine.push((_req, res, _end) => { + res.error = new Error(errorMessage); + }); + + const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; + + engine.handle(payload, (err, _res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.data.responseError.message, errorMessage, 'should have preserved original error'); + done(); + }); + }); + + it('overwrites error set on response in middleware, with calling end', function (done) { + const errorMessage = 'foo'; + const engine = new JsonRpcEngine(); + + engine.push((_req, res, end) => { + res.error = new Error(errorMessage); + end(); + }); + + const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; + + engine.handle(payload, (err, _res) => { + assert.ok(err, 'should error'); + assert.strictEqual(err.data.responseError.message, errorMessage, 'should have preserved original error'); + done(); + }); + }); + + it('errors if middleware returns truthy non-function', function (done) { + const engine = new JsonRpcEngine(); + + engine.push((_req, _res, _end) => { return true; }); const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; - engine.handle(payload, function (err, res) { + engine.handle(payload, (err, res) => { assert.ok(err, 'should error'); assert.ok(res, 'should have response'); assert.ok(res.error, 'should have error on response'); - assert.equal(res.error.code, -32603, 'should have expected error'); + assert.strictEqual(res.error.code, -32603, 'should have expected error'); assert.ok( - res.error.message.startsWith('JsonRpcEngine: return handlers must be functions.'), + res.error.message.startsWith('JsonRpcEngine: Return handlers must be functions.'), 'should have expected error', ); assert.ok(!res.result, 'should not have result on response'); @@ -246,9 +346,7 @@ describe('JsonRpcEngine', function () { engine.push(function (req, res, end) { if (req.id === 4) { - delete res.result; - res.error = new Error('foobar'); - return end(res.error); + throw new Error('foobar'); } res.result = req.id; return end(); @@ -265,12 +363,13 @@ describe('JsonRpcEngine', function () { assert.ifError(err, 'did not error'); assert.ok(res, 'has res'); assert.ok(Array.isArray(res), 'res is array'); - assert.equal(res[0].result, 1, 'has expected result'); - assert.equal(res[1].result, 2, 'has expected result'); - assert.equal(res[2].result, 3, 'has expected result'); + assert.strictEqual(res[0].result, 1, 'has expected result'); + assert.strictEqual(res[1].result, 2, 'has expected result'); + assert.strictEqual(res[2].result, 3, 'has expected result'); assert.ok(!res[3].result, 'has no result'); - assert.equal(res[3].error.code, -32603, 'has expected error'); - assert.equal(res[4].result, 5, 'has expected result'); + assert.strictEqual(res[3].error.message, 'foobar', 'has correct error'); + assert.strictEqual(res[3].error.code, -32603, 'has expected error'); + assert.strictEqual(res[4].result, 5, 'has expected result'); done(); }); }); @@ -281,8 +380,7 @@ describe('JsonRpcEngine', function () { engine.push(function (req, res, end) { if (req.id === 4) { delete res.result; - res.error = new Error('foobar'); - return end(res.error); + throw new Error('foobar'); } res.result = req.id; return end(); @@ -298,12 +396,13 @@ describe('JsonRpcEngine', function () { const res = await engine.handle(payload); assert.ok(res, 'has res'); assert.ok(Array.isArray(res), 'res is array'); - assert.equal(res[0].result, 1, 'has expected result'); - assert.equal(res[1].result, 2, 'has expected result'); - assert.equal(res[2].result, 3, 'has expected result'); + assert.strictEqual(res[0].result, 1, 'has expected result'); + assert.strictEqual(res[1].result, 2, 'has expected result'); + assert.strictEqual(res[2].result, 3, 'has expected result'); assert.ok(!res[3].result, 'has no result'); - assert.equal(res[3].error.code, -32603, 'has expected error'); - assert.equal(res[4].result, 5, 'has expected result'); + assert.strictEqual(res[3].error.code, -32603, 'has expected error'); + assert.strictEqual(res[3].error.message, 'foobar', 'has correct error'); + assert.strictEqual(res[4].result, 5, 'has expected result'); }); it('handle: batch payload with bad request object', async function () { @@ -322,17 +421,17 @@ describe('JsonRpcEngine', function () { const res = await engine.handle(payload); assert.ok(res, 'has res'); assert.ok(Array.isArray(res), 'res is array'); - assert.equal(res[0].result, 1, 'should have expected result'); - assert.equal(res[1].error.code, -32600, 'should have expected error'); + assert.strictEqual(res[0].result, 1, 'should have expected result'); + assert.strictEqual(res[1].error.code, -32600, 'should have expected error'); assert.ok(!res[1].result, 'should have no result'); - assert.equal(res[2].result, 3, 'should have expected result'); + assert.strictEqual(res[2].result, 3, 'should have expected result'); }); it('basic notifications', function (done) { const engine = new JsonRpcEngine(); engine.once('notification', (notif) => { - assert.equal(notif.method, 'test_notif'); + assert.strictEqual(notif.method, 'test_notif'); done(); }); @@ -411,11 +510,11 @@ describe('JsonRpcEngine', function () { engine.handle(payload, function (err, _res) { assert.ifError(err, 'did not error'); - assert.equal(events[0], '1-middleware', '(event 0) was "1-middleware"'); - assert.equal(events[1], '2-middleware', '(event 1) was "2-middleware"'); - assert.equal(events[2], '3-end', '(event 2) was "3-end"'); - assert.equal(events[3], '2-return', '(event 3) was "2-return"'); - assert.equal(events[4], '1-return', '(event 4) was "1-return"'); + assert.strictEqual(events[0], '1-middleware', '(event 0) was "1-middleware"'); + assert.strictEqual(events[1], '2-middleware', '(event 1) was "2-middleware"'); + assert.strictEqual(events[2], '3-end', '(event 2) was "3-end"'); + assert.strictEqual(events[3], '2-return', '(event 3) was "2-return"'); + assert.strictEqual(events[4], '1-return', '(event 4) was "1-return"'); done(); }); }); @@ -431,8 +530,8 @@ describe('JsonRpcEngine', function () { }; }); - engine.push(function (_req, _res, end) { - end(new Error('boom')); + engine.push(function (_req, _res, _end) { + throw new Error('boom'); }); const payload = { id: 1, jsonrpc: '2.0', method: 'hello' }; @@ -508,7 +607,7 @@ describe('JsonRpcEngine', function () { engine.handle(payload, (err, _res) => { assert.ok(err, 'did error'); - assert.equal(err.message, 'foo', 'error has expected message'); + assert.strictEqual(err.message, 'foo', 'error has expected message'); done(); }); }); @@ -531,7 +630,7 @@ describe('JsonRpcEngine', function () { engine.handle(payload, (err, _res) => { assert.ok(err, 'did error'); - assert.equal(err.message, 'foo', 'error has expected message'); + assert.strictEqual(err.message, 'foo', 'error has expected message'); done(); }); }); @@ -547,8 +646,9 @@ describe('JsonRpcEngine', function () { engine.handle(payload, (err, res) => { assert.ok(err, 'should have errored'); - assert.ok( - err.message.startsWith('JsonRpcEngine: Nothing ended request:'), + assert.strictEqual( + err.message, + 'JsonRpcEngine: Nothing ended request.', 'should have expected error message', ); assert.ok(!res.result, 'should not have result'); @@ -562,7 +662,7 @@ describe('JsonRpcEngine', function () { engine.handle([{}], (err) => { assert.ok(err, 'did error'); - assert.equal(err.message, 'foo', 'error has expected message'); + assert.strictEqual(err.message, 'foo', 'error has expected message'); done(); }); }); @@ -576,7 +676,7 @@ describe('JsonRpcEngine', function () { assert.fail('should have errored'); } catch (err) { assert.ok(err, 'did error'); - assert.equal(err.message, 'foo', 'error has expected message'); + assert.strictEqual(err.message, 'foo', 'error has expected message'); } }); });