From 9881228977ff4f5d03980a4a74403f65eec4cd69 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 14:30:53 -0400 Subject: [PATCH 01/28] chore(NODE-3924): update package-lock.json --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 05084a792b8..59d024a7f31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "yargs": "^17.7.2" }, "engines": { - "node": ">=14.20.1" + "node": ">=16.20.1" }, "optionalDependencies": { "saslprep": "^1.0.3" From b7763aea95e1660ddf733988d3f923289c912a70 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 14:31:50 -0400 Subject: [PATCH 02/28] feat(NODE-3924)!: read tls files async --- src/connection_string.ts | 17 ++++++++--------- src/mongo_client.ts | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index b71ec691494..c1822ed0ad8 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1,5 +1,4 @@ import * as dns from 'dns'; -import * as fs from 'fs'; import ConnectionString from 'mongodb-connection-string-url'; import { URLSearchParams } from 'url'; @@ -1097,16 +1096,16 @@ export const OPTIONS = { } }, tlsCAFile: { - target: 'ca', - transform({ values: [value] }) { - return fs.readFileSync(String(value), { encoding: 'ascii' }); - } + target: 'caFileName', + type: 'string' + }, + tlsCertificateFile: { + target: 'certFileName', + type: 'string' }, tlsCertificateKeyFile: { - target: 'key', - transform({ values: [value] }) { - return fs.readFileSync(String(value), { encoding: 'ascii' }); - } + target: 'certKeyFileName', + type: 'string' }, tlsCertificateKeyFilePassword: { target: 'passphrase', diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 442384868de..4861fa7895b 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -1,3 +1,4 @@ +import { promises as fs } from 'fs'; import type { TcpNetConnectOpts } from 'net'; import type { ConnectionOptions as TLSConnectionOptions, TLSSocketOptions } from 'tls'; import { promisify } from 'util'; @@ -429,6 +430,18 @@ export class MongoClient extends TypedEventEmitter { const options = this[kOptions]; + if (options.tls) { + if (!options.ca && typeof options.caFileName === 'string' && options.caFileName.length > 0) { + options.ca = await fs.readFile(options.caFileName, { encoding: 'utf8' }); + } + if ( + !options.key && + typeof options.certKeyFileName === 'string' && + options.certKeyFileName.length > 0 + ) { + options.key = await fs.readFile(options.certKeyFileName, { encoding: 'utf8' }); + } + } if (typeof options.srvHost === 'string') { const hosts = await resolveSRVRecord(options); @@ -780,6 +793,9 @@ export interface MongoOptions */ tls: boolean; + caFileName?: string; + certKeyFileName?: string; + /** @internal */ [featureFlag: symbol]: any; From 0fbb2f9c1b5780e095b4c3f136bdcb5a2f7925c6 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 14:32:15 -0400 Subject: [PATCH 03/28] test(NODE-3924): add unit test --- test/unit/connection_string.test.ts | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index b0ca1e8a426..1ccf6a5f230 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -1,5 +1,6 @@ import { expect } from 'chai'; import * as dns from 'dns'; +import { promises as fs } from 'fs'; import * as sinon from 'sinon'; import { @@ -16,7 +17,8 @@ import { MongoParseError, MongoRuntimeError, parseOptions, - resolveSRVRecord + resolveSRVRecord, + Topology } from '../mongodb'; describe('Connection String', function () { @@ -479,6 +481,35 @@ describe('Connection String', function () { }); }); + context('when tls filepaths are provided', () => { + beforeEach(async () => { + sinon.stub(Topology.prototype, 'connect').yieldsRight(); + await fs.writeFile('caFileName.txt', 'abc', { encoding: 'utf8' }); + await fs.writeFile('certKeyFileName.txt', 'abc', { encoding: 'utf8' }); + }); + + afterEach(() => sinon.restore()); + afterEach(() => fs.unlink('caFileName.txt')); + afterEach(() => fs.unlink('certKeyFileName.txt')); + + it('should read in files async', async () => { + const client = new MongoClient('mongodb://iLoveJavaScript?tls=true', { + tlsCAFile: 'caFileName.txt', + tlsCertificateKeyFile: 'certKeyFileName.txt' + }); + + expect(client.options).property('caFileName', 'caFileName.txt'); + expect(client.options).property('certKeyFileName', 'certKeyFileName.txt'); + expect(client.options).not.have.property('ca'); + expect(client.options).not.have.property('key'); + + await client.connect(); + + expect(client.options).property('ca', 'abc'); + expect(client.options).property('key', 'abc'); + }); + }); + describe('validation', function () { it('should validate compressors options', function () { let thrownError; From 28a2ba5d37d6b1a33e3584a82ad3011dfafb9ced Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 15:22:49 -0400 Subject: [PATCH 04/28] test(NODE-3924): move tests to manual tests --- package.json | 2 +- test/manual/tls_support.test.js | 44 -------------- test/manual/tls_support.test.ts | 102 ++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 45 deletions(-) delete mode 100644 test/manual/tls_support.test.js create mode 100644 test/manual/tls_support.test.ts diff --git a/package.json b/package.json index 5d4f13320c8..c160412f1fc 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "check:oidc-azure": "mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_oidc_azure.prose.test.ts", "check:ocsp": "mocha --config test/manual/mocharc.json test/manual/ocsp_support.test.js", "check:kerberos": "nyc mocha --config test/manual/mocharc.json test/manual/kerberos.test.ts", - "check:tls": "mocha --config test/manual/mocharc.json test/manual/tls_support.test.js", + "check:tls": "mocha --config test/manual/mocharc.json test/manual/tls_support.test.ts", "check:ldap": "nyc mocha --config test/manual/mocharc.json test/manual/ldap.test.js", "check:socks5": "mocha --config test/manual/mocharc.json test/manual/socks5.test.ts", "check:csfle": "mocha --config test/mocha_mongodb.json test/integration/client-side-encryption", diff --git a/test/manual/tls_support.test.js b/test/manual/tls_support.test.js deleted file mode 100644 index f6bbca59ebf..00000000000 --- a/test/manual/tls_support.test.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; -const { MongoClient } = require('../mongodb'); -const { LEGACY_HELLO_COMMAND } = require('../mongodb'); - -const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE']; - -describe('TLS Support', function () { - for (let key of REQUIRED_ENV) { - if (process.env[key] == null) { - throw new Error(`skipping SSL tests, ${key} environment variable is not defined`); - } - } - - const connectionString = process.env.MONGODB_URI; - const tlsCertificateKeyFile = process.env.SSL_KEY_FILE; - const tlsCAFile = process.env.SSL_CA_FILE; - const tlsSettings = { tls: true, tlsCertificateKeyFile, tlsCAFile }; - - it( - 'should connect with tls via client options', - makeConnectionTest(connectionString, tlsSettings) - ); - - it( - 'should connect with tls via url options', - makeConnectionTest( - `${connectionString}?${Object.keys(tlsSettings) - .map(key => `${key}=${tlsSettings[key]}`) - .join('&')}` - ) - ); -}); - -function makeConnectionTest(connectionString, clientOptions) { - return function () { - const client = new MongoClient(connectionString, clientOptions); - - return client - .connect() - .then(() => client.db('admin').command({ [LEGACY_HELLO_COMMAND]: 1 })) - .then(() => client.db('test').collection('test').findOne({})) - .then(() => client.close()); - }; -} diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts new file mode 100644 index 00000000000..85dd40da6df --- /dev/null +++ b/test/manual/tls_support.test.ts @@ -0,0 +1,102 @@ +import { MongoClient, MongoClientOptions, Topology } from '../mongodb'; +import { LEGACY_HELLO_COMMAND } from '../mongodb'; +import { expect } from 'chai'; +import { promises as fs } from 'fs'; +import * as sinon from 'sinon'; + +const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE']; + +describe('TLS Support', function() { + for (let key of REQUIRED_ENV) { + if (process.env[key] == null) { + throw new Error(`skipping SSL tests, ${key} environment variable is not defined`); + } + } + + const CONNECTION_STRING = process.env.MONGODB_URI as string; + const TLS_CERT_KEY_FILE = process.env.SSL_KEY_FILE as string; + const TLS_CA_FILE = process.env.SSL_CA_FILE as string; + const tlsSettings = { tls: true, tlsCertificateKeyFile: TLS_CERT_KEY_FILE, tlsCAFile: TLS_CA_FILE }; + + it( + 'should connect with tls via client options', + makeConnectionTest(CONNECTION_STRING, tlsSettings) + ); + + it( + 'should connect with tls via url options', + makeConnectionTest( + `${CONNECTION_STRING}?${Object.keys(tlsSettings) + .map(key => `${key}=${tlsSettings[key]}`) + .join('&')}` + ) + ); + + context('when tls filepaths are provided', () => { + let client: MongoClient; + context('when tls filepaths have length > 0', () => { + beforeEach(async () => { + client = new MongoClient(CONNECTION_STRING, tlsSettings); + }); + + afterEach(async () => { + if (client) await client.close(); + }); + + it('should read in files async at connect time', async () => { + expect(client.options).property('caFileName', TLS_CA_FILE); + expect(client.options).property('certKeyFileName', TLS_CERT_KEY_FILE); + expect(client.options).not.have.property('ca'); + expect(client.options).not.have.property('key'); + + await client.connect(); + + expect(client.options).property('ca', 'abc'); + expect(client.options).property('key', 'abc'); + }); + + context('when client has been opened and closed more than once', function() { + it('should only read files once', async () => { + await client.connect(); + await client.close(); + + const caFileAccessTime = (await fs.stat(TLS_CA_FILE)).atime; + const certKeyFileAccessTime = (await fs.stat(TLS_CERT_KEY_FILE)).atime; + + await client.connect(); + + expect((await fs.stat(TLS_CA_FILE)).atime).to.deep.equal(caFileAccessTime); + expect((await fs.stat(TLS_CERT_KEY_FILE)).atime).to.deep.equal(certKeyFileAccessTime); + }); + }); + }); + context('when tls filepaths have length == 0', () => { + beforeEach(async () => { + client = new MongoClient(CONNECTION_STRING, { + tls: true, + tlsCAFile: '', + tlsCertificateKeyFile: '' + }); + }); + afterEach(() => sinon.restore()); + + it('ignores file path and fails to connect', () => { + expect(async () => { + await client.connect(); + }).to.throw(); + }); + }); + }); +}); + +function makeConnectionTest(connectionString: string, clientOptions?: MongoClientOptions) { + return async function() { + const client = new MongoClient(connectionString, clientOptions); + + await client + .connect(); + await client.db('admin').command({ [LEGACY_HELLO_COMMAND]: 1 }); + await client.db('test').collection('test').findOne({}); + return await client.close(); + }; +} From 8bc7296548a9a7b0d61fd65e1427aaeef1050fb0 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 15:24:37 -0400 Subject: [PATCH 05/28] style(NODE-3924): eslint --- test/manual/tls_support.test.ts | 21 ++++++++++-------- test/unit/connection_string.test.ts | 33 +---------------------------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 85dd40da6df..9ef65db7d17 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -1,13 +1,13 @@ -import { MongoClient, MongoClientOptions, Topology } from '../mongodb'; -import { LEGACY_HELLO_COMMAND } from '../mongodb'; import { expect } from 'chai'; import { promises as fs } from 'fs'; import * as sinon from 'sinon'; +import { LEGACY_HELLO_COMMAND, MongoClient, type MongoClientOptions } from '../mongodb'; + const REQUIRED_ENV = ['MONGODB_URI', 'SSL_KEY_FILE', 'SSL_CA_FILE']; -describe('TLS Support', function() { - for (let key of REQUIRED_ENV) { +describe('TLS Support', function () { + for (const key of REQUIRED_ENV) { if (process.env[key] == null) { throw new Error(`skipping SSL tests, ${key} environment variable is not defined`); } @@ -16,7 +16,11 @@ describe('TLS Support', function() { const CONNECTION_STRING = process.env.MONGODB_URI as string; const TLS_CERT_KEY_FILE = process.env.SSL_KEY_FILE as string; const TLS_CA_FILE = process.env.SSL_CA_FILE as string; - const tlsSettings = { tls: true, tlsCertificateKeyFile: TLS_CERT_KEY_FILE, tlsCAFile: TLS_CA_FILE }; + const tlsSettings = { + tls: true, + tlsCertificateKeyFile: TLS_CERT_KEY_FILE, + tlsCAFile: TLS_CA_FILE + }; it( 'should connect with tls via client options', @@ -55,7 +59,7 @@ describe('TLS Support', function() { expect(client.options).property('key', 'abc'); }); - context('when client has been opened and closed more than once', function() { + context('when client has been opened and closed more than once', function () { it('should only read files once', async () => { await client.connect(); await client.close(); @@ -90,11 +94,10 @@ describe('TLS Support', function() { }); function makeConnectionTest(connectionString: string, clientOptions?: MongoClientOptions) { - return async function() { + return async function () { const client = new MongoClient(connectionString, clientOptions); - await client - .connect(); + await client.connect(); await client.db('admin').command({ [LEGACY_HELLO_COMMAND]: 1 }); await client.db('test').collection('test').findOne({}); return await client.close(); diff --git a/test/unit/connection_string.test.ts b/test/unit/connection_string.test.ts index 1ccf6a5f230..b0ca1e8a426 100644 --- a/test/unit/connection_string.test.ts +++ b/test/unit/connection_string.test.ts @@ -1,6 +1,5 @@ import { expect } from 'chai'; import * as dns from 'dns'; -import { promises as fs } from 'fs'; import * as sinon from 'sinon'; import { @@ -17,8 +16,7 @@ import { MongoParseError, MongoRuntimeError, parseOptions, - resolveSRVRecord, - Topology + resolveSRVRecord } from '../mongodb'; describe('Connection String', function () { @@ -481,35 +479,6 @@ describe('Connection String', function () { }); }); - context('when tls filepaths are provided', () => { - beforeEach(async () => { - sinon.stub(Topology.prototype, 'connect').yieldsRight(); - await fs.writeFile('caFileName.txt', 'abc', { encoding: 'utf8' }); - await fs.writeFile('certKeyFileName.txt', 'abc', { encoding: 'utf8' }); - }); - - afterEach(() => sinon.restore()); - afterEach(() => fs.unlink('caFileName.txt')); - afterEach(() => fs.unlink('certKeyFileName.txt')); - - it('should read in files async', async () => { - const client = new MongoClient('mongodb://iLoveJavaScript?tls=true', { - tlsCAFile: 'caFileName.txt', - tlsCertificateKeyFile: 'certKeyFileName.txt' - }); - - expect(client.options).property('caFileName', 'caFileName.txt'); - expect(client.options).property('certKeyFileName', 'certKeyFileName.txt'); - expect(client.options).not.have.property('ca'); - expect(client.options).not.have.property('key'); - - await client.connect(); - - expect(client.options).property('ca', 'abc'); - expect(client.options).property('key', 'abc'); - }); - }); - describe('validation', function () { it('should validate compressors options', function () { let thrownError; From ddb1e7f749829b4b185a0b700f306710af8eca3f Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 15:29:30 -0400 Subject: [PATCH 06/28] test(NODE-3924): update assertions --- test/manual/tls_support.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 9ef65db7d17..27e89d749cf 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -55,8 +55,8 @@ describe('TLS Support', function () { await client.connect(); - expect(client.options).property('ca', 'abc'); - expect(client.options).property('key', 'abc'); + expect(client.options).property('ca').to.exist; + expect(client.options).property('key').to.exist; }); context('when client has been opened and closed more than once', function () { From 32de88bb8ba7dc38825062d3394bd26648d1e92a Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 19 Jul 2023 16:40:35 -0400 Subject: [PATCH 07/28] style(NODE-3924): remove unneeded import --- test/manual/tls_support.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 27e89d749cf..f283da29d32 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -1,6 +1,5 @@ import { expect } from 'chai'; import { promises as fs } from 'fs'; -import * as sinon from 'sinon'; import { LEGACY_HELLO_COMMAND, MongoClient, type MongoClientOptions } from '../mongodb'; @@ -82,11 +81,11 @@ describe('TLS Support', function () { tlsCertificateKeyFile: '' }); }); - afterEach(() => sinon.restore()); - it('ignores file path and fails to connect', () => { + it('ignores file paths and attempts to connect', () => { expect(async () => { await client.connect(); + await client.db('test').collection('test').insertOne({ hello: 'world' }); }).to.throw(); }); }); From d69dd699ad841fadaff8baae72cdd0f733597d7f Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 20 Jul 2023 10:27:34 -0400 Subject: [PATCH 08/28] fix(NODE-3924): remove unused option --- src/connection_string.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index c1822ed0ad8..17f4ccea632 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1099,10 +1099,6 @@ export const OPTIONS = { target: 'caFileName', type: 'string' }, - tlsCertificateFile: { - target: 'certFileName', - type: 'string' - }, tlsCertificateKeyFile: { target: 'certKeyFileName', type: 'string' From b418f9941d37351be4555065c6f5cb6cc2594fa7 Mon Sep 17 00:00:00 2001 From: Warren James Date: Thu, 20 Jul 2023 10:29:49 -0400 Subject: [PATCH 09/28] test(NODE-3924): update test name --- test/manual/tls_support.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index f283da29d32..9ffd75c70d3 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -82,10 +82,11 @@ describe('TLS Support', function () { }); }); - it('ignores file paths and attempts to connect', () => { + it('ignores file paths and fails to connect', () => { expect(async () => { await client.connect(); await client.db('test').collection('test').insertOne({ hello: 'world' }); + await client.db('test').collection('test').findOne({}); }).to.throw(); }); }); From e571b80deeffd4e6e2fb2dd577c0de6b116ec6c0 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 11:20:27 -0400 Subject: [PATCH 10/28] test(NODE-3924): fix test setup and timeouts --- .evergreen/config.in.yml | 1 + .evergreen/config.yml | 1 + test/manual/mocharc.json | 3 ++- test/manual/tls_support.test.ts | 10 ++++------ 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index f1b94201d76..916073a5463 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -627,6 +627,7 @@ functions: - command: shell.exec type: test params: + timeout_secs: 10 working_dir: "src" script: | export PROJECT_DIRECTORY="$(pwd)" diff --git a/.evergreen/config.yml b/.evergreen/config.yml index d99cd749cc8..9fcbbf2c3cb 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -580,6 +580,7 @@ functions: - command: shell.exec type: test params: + timeout_secs: 10 working_dir: src script: | export PROJECT_DIRECTORY="$(pwd)" diff --git a/test/manual/mocharc.json b/test/manual/mocharc.json index b129ce1fb9b..b52cf660c22 100644 --- a/test/manual/mocharc.json +++ b/test/manual/mocharc.json @@ -2,5 +2,6 @@ "require": "ts-node/register", "reporter": "test/tools/reporter/mongodb_reporter.js", "failZero": true, - "color": true + "color": true, + "timeout": 10000 } diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 9ffd75c70d3..97751701207 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -76,18 +76,16 @@ describe('TLS Support', function () { context('when tls filepaths have length == 0', () => { beforeEach(async () => { client = new MongoClient(CONNECTION_STRING, { + serverSelectionTimeoutMS: 2000, tls: true, tlsCAFile: '', tlsCertificateKeyFile: '' }); }); - it('ignores file paths and fails to connect', () => { - expect(async () => { - await client.connect(); - await client.db('test').collection('test').insertOne({ hello: 'world' }); - await client.db('test').collection('test').findOne({}); - }).to.throw(); + it('ignores file paths and fails to connect', async () => { + const err = await client.connect().catch(e => e); + expect(err).to.be.instanceOf(Error); }); }); }); From 84a3a892c8a1a0fd405845604243e6dcb5f3472b Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 14:15:32 -0400 Subject: [PATCH 11/28] test(NODE-3924): update existing unit test --- test/unit/mongo_client.test.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index c2199250f1f..9578a52d8f6 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -46,9 +46,9 @@ describe('MongoOptions', function () { * * | nodejs native option | driver spec compliant option name | driver option type | * |:----------------------|:----------------------------------------------|:-------------------| - * | `ca` | `tlsCAFile` | `string` | + * | `caFile` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | - * | `key` | `tlsCertificateKeyFile` | `string` | + * | `certKeyFile` | `tlsCertificateKeyFile` | `string` | * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | @@ -58,9 +58,10 @@ describe('MongoOptions', function () { expect(options).to.not.have.property('tlsCertificateKeyFile'); expect(options).to.not.have.property('tlsCAFile'); expect(options).to.not.have.property('tlsCertificateKeyFilePassword'); - expect(options).has.property('ca', ''); - expect(options).has.property('key'); - expect(options.key).has.length(0); + expect(options).to.not.have.property('key'); + expect(options).to.not.have.property('ca'); + expect(options).has.property('certKeyFileName', filename); + expect(options).has.property('caFileName', filename); expect(options).has.property('passphrase', 'tlsCertificateKeyFilePassword'); expect(options).has.property('tls', true); }); @@ -394,10 +395,10 @@ describe('MongoOptions', function () { const optsFromObject = parseOptions('mongodb://localhost/', { tlsCertificateKeyFile: 'testCertKey.pem' }); - expect(optsFromObject).to.have.property('key', 'cert key'); + expect(optsFromObject).to.have.property('certKeyFileName', 'testCertKey.pem'); const optsFromUri = parseOptions('mongodb://localhost?tlsCertificateKeyFile=testCertKey.pem'); - expect(optsFromUri).to.have.property('key', 'cert key'); + expect(optsFromUri).to.have.property('certKeyFileName', 'testCertKey.pem'); }); }); From 8acfbb6dbb533ae348f18ffc604aed9dd97e4a38 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 14:17:28 -0400 Subject: [PATCH 12/28] test(NODE-3924): update table --- test/unit/mongo_client.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 9578a52d8f6..4bd7970ba0a 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -46,9 +46,9 @@ describe('MongoOptions', function () { * * | nodejs native option | driver spec compliant option name | driver option type | * |:----------------------|:----------------------------------------------|:-------------------| - * | `caFile` | `tlsCAFile` | `string` | + * | `caFileName` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | - * | `certKeyFile` | `tlsCertificateKeyFile` | `string` | + * | `certKeyFileName` | `tlsCertificateKeyFile` | `string` | * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | From 52bf930c8053b8a680568e1b2a40293678b3a02c Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 14:27:54 -0400 Subject: [PATCH 13/28] docs(NODE-3924): update tls options API docs --- src/mongo_client.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 4861fa7895b..76e255c1163 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -790,6 +790,14 @@ export interface MongoOptions * If `tlsInsecure` is set to `false`, then it will set the node native options `checkServerIdentity` * to a no-op and `rejectUnauthorized` to the inverse value of `tlsAllowInvalidCertificates`. If * `tlsAllowInvalidCertificates` is not set, then `rejectUnauthorized` will be set to `true`. + * + * ### Note on `tlsCAFile` and `tlsCertificateKeyFile` + * + * The files specified by the paths passed in to the `tlsCAFile` and `tlsCertificateKeyFile` fields + * are read lazily on the first call to `MongoClient.connect`. Once these files have been read and + * the `ca` and `key` fields are populated, they will not be read again on subsequent calls to + * `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca` + * and `key` fields will be undefined. */ tls: boolean; From 694b4a75a2cb7b2ce27a07d5898b946706442365 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 15:07:45 -0400 Subject: [PATCH 14/28] style(NODE-3924): eslint --- src/mongo_client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 76e255c1163..01d62ddf946 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -790,14 +790,14 @@ export interface MongoOptions * If `tlsInsecure` is set to `false`, then it will set the node native options `checkServerIdentity` * to a no-op and `rejectUnauthorized` to the inverse value of `tlsAllowInvalidCertificates`. If * `tlsAllowInvalidCertificates` is not set, then `rejectUnauthorized` will be set to `true`. - * + * * ### Note on `tlsCAFile` and `tlsCertificateKeyFile` * * The files specified by the paths passed in to the `tlsCAFile` and `tlsCertificateKeyFile` fields * are read lazily on the first call to `MongoClient.connect`. Once these files have been read and * the `ca` and `key` fields are populated, they will not be read again on subsequent calls to * `MongoClient.connect`. As a result, until the first call to `MongoClient.connect`, the `ca` - * and `key` fields will be undefined. + * and `key` fields will be undefined. */ tls: boolean; From 0dfac8882d6c4a6064fff0ab2634ec6ac3c2482b Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 15:26:45 -0400 Subject: [PATCH 15/28] test(NODE-3924): fix spec test --- test/tools/uri_spec_runner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tools/uri_spec_runner.ts b/test/tools/uri_spec_runner.ts index e7f493f3897..b044e591a49 100644 --- a/test/tools/uri_spec_runner.ts +++ b/test/tools/uri_spec_runner.ts @@ -314,13 +314,13 @@ export function executeUriValidationTest( .equal(optionValue); break; case 'tlsCertificateKeyFile': - expectedProp = 'key'; + expectedProp = 'certKeyFileName'; expect(options, `${errorMessage} ${optionKey} -> ${expectedProp}`) .to.have.property(expectedProp) .equal(optionValue); break; case 'tlsCAFile': - expectedProp = 'ca'; + expectedProp = 'caFileName'; expect(options, `${errorMessage} ${optionKey} -> ${expectedProp}`) .to.have.property(expectedProp) .equal(optionValue); From 135aadf04b6402f3f443b469b2dc02830e2776e9 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 21 Jul 2023 16:16:17 -0400 Subject: [PATCH 16/28] fix(NODE-3924): change option names to shadow spec tls option names --- src/connection_string.ts | 2 -- src/mongo_client.ts | 14 +++++++------- test/manual/tls_support.test.ts | 4 ++-- test/tools/uri_spec_runner.ts | 4 ++-- test/unit/mongo_client.test.js | 28 +++++++++++++--------------- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index 17f4ccea632..a6a1677412f 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1096,11 +1096,9 @@ export const OPTIONS = { } }, tlsCAFile: { - target: 'caFileName', type: 'string' }, tlsCertificateKeyFile: { - target: 'certKeyFileName', type: 'string' }, tlsCertificateKeyFilePassword: { diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 01d62ddf946..59248f138a9 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -431,15 +431,15 @@ export class MongoClient extends TypedEventEmitter { const options = this[kOptions]; if (options.tls) { - if (!options.ca && typeof options.caFileName === 'string' && options.caFileName.length > 0) { - options.ca = await fs.readFile(options.caFileName, { encoding: 'utf8' }); + if (!options.ca && typeof options.tlsCAFile === 'string' && options.tlsCAFile.length > 0) { + options.ca = await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); } if ( !options.key && - typeof options.certKeyFileName === 'string' && - options.certKeyFileName.length > 0 + typeof options.tlsCertificateKeyFile === 'string' && + options.tlsCertificateKeyFile.length > 0 ) { - options.key = await fs.readFile(options.certKeyFileName, { encoding: 'utf8' }); + options.key = await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); } } if (typeof options.srvHost === 'string') { @@ -801,8 +801,8 @@ export interface MongoOptions */ tls: boolean; - caFileName?: string; - certKeyFileName?: string; + tlsCAFile?: string; + tlsCertificateKeyFile?: string; /** @internal */ [featureFlag: symbol]: any; diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 97751701207..242e3898aa8 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -47,8 +47,8 @@ describe('TLS Support', function () { }); it('should read in files async at connect time', async () => { - expect(client.options).property('caFileName', TLS_CA_FILE); - expect(client.options).property('certKeyFileName', TLS_CERT_KEY_FILE); + expect(client.options).property('tlsCAFile', TLS_CA_FILE); + expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE); expect(client.options).not.have.property('ca'); expect(client.options).not.have.property('key'); diff --git a/test/tools/uri_spec_runner.ts b/test/tools/uri_spec_runner.ts index b044e591a49..3c89f93e1cf 100644 --- a/test/tools/uri_spec_runner.ts +++ b/test/tools/uri_spec_runner.ts @@ -314,13 +314,13 @@ export function executeUriValidationTest( .equal(optionValue); break; case 'tlsCertificateKeyFile': - expectedProp = 'certKeyFileName'; + expectedProp = 'tlsCertificateKeyFile'; expect(options, `${errorMessage} ${optionKey} -> ${expectedProp}`) .to.have.property(expectedProp) .equal(optionValue); break; case 'tlsCAFile': - expectedProp = 'caFileName'; + expectedProp = 'tlsCAFile'; expect(options, `${errorMessage} ${optionKey} -> ${expectedProp}`) .to.have.property(expectedProp) .equal(optionValue); diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 4bd7970ba0a..6a6238c2339 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -44,24 +44,22 @@ describe('MongoOptions', function () { * * ### Additional options: * - * | nodejs native option | driver spec compliant option name | driver option type | - * |:----------------------|:----------------------------------------------|:-------------------| - * | `caFileName` | `tlsCAFile` | `string` | - * | `crl` | N/A | `string` | - * | `certKeyFileName` | `tlsCertificateKeyFile` | `string` | - * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | - * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | - * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | - * | see note below | `tlsInsecure` | `boolean` | + * | nodejs native option | driver spec compliant option name | driver option type | + * |:------------------------|:----------------------------------------------|:-------------------| + * | `tlsCAFile` | `tlsCAFile` | `string` | + * | `crl` | N/A | `string` | + * | `tlsCertificateKeyFile` | `tlsCertificateKeyFile` | `string` | + * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | + * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | + * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | + * | see note below | `tlsInsecure` | `boolean` | * */ - expect(options).to.not.have.property('tlsCertificateKeyFile'); - expect(options).to.not.have.property('tlsCAFile'); expect(options).to.not.have.property('tlsCertificateKeyFilePassword'); expect(options).to.not.have.property('key'); expect(options).to.not.have.property('ca'); - expect(options).has.property('certKeyFileName', filename); - expect(options).has.property('caFileName', filename); + expect(options).to.have.property('tlsCertificateKeyFile', filename); + expect(options).to.have.property('tlsCAFile', filename); expect(options).has.property('passphrase', 'tlsCertificateKeyFilePassword'); expect(options).has.property('tls', true); }); @@ -395,10 +393,10 @@ describe('MongoOptions', function () { const optsFromObject = parseOptions('mongodb://localhost/', { tlsCertificateKeyFile: 'testCertKey.pem' }); - expect(optsFromObject).to.have.property('certKeyFileName', 'testCertKey.pem'); + expect(optsFromObject).to.have.property('tlsCertificateKeyFile', 'testCertKey.pem'); const optsFromUri = parseOptions('mongodb://localhost?tlsCertificateKeyFile=testCertKey.pem'); - expect(optsFromUri).to.have.property('certKeyFileName', 'testCertKey.pem'); + expect(optsFromUri).to.have.property('tlsCertificateKeyFile', 'testCertKey.pem'); }); }); From 10534525fd742b8b2e92e5e76a95cad6c6c271f6 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 24 Jul 2023 14:26:56 -0400 Subject: [PATCH 17/28] ci(NODE-3924): revert unneeded ci change --- .evergreen/config.in.yml | 1 - .evergreen/config.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 19299340b38..dd30ee38a3e 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -633,7 +633,6 @@ functions: - command: shell.exec type: test params: - timeout_secs: 10 working_dir: "src" script: | export PROJECT_DIRECTORY="$(pwd)" diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 765aaf810f5..8b1c7752674 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -586,7 +586,6 @@ functions: - command: shell.exec type: test params: - timeout_secs: 10 working_dir: src script: | export PROJECT_DIRECTORY="$(pwd)" From 1e6d240fe2c702706c40af4e9c19e9dd400db5ef Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 24 Jul 2023 14:33:07 -0400 Subject: [PATCH 18/28] fix(NODE-3924): throw error on parse --- src/connection_string.ts | 14 ++++++++++++-- src/mongo_client.ts | 7 +++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index a6a1677412f..9a203edc2ae 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1096,10 +1096,20 @@ export const OPTIONS = { } }, tlsCAFile: { - type: 'string' + transform({ name, values: [value] }) { + if (typeof value === 'string' && value.length === 0) { + throw new MongoParseError(`${name} must have non-zero length`); + } + return value; + } }, tlsCertificateKeyFile: { - type: 'string' + transform({ name, values: [value] }) { + if (typeof value === 'string' && value.length === 0) { + throw new MongoParseError(`${name} must have non-zero length`); + } + return value; + } }, tlsCertificateKeyFilePassword: { target: 'passphrase', diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 73412289b93..f7ed5f57a0c 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -435,15 +435,14 @@ export class MongoClient extends TypedEventEmitter { const options = this[kOptions]; if (options.tls) { - if (!options.ca && typeof options.tlsCAFile === 'string' && options.tlsCAFile.length > 0) { - options.ca = await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); + if (typeof options.tlsCAFile === 'string' && options.tlsCAFile.length > 0) { + options.ca ??= await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); } if ( - !options.key && typeof options.tlsCertificateKeyFile === 'string' && options.tlsCertificateKeyFile.length > 0 ) { - options.key = await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); + options.key ??= await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); } } if (typeof options.srvHost === 'string') { From 20d09eaa9ace96675686c0c6dc06f2c9cfaf99af Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 24 Jul 2023 14:35:03 -0400 Subject: [PATCH 19/28] test(NODE-3924): update tests --- test/manual/tls_support.test.ts | 15 --------------- test/unit/mongo_client.test.js | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index 242e3898aa8..a3ee4b78a40 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -73,21 +73,6 @@ describe('TLS Support', function () { }); }); }); - context('when tls filepaths have length == 0', () => { - beforeEach(async () => { - client = new MongoClient(CONNECTION_STRING, { - serverSelectionTimeoutMS: 2000, - tls: true, - tlsCAFile: '', - tlsCertificateKeyFile: '' - }); - }); - - it('ignores file paths and fails to connect', async () => { - const err = await client.connect().catch(e => e); - expect(err).to.be.instanceOf(Error); - }); - }); }); }); diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 6a6238c2339..7456c2dd35c 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -455,6 +455,26 @@ describe('MongoOptions', function () { expect(optionsUndefined.checkServerIdentity).to.equal(undefined); }); + context('when tlsCAPath is provided as an empty string', function () { + it('throws MongoParseError', function () { + expect(() => { + parseOptions('mongodb://localhost:27017/?tls=true', { + tlsCAFile: '' + }); + }).to.throw(MongoParseError); + }); + }); + + context('when tlsCertificateKeyFile is provided as an empty string', function () { + it('throws MongoParseError', function () { + expect(() => { + parseOptions('mongodb://localhost:27017/?tls=true', { + tlsCertificateKeyFile: '' + }); + }).to.throw(MongoParseError); + }); + }); + describe('compressors', function () { it('can be set when passed in as an array in the options object', function () { const clientViaOpt = new MongoClient('mongodb://localhost', { From 702f6f877fd89e00598c3e0352e22d08af0cdfa2 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 24 Jul 2023 14:51:44 -0400 Subject: [PATCH 20/28] docs(NODE-3924): revert table change and modify heading --- src/mongo_client.ts | 2 +- test/unit/mongo_client.test.js | 132 ++++++++++++++++----------------- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/mongo_client.ts b/src/mongo_client.ts index f7ed5f57a0c..1b4c7b1449c 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -779,7 +779,7 @@ export interface MongoOptions * * ### Additional options: * - * | nodejs native option | driver spec compliant option name | driver option type | + * | nodejs native option | driver spec equivalent option name | driver option type | * |:----------------------|:----------------------------------------------|:-------------------| * | `ca` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 7456c2dd35c..446a34e080e 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -13,13 +13,13 @@ const { MongoLogger } = require('../mongodb'); const sinon = require('sinon'); const { Writable } = require('stream'); -describe('MongoOptions', function () { - it('MongoClient should always freeze public options', function () { +describe('MongoOptions', function() { + it('MongoClient should always freeze public options', function() { const client = new MongoClient('mongodb://localhost:27017'); expect(client.options).to.be.frozen; }); - it('programmatic options should override URI options', function () { + it('programmatic options should override URI options', function() { const options = parseOptions('mongodb://localhost:27017/test?directConnection=true', { directConnection: false }); @@ -29,7 +29,7 @@ describe('MongoOptions', function () { expect(options.prototype).to.not.exist; }); - it('should rename tls options correctly', function () { + it('should rename tls options correctly', function() { const filename = `${os.tmpdir()}/tmp.pem`; fs.closeSync(fs.openSync(filename, 'w')); const options = parseOptions('mongodb://localhost:27017/?ssl=true', { @@ -44,15 +44,15 @@ describe('MongoOptions', function () { * * ### Additional options: * - * | nodejs native option | driver spec compliant option name | driver option type | - * |:------------------------|:----------------------------------------------|:-------------------| - * | `tlsCAFile` | `tlsCAFile` | `string` | - * | `crl` | N/A | `string` | - * | `tlsCertificateKeyFile` | `tlsCertificateKeyFile` | `string` | - * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | - * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | - * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | - * | see note below | `tlsInsecure` | `boolean` | + * | nodejs native option | driver spec compliant option name | driver option type | + * |:----------------------|:----------------------------------------------|:-------------------| + * | `ca` | `tlsCAFile` | `string` | + * | `crl` | N/A | `string` | + * | `key` | `tlsCertificateKeyFile` | `string` | + * | `passphrase` | `tlsCertificateKeyFilePassword` | `string` | + * | `rejectUnauthorized` | `tlsAllowInvalidCertificates` | `boolean` | + * | `checkServerIdentity` | `tlsAllowInvalidHostnames` | `boolean` | + * | see note below | `tlsInsecure` | `boolean` | * */ expect(options).to.not.have.property('tlsCertificateKeyFilePassword'); @@ -129,7 +129,7 @@ describe('MongoOptions', function () { zlibCompressionLevel: 2 }; - it('should parse all options from the options object', function () { + it('should parse all options from the options object', function() { const options = parseOptions('mongodb://localhost:27017/', ALL_OPTIONS); // Check consolidated options expect(options).has.property('writeConcern'); @@ -174,7 +174,7 @@ describe('MongoOptions', function () { 'zlibCompressionLevel=2' ].join('&'); - it('should parse all options from the URI string', function () { + it('should parse all options from the URI string', function() { const options = parseOptions(allURIOptions); expect(options).has.property('zlibCompressionLevel', 2); @@ -183,7 +183,7 @@ describe('MongoOptions', function () { expect(options.writeConcern).has.property('wtimeout', 2); }); - it('should ignore undefined and null values in the options object', function () { + it('should ignore undefined and null values in the options object', function() { const options = parseOptions('mongodb://localhost:27017/', { maxPoolSize: null, servername: undefined, @@ -202,7 +202,7 @@ describe('MongoOptions', function () { expect(options).not.to.have.property('otherrandomopt'); }); - it('should throw an error on unrecognized keys in the options object if they are defined', function () { + it('should throw an error on unrecognized keys in the options object if they are defined', function() { expect(() => parseOptions('mongodb://localhost:27017/', { randomopt: 'test' @@ -217,43 +217,43 @@ describe('MongoOptions', function () { ).to.throw(MongoParseError, 'options randomopt, randomopt2 are not supported'); }); - it('srvHost saved to options for later resolution', function () { + it('srvHost saved to options for later resolution', function() { const options = parseOptions('mongodb+srv://server.example.com/'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('ssl= can be used to set tls=false', function () { + it('ssl= can be used to set tls=false', function() { const options = parseOptions('mongodb+srv://server.example.com/?ssl=false'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', false); }); - it('tls= can be used to set tls=false', function () { + it('tls= can be used to set tls=false', function() { const options = parseOptions('mongodb+srv://server.example.com/?tls=false'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', false); }); - it('ssl= can be used to set tls=true', function () { + it('ssl= can be used to set tls=true', function() { const options = parseOptions('mongodb+srv://server.example.com/?ssl=true'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('tls= can be used to set tls=true', function () { + it('tls= can be used to set tls=true', function() { const options = parseOptions('mongodb+srv://server.example.com/?tls=true'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('supports ReadPreference option in url', function () { + it('supports ReadPreference option in url', function() { const options = parseOptions('mongodb://localhost/?readPreference=nearest'); expect(options.readPreference).to.be.an.instanceof(ReadPreference); expect(options.readPreference.mode).to.equal('nearest'); }); - it('supports ReadPreference option in object plain', function () { + it('supports ReadPreference option in object plain', function() { const options = parseOptions('mongodb://localhost', { readPreference: { mode: 'nearest', hedge: { enabled: true } } }); @@ -262,7 +262,7 @@ describe('MongoOptions', function () { expect(options.readPreference.hedge).to.include({ enabled: true }); }); - it('supports ReadPreference option in object proper class', function () { + it('supports ReadPreference option in object proper class', function() { const tag = { rack: 1 }; const options = parseOptions('mongodb://localhost', { readPreference: new ReadPreference('nearest', [tag], { maxStalenessSeconds: 20 }) @@ -283,13 +283,13 @@ describe('MongoOptions', function () { // Passing readPreference in URI will always be string }); - it('supports WriteConcern option in url', function () { + it('supports WriteConcern option in url', function() { const options = parseOptions('mongodb://localhost/?w=3'); expect(options.writeConcern).to.be.an.instanceof(WriteConcern); expect(options.writeConcern.w).to.equal(3); }); - it('supports WriteConcern option in object plain', function () { + it('supports WriteConcern option in object plain', function() { const options = parseOptions('mongodb://localhost', { writeConcern: { w: 'majority', wtimeoutMS: 300 } }); @@ -298,7 +298,7 @@ describe('MongoOptions', function () { expect(options.writeConcern.wtimeout).to.equal(300); }); - it('supports WriteConcern option in object proper class', function () { + it('supports WriteConcern option in object proper class', function() { const options = parseOptions('mongodb://localhost', { writeConcern: new WriteConcern(5, 200, true) }); @@ -308,13 +308,13 @@ describe('MongoOptions', function () { expect(options.writeConcern.j).to.equal(true); }); - it('supports ReadConcern option in url', function () { + it('supports ReadConcern option in url', function() { const options = parseOptions('mongodb://localhost/?readConcernLevel=available'); expect(options.readConcern).to.be.an.instanceof(ReadConcern); expect(options.readConcern.level).to.equal('available'); }); - it('supports ReadConcern option in object plain', function () { + it('supports ReadConcern option in object plain', function() { const options = parseOptions('mongodb://localhost', { readConcern: { level: 'linearizable' } }); @@ -322,7 +322,7 @@ describe('MongoOptions', function () { expect(options.readConcern.level).to.equal('linearizable'); }); - it('supports ReadConcern option in object proper class', function () { + it('supports ReadConcern option in object proper class', function() { const options = parseOptions('mongodb://localhost', { readConcern: new ReadConcern('snapshot') }); @@ -330,7 +330,7 @@ describe('MongoOptions', function () { expect(options.readConcern.level).to.equal('snapshot'); }); - it('supports Credentials option in url', function () { + it('supports Credentials option in url', function() { const options = parseOptions('mongodb://USERNAME:PASSWORD@localhost/'); expect(options.credentials).to.be.an.instanceof(MongoCredentials); expect(options.credentials.username).to.equal('USERNAME'); @@ -338,7 +338,7 @@ describe('MongoOptions', function () { expect(options.credentials.source).to.equal('admin'); }); - it('supports Credentials option in url with db', function () { + it('supports Credentials option in url with db', function() { const options = parseOptions('mongodb://USERNAME:PASSWORD@localhost/foo'); expect(options.credentials).to.be.an.instanceof(MongoCredentials); expect(options.credentials.username).to.equal('USERNAME'); @@ -346,7 +346,7 @@ describe('MongoOptions', function () { expect(options.credentials.source).to.equal('foo'); }); - it('supports Credentials option in auth object plain', function () { + it('supports Credentials option in auth object plain', function() { const options = parseOptions('mongodb://localhost/', { auth: { username: 'USERNAME', password: 'PASSWORD' } }); @@ -355,7 +355,7 @@ describe('MongoOptions', function () { expect(options.credentials.password).to.equal('PASSWORD'); }); - it('transforms tlsAllowInvalidCertificates and tlsAllowInvalidHostnames correctly', function () { + it('transforms tlsAllowInvalidCertificates and tlsAllowInvalidHostnames correctly', function() { const optionsTrue = parseOptions('mongodb://localhost/', { tlsAllowInvalidCertificates: true, tlsAllowInvalidHostnames: true @@ -389,7 +389,7 @@ describe('MongoOptions', function () { fs.unlinkSync('testCert.pem'); }); - it('correctly sets the cert and key if only tlsCertificateKeyFile is provided', function () { + it('correctly sets the cert and key if only tlsCertificateKeyFile is provided', function() { const optsFromObject = parseOptions('mongodb://localhost/', { tlsCertificateKeyFile: 'testCertKey.pem' }); @@ -436,7 +436,7 @@ describe('MongoOptions', function () { expect(parseOptions('mongodb://localhost?ssl=false&tls=false')).to.have.property('tls', false); }); - it('transforms tlsInsecure correctly', function () { + it('transforms tlsInsecure correctly', function() { const optionsTrue = parseOptions('mongodb://localhost/', { tlsInsecure: true }); @@ -455,8 +455,8 @@ describe('MongoOptions', function () { expect(optionsUndefined.checkServerIdentity).to.equal(undefined); }); - context('when tlsCAPath is provided as an empty string', function () { - it('throws MongoParseError', function () { + context('when tlsCAPath is provided as an empty string', function() { + it('throws MongoParseError', function() { expect(() => { parseOptions('mongodb://localhost:27017/?tls=true', { tlsCAFile: '' @@ -465,8 +465,8 @@ describe('MongoOptions', function () { }); }); - context('when tlsCertificateKeyFile is provided as an empty string', function () { - it('throws MongoParseError', function () { + context('when tlsCertificateKeyFile is provided as an empty string', function() { + it('throws MongoParseError', function() { expect(() => { parseOptions('mongodb://localhost:27017/?tls=true', { tlsCertificateKeyFile: '' @@ -475,15 +475,15 @@ describe('MongoOptions', function () { }); }); - describe('compressors', function () { - it('can be set when passed in as an array in the options object', function () { + describe('compressors', function() { + it('can be set when passed in as an array in the options object', function() { const clientViaOpt = new MongoClient('mongodb://localhost', { compressors: ['zlib', 'snappy'] }); expect(clientViaOpt.options).to.have.property('compressors').deep.equal(['zlib', 'snappy']); }); - it('can be set when passed in as a comma-delimited string in the options object or URI', function () { + it('can be set when passed in as a comma-delimited string in the options object or URI', function() { const clientViaOpt = new MongoClient('mongodb://localhost', { compressors: 'zlib,snappy' }); @@ -492,7 +492,7 @@ describe('MongoOptions', function () { expect(clientViaUri.options).to.have.property('compressors').deep.equal(['zlib', 'snappy']); }); - it('should validate that a string or an array of strings is provided as input', function () { + it('should validate that a string or an array of strings is provided as input', function() { expect( () => new MongoClient('mongodb://localhost', { @@ -501,7 +501,7 @@ describe('MongoOptions', function () { ).to.throw(/^compressors must be an array or a comma-delimited list of strings/); }); - it('should throw an error if an unrecognized compressor is specified', function () { + it('should throw an error if an unrecognized compressor is specified', function() { const expectedErrRegex = /not a valid compression mechanism/; expect( () => @@ -521,8 +521,8 @@ describe('MongoOptions', function () { }); }); - describe('serverApi', function () { - it('is supported as a client option when it is a valid ServerApiVersion string', function () { + describe('serverApi', function() { + it('is supported as a client option when it is a valid ServerApiVersion string', function() { const validVersions = Object.values(ServerApiVersion); expect(validVersions.length).to.be.at.least(1); for (const version of validVersions) { @@ -533,7 +533,7 @@ describe('MongoOptions', function () { } }); - it('is supported as a client option when it is an object with a valid version property', function () { + it('is supported as a client option when it is an object with a valid version property', function() { const validVersions = Object.values(ServerApiVersion); expect(validVersions.length).to.be.at.least(1); for (const version of validVersions) { @@ -544,7 +544,7 @@ describe('MongoOptions', function () { } }); - it('is not supported as a client option when it is an invalid string', function () { + it('is not supported as a client option when it is an invalid string', function() { expect(() => parseOptions('mongodb://localhost/', { serverApi: 'bad' @@ -552,7 +552,7 @@ describe('MongoOptions', function () { ).to.throw(/^Invalid server API version=bad;/); }); - it('is not supported as a client option when it is a number', function () { + it('is not supported as a client option when it is a number', function() { expect(() => parseOptions('mongodb://localhost/', { serverApi: 1 @@ -560,7 +560,7 @@ describe('MongoOptions', function () { ).to.throw(/^Invalid `serverApi` property;/); }); - it('is not supported as a client option when it is an object without a specified version', function () { + it('is not supported as a client option when it is an object without a specified version', function() { expect(() => parseOptions('mongodb://localhost/', { serverApi: {} @@ -568,7 +568,7 @@ describe('MongoOptions', function () { ).to.throw(/^Invalid `serverApi` property;/); }); - it('is not supported as a client option when it is an object with an invalid specified version', function () { + it('is not supported as a client option when it is an object with an invalid specified version', function() { expect(() => parseOptions('mongodb://localhost/', { serverApi: { version: 1 } @@ -581,7 +581,7 @@ describe('MongoOptions', function () { ).to.throw(/^Invalid server API version=bad;/); }); - it('is not supported as a URI option even when it is a valid ServerApiVersion string', function () { + it('is not supported as a URI option even when it is a valid ServerApiVersion string', function() { expect(() => parseOptions('mongodb://localhost/?serverApi=1')).to.throw( 'URI cannot contain `serverApi`, it can only be passed to the client' ); @@ -638,7 +638,7 @@ describe('MongoOptions', function () { } }); - it('set monitorCommands to false (NODE-3513)', function () { + it('set monitorCommands to false (NODE-3513)', function() { const client = new MongoClient('mongodb://localhost'); const clientOptions = client.options; @@ -649,7 +649,7 @@ describe('MongoOptions', function () { expect(client[optionsSym]).to.have.property('monitorCommands', false); }); - it('respects monitorCommands option passed in', function () { + it('respects monitorCommands option passed in', function() { const clientViaOpt = new MongoClient('mongodb://localhost', { monitorCommands: true }); const clientViaUri = new MongoClient('mongodb://localhost?monitorCommands=true'); @@ -668,27 +668,27 @@ describe('MongoOptions', function () { }); }); - context('when loadBalanced=true is in the URI', function () { - it('sets the option', function () { + context('when loadBalanced=true is in the URI', function() { + it('sets the option', function() { const options = parseOptions('mongodb://a/?loadBalanced=true'); expect(options.loadBalanced).to.be.true; }); - it('errors with multiple hosts', function () { + it('errors with multiple hosts', function() { const parse = () => { parseOptions('mongodb://a,b/?loadBalanced=true'); }; expect(parse).to.throw(/single host/); }); - it('errors with a replicaSet option', function () { + it('errors with a replicaSet option', function() { const parse = () => { parseOptions('mongodb://a/?loadBalanced=true&replicaSet=test'); }; expect(parse).to.throw(/replicaSet/); }); - it('errors with a directConnection option', function () { + it('errors with a directConnection option', function() { const parse = () => { parseOptions('mongodb://a/?loadBalanced=true&directConnection=true'); }; @@ -696,15 +696,15 @@ describe('MongoOptions', function () { }); }); - context('when loadBalanced is in the options object', function () { - it('errors when the option is true', function () { + context('when loadBalanced is in the options object', function() { + it('errors when the option is true', function() { const parse = () => { parseOptions('mongodb://a/', { loadBalanced: true }); }; expect(parse).to.throw(/URI/); }); - it('errors when the option is false', function () { + it('errors when the option is false', function() { const parse = () => { parseOptions('mongodb://a/', { loadBalanced: false }); }; @@ -831,7 +831,7 @@ describe('MongoOptions', function () { }); }); - context('loggingOptions', function () { + context('loggingOptions', function() { const expectedLoggingObject = { maxDocumentLength: 20, logDestination: new Writable() @@ -847,7 +847,7 @@ describe('MongoOptions', function () { sinon.restore(); }); - it('assigns the parsed options to the mongoLoggerOptions option', function () { + it('assigns the parsed options to the mongoLoggerOptions option', function() { const client = new MongoClient('mongodb://localhost:27017'); expect(client.options).to.have.property('mongoLoggerOptions').to.equal(expectedLoggingObject); }); From 7438df7852565df6201b071f06628ed96e640915 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 24 Jul 2023 15:41:10 -0400 Subject: [PATCH 21/28] style(NODE-3924): eslint --- test/unit/mongo_client.test.js | 114 ++++++++++++++++----------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 446a34e080e..bf332a059ba 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -13,13 +13,13 @@ const { MongoLogger } = require('../mongodb'); const sinon = require('sinon'); const { Writable } = require('stream'); -describe('MongoOptions', function() { - it('MongoClient should always freeze public options', function() { +describe('MongoOptions', function () { + it('MongoClient should always freeze public options', function () { const client = new MongoClient('mongodb://localhost:27017'); expect(client.options).to.be.frozen; }); - it('programmatic options should override URI options', function() { + it('programmatic options should override URI options', function () { const options = parseOptions('mongodb://localhost:27017/test?directConnection=true', { directConnection: false }); @@ -29,7 +29,7 @@ describe('MongoOptions', function() { expect(options.prototype).to.not.exist; }); - it('should rename tls options correctly', function() { + it('should rename tls options correctly', function () { const filename = `${os.tmpdir()}/tmp.pem`; fs.closeSync(fs.openSync(filename, 'w')); const options = parseOptions('mongodb://localhost:27017/?ssl=true', { @@ -129,7 +129,7 @@ describe('MongoOptions', function() { zlibCompressionLevel: 2 }; - it('should parse all options from the options object', function() { + it('should parse all options from the options object', function () { const options = parseOptions('mongodb://localhost:27017/', ALL_OPTIONS); // Check consolidated options expect(options).has.property('writeConcern'); @@ -174,7 +174,7 @@ describe('MongoOptions', function() { 'zlibCompressionLevel=2' ].join('&'); - it('should parse all options from the URI string', function() { + it('should parse all options from the URI string', function () { const options = parseOptions(allURIOptions); expect(options).has.property('zlibCompressionLevel', 2); @@ -183,7 +183,7 @@ describe('MongoOptions', function() { expect(options.writeConcern).has.property('wtimeout', 2); }); - it('should ignore undefined and null values in the options object', function() { + it('should ignore undefined and null values in the options object', function () { const options = parseOptions('mongodb://localhost:27017/', { maxPoolSize: null, servername: undefined, @@ -202,7 +202,7 @@ describe('MongoOptions', function() { expect(options).not.to.have.property('otherrandomopt'); }); - it('should throw an error on unrecognized keys in the options object if they are defined', function() { + it('should throw an error on unrecognized keys in the options object if they are defined', function () { expect(() => parseOptions('mongodb://localhost:27017/', { randomopt: 'test' @@ -217,43 +217,43 @@ describe('MongoOptions', function() { ).to.throw(MongoParseError, 'options randomopt, randomopt2 are not supported'); }); - it('srvHost saved to options for later resolution', function() { + it('srvHost saved to options for later resolution', function () { const options = parseOptions('mongodb+srv://server.example.com/'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('ssl= can be used to set tls=false', function() { + it('ssl= can be used to set tls=false', function () { const options = parseOptions('mongodb+srv://server.example.com/?ssl=false'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', false); }); - it('tls= can be used to set tls=false', function() { + it('tls= can be used to set tls=false', function () { const options = parseOptions('mongodb+srv://server.example.com/?tls=false'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', false); }); - it('ssl= can be used to set tls=true', function() { + it('ssl= can be used to set tls=true', function () { const options = parseOptions('mongodb+srv://server.example.com/?ssl=true'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('tls= can be used to set tls=true', function() { + it('tls= can be used to set tls=true', function () { const options = parseOptions('mongodb+srv://server.example.com/?tls=true'); expect(options).has.property('srvHost', 'server.example.com'); expect(options).has.property('tls', true); }); - it('supports ReadPreference option in url', function() { + it('supports ReadPreference option in url', function () { const options = parseOptions('mongodb://localhost/?readPreference=nearest'); expect(options.readPreference).to.be.an.instanceof(ReadPreference); expect(options.readPreference.mode).to.equal('nearest'); }); - it('supports ReadPreference option in object plain', function() { + it('supports ReadPreference option in object plain', function () { const options = parseOptions('mongodb://localhost', { readPreference: { mode: 'nearest', hedge: { enabled: true } } }); @@ -262,7 +262,7 @@ describe('MongoOptions', function() { expect(options.readPreference.hedge).to.include({ enabled: true }); }); - it('supports ReadPreference option in object proper class', function() { + it('supports ReadPreference option in object proper class', function () { const tag = { rack: 1 }; const options = parseOptions('mongodb://localhost', { readPreference: new ReadPreference('nearest', [tag], { maxStalenessSeconds: 20 }) @@ -283,13 +283,13 @@ describe('MongoOptions', function() { // Passing readPreference in URI will always be string }); - it('supports WriteConcern option in url', function() { + it('supports WriteConcern option in url', function () { const options = parseOptions('mongodb://localhost/?w=3'); expect(options.writeConcern).to.be.an.instanceof(WriteConcern); expect(options.writeConcern.w).to.equal(3); }); - it('supports WriteConcern option in object plain', function() { + it('supports WriteConcern option in object plain', function () { const options = parseOptions('mongodb://localhost', { writeConcern: { w: 'majority', wtimeoutMS: 300 } }); @@ -298,7 +298,7 @@ describe('MongoOptions', function() { expect(options.writeConcern.wtimeout).to.equal(300); }); - it('supports WriteConcern option in object proper class', function() { + it('supports WriteConcern option in object proper class', function () { const options = parseOptions('mongodb://localhost', { writeConcern: new WriteConcern(5, 200, true) }); @@ -308,13 +308,13 @@ describe('MongoOptions', function() { expect(options.writeConcern.j).to.equal(true); }); - it('supports ReadConcern option in url', function() { + it('supports ReadConcern option in url', function () { const options = parseOptions('mongodb://localhost/?readConcernLevel=available'); expect(options.readConcern).to.be.an.instanceof(ReadConcern); expect(options.readConcern.level).to.equal('available'); }); - it('supports ReadConcern option in object plain', function() { + it('supports ReadConcern option in object plain', function () { const options = parseOptions('mongodb://localhost', { readConcern: { level: 'linearizable' } }); @@ -322,7 +322,7 @@ describe('MongoOptions', function() { expect(options.readConcern.level).to.equal('linearizable'); }); - it('supports ReadConcern option in object proper class', function() { + it('supports ReadConcern option in object proper class', function () { const options = parseOptions('mongodb://localhost', { readConcern: new ReadConcern('snapshot') }); @@ -330,7 +330,7 @@ describe('MongoOptions', function() { expect(options.readConcern.level).to.equal('snapshot'); }); - it('supports Credentials option in url', function() { + it('supports Credentials option in url', function () { const options = parseOptions('mongodb://USERNAME:PASSWORD@localhost/'); expect(options.credentials).to.be.an.instanceof(MongoCredentials); expect(options.credentials.username).to.equal('USERNAME'); @@ -338,7 +338,7 @@ describe('MongoOptions', function() { expect(options.credentials.source).to.equal('admin'); }); - it('supports Credentials option in url with db', function() { + it('supports Credentials option in url with db', function () { const options = parseOptions('mongodb://USERNAME:PASSWORD@localhost/foo'); expect(options.credentials).to.be.an.instanceof(MongoCredentials); expect(options.credentials.username).to.equal('USERNAME'); @@ -346,7 +346,7 @@ describe('MongoOptions', function() { expect(options.credentials.source).to.equal('foo'); }); - it('supports Credentials option in auth object plain', function() { + it('supports Credentials option in auth object plain', function () { const options = parseOptions('mongodb://localhost/', { auth: { username: 'USERNAME', password: 'PASSWORD' } }); @@ -355,7 +355,7 @@ describe('MongoOptions', function() { expect(options.credentials.password).to.equal('PASSWORD'); }); - it('transforms tlsAllowInvalidCertificates and tlsAllowInvalidHostnames correctly', function() { + it('transforms tlsAllowInvalidCertificates and tlsAllowInvalidHostnames correctly', function () { const optionsTrue = parseOptions('mongodb://localhost/', { tlsAllowInvalidCertificates: true, tlsAllowInvalidHostnames: true @@ -389,7 +389,7 @@ describe('MongoOptions', function() { fs.unlinkSync('testCert.pem'); }); - it('correctly sets the cert and key if only tlsCertificateKeyFile is provided', function() { + it('correctly sets the cert and key if only tlsCertificateKeyFile is provided', function () { const optsFromObject = parseOptions('mongodb://localhost/', { tlsCertificateKeyFile: 'testCertKey.pem' }); @@ -436,7 +436,7 @@ describe('MongoOptions', function() { expect(parseOptions('mongodb://localhost?ssl=false&tls=false')).to.have.property('tls', false); }); - it('transforms tlsInsecure correctly', function() { + it('transforms tlsInsecure correctly', function () { const optionsTrue = parseOptions('mongodb://localhost/', { tlsInsecure: true }); @@ -455,8 +455,8 @@ describe('MongoOptions', function() { expect(optionsUndefined.checkServerIdentity).to.equal(undefined); }); - context('when tlsCAPath is provided as an empty string', function() { - it('throws MongoParseError', function() { + context('when tlsCAPath is provided as an empty string', function () { + it('throws MongoParseError', function () { expect(() => { parseOptions('mongodb://localhost:27017/?tls=true', { tlsCAFile: '' @@ -465,8 +465,8 @@ describe('MongoOptions', function() { }); }); - context('when tlsCertificateKeyFile is provided as an empty string', function() { - it('throws MongoParseError', function() { + context('when tlsCertificateKeyFile is provided as an empty string', function () { + it('throws MongoParseError', function () { expect(() => { parseOptions('mongodb://localhost:27017/?tls=true', { tlsCertificateKeyFile: '' @@ -475,15 +475,15 @@ describe('MongoOptions', function() { }); }); - describe('compressors', function() { - it('can be set when passed in as an array in the options object', function() { + describe('compressors', function () { + it('can be set when passed in as an array in the options object', function () { const clientViaOpt = new MongoClient('mongodb://localhost', { compressors: ['zlib', 'snappy'] }); expect(clientViaOpt.options).to.have.property('compressors').deep.equal(['zlib', 'snappy']); }); - it('can be set when passed in as a comma-delimited string in the options object or URI', function() { + it('can be set when passed in as a comma-delimited string in the options object or URI', function () { const clientViaOpt = new MongoClient('mongodb://localhost', { compressors: 'zlib,snappy' }); @@ -492,7 +492,7 @@ describe('MongoOptions', function() { expect(clientViaUri.options).to.have.property('compressors').deep.equal(['zlib', 'snappy']); }); - it('should validate that a string or an array of strings is provided as input', function() { + it('should validate that a string or an array of strings is provided as input', function () { expect( () => new MongoClient('mongodb://localhost', { @@ -501,7 +501,7 @@ describe('MongoOptions', function() { ).to.throw(/^compressors must be an array or a comma-delimited list of strings/); }); - it('should throw an error if an unrecognized compressor is specified', function() { + it('should throw an error if an unrecognized compressor is specified', function () { const expectedErrRegex = /not a valid compression mechanism/; expect( () => @@ -521,8 +521,8 @@ describe('MongoOptions', function() { }); }); - describe('serverApi', function() { - it('is supported as a client option when it is a valid ServerApiVersion string', function() { + describe('serverApi', function () { + it('is supported as a client option when it is a valid ServerApiVersion string', function () { const validVersions = Object.values(ServerApiVersion); expect(validVersions.length).to.be.at.least(1); for (const version of validVersions) { @@ -533,7 +533,7 @@ describe('MongoOptions', function() { } }); - it('is supported as a client option when it is an object with a valid version property', function() { + it('is supported as a client option when it is an object with a valid version property', function () { const validVersions = Object.values(ServerApiVersion); expect(validVersions.length).to.be.at.least(1); for (const version of validVersions) { @@ -544,7 +544,7 @@ describe('MongoOptions', function() { } }); - it('is not supported as a client option when it is an invalid string', function() { + it('is not supported as a client option when it is an invalid string', function () { expect(() => parseOptions('mongodb://localhost/', { serverApi: 'bad' @@ -552,7 +552,7 @@ describe('MongoOptions', function() { ).to.throw(/^Invalid server API version=bad;/); }); - it('is not supported as a client option when it is a number', function() { + it('is not supported as a client option when it is a number', function () { expect(() => parseOptions('mongodb://localhost/', { serverApi: 1 @@ -560,7 +560,7 @@ describe('MongoOptions', function() { ).to.throw(/^Invalid `serverApi` property;/); }); - it('is not supported as a client option when it is an object without a specified version', function() { + it('is not supported as a client option when it is an object without a specified version', function () { expect(() => parseOptions('mongodb://localhost/', { serverApi: {} @@ -568,7 +568,7 @@ describe('MongoOptions', function() { ).to.throw(/^Invalid `serverApi` property;/); }); - it('is not supported as a client option when it is an object with an invalid specified version', function() { + it('is not supported as a client option when it is an object with an invalid specified version', function () { expect(() => parseOptions('mongodb://localhost/', { serverApi: { version: 1 } @@ -581,7 +581,7 @@ describe('MongoOptions', function() { ).to.throw(/^Invalid server API version=bad;/); }); - it('is not supported as a URI option even when it is a valid ServerApiVersion string', function() { + it('is not supported as a URI option even when it is a valid ServerApiVersion string', function () { expect(() => parseOptions('mongodb://localhost/?serverApi=1')).to.throw( 'URI cannot contain `serverApi`, it can only be passed to the client' ); @@ -638,7 +638,7 @@ describe('MongoOptions', function() { } }); - it('set monitorCommands to false (NODE-3513)', function() { + it('set monitorCommands to false (NODE-3513)', function () { const client = new MongoClient('mongodb://localhost'); const clientOptions = client.options; @@ -649,7 +649,7 @@ describe('MongoOptions', function() { expect(client[optionsSym]).to.have.property('monitorCommands', false); }); - it('respects monitorCommands option passed in', function() { + it('respects monitorCommands option passed in', function () { const clientViaOpt = new MongoClient('mongodb://localhost', { monitorCommands: true }); const clientViaUri = new MongoClient('mongodb://localhost?monitorCommands=true'); @@ -668,27 +668,27 @@ describe('MongoOptions', function() { }); }); - context('when loadBalanced=true is in the URI', function() { - it('sets the option', function() { + context('when loadBalanced=true is in the URI', function () { + it('sets the option', function () { const options = parseOptions('mongodb://a/?loadBalanced=true'); expect(options.loadBalanced).to.be.true; }); - it('errors with multiple hosts', function() { + it('errors with multiple hosts', function () { const parse = () => { parseOptions('mongodb://a,b/?loadBalanced=true'); }; expect(parse).to.throw(/single host/); }); - it('errors with a replicaSet option', function() { + it('errors with a replicaSet option', function () { const parse = () => { parseOptions('mongodb://a/?loadBalanced=true&replicaSet=test'); }; expect(parse).to.throw(/replicaSet/); }); - it('errors with a directConnection option', function() { + it('errors with a directConnection option', function () { const parse = () => { parseOptions('mongodb://a/?loadBalanced=true&directConnection=true'); }; @@ -696,15 +696,15 @@ describe('MongoOptions', function() { }); }); - context('when loadBalanced is in the options object', function() { - it('errors when the option is true', function() { + context('when loadBalanced is in the options object', function () { + it('errors when the option is true', function () { const parse = () => { parseOptions('mongodb://a/', { loadBalanced: true }); }; expect(parse).to.throw(/URI/); }); - it('errors when the option is false', function() { + it('errors when the option is false', function () { const parse = () => { parseOptions('mongodb://a/', { loadBalanced: false }); }; @@ -831,7 +831,7 @@ describe('MongoOptions', function() { }); }); - context('loggingOptions', function() { + context('loggingOptions', function () { const expectedLoggingObject = { maxDocumentLength: 20, logDestination: new Writable() @@ -847,7 +847,7 @@ describe('MongoOptions', function() { sinon.restore(); }); - it('assigns the parsed options to the mongoLoggerOptions option', function() { + it('assigns the parsed options to the mongoLoggerOptions option', function () { const client = new MongoClient('mongodb://localhost:27017'); expect(client.options).to.have.property('mongoLoggerOptions').to.equal(expectedLoggingObject); }); From b1d4e5178ff1d001f11aa9b541099a8a627e59df Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 26 Jul 2023 10:33:47 -0400 Subject: [PATCH 22/28] fix(NODE-3924): address review comments --- src/connection_string.ts | 18 ++++++++++++++---- src/mongo_client.ts | 7 ++----- src/operations/find_and_modify.ts | 2 +- test/unit/mongo_client.test.js | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index 9a203edc2ae..4f12722bf1f 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1097,17 +1097,27 @@ export const OPTIONS = { }, tlsCAFile: { transform({ name, values: [value] }) { - if (typeof value === 'string' && value.length === 0) { - throw new MongoParseError(`${name} must have non-zero length`); + if (typeof value !== 'string') { + throw new MongoParseError(`${name} must be of type string`); } + + if (value.length === 0) { + throw new MongoParseError(`${name} must be have non-zero length`); + } + return value; } }, tlsCertificateKeyFile: { transform({ name, values: [value] }) { - if (typeof value === 'string' && value.length === 0) { - throw new MongoParseError(`${name} must have non-zero length`); + if (typeof value !== 'string') { + throw new MongoParseError(`${name} must be of type string`); } + + if (value.length === 0) { + throw new MongoParseError(`${name} must be have non-zero length`); + } + return value; } }, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 1b4c7b1449c..d2cc471593e 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -435,13 +435,10 @@ export class MongoClient extends TypedEventEmitter { const options = this[kOptions]; if (options.tls) { - if (typeof options.tlsCAFile === 'string' && options.tlsCAFile.length > 0) { + if (options.tlsCAFile) { options.ca ??= await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); } - if ( - typeof options.tlsCertificateKeyFile === 'string' && - options.tlsCertificateKeyFile.length > 0 - ) { + if (options.tlsCertificateKeyFile) { options.key ??= await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); } } diff --git a/src/operations/find_and_modify.ts b/src/operations/find_and_modify.ts index 763ec149303..57074598b22 100644 --- a/src/operations/find_and_modify.ts +++ b/src/operations/find_and_modify.ts @@ -142,7 +142,7 @@ class FindAndModifyOperation extends CommandCallbackOperation { upsert: false }; - options.includeResultMetadata ??= true; + options.includeResultMetadata ??= false; const sort = formatSort(options.sort); if (sort) { diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index bf332a059ba..61bf806b4ed 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -44,7 +44,7 @@ describe('MongoOptions', function () { * * ### Additional options: * - * | nodejs native option | driver spec compliant option name | driver option type | + * | nodejs native option | driver spec equicalient option name | driver option type | * |:----------------------|:----------------------------------------------|:-------------------| * | `ca` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | From 253aa6d2fbbe0f30cf05d79ffbb350f276f30d66 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 26 Jul 2023 10:55:12 -0400 Subject: [PATCH 23/28] fix(NODE-3924): revert unintended change --- src/operations/find_and_modify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/find_and_modify.ts b/src/operations/find_and_modify.ts index 57074598b22..763ec149303 100644 --- a/src/operations/find_and_modify.ts +++ b/src/operations/find_and_modify.ts @@ -142,7 +142,7 @@ class FindAndModifyOperation extends CommandCallbackOperation { upsert: false }; - options.includeResultMetadata ??= false; + options.includeResultMetadata ??= true; const sort = formatSort(options.sort); if (sort) { From c454e3771abcec37b59a173f14cfe7bf7b210673 Mon Sep 17 00:00:00 2001 From: Warren James Date: Wed, 26 Jul 2023 11:39:08 -0400 Subject: [PATCH 24/28] Update test/unit/mongo_client.test.js Co-authored-by: Bailey Pearson --- test/unit/mongo_client.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 61bf806b4ed..4a676e15c34 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -44,7 +44,7 @@ describe('MongoOptions', function () { * * ### Additional options: * - * | nodejs native option | driver spec equicalient option name | driver option type | + * | nodejs native option | driver spec equivalent option name | driver option type | * |:----------------------|:----------------------------------------------|:-------------------| * | `ca` | `tlsCAFile` | `string` | * | `crl` | N/A | `string` | From 5352581b6b1dfce02f80daec74960f5dbb9e7fc4 Mon Sep 17 00:00:00 2001 From: Warren James Date: Fri, 28 Jul 2023 14:22:29 -0400 Subject: [PATCH 25/28] fix(NODE-3924): Change to throw at connect time --- src/connection_string.ts | 24 ++------------------ src/mongo_client.ts | 4 ++-- test/manual/tls_support.test.ts | 40 +++++++++++++++++++++++++++++---- test/unit/mongo_client.test.js | 20 ----------------- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index 4f12722bf1f..a6a1677412f 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1096,30 +1096,10 @@ export const OPTIONS = { } }, tlsCAFile: { - transform({ name, values: [value] }) { - if (typeof value !== 'string') { - throw new MongoParseError(`${name} must be of type string`); - } - - if (value.length === 0) { - throw new MongoParseError(`${name} must be have non-zero length`); - } - - return value; - } + type: 'string' }, tlsCertificateKeyFile: { - transform({ name, values: [value] }) { - if (typeof value !== 'string') { - throw new MongoParseError(`${name} must be of type string`); - } - - if (value.length === 0) { - throw new MongoParseError(`${name} must be have non-zero length`); - } - - return value; - } + type: 'string' }, tlsCertificateKeyFilePassword: { target: 'passphrase', diff --git a/src/mongo_client.ts b/src/mongo_client.ts index d2cc471593e..b4203b07d75 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -435,10 +435,10 @@ export class MongoClient extends TypedEventEmitter { const options = this[kOptions]; if (options.tls) { - if (options.tlsCAFile) { + if (typeof options.tlsCAFile === 'string') { options.ca ??= await fs.readFile(options.tlsCAFile, { encoding: 'utf8' }); } - if (options.tlsCertificateKeyFile) { + if (typeof options.tlsCertificateKeyFile === 'string') { options.key ??= await fs.readFile(options.tlsCertificateKeyFile, { encoding: 'utf8' }); } } diff --git a/test/manual/tls_support.test.ts b/test/manual/tls_support.test.ts index a3ee4b78a40..179527b1c4b 100644 --- a/test/manual/tls_support.test.ts +++ b/test/manual/tls_support.test.ts @@ -37,15 +37,15 @@ describe('TLS Support', function () { context('when tls filepaths are provided', () => { let client: MongoClient; + afterEach(async () => { + if (client) await client.close(); + }); + context('when tls filepaths have length > 0', () => { beforeEach(async () => { client = new MongoClient(CONNECTION_STRING, tlsSettings); }); - afterEach(async () => { - if (client) await client.close(); - }); - it('should read in files async at connect time', async () => { expect(client.options).property('tlsCAFile', TLS_CA_FILE); expect(client.options).property('tlsCertificateKeyFile', TLS_CERT_KEY_FILE); @@ -73,6 +73,38 @@ describe('TLS Support', function () { }); }); }); + + context('when tlsCAFile has length === 0', () => { + beforeEach(() => { + client = new MongoClient(CONNECTION_STRING, { + tls: true, + tlsCAFile: '', + tlsCertificateKeyFile: TLS_CERT_KEY_FILE + }); + }); + + it('should throw an error at connect time', async () => { + const err = await client.connect().catch(e => e); + + expect(err).to.be.instanceof(Error); + }); + }); + + context('when tlsCertificateKeyFile has length === 0', () => { + beforeEach(() => { + client = new MongoClient(CONNECTION_STRING, { + tls: true, + tlsCAFile: TLS_CA_FILE, + tlsCertificateKeyFile: '' + }); + }); + + it('should throw an error at connect time', async () => { + const err = await client.connect().catch(e => e); + + expect(err).to.be.instanceof(Error); + }); + }); }); }); diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 4a676e15c34..2869a7d270e 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -455,26 +455,6 @@ describe('MongoOptions', function () { expect(optionsUndefined.checkServerIdentity).to.equal(undefined); }); - context('when tlsCAPath is provided as an empty string', function () { - it('throws MongoParseError', function () { - expect(() => { - parseOptions('mongodb://localhost:27017/?tls=true', { - tlsCAFile: '' - }); - }).to.throw(MongoParseError); - }); - }); - - context('when tlsCertificateKeyFile is provided as an empty string', function () { - it('throws MongoParseError', function () { - expect(() => { - parseOptions('mongodb://localhost:27017/?tls=true', { - tlsCertificateKeyFile: '' - }); - }).to.throw(MongoParseError); - }); - }); - describe('compressors', function () { it('can be set when passed in as an array in the options object', function () { const clientViaOpt = new MongoClient('mongodb://localhost', { From a67d3a1665cda828958c94b3843ad26435f80efd Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 31 Jul 2023 15:59:03 -0400 Subject: [PATCH 26/28] fix(NODE-3924): throw on non-string --- src/connection_string.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index a6a1677412f..a15c503a4d1 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1096,10 +1096,16 @@ export const OPTIONS = { } }, tlsCAFile: { - type: 'string' + transform({ name, values: [value] }) { + if (typeof value !== 'string') throw new MongoParseError(`${name} must be a string`); + return value; + } }, tlsCertificateKeyFile: { - type: 'string' + transform({ name, values: [value] }) { + if (typeof value !== 'string') throw new MongoParseError(`${name} must be a string`); + return value; + } }, tlsCertificateKeyFilePassword: { target: 'passphrase', From af14ffe44927d07cf84ffd62b97ca7a9a50c1b5e Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 31 Jul 2023 16:05:59 -0400 Subject: [PATCH 27/28] test(NODE-3924): add test to throw on non-string values --- test/unit/mongo_client.test.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 2869a7d270e..ff252092383 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -64,6 +64,28 @@ describe('MongoOptions', function () { expect(options).has.property('tls', true); }); + context('tlsCAFile', function () { + it('throws MongoParseError when passed a URL Object', function () { + it('throws MongoParseError when passed a URL Object', function () { + expect( + () => + new MongoClient('mongodb://localhost:27017', { + tlsCAFile: new URL('file://hello') + }) + ).to.throw(MongoParseError); + }); + }); + }); + context('tlsCertificateKeyFile', function () { + it('throws MongoParseError when passed a URL Object', function () { + expect( + () => + new MongoClient('mongodb://localhost:27017', { + tlsCertificateKeyFile: new URL('file://hello') + }) + ).to.throw(MongoParseError); + }); + }); const ALL_OPTIONS = { appName: 'cats', auth: { username: 'username', password: 'password' }, From 1550a94634c29966b944ac2a0dec5fb7e839c6f4 Mon Sep 17 00:00:00 2001 From: Warren James Date: Mon, 31 Jul 2023 17:08:51 -0400 Subject: [PATCH 28/28] revert validation --- src/connection_string.ts | 10 ++-------- test/unit/mongo_client.test.js | 22 ---------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/connection_string.ts b/src/connection_string.ts index a15c503a4d1..a6a1677412f 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -1096,16 +1096,10 @@ export const OPTIONS = { } }, tlsCAFile: { - transform({ name, values: [value] }) { - if (typeof value !== 'string') throw new MongoParseError(`${name} must be a string`); - return value; - } + type: 'string' }, tlsCertificateKeyFile: { - transform({ name, values: [value] }) { - if (typeof value !== 'string') throw new MongoParseError(`${name} must be a string`); - return value; - } + type: 'string' }, tlsCertificateKeyFilePassword: { target: 'passphrase', diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index ff252092383..2869a7d270e 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -64,28 +64,6 @@ describe('MongoOptions', function () { expect(options).has.property('tls', true); }); - context('tlsCAFile', function () { - it('throws MongoParseError when passed a URL Object', function () { - it('throws MongoParseError when passed a URL Object', function () { - expect( - () => - new MongoClient('mongodb://localhost:27017', { - tlsCAFile: new URL('file://hello') - }) - ).to.throw(MongoParseError); - }); - }); - }); - context('tlsCertificateKeyFile', function () { - it('throws MongoParseError when passed a URL Object', function () { - expect( - () => - new MongoClient('mongodb://localhost:27017', { - tlsCertificateKeyFile: new URL('file://hello') - }) - ).to.throw(MongoParseError); - }); - }); const ALL_OPTIONS = { appName: 'cats', auth: { username: 'username', password: 'password' },