From e03178ebc84e489e9058a72f576646a847470379 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 25 May 2023 10:59:55 -0600 Subject: [PATCH] fix(NODE-5296): construct error messages for AggregateErrors in Node16+ (#3682) --- global.d.ts | 1 + src/error.ts | 23 ++++++-- test/integration/node-specific/errors.ts | 54 +++++++++++++++++++ .../runner/filters/node_version_filter.js | 26 +++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/integration/node-specific/errors.ts create mode 100644 test/tools/runner/filters/node_version_filter.js diff --git a/global.d.ts b/global.d.ts index 010e123336..7c8dc818cc 100644 --- a/global.d.ts +++ b/global.d.ts @@ -12,6 +12,7 @@ declare global { serverless?: 'forbid' | 'allow' | 'require'; auth?: 'enabled' | 'disabled'; idmsMockServer?: true; + nodejs?: string; }; sessions?: { diff --git a/src/error.ts b/src/error.ts index f40dfd5229..3357adfa77 100644 --- a/src/error.ts +++ b/src/error.ts @@ -108,6 +108,10 @@ export interface ErrorDescription extends Document { errInfo?: Document; } +function isAggregateError(e: Error): e is Error & { errors: Error[] } { + return 'errors' in e && Array.isArray(e.errors); +} + /** * @public * @category Error @@ -131,15 +135,28 @@ export class MongoError extends Error { cause?: Error; // depending on the node version, this may or may not exist on the base constructor(message: string | Error) { + super(MongoError.buildErrorMessage(message)); if (message instanceof Error) { - super(message.message); this.cause = message; - } else { - super(message); } + this[kErrorLabels] = new Set(); } + /** @internal */ + private static buildErrorMessage(e: Error | string): string { + if (typeof e === 'string') { + return e; + } + if (isAggregateError(e) && e.message.length === 0) { + return e.errors.length === 0 + ? 'AggregateError has an empty errors array. Please check the `cause` property for more information.' + : e.errors.map(({ message }) => message).join(', '); + } + + return e.message; + } + override get name(): string { return 'MongoError'; } diff --git a/test/integration/node-specific/errors.ts b/test/integration/node-specific/errors.ts new file mode 100644 index 0000000000..7e86cf5ca3 --- /dev/null +++ b/test/integration/node-specific/errors.ts @@ -0,0 +1,54 @@ +import { expect } from 'chai'; + +import { MongoClient, MongoError, MongoServerSelectionError } from '../../mongodb'; + +describe('Error (Integration)', function () { + describe('AggregateErrors', function () { + for (const { errors, message } of [ + { + errors: [], + message: + 'AggregateError has an empty errors array. Please check the `cause` property for more information.' + }, + { errors: [new Error('message 1')], message: 'message 1' }, + { + errors: [new Error('message 1'), new Error('message 2')], + message: 'message 1, message 2' + } + ]) { + it( + `constructs the message properly with an array of ${errors.length} errors`, + { requires: { nodejs: '>=16' } }, + () => { + const error = new AggregateError(errors); + const mongoError = new MongoError(error); + + expect(mongoError.message).to.equal(message); + } + ); + } + + context('when the message on the AggregateError is non-empty', () => { + it(`uses the AggregateError's message`, { requires: { nodejs: '>=16' } }, () => { + const error = new AggregateError([new Error('non-empty')]); + error.message = 'custom error message'; + const mongoError = new MongoError(error); + expect(mongoError.message).to.equal('custom error message'); + }); + }); + + it('sets the AggregateError to the cause property', { requires: { nodejs: '>=16' } }, () => { + const error = new AggregateError([new Error('error 1')]); + const mongoError = new MongoError(error); + expect(mongoError.cause).to.equal(error); + }); + }); + + it('NODE-5296: handles aggregate errors from dns lookup', async function () { + const error = await MongoClient.connect('mongodb://localhost:27222', { + serverSelectionTimeoutMS: 1000 + }).catch(e => e); + expect(error).to.be.instanceOf(MongoServerSelectionError); + expect(error.message).not.to.be.empty; + }); +}); diff --git a/test/tools/runner/filters/node_version_filter.js b/test/tools/runner/filters/node_version_filter.js new file mode 100644 index 0000000000..0c16139f71 --- /dev/null +++ b/test/tools/runner/filters/node_version_filter.js @@ -0,0 +1,26 @@ +'use strict'; + +const { satisfies } = require('semver'); + +/** + * Filter for specific nodejs versions + * + * example: + * metadata: { + * requires: { + * nodejs: '>=14' + * } + * } + */ +class NodeVersionFilter { + filter(test) { + const nodeVersionRange = test?.metadata?.requires?.nodejs; + if (!nodeVersionRange) { + return true; + } + + return satisfies(process.version, nodeVersionRange); + } +} + +module.exports = NodeVersionFilter;