From 6221f05cb6de8a14051a83092cd886dc34472200 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 11 May 2023 14:28:53 -0600 Subject: [PATCH 1/3] test(no-story): bump mongodb-client-encryption pinned commits to 2.8.0 release (#3683) --- .evergreen/config.yml | 6 +++--- .evergreen/generate_evergreen_tasks.js | 2 +- .evergreen/run-custom-csfle-tests.sh | 6 ++---- .evergreen/run-serverless-tests.sh | 3 +-- .evergreen/run-tests.sh | 7 +++---- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index aac26b1ff6a..dae0efade8e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -2448,7 +2448,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 1524eac203e4145e9f4835e519f1e4663ff15953 + CSFLE_GIT_REF: c56c70340093070b1ef5c8a28190187eea21a6e9 - name: run-custom-csfle-tests-5.0-master tags: - run-custom-dependency-tests @@ -2478,7 +2478,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 1524eac203e4145e9f4835e519f1e4663ff15953 + CSFLE_GIT_REF: c56c70340093070b1ef5c8a28190187eea21a6e9 - name: run-custom-csfle-tests-rapid-master tags: - run-custom-dependency-tests @@ -2508,7 +2508,7 @@ tasks: - func: bootstrap kms servers - func: run custom csfle tests vars: - CSFLE_GIT_REF: 1524eac203e4145e9f4835e519f1e4663ff15953 + CSFLE_GIT_REF: c56c70340093070b1ef5c8a28190187eea21a6e9 - name: run-custom-csfle-tests-latest-master tags: - run-custom-dependency-tests diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 8532283244a..f72b38bed36 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -605,7 +605,7 @@ const oneOffFuncAsTasks = oneOffFuncs.map(oneOffFunc => ({ ] })); -const FLE_PINNED_COMMIT = '1524eac203e4145e9f4835e519f1e4663ff15953' +const FLE_PINNED_COMMIT = 'c56c70340093070b1ef5c8a28190187eea21a6e9'; for (const version of ['5.0', 'rapid', 'latest']) { for (const ref of [FLE_PINNED_COMMIT, 'master']) { diff --git a/.evergreen/run-custom-csfle-tests.sh b/.evergreen/run-custom-csfle-tests.sh index 7cd7f0210a2..957f5386fbd 100644 --- a/.evergreen/run-custom-csfle-tests.sh +++ b/.evergreen/run-custom-csfle-tests.sh @@ -52,8 +52,7 @@ popd # mongo-c-driver pushd libmongocrypt/bindings/node -# TODO(NODE-5180): remove --force option -npm install --force --production --ignore-scripts +npm install --production --ignore-scripts bash ./etc/build-static.sh popd # libmongocrypt/bindings/node @@ -82,8 +81,7 @@ pushd ../csfle-deps-tmp/libmongocrypt/bindings/node killall mongocryptd || true # only prod deps were installed earlier, install devDependencies here (except for mongodb!) -# TODO(NODE-5180): remove --force option -npm install --force --ignore-scripts +npm install --ignore-scripts # copy mongodb into CSFLE's node_modules rm -rf node_modules/mongodb diff --git a/.evergreen/run-serverless-tests.sh b/.evergreen/run-serverless-tests.sh index 55cacd6b6da..fbb21d77d42 100755 --- a/.evergreen/run-serverless-tests.sh +++ b/.evergreen/run-serverless-tests.sh @@ -10,8 +10,7 @@ if [ -z ${MONGODB_URI+omitted} ]; then echo "MONGODB_URI is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_USER+omitted} ]; then echo "SERVERLESS_ATLAS_USER is unset" && exit 1; fi if [ -z ${SERVERLESS_ATLAS_PASSWORD+omitted} ]; then echo "SERVERLESS_ATLAS_PASSWORD is unset" && exit 1; fi -# TODO(NODE-5180): remove --force option -npm install --force 'mongodb-client-encryption@alpha' +npm install mongodb-client-encryption npx mocha \ --config test/mocha_mongodb.json \ diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 7600e2ebeff..4b09680ad96 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -52,10 +52,9 @@ else source "$DRIVERS_TOOLS"/.evergreen/csfle/set-temp-creds.sh fi -# TODO(NODE-5180): remove --force option -npm install --force 'mongodb-client-encryption@alpha' -npm install --force @mongodb-js/zstd -npm install --force snappy +npm install mongodb-client-encryption +npm install @mongodb-js/zstd +npm install snappy export AUTH=$AUTH export SINGLE_MONGOS_LB_URI=${SINGLE_MONGOS_LB_URI} From 70f1357410c8a1e9bfd18157f9a8919ef655f917 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 10 May 2023 14:37:35 -0400 Subject: [PATCH 2/3] fix(NODE-5262): AWS Lambda metadata detection logic is too permissive (#3683) --- src/cmap/handshake/client_metadata.ts | 3 +- .../mongodb-handshake.prose.test.ts | 5 ++ .../cmap/handshake/client_metadata.test.ts | 65 ++++++++++++++----- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/cmap/handshake/client_metadata.ts b/src/cmap/handshake/client_metadata.ts index ab66328ca5a..fb1ba40b14e 100644 --- a/src/cmap/handshake/client_metadata.ts +++ b/src/cmap/handshake/client_metadata.ts @@ -176,7 +176,8 @@ export function getFAASEnv(): Map | null { VERCEL_REGION = '' } = process.env; - const isAWSFaaS = AWS_EXECUTION_ENV.length > 0 || AWS_LAMBDA_RUNTIME_API.length > 0; + const isAWSFaaS = + AWS_EXECUTION_ENV.startsWith('AWS_Lambda_') || AWS_LAMBDA_RUNTIME_API.length > 0; const isAzureFaaS = FUNCTIONS_WORKER_RUNTIME.length > 0; const isGCPFaaS = K_SERVICE.length > 0 || FUNCTION_NAME.length > 0; const isVercelFaaS = VERCEL.length > 0; diff --git a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts index cb20e9ac097..fefb10b86aa 100644 --- a/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts +++ b/test/integration/mongodb-handshake/mongodb-handshake.prose.test.ts @@ -71,6 +71,11 @@ describe('Handshake Prose Tests', function () { ['AWS_EXECUTION_ENV', 'AWS_Lambda_java8'], ['AWS_LAMBDA_FUNCTION_MEMORY_SIZE', 'big'] ] + }, + { + expectedProvider: undefined, + context: '8. Invalid - AWS_EXECUTION_ENV does not start with "AWS_Lambda_"', + env: [['AWS_EXECUTION_ENV', 'EC2']] } ]; diff --git a/test/unit/cmap/handshake/client_metadata.test.ts b/test/unit/cmap/handshake/client_metadata.test.ts index 98eef138e60..590a27a7d10 100644 --- a/test/unit/cmap/handshake/client_metadata.test.ts +++ b/test/unit/cmap/handshake/client_metadata.test.ts @@ -37,8 +37,7 @@ describe('client metadata module', () => { }); describe('getFAASEnv()', function () { - const tests: Array<[string, string]> = [ - ['AWS_EXECUTION_ENV', 'aws.lambda'], + const tests: Array<[envVariable: string, provider: string]> = [ ['AWS_LAMBDA_RUNTIME_API', 'aws.lambda'], ['FUNCTIONS_WORKER_RUNTIME', 'azure.func'], ['K_SERVICE', 'gcp.func'], @@ -46,9 +45,9 @@ describe('client metadata module', () => { ['VERCEL', 'vercel'] ]; for (const [envVariable, provider] of tests) { - context(`when ${envVariable} is in the environment`, () => { + context(`when ${envVariable} is set to a non-empty string`, () => { before(() => { - process.env[envVariable] = 'non empty string'; + process.env[envVariable] = 'non_empty_string'; }); after(() => { delete process.env[envVariable]; @@ -56,9 +55,45 @@ describe('client metadata module', () => { it('determines the correct provider', () => { expect(getFAASEnv()?.get('name')).to.equal(provider); }); + + context(`when ${envVariable} is set to an empty string`, () => { + before(() => { + process.env[envVariable] = ''; + }); + after(() => { + delete process.env[envVariable]; + }); + it('returns null', () => { + expect(getFAASEnv()).to.be.null; + }); + }); }); } + context('when AWS_EXECUTION_ENV starts with "AWS_Lambda_"', () => { + before(() => { + process.env.AWS_EXECUTION_ENV = 'AWS_Lambda_correctStartString'; + }); + after(() => { + delete process.env.AWS_EXECUTION_ENV; + }); + it('indicates the runtime is aws lambda', () => { + expect(getFAASEnv()?.get('name')).to.equal('aws.lambda'); + }); + }); + + context('when AWS_EXECUTION_ENV does not start with "AWS_Lambda_"', () => { + before(() => { + process.env.AWS_EXECUTION_ENV = 'AWS_LambdaIncorrectStartString'; + }); + after(() => { + delete process.env.AWS_EXECUTION_ENV; + }); + it('returns null', () => { + expect(getFAASEnv()).to.be.null; + }); + }); + context('when there is no FAAS provider data in the env', () => { it('returns null', () => { expect(getFAASEnv()).to.be.null; @@ -69,9 +104,9 @@ describe('client metadata module', () => { context('unrelated environments', () => { before(() => { // aws - process.env.AWS_EXECUTION_ENV = 'non-empty-string'; + process.env.AWS_EXECUTION_ENV = 'AWS_Lambda_non_empty_string'; // azure - process.env.FUNCTIONS_WORKER_RUNTIME = 'non-empty-string'; + process.env.FUNCTIONS_WORKER_RUNTIME = 'non_empty_string'; }); after(() => { delete process.env.AWS_EXECUTION_ENV; @@ -85,10 +120,10 @@ describe('client metadata module', () => { context('vercel and aws which share env variables', () => { before(() => { // vercel - process.env.VERCEL = 'non-empty-string'; + process.env.VERCEL = 'non_empty_string'; // aws - process.env.AWS_EXECUTION_ENV = 'non-empty-string'; - process.env.AWS_LAMBDA_RUNTIME_API = 'non-empty-string'; + process.env.AWS_EXECUTION_ENV = 'non_empty_string'; + process.env.AWS_LAMBDA_RUNTIME_API = 'non_empty_string'; }); after(() => { delete process.env.VERCEL; @@ -383,7 +418,7 @@ describe('client metadata module', () => { aws: [ { context: 'no additional metadata', - env: [['AWS_EXECUTION_ENV', 'non-empty string']], + env: [['AWS_EXECUTION_ENV', 'AWS_Lambda_non_empty_string']], outcome: { name: 'aws.lambda' } @@ -391,7 +426,7 @@ describe('client metadata module', () => { { context: 'AWS_REGION provided', env: [ - ['AWS_EXECUTION_ENV', 'non-empty string'], + ['AWS_EXECUTION_ENV', 'AWS_Lambda_non_empty_string'], ['AWS_REGION', 'non-null'] ], outcome: { @@ -402,7 +437,7 @@ describe('client metadata module', () => { { context: 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE provided', env: [ - ['AWS_EXECUTION_ENV', 'non-empty string'], + ['AWS_EXECUTION_ENV', 'AWS_Lambda_non_empty_string'], ['AWS_LAMBDA_FUNCTION_MEMORY_SIZE', '3'] ], outcome: { @@ -506,7 +541,7 @@ describe('client metadata module', () => { context('when a numeric FAAS env variable is not numerically parsable', () => { before(() => { - process.env.AWS_EXECUTION_ENV = 'non-empty-string'; + process.env.AWS_EXECUTION_ENV = 'AWS_Lambda_non_empty_string'; process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '123not numeric'; }); @@ -525,7 +560,7 @@ describe('client metadata module', () => { context('when faas region is too large', () => { beforeEach('1. Omit fields from `env` except `env.name`.', () => { sinon.stub(process, 'env').get(() => ({ - AWS_EXECUTION_ENV: 'iLoveJavaScript', + AWS_EXECUTION_ENV: 'AWS_Lambda_iLoveJavaScript', AWS_REGION: 'a'.repeat(512) })); }); @@ -542,7 +577,7 @@ describe('client metadata module', () => { context('release too large', () => { beforeEach('2. Omit fields from `os` except `os.type`.', () => { sinon.stub(process, 'env').get(() => ({ - AWS_EXECUTION_ENV: 'iLoveJavaScript', + AWS_EXECUTION_ENV: 'AWS_Lambda_iLoveJavaScript', AWS_REGION: 'abc' })); sinon.stub(os, 'release').returns('a'.repeat(512)); From f4e31ba5161af185d134e514e9d852c6cb676595 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 23 May 2023 14:02:48 -0600 Subject: [PATCH 3/3] fix(NODE-5311): construct error messages for AggregateErrors in Node16+ (#3683) --- global.d.ts | 1 + src/error.ts | 23 ++++++-- test/integration/node-specific/errors.ts | 54 +++++++++++++++++++ .../runner/filters/node_version_filter.js | 25 +++++++++ 4 files changed, 100 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 83c66eace0d..cf1b75dae7d 100644 --- a/global.d.ts +++ b/global.d.ts @@ -11,6 +11,7 @@ declare global { clientSideEncryption?: boolean; serverless?: 'forbid' | 'allow' | 'require'; auth?: 'enabled' | 'disabled'; + nodejs?: string; }; sessions?: { diff --git a/src/error.ts b/src/error.ts index 0e9590423f7..6e5f6a515ae 100644 --- a/src/error.ts +++ b/src/error.ts @@ -107,6 +107,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 @@ -130,15 +134,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 00000000000..c487f29cfcb --- /dev/null +++ b/test/integration/node-specific/errors.ts @@ -0,0 +1,54 @@ +import { expect } from 'chai'; + +import { MongoClient, MongoError, MongoServerSelectionError } from '../../../src'; + +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 00000000000..61c11adc643 --- /dev/null +++ b/test/tools/runner/filters/node_version_filter.js @@ -0,0 +1,25 @@ +'use strict'; + +const { satisfies } = require('semver'); + +/** + * Filter for specific nodejs versions + * + * example: + * metadata: { + * requires: { + * nodejs: '>=14' + * } + * } + */ +class NodeVersionFilter { + filter(test) { + if (!test.metadata) return true; + if (!test.metadata.requires) return true; + if (!test.metadata.requires.nodejs) return true; + + return satisfies(process.version, test.metadata.requires.nodejs); + } +} + +module.exports = NodeVersionFilter;