diff --git a/README.md b/README.md index ae511425..e35971d8 100644 --- a/README.md +++ b/README.md @@ -27,34 +27,32 @@ console.log(c.driverUrl) ```javascript const Connection = require('mongodb-connection-model'); -Connection.from( - 'mongodb://someUsername:testPassword@localhost', - (error, result) => { - console.log(result); - >>> `{ - hosts: [{ host: 'localhost', port: 27017 }], - hostname: 'localhost', - port: 27017, - auth: { - username: 'someUsername', - password: 'testPassword', - db: 'admin' - }, - isSrvRecord: false, - authStrategy: 'MONGODB', - mongodbUsername: 'someUsername', - mongodbPassword: 'testPassword', - mongodbDatabaseName: 'admin', - extraOptions: {}, - connectionType: 'NODE_DRIVER', - readPreference: 'primary', - kerberosCanonicalizeHostname: false, - sslMethod: 'NONE', - sshTunnel: 'NONE', - sshTunnelPort: 22 - }` - } +const result = await Connection.from( + 'mongodb://someUsername:testPassword@localhost' ); +console.log(result); +>>> `{ + hosts: [{ host: 'localhost', port: 27017 }], + hostname: 'localhost', + port: 27017, + auth: { + username: 'someUsername', + password: 'testPassword', + db: 'admin' + }, + isSrvRecord: false, + authStrategy: 'MONGODB', + mongodbUsername: 'someUsername', + mongodbPassword: 'testPassword', + mongodbDatabaseName: 'admin', + extraOptions: {}, + connectionType: 'NODE_DRIVER', + readPreference: 'primary', + kerberosCanonicalizeHostname: false, + sslMethod: 'NONE', + sshTunnel: 'NONE', + sshTunnelPort: 22 +}` ``` ## Properties @@ -415,9 +413,6 @@ This will log the following events to the console: ```javascript >>> status: { message: 'Validate', pending: true } >>> status: { message: 'Validate', complete: true } ->>> status: { message: 'Load SSL files', pending: true } ->>> status: { message: 'Load SSL files', skipped: true, - reason: 'The selected SSL mode does not need to load any files.' } >>> status: { message: 'Create SSH Tunnel', pending: true } >>> status: { message: 'Create SSH Tunnel', complete: true} >>> status: { message: 'Connect to MongoDB', pending: true } @@ -445,8 +440,6 @@ This will log the following events to the console: ```javascript >>> status: { message: 'Validate', pending: true } >>> status: { message: 'Validate', complete: true } ->>> status: { message: 'Load SSL files', pending: true } ->>> status: { message: 'Load SSL files', complete: true} >>> status: { message: 'Create SSH Tunnel', pending: true } >>> status: { message: 'Create SSH Tunnel', skipped: true, reason: 'The selected SSH Tunnel mode is NONE.'} diff --git a/constants/ssl-method-values.js b/constants/ssl-method-values.js index 7c714720..0fabd5d3 100644 --- a/constants/ssl-method-values.js +++ b/constants/ssl-method-values.js @@ -1,27 +1,44 @@ // Allowed values for the `sslMethod` field + +/** + * Do not use SSL for anything. + */ +const NONE = 'NONE'; +/** + * Use system CA. + */ +const SYSTEMCA = 'SYSTEMCA'; +/** + * Use SSL if available. + */ +const IFAVAILABLE = 'IFAVAILABLE'; +/** + * Use SSL but do not perform any validation of the certificate chain. + */ +const UNVALIDATED = 'UNVALIDATED'; +/** + * The driver should validate the server certificate and fail to connect if validation fails. + */ +const SERVER = 'SERVER'; +/** + * The driver must present a valid certificate and validate the server certificate. + */ +const ALL = 'ALL'; + module.exports = [ - /** - * Do not use SSL for anything. - */ - 'NONE', - /** - * Use system CA. - */ - 'SYSTEMCA', - /** - * Use SSL if available. - */ - 'IFAVAILABLE', - /** - * Use SSL but do not perform any validation of the certificate chain. - */ - 'UNVALIDATED', - /** - * The driver should validate the server certificate and fail to connect if validation fails. - */ - 'SERVER', - /** - * The driver must present a valid certificate and validate the server certificate. - */ - 'ALL' + NONE, + SYSTEMCA, + IFAVAILABLE, + UNVALIDATED, + SERVER, + ALL ]; + +module.exports.SSL_METHODS = { + NONE, + SYSTEMCA, + IFAVAILABLE, + UNVALIDATED, + SERVER, + ALL +}; diff --git a/lib/connect.js b/lib/connect.js index b6aef2fe..50beffa0 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -1,345 +1,147 @@ -const { EventEmitter } = require('events'); -const fs = require('fs'); -const async = require('async'); -const { - includes, - clone, - assign, - isString, - isFunction, - omit -} = require('lodash'); const { MongoClient } = require('mongodb'); -const { parseConnectionString } = require('mongodb/lib/core'); -const Connection = require('./extended-model'); const createSSHTunnel = require('./ssh-tunnel'); +const Connection = require('./extended-model'); +const fs = require('fs'); +const { promisify } = require('util'); const debug = require('debug')('mongodb-connection-model:connect'); -const needToLoadSSLFiles = (model) => - !includes(['NONE', 'UNVALIDATED'], model.sslType); - -const loadOptions = (model, done) => { - if (!needToLoadSSLFiles(model)) { - process.nextTick(() => done(null, model.driverOptions)); +/* Some of the SSL options can be arrays with one file path in them. + * This method returns the options so that those arrays are instead just + * a string that is the first item in the array. + **/ +const flattenSSLOptions = (driverOptions) => { + const flattenedOptions = {}; + + ['sslCA', 'sslCert', 'sslKey'].forEach((key) => { + const option = driverOptions[key]; + if (Array.isArray(option)) { + flattenedOptions[key] = option[0]; + } else if (option) { + flattenedOptions[key] = option; + } + }); - return; - } + return flattenedOptions; +}; - const tasks = {}; - const opts = clone(model.driverOptions, true); +const getSSLFileOptionsAsBuffers = async(driverOptions) => { + const sslOptions = { + ...flattenSSLOptions(driverOptions) + }; - Object.keys(opts).map((key) => { - if (key.indexOf('ssl') === -1) { - return; - } + await Promise.all(Object.keys(sslOptions).map(async(key) => { + const runReadFile = promisify(fs.readFile); + sslOptions[key] = await runReadFile(sslOptions[key]); + })); - if (Array.isArray(opts[key])) { - opts[key].forEach((value) => { - if (typeof value === 'string') { - tasks[key] = (cb) => - async.parallel( - opts[key].map((k) => fs.readFile.bind(null, k)), - cb - ); - } - }); - } - - if (typeof opts[key] !== 'string') { - return; - } + return sslOptions; +}; - if (key === 'sslPass') { - return; - } +const createConnectionOptions = async(model) => { + const sslFileOptionsAsBuffers = await getSSLFileOptionsAsBuffers( + model.driverOptions + ); - tasks[key] = fs.readFile.bind(null, opts[key]); - }); + const options = { + ...model.driverOptions, + useNewUrlParser: true, + useUnifiedTopology: true, + ...sslFileOptionsAsBuffers + }; - async.parallel(tasks, (err, res) => { - if (err) { - return done(err); - } + if ( + model.directConnection === undefined && + model.hosts.length === 1 && + !model.isSrvRecord && + (model.replicaSet === undefined || model.replicaSet === '') + ) { + // Previous to the node driver 3.6.3, directConnection was + // set to true under these conditions. In 3.6.3 this defaulting + // behavior was removed and now we add it. COMPASS-4534 + // https://github.com/mongodb/node-mongodb-native/commit/f8fd310a11a91db82f1c0ddc57482b8edabc231b + options.directConnection = true; + } - Object.keys(res).map((key) => { - opts[key] = res[key]; - }); + delete options.auth; - done(null, opts); - }); + return options; }; /** * Make sure the driver doesn't puke on the URL and cause * an uncaughtException. * - * @param {Connection} model - * @param {Function} done + * @param {Connection | Object} connectionModel + * @param {Function} setupListeners - A function to be called with the + * mongoClient to listen to SDAM events. + * @returns {Array} The first index being an error, if there's no error, + * the second index is the client, and the third is the connectionOptions. */ -const validateURL = (model, done) => { - const url = model.driverUrl; - - parseConnectionString(url, {}, (err, result) => { - // URL parsing errors are just generic `Error` instances - // so overwrite name so mongodb-js-server will know - // the message is safe to display. - if (err) { - err.name = 'MongoError'; - } - - done(err, result); - }); -}; - -const getStatusStateString = (evt) => { - if (!evt) { - return 'UNKNOWN'; - } - - if (evt.pending) { - return 'PENDING'; - } - - if (evt.skipped) { - return 'SKIPPED'; - } - - if (evt.error) { - return 'ERROR'; - } - - if (evt.complete) { - return 'COMPLETE'; +const connect = async(connectionModel, setupListeners) => { + let model; + if (connectionModel.serialize === undefined) { + model = new Connection(model); + } else { + model = connectionModel; } -}; -const Tasks = { - LoadSSLFiles: 'Load SSL files', - CreateSSHTunnel: 'Create SSH Tunnel', - ConnectToMongoDB: 'Connect to MongoDB' -}; + const connectionOptions = await createConnectionOptions(model); -const getTasks = (model, setupListeners) => { - const state = new EventEmitter(); - const tasks = {}; - const _statuses = {}; - let options = {}; let tunnel; - let client; - const status = (message, cb) => { - if (_statuses[message]) { - return _statuses[message]; + if (model.sshTunnel !== 'NONE') { + debug('creating SSH tunnel'); + try { + tunnel = await createSSHTunnel(model); + } catch (error) { + debug('unable to create SSH tunnel:', error); + throw error; } + debug('created SSH tunnel'); + } - const ctx = (error, opts) => { - options = { ...model.driverOptions, ...opts }; - - if (error) { - state.emit('status', { message, error }); - - if (cb) { - return cb(error); - } - - return error; - } - - state.emit('status', { message, complete: true }); - - if (cb) { - return cb(); - } - }; - - ctx.skip = (reason) => { - state.emit('status', { message, skipped: true, reason }); - - if (cb) { - return cb(); - } - }; - - if (!ctx._initialized) { - state.emit('status', { message, pending: true }); - ctx._initialized = true; - } - - return ctx; - }; - - /** - * TODO (imlucas) If localhost, check if MongoDB installed -> no: click/prompt to download - * TODO (imlucas) If localhost, check if MongoDB running -> no: click/prompt to start - * TODO (imlucas) dns.lookup() model.hostname and model.sshTunnelHostname to check for typos - */ - assign(tasks, { - [Tasks.LoadSSLFiles]: (cb) => { - const ctx = status('Load SSL files', cb); - - if (!needToLoadSSLFiles(model)) { - return ctx.skip( - 'The selected SSL mode does not need to load any files.' - ); - } - - loadOptions(model, ctx); - } - }); - - assign(tasks, { - [Tasks.CreateSSHTunnel]: (cb) => { - const ctx = status('Create SSH Tunnel', cb); - - if (model.sshTunnel === 'NONE') { - return ctx.skip('The selected SSH Tunnel mode is NONE.'); - } + debug('connecting to MongoDB'); - tunnel = createSSHTunnel(model, ctx); + let client; + try { + const mongoClient = new MongoClient( + model.driverUrlWithSsh, + connectionOptions + ); + + if (setupListeners) { + setupListeners(mongoClient); } - }); - - assign(tasks, { - [Tasks.ConnectToMongoDB]: (cb) => { - const ctx = status('Connect to MongoDB'); - - // @note: Durran: - // This check here is to prevent the options getting set to a string when a URI - // is passed through. This is a temporary solution until we refactor all of this. - if (isString(options) || !options) { - options = {}; - } - - const validOptions = omit(options, 'auth'); - - validOptions.useNewUrlParser = true; - validOptions.useUnifiedTopology = true; - if ( - model.directConnection === undefined && - model.hosts.length === 1 && - !model.isSrvRecord && - (model.replicaSet === undefined || model.replicaSet === '') - ) { - // Previous to the node driver 3.6.3, directConnection was - // set to true under these conditions. In 3.6.3 this defaulting - // behavior was removed and now we add it. COMPASS-4534 - // https://github.com/mongodb/node-mongodb-native/commit/f8fd310a11a91db82f1c0ddc57482b8edabc231b - validOptions.directConnection = true; - } - - const mongoClient = new MongoClient(model.driverUrlWithSsh, validOptions); - - if (setupListeners) { - setupListeners(mongoClient); - } - - mongoClient.connect((err, _client) => { - ctx(err); - if (err) { - if (tunnel) { - debug('data-service connection error, shutting down ssh tunnel'); - tunnel.close(); - } + client = await mongoClient.connect(); - return cb(err); - } + debug('connected to MongoDB'); - client = _client; - - if (tunnel) { - client.on('close', () => { - debug('data-service disconnected. shutting down ssh tunnel'); - tunnel.close(); - }); - } - - cb(null, { url: model.driverUrlWithSsh, options: validOptions }); + if (tunnel) { + client.on('close', () => { + debug('data-service disconnected. shutting down ssh tunnel'); + tunnel.close(); }); } - }); - - /** - * TODO (imlucas) Could have unintended consequences. - */ - // _.assign(tasks, { - // 'List Databases': function(cb) { - // var ctx = status('List Databases', cb); - // db.db('admin').command({listDatabases: 1}, - // {readPreference: ReadPreference.secondaryPreferred}, ctx); - // } - // }); - - Object.defineProperties(tasks, { - model: { - get: () => model, - enumerable: false - }, - driverOptions: { - get: () => options, - enumerable: false - }, - client: { - get: () => client, - enumerable: false - }, - tunnel: { - get: () => tunnel, - enumerable: false - }, - state: { - get: () => state, - enumerable: false + } catch (error) { + debug('error connecting to MongoDB:', error); + if (tunnel) { + debug('data-service connection error, shutting down ssh tunnel'); + tunnel.close(); } - }); - - return tasks; -}; - -const connect = (model, setupListeners, done) => { - if (model.serialize === undefined) { - model = new Connection(model); - } - if (!isFunction(done)) { - done = (err) => { - if (err) { - throw err; - } - }; + throw new Error(error); } - const tasks = getTasks(model, setupListeners); - const logTaskStatus = require('debug')( - 'mongodb-connection-model:connect:status' - ); - - tasks.state.on('status', (evt) => { - logTaskStatus('%s [%s]', evt.message, getStatusStateString(evt)); - }); - - logTaskStatus('Connecting...'); - - async.series(tasks, (err, tasksArgs) => { - const connectionOptions = tasksArgs[Tasks.ConnectToMongoDB]; - - if (err) { - logTaskStatus('Error connecting:', err); - - return done(err); + return [ + client, + { + url: model.driverUrlWithSsh, + options: connectionOptions } - - logTaskStatus('Successfully connected'); - - return done(null, tasks.client, connectionOptions); - }); - - return tasks.state; + ]; }; module.exports = connect; -module.exports.loadOptions = loadOptions; -module.exports.validateURL = validateURL; -module.exports.getTasks = getTasks; -module.exports.getStatusStateString = getStatusStateString; +module.exports.createConnectionOptions = createConnectionOptions; diff --git a/lib/extended-model.js b/lib/extended-model.js index 97bbbad1..f8c90642 100644 --- a/lib/extended-model.js +++ b/lib/extended-model.js @@ -16,10 +16,7 @@ try { basepath = electron.remote ? electron.remote.app.getPath('userData') : undefined; -} catch (e) { - /* eslint no-console: 0 */ - console.log('Could not load electron', e.message); -} +} catch (e) { /* */ } /** * Configuration for connecting to a MongoDB Deployment. @@ -111,13 +108,10 @@ const ExtendedConnection = Connection.extend(storageMixin, { * * @returns {Function} callback */ -ExtendedConnection.from = (url, callback) => - Connection.from(url, (error, c) => { - if (error) { - return callback(error); - } +ExtendedConnection.from = async(url) => { + const model = await Connection.from(url); - callback(null, new ExtendedConnection(c.getAttributes({ props: true }))); - }); + return new ExtendedConnection(model.getAttributes({ props: true })); +}; module.exports = ExtendedConnection; diff --git a/lib/model.js b/lib/model.js index b794e696..1e57cde0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1,7 +1,7 @@ /* eslint complexity: 0 */ const URL = require('url'); const toURL = URL.format; -const { format, promisify, callbackify } = require('util'); +const { format, promisify } = require('util'); const fs = require('fs'); const { @@ -468,7 +468,10 @@ function addAuthToUrl({ url, isPasswordProtected }) { // so a single string replace should always work. url = url.replace('AUTH_TOKEN', authField, 1); - if (includes(['LDAP', 'KERBEROS', 'X509'], this.authStrategy)) { + if ( + includes(['LDAP', 'KERBEROS', 'X509'], this.authStrategy) + && !url.includes('authSource=') + ) { url = `${url}&authSource=$external`; } @@ -1132,9 +1135,9 @@ async function createConnectionFromUrl(url) { * const client = createClient(args.endpoint, Connection.from(url, () => {})); * * @param {String} [url] - * @param {Function} [callback] + * @returns {Promise} */ -Connection.from = callbackify(createConnectionFromUrl); +Connection.from = createConnectionFromUrl; /** * Helper function to improve the Atlas user experience by diff --git a/lib/ssh-tunnel.js b/lib/ssh-tunnel.js index 8208abbd..13165bad 100644 --- a/lib/ssh-tunnel.js +++ b/lib/ssh-tunnel.js @@ -1,11 +1,12 @@ -var assert = require('assert'); -var ssh2 = require('ssh2'); -var net = require('net'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('util').inherits; -var debug = require('debug')('mongodb-connection-model:ssh-tunnel'); -var ssh2debug = require('debug')('ssh2:client'); -var async = require('async'); +const assert = require('assert'); +const ssh2 = require('ssh2'); +const net = require('net'); +const EventEmitter = require('events').EventEmitter; +const inherits = require('util').inherits; +const debug = require('debug')('mongodb-connection-model:ssh-tunnel'); +const ssh2debug = require('debug')('ssh2:client'); +const async = require('async'); +const util = require('util'); function SSHTunnel(model) { assert(model.hostname, 'hostname required'); @@ -159,7 +160,7 @@ SSHTunnel.prototype.createServer = function (done) { return this.server; }; -SSHTunnel.prototype.listen = function (done) { +SSHTunnel.prototype.startTunnel = function (done) { this.emit('status', { message: 'Create SSH Tunnel', pending: true @@ -204,16 +205,17 @@ SSHTunnel.prototype.close = function () { }); }; -module.exports = (model, done) => { +module.exports = async(model) => { const tunnel = new SSHTunnel(model); if (model.sshTunnel === 'NONE') { - done(); - - return tunnel; + return; } - tunnel.listen(done); + const runStartTunnel = util.promisify( + tunnel.startTunnel.bind(tunnel) + ); + await runStartTunnel(); return tunnel; }; diff --git a/test/build-uri.test.js b/test/build-uri.test.js index 8519a78b..73f29bce 100644 --- a/test/build-uri.test.js +++ b/test/build-uri.test.js @@ -1,40 +1,33 @@ const Connection = require('../'); +const { URL } = require('url'); const chai = require('chai'); const fixture = require('mongodb-connection-fixture'); -const fs = require('fs'); const expect = chai.expect; -const loadOptions = Connection.connect.loadOptions; -const getTasks = Connection.connect.getTasks; const encodeURIComponentRFC3986 = Connection.encodeURIComponentRFC3986; chai.use(require('chai-subset')); describe('Connection model builder', () => { context('when building URI', () => { - it('should include default host, port, readPreference and ssl', (done) => { + it('should include default host, port, readPreference and ssl', async() => { const c = new Connection(); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + await Connection.from(c.driverUrl); }); - it('should include appname', (done) => { + it('should include appname', async() => { const c = new Connection({ appname: 'My App' }); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?readPreference=primary&appname=My%20App&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); it('should include srv prefix', () => { @@ -45,46 +38,39 @@ describe('Connection model builder', () => { ); }); - it('should include replicaSet', (done) => { + it('should include replicaSet', async() => { const c = new Connection({ appname: 'My App', replicaSet: 'testing' }); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?replicaSet=testing&readPreference=primary&appname=My%20App&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + await Connection.from(c.driverUrl); }); - it('does not include empty replicaSet', (done) => { + it('does not include empty replicaSet', async() => { const c = new Connection({ appname: 'My App', replicaSet: '' }); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?readPreference=primary&appname=My%20App&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal NONE', (done) => { + it('should include sslMethod equal NONE', async() => { const c = new Connection({ sslMethod: 'NONE' }); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal UNVALIDATED', (done) => { + it('should include sslMethod equal UNVALIDATED', async() => { const c = new Connection({ sslMethod: 'UNVALIDATED' }); const options = Object.assign({}, Connection.DRIVER_OPTIONS_DEFAULT, { checkServerIdentity: false, @@ -98,13 +84,11 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal SYSTEMCA', (done) => { + it('should include sslMethod equal SYSTEMCA', async() => { const c = new Connection({ sslMethod: 'SYSTEMCA' }); const options = Object.assign({}, Connection.DRIVER_OPTIONS_DEFAULT, { checkServerIdentity: true, @@ -118,13 +102,11 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal IFAVAILABLE', (done) => { + it('should include sslMethod equal IFAVAILABLE', async() => { const c = new Connection({ sslMethod: 'IFAVAILABLE' }); const options = Object.assign({}, Connection.DRIVER_OPTIONS_DEFAULT, { checkServerIdentity: false, @@ -138,13 +120,11 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal SERVER', (done) => { + it('should include sslMethod equal SERVER', async() => { const c = new Connection({ sslMethod: 'SERVER', sslCA: fixture.ssl.ca }); const options = Object.assign({}, Connection.DRIVER_OPTIONS_DEFAULT, { sslCA: [fixture.ssl.ca], @@ -158,13 +138,11 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should include sslMethod equal ALL and authMechanism equal X509', (done) => { + it('should include sslMethod equal ALL and authMechanism equal X509', async() => { const c = new Connection({ sslMethod: 'ALL', sslCA: fixture.ssl.ca, @@ -188,13 +166,13 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should include sslMethod equal ALL and passwordless private keys', (done) => { + it('should include sslMethod equal ALL and passwordless private keys', () => { const c = new Connection({ sslMethod: 'ALL', sslCA: fixture.ssl.ca, @@ -214,28 +192,9 @@ describe('Connection model builder', () => { 'mongodb://localhost:27017/?readPreference=primary&ssl=true' ); expect(c.driverOptions).to.deep.equal(options); - - /* eslint-disable no-sync */ - const expectAfterLoad = { - sslCA: [fs.readFileSync(fixture.ssl.ca)], - sslCert: fs.readFileSync(fixture.ssl.server), - sslKey: fs.readFileSync(fixture.ssl.server), - sslValidate: true, - connectWithNoPrimary: true, - readPreference: 'primary' - }; - /* eslint-enable no-sync */ - const tasks = getTasks(c); - // Trigger relevant side-effect, loading the SSL files into memory. - // eslint-disable-next-line new-cap - tasks['Load SSL files'](function () { - // Read files into memory as the connect function does - expect(tasks.driverOptions).to.deep.equal(expectAfterLoad); - done(); - }); }); - it('should include sslMethod equal ALL and password protected private keys', (done) => { + it('should include sslMethod equal ALL and password protected private keys', async() => { const c = new Connection({ sslMethod: 'ALL', sslCA: fixture.ssl.ca, @@ -258,24 +217,21 @@ describe('Connection model builder', () => { ); expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should convert sslCA into an array', (done) => { + it('should convert sslCA into an array', async() => { const c = new Connection({ sslCA: fixture.ssl.ca }); expect(Array.isArray(c.sslCA)).to.be.equal(true); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should urlencode credentials when using SCRAM-SHA-256 auth', (done) => { + it('should urlencode credentials when using SCRAM-SHA-256 auth', async() => { const c = new Connection({ mongodbUsername: '@rlo', mongodbPassword: 'w@of', @@ -286,13 +242,12 @@ describe('Connection model builder', () => { 'mongodb://%40rlo:w%40of@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-256&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should urlencode credentials when using no auth', (done) => { + it('should urlencode credentials when using no auth', async() => { const c = new Connection({ mongodbUsername: '@rlo', mongodbPassword: 'w@of' @@ -302,13 +257,12 @@ describe('Connection model builder', () => { 'mongodb://%40rlo:w%40of@localhost:27017/?authSource=admin&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should urlencode credentials when using MONGODB auth', (done) => { + it('should urlencode credentials when using MONGODB auth', async() => { const mongodbUsername = 'user'; const mongodbPassword = 'C;Ib86n5b8{AnExew[TU%XZy,)E6G!dk'; const c = new Connection({ mongodbUsername, mongodbPassword }); @@ -317,13 +271,12 @@ describe('Connection model builder', () => { 'mongodb://user:C%3BIb86n5b8%7BAnExew%5BTU%25XZy%2C%29E6G%21dk@localhost:27017/?authSource=admin&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should urlencode credentials when using MONGODB auth with emoji 💕', (done) => { + it('should urlencode credentials when using MONGODB auth with emoji 💕', async() => { const mongodbUsername = '👌emoji😂😍😘🔥💕🎁💯🌹'; const mongodbPassword = '👌emoji😂😍😘🔥💕🎁💯🌹'; const c = new Connection({ mongodbUsername, mongodbPassword }); @@ -338,13 +291,11 @@ describe('Connection model builder', () => { ].join('') ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should urlencode credentials when using LDAP auth', (done) => { + it('should urlencode credentials when using LDAP auth', async() => { const ldapUsername = 'user@-azMPk]&3Wt)iP_9C:PMQ='; const ldapPassword = 'user@-azMPk]&3Wt)iP_9C:PMQ='; const c = new Connection({ ldapUsername, ldapPassword }); @@ -356,13 +307,13 @@ describe('Connection model builder', () => { ].join('') ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should urlencode credentials when using KERBEROS auth', (done) => { + it('should urlencode credentials when using KERBEROS auth', async() => { const kerberosPrincipal = 'user@-azMPk]&3Wt)iP_9C:PMQ='; const c = new Connection({ kerberosPrincipal }); @@ -373,13 +324,13 @@ describe('Connection model builder', () => { ].join('') ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should urlencode credentials when using KERBEROS auth with canonicalizing the host name', (done) => { + it('should urlencode credentials when using KERBEROS auth with canonicalizing the host name', async() => { const kerberosPrincipal = 'user@-azMPk]&3Wt)iP_9C:PMQ='; const c = new Connection({ kerberosCanonicalizeHostname: true, @@ -393,26 +344,26 @@ describe('Connection model builder', () => { ].join('') ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should replace default readPreference with a custom value', (done) => { + it('should replace default readPreference with a custom value', async() => { const c = new Connection({ readPreference: 'secondary' }); expect(c.driverUrl).to.be.equal( 'mongodb://localhost:27017/?readPreference=secondary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should build safeUrl for LDAP auth', (done) => { + it('should build safeUrl for LDAP auth', async() => { const attrs = { ldapUsername: 'ldap-user', ldapPassword: 'ldap-password', @@ -427,13 +378,13 @@ describe('Connection model builder', () => { 'mongodb://ldap-user:*****@localhost:27017/?authMechanism=PLAIN&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should include non-dependent attribute', (done) => { + it('should include non-dependent attribute', async() => { const c = new Connection({ authStrategy: 'LDAP' }); c.ldapUsername = 'ldap-user'; @@ -443,13 +394,13 @@ describe('Connection model builder', () => { 'mongodb://ldap-user:ldap-password@localhost:27017/?authMechanism=PLAIN&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should urlencode ldapPassword when using LDAP auth', (done) => { + it('should urlencode ldapPassword when using LDAP auth', async() => { const c = new Connection({ authStrategy: 'LDAP', ldapUsername: 'arlo', @@ -462,13 +413,13 @@ describe('Connection model builder', () => { 'mongodb://arlo:w%40of@localhost:27017/ldap?authMechanism=PLAIN&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should urlencode ldapUsername when using LDAP auth', (done) => { + it('should urlencode ldapUsername when using LDAP auth', async() => { // COMPASS-745 - should urlencode @ once onl const c = new Connection({ authStrategy: 'LDAP', @@ -482,13 +433,13 @@ describe('Connection model builder', () => { 'mongodb://arlo%40t.co:woof@localhost:27017/ldap?authMechanism=PLAIN&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should urlencode credentials when using X509 auth', (done) => { + it('should urlencode credentials when using X509 auth', async() => { const c = new Connection({ authStrategy: 'X509', x509Username: @@ -502,13 +453,13 @@ describe('Connection model builder', () => { '?authMechanism=MONGODB-X509&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should not include credentials when using X509 auth and there is no username', (done) => { + it('should not include credentials when using X509 auth and there is no username', async() => { const c = new Connection({ authStrategy: 'X509' }); @@ -519,16 +470,16 @@ describe('Connection model builder', () => { '&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); }); context('when building a connection object', () => { context('authStrategy', () => { - it('should set authStrategy to SCRAM-SHA-256', (done) => { + it('should set authStrategy to SCRAM-SHA-256', async() => { const c = new Connection({ mongodbUsername: 'arlo', mongodbPassword: 'woof', @@ -537,13 +488,12 @@ describe('Connection model builder', () => { expect(c.authStrategy).to.be.equal('SCRAM-SHA-256'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set default admin authSource when using SCRAM-SHA-256 auth', (done) => { + it('should set default admin authSource when using SCRAM-SHA-256 auth', async() => { const attrs = { mongodbUsername: 'arlo', mongodbPassword: 'woof', @@ -555,13 +505,12 @@ describe('Connection model builder', () => { 'mongodb://arlo:woof@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-256&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should build safeUrl for SCRAM-SHA-256 auth', (done) => { + it('should build safeUrl for SCRAM-SHA-256 auth', async() => { const attrs = { mongodbUsername: 'arlo', mongodbPassword: 'woof', @@ -576,10 +525,9 @@ describe('Connection model builder', () => { 'mongodb://arlo:*****@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-256&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should throw the error if auth is SCRAM-SHA-256 and mongodbUsername is missing', () => { @@ -621,7 +569,7 @@ describe('Connection model builder', () => { ); }); - it('should set authStrategy to MONGODB', (done) => { + it('should set authStrategy to MONGODB', async() => { const c = new Connection({ mongodbUsername: 'arlo', mongodbPassword: 'woof' @@ -629,13 +577,12 @@ describe('Connection model builder', () => { expect(c.authStrategy).to.be.equal('MONGODB'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set default admin authSource when using MONGODB auth', (done) => { + it('should set default admin authSource when using MONGODB auth', async() => { const attrs = { authStrategy: 'MONGODB', mongodbUsername: 'user', @@ -647,13 +594,12 @@ describe('Connection model builder', () => { 'mongodb://user:somepass@localhost:27017/?authSource=admin&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should build safeUrl for MONGODB auth', (done) => { + it('should build safeUrl for MONGODB auth', async() => { const attrs = { authStrategy: 'MONGODB', mongodbUsername: 'user', @@ -668,10 +614,9 @@ describe('Connection model builder', () => { 'mongodb://user:*****@localhost:27017/?authSource=admin&readPreference=primary&ssl=false' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should throw the error if auth is MONGODB and mongodbUsername is missing', () => { @@ -683,7 +628,7 @@ describe('Connection model builder', () => { expect(error.message).to.include('The \'Username\' field is required when using \'Username/Password\' or \'SCRAM-SHA-256\' for authentication.'); }); - it('should throw the error if auth is MONGODB and mongodbPassword is missing', (done) => { + it('should throw the error if auth is MONGODB and mongodbPassword is missing', async() => { const c = new Connection({ mongodbUsername: 'arlo', mongodbPassword: 'woof' @@ -693,10 +638,9 @@ describe('Connection model builder', () => { Connection.MONGODB_DATABASE_NAME_DEFAULT ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should add a real password to driver url', () => { @@ -711,7 +655,7 @@ describe('Connection model builder', () => { expect(c.driverUrl).to.be.equal(c.driverUrlWithSsh); }); - it('should set authStrategy to LDAP', (done) => { + it('should set authStrategy to LDAP', async() => { const c = new Connection({ ldapUsername: 'arlo', ldapPassword: 'w@of' @@ -719,10 +663,11 @@ describe('Connection model builder', () => { expect(c.authStrategy).to.be.equal('LDAP'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should throw the error if auth is LDAP and ldapUsername is missing', () => { @@ -743,7 +688,7 @@ describe('Connection model builder', () => { expect(error.message).to.equal('The \'Password\' field is required when using LDAP for authentication.'); }); - it('should set authStrategy to X509', (done) => { + it('should set authStrategy to X509', async() => { const c = new Connection({ x509Username: 'CN=client,OU=kerneluser,O=10Gen,L=New York City,ST=New York,C=US' @@ -751,10 +696,11 @@ describe('Connection model builder', () => { expect(c.authStrategy).to.be.equal('X509'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should not throw the error if auth is X509 and x509Username is missing', () => { @@ -781,7 +727,7 @@ describe('Connection model builder', () => { expect(error.message).to.equal('SSL method is required to be set to \'Server and Client Validation\' when using X.509 authentication.'); }); - it('should set default mongodb gssapiServiceName when using KERBEROS auth', (done) => { + it('should set default mongodb gssapiServiceName when using KERBEROS auth', async() => { const c = new Connection({ kerberosPrincipal: 'lucas@kerb.mongodb.parts' }); @@ -790,23 +736,25 @@ describe('Connection model builder', () => { 'mongodb://lucas%40kerb.mongodb.parts@localhost:27017/?gssapiServiceName=mongodb&authMechanism=GSSAPI&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set authStrategy to KERBEROS', (done) => { + it('should set authStrategy to KERBEROS', async() => { const c = new Connection({ kerberosPrincipal: 'lucas@kerb.mongodb.parts' }); expect(c.authStrategy).to.be.equal('KERBEROS'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should throw the error if auth is KERBEROS and kerberosPrincipal is missing', () => { @@ -828,7 +776,7 @@ describe('Connection model builder', () => { expect(c.isValid()).to.be.equal(true); }); - it('should build safeUrl for KERBEROS auth when password is specified', (done) => { + it('should build safeUrl for KERBEROS auth when password is specified', async() => { const attrs = { kerberosPrincipal: 'alena@test.test', ldapPassword: 'ldap-password', @@ -842,13 +790,14 @@ describe('Connection model builder', () => { 'mongodb://alena%40test.test@localhost:27017/?gssapiServiceName=mongodb&authMechanism=GSSAPI&readPreference=primary&ssl=false&authSource=$external' ); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set driverAuthMechanism to GSSAPI when a password is provided', (done) => { + it('should set driverAuthMechanism to GSSAPI when a password is provided', async() => { const c = new Connection({ kerberosPrincipal: 'arlo/dog@krb5.mongodb.parts', kerberosServiceName: 'mongodb' @@ -856,13 +805,14 @@ describe('Connection model builder', () => { expect(c.driverAuthMechanism).to.be.equal('GSSAPI'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set driverAuthMechanism to GSSAPI when a password is provided and urlencode the principal', (done) => { + it('should set driverAuthMechanism to GSSAPI when a password is provided and urlencode the principal', async() => { const c = new Connection({ kerberosPrincipal: 'arlo/dog@krb5.mongodb.parts', kerberosServiceName: 'mongodb' @@ -875,26 +825,26 @@ describe('Connection model builder', () => { expect(c.driverAuthMechanism).to.be.equal('GSSAPI'); expect(c.driverUrl.indexOf(expectedPrefix)).to.be.equal(0); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should set driverAuthMechanism to GSSAPI when a password is not provided', (done) => { + it('should set driverAuthMechanism to GSSAPI when a password is not provided', async() => { const c = new Connection({ kerberosPrincipal: 'arlo/dog@krb5.mongodb.parts' }); expect(c.driverAuthMechanism).to.be.equal('GSSAPI'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); - it('should not include the `:` auth seperator', (done) => { + it('should not include the `:` auth seperator', async() => { const c = new Connection({ kerberosPrincipal: 'lucas@kerb.mongodb.parts' }); @@ -905,15 +855,15 @@ describe('Connection model builder', () => { expect(c.driverUrl.indexOf(expectedPrefix)).to.be.equal(0); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect( + new URL(c.driverUrl).searchParams + ).to.deep.equal(new URL(model.driverUrl).searchParams); }); }); context('top level properties', () => { - it('should set the default read preference to primary preferred', (done) => { + it('should set the default read preference to primary preferred', async() => { const c = new Connection({ appname: 'My App' }); expect(c.driverOptions).to.be.deep.equal({ @@ -921,21 +871,19 @@ describe('Connection model builder', () => { connectWithNoPrimary: true }); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set isSrvRecord defaults to false', (done) => { + it('should set isSrvRecord defaults to false', async() => { const c = new Connection(); expect(c.isSrvRecord).to.be.equal(false); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should allow the mongodbDatabaseName to be optional', () => { @@ -959,48 +907,22 @@ describe('Connection model builder', () => { expect(c.driverUrl).to.not.be.equal(''); expect(c.sshTunnelBindToLocalPort).to.not.exist; }); - - it('should load all of the files from the filesystem if sslMethod ia ALL', (done) => { - const c = new Connection({ - sslMethod: 'ALL', - sslCA: [fixture.ssl.ca], - sslCert: fixture.ssl.server, - sslKey: fixture.ssl.server - }); - - loadOptions(c, (error, driverOptions) => { - if (error) { - return done(error); - } - - const opts = driverOptions; - - expect(opts.sslValidate).to.be.equal(true); - expect(Array.isArray(opts.sslCA)).to.be.equal(true); - expect(Buffer.isBuffer(opts.sslCA[0])).to.be.equal(true); - expect(opts.sslPass).to.not.exist; - expect(Buffer.isBuffer(opts.sslCert)).to.be.equal(true); - expect(Buffer.isBuffer(opts.sslKey)).to.be.equal(true); - done(); - }); - }); }); context('extra options', () => { - it('should use default driverOptions when there is no extra options', (done) => { + it('should use default driverOptions when there is no extra options', async() => { const c = new Connection(); expect(c.driverOptions).to.have.property('connectWithNoPrimary'); expect(c.driverOptions).to.have.property('readPreference'); expect(c.driverOptions).to.not.have.property('socketTimeoutMS'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should include extra options in driverOptions when specified', (done) => { + it('should include extra options in driverOptions when specified', async() => { const c = new Connection({ extraOptions: { socketTimeoutMS: 1000 } }); const options = Object.assign({}, Connection.DRIVER_OPTIONS_DEFAULT, { socketTimeoutMS: 1000, @@ -1009,72 +931,63 @@ describe('Connection model builder', () => { expect(c.driverOptions).to.deep.equal(options); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); }); context('promote values', () => { - it('should not include promoteValues when not specified', (done) => { + it('should not include promoteValues when not specified', async() => { const c = new Connection(); expect(c.driverOptions).to.not.have.property('promoteValues'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set promoteValues to true', (done) => { + it('should set promoteValues to true', async() => { const c = new Connection({ promoteValues: true }); expect(c.driverOptions).to.have.property('promoteValues'); expect(c.driverOptions.promoteValues).to.be.equal(true); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); - it('should set promoteValues to false', (done) => { + it('should set promoteValues to false', async() => { const c = new Connection({ promoteValues: false }); expect(c.driverOptions).to.have.property('promoteValues'); expect(c.driverOptions.promoteValues).to.be.equal(false); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); }); }); context('connection type', () => { - it('should set default connectionType to NODE_DRIVER', (done) => { + it('should set default connectionType to NODE_DRIVER', async() => { const c = new Connection({}); expect(c.connectionType).to.be.equal('NODE_DRIVER'); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); - it('should set default host and port when connectionType is NODE_DRIVER', (done) => { + it('should set default host and port when connectionType is NODE_DRIVER', async() => { const c = new Connection({ connectionType: 'NODE_DRIVER' }); expect(c.hostname).to.be.equal('localhost'); expect(c.port).to.be.equal(27017); - Connection.from(c.driverUrl, (error) => { - expect(error).to.not.exist; - done(); - }); + const model = await Connection.from(c.driverUrl); + expect(c.driverUrl).to.be.equal(model.driverUrl); + expect(c.driverOptions).to.deep.equal(model.driverOptions); }); it('should not allow stitchClientAppId', () => { diff --git a/test/connect.test.js b/test/connect.test.js index af1bb5a6..6018c663 100644 --- a/test/connect.test.js +++ b/test/connect.test.js @@ -1,16 +1,79 @@ -/* eslint no-console:0 */ const assert = require('assert'); const Connection = require('../'); const connect = Connection.connect; +const { createConnectionOptions } = require('../lib/connect'); const mock = require('mock-require'); const sinon = require('sinon'); +const fixture = require('mongodb-connection-fixture'); +const fs = require('fs'); const setupListeners = () => {}; // TODO: These instances are now turned off const data = require('mongodb-connection-fixture'); +const { expect } = require('chai'); describe('connection model connector', () => { + describe('#createConnectionOptions', () => { + let expectedCA; + let expectedClient; + + before(() => { + /* eslint-disable no-sync */ + expectedCA = fs.readFileSync(fixture.ssl.ca); + expectedClient = fs.readFileSync(fixture.ssl.client); + /* eslint-enable no-sync */ + }); + + + it('should load ssl files into buffers', async() => { + const model = new Connection({ + sslMethod: 'ALL', + sslCA: fixture.ssl.ca, + sslCert: fixture.ssl.client, + sslKey: fixture.ssl.client + }); + + const connectionOptions = await createConnectionOptions(model); + + expect( + connectionOptions.sslCA + ).to.deep.equal(expectedCA); + expect( + connectionOptions.sslCert + ).to.deep.equal(expectedClient); + expect( + connectionOptions.sslKey + ).to.deep.equal(expectedClient); + }); + + it('should load ssl files that are arrays into buffers', async() => { + const model = new Connection({ + sslMethod: 'ALL', + sslCA: [fixture.ssl.ca], + sslCert: [fixture.ssl.client], + sslKey: [fixture.ssl.client] + }); + + const connectionOptions = await createConnectionOptions(model); + + /* eslint-disable no-sync */ + expectedCA = fs.readFileSync(fixture.ssl.ca); + expectedClient = fs.readFileSync(fixture.ssl.client); + /* eslint-enable no-sync */ + + expect( + connectionOptions.sslCA + ).to.deep.equal(expectedCA); + expect( + connectionOptions.sslCert + ).to.deep.equal(expectedClient); + expect( + connectionOptions.sslKey + ).to.deep.equal(expectedClient); + }); + }); + describe('local', () => { before( require('mongodb-runner/mocha/before')({ port: 27018, version: '4.0.0' }) @@ -20,73 +83,63 @@ describe('connection model connector', () => { require('mongodb-runner/mocha/after')({ port: 27018, version: '4.0.0' }) ); - it('should return connection config when connected successfully', (done) => { - Connection.from('mongodb://localhost:27018', (parseErr, model) => { - if (parseErr) throw parseErr; - - connect( - model, - setupListeners, - (connectErr, client, { url, options }) => { - if (connectErr) throw connectErr; - - assert.strictEqual( - url, - 'mongodb://localhost:27018/?readPreference=primary&ssl=false' - ); - - assert.deepStrictEqual(options, { - connectWithNoPrimary: true, - directConnection: true, - readPreference: 'primary', - useNewUrlParser: true, - useUnifiedTopology: true - }); - - client.close(true); - done(); - } - ); + it('should return connection config when connected successfully', async() => { + const model = await Connection.from('mongodb://localhost:27018'); + + const [ + client, + { url, options } + ] = await connect( + model, + setupListeners + ); + + assert.strictEqual( + url, + 'mongodb://localhost:27018/?readPreference=primary&ssl=false' + ); + + assert.deepStrictEqual(options, { + connectWithNoPrimary: true, + directConnection: true, + readPreference: 'primary', + useNewUrlParser: true, + useUnifiedTopology: true }); + + client.close(true); }); - it('should connect to `localhost:27018 with model`', (done) => { - Connection.from('mongodb://localhost:27018', (parseErr, model) => { - assert.equal(parseErr, null); - connect(model, setupListeners, (connectErr, client) => { - assert.equal(connectErr, null); - client.close(true); - done(); - }); - }); + it('should connect to `localhost:27018 with model`', async() => { + const model = await Connection.from('mongodb://localhost:27018'); + + const [ + client + ] = await connect(model, setupListeners); + + client.close(true); }); - it('should connect to `localhost:27018 with object`', (done) => { - connect( + it('should connect to `localhost:27018 with object`', async() => { + const [ client ] = await connect( { port: 27018, host: 'localhost' }, - setupListeners, - (err, client) => { - assert.equal(err, null); - client.close(true); - done(); - } + setupListeners ); + client.close(true); }); describe('ssh tunnel failures', () => { const spy = sinon.spy(); - mock('../lib/ssh-tunnel', (model, cb) => { - // simulate successful tunnel creation - cb(); - // then return a mocked tunnel object with a spy close() function + mock('../lib/ssh-tunnel', () => { + // Return a mocked tunnel object with a spy close() function. return { close: spy }; }); const MockConnection = mock.reRequire('../lib/extended-model'); const mockConnect = mock.reRequire('../lib/connect'); - it('should close ssh tunnel if the connection fails', (done) => { + it('should close ssh tunnel if the connection fails', async() => { const model = new MockConnection({ hostname: 'localhost', port: 27020, @@ -98,14 +151,16 @@ describe('connection model connector', () => { }); assert(model.isValid()); - mockConnect(model, setupListeners, (err) => { - // must throw error here, because the connection details are invalid + try { + await mockConnect(model, setupListeners); + assert(false); + } catch (err) { + // Must throw error here, because the connection details are invalid. assert.ok(err); assert.ok(/ECONNREFUSED/.test(err.message)); - // assert that tunnel.close() was called once + // Assert that tunnel.close() was called once. assert.ok(spy.calledOnce); - done(); - }); + } }); }); }); diff --git a/test/parse-and-build-uri.js b/test/parse-and-build-uri.js index d818006e..81acbda0 100644 --- a/test/parse-and-build-uri.js +++ b/test/parse-and-build-uri.js @@ -101,7 +101,7 @@ const tests = [ expectedConnectionString: 'mongodb://%40rlo@localhost:27017/?' + 'gssapiServiceName=mongodb&authMechanism=GSSAPI&readPreference=primary&' + - 'authSource=%24external&ssl=false&authSource=$external' + 'authSource=%24external&ssl=false' }, { description: 'with authMechanismProperties and gssapiServiceName', @@ -109,7 +109,7 @@ const tests = [ 'mongodb://%40rlo@localhost:27017/?' + 'gssapiServiceName=mongodb&authMechanism=GSSAPI&readPreference=primary&' + 'authSource=%24external&authMechanismProperties=CANONICALIZE_HOST_NAME%3Atrue&' + - 'gssapiCanonicalizeHostName=true&ssl=false&authSource=$external' + 'gssapiCanonicalizeHostName=true&ssl=false' }, { description: @@ -145,15 +145,13 @@ const tests = [ describe('connection model', () => { describe('should parse a connection string and build the same string back', function () { tests.forEach(function (test) { - const runTest = (done) => - Connection.from(test.connectionString, (error, result) => { - expect(error).to.not.exist; + const runTest = async() => { + const result = await Connection.from(test.connectionString); - const c = new Connection(result.toJSON()); + const c = new Connection(result.toJSON()); - expect(c.driverUrl).to.be.equal(test.expectedConnectionString || test.connectionString); - done(); - }); + expect(c.driverUrl).to.be.equal(test.expectedConnectionString || test.connectionString); + }; const runMode = test.only ? it.only : it; runMode(test.description, runTest); }); diff --git a/test/parse-uri-common-targets.test.js b/test/parse-uri-common-targets.test.js index a6705c49..91122d7c 100644 --- a/test/parse-uri-common-targets.test.js +++ b/test/parse-uri-common-targets.test.js @@ -18,66 +18,51 @@ describe('connection model parser should parse URI strings for common connection okAtlasPassword ); - it('sets replicaSet, readPreference, ssl, ns, authSource and clears the default password', (done) => { - Connection.from(atlasConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('a-compass-atlas-test-shard-0'); - expect(result.readPreference).to.be.equal('secondary'); - expect(result.sslMethod).to.be.equal('SYSTEMCA'); - expect(result.mongodbPassword).to.be.equal(''); - expect(result.ns).to.be.equal('admin'); - expect(result.driverUrl).to.include('authSource=admin'); - done(); - }); + it('sets replicaSet, readPreference, ssl, ns, authSource and clears the default password', async() => { + const result = await Connection.from(atlasConnection); + expect(result.replicaSet).to.be.equal('a-compass-atlas-test-shard-0'); + expect(result.readPreference).to.be.equal('secondary'); + expect(result.sslMethod).to.be.equal('SYSTEMCA'); + expect(result.mongodbPassword).to.be.equal(''); + expect(result.ns).to.be.equal('admin'); + expect(result.driverUrl).to.include('authSource=admin'); }); - it('does not clear sufficiently long passwords that happen to contain PASSWORD', (done) => { - Connection.from(okAtlasPasswordConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.mongodbPassword).to.be.equal(okAtlasPassword); - done(); - }); + it('does not clear sufficiently long passwords that happen to contain PASSWORD', async() => { + const result = await Connection.from(okAtlasPasswordConnection); + expect(result.mongodbPassword).to.be.equal(okAtlasPassword); }); - it('works with a non-default secure password', (done) => { + it('works with a non-default secure password', async() => { const userPass = '6NuZPtHCrjYBAWnI7Iq6jvtsdJx67X0'; const modifiedAtlasConnection = atlasConnection.replace( '', userPass ); - Connection.from(modifiedAtlasConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.sslMethod).to.be.equal('SYSTEMCA'); - expect(result.mongodbPassword).to.be.equal(userPass); - done(); - }); + const result = await Connection.from(modifiedAtlasConnection); + expect(result.sslMethod).to.be.equal('SYSTEMCA'); + expect(result.mongodbPassword).to.be.equal(userPass); }); - it('does not false positive on hi.mongodb.net.my.domain.com', (done) => { + it('does not false positive on hi.mongodb.net.my.domain.com', async() => { const modifiedAtlasConnection = atlasConnection.replace( /mongodb.net/g, 'hi.mongodb.net.my.domain.com' ); - Connection.from(modifiedAtlasConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.sslMethod).to.be.equal('NONE'); - done(); - }); + const result = await Connection.from(modifiedAtlasConnection); + expect(result.sslMethod).to.be.equal('NONE'); }); - it('is case insensitive, see RFC4343', (done) => { + it('is case insensitive, see RFC4343', async() => { const modifiedAtlasConnection = atlasConnection.replace( /mongodb.net/g, 'mOnGOdB.NeT' ); - Connection.from(modifiedAtlasConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.sslMethod).to.be.equal('SYSTEMCA'); - done(); - }); + const result = await Connection.from(modifiedAtlasConnection); + expect(result.sslMethod).to.be.equal('SYSTEMCA'); }); }); @@ -88,363 +73,284 @@ describe('connection model parser should parse URI strings for common connection 'a-compass-atlas-test-shard-00-01-vll9l.mongodb.net:38128,' + 'a-compass-atlas-test-shard-00-02-vll9l.mongodb.net:38128'; - it('sets hostname, port, ns, authSource', (done) => { - Connection.from(atlasConnection, (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal( - 'a-compass-atlas-test-shard-00-00-vll9l.mongodb.net' - ); - expect(result.port).to.be.equal(38128); - expect(result.ns).to.be.equal('test'); - expect(result.driverUrl).to.include('authSource=admin'); - done(); - }); + it('sets hostname, port, ns, authSource', async() => { + const result = await Connection.from(atlasConnection); + expect(result.hostname).to.be.equal( + 'a-compass-atlas-test-shard-00-00-vll9l.mongodb.net' + ); + expect(result.port).to.be.equal(38128); + expect(result.ns).to.be.equal('test'); + expect(result.driverUrl).to.include('authSource=admin'); }); }); context('localhost', () => { - it('database server running locally', (done) => { - Connection.from('mongodb://localhost', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - done(); - }); + it('database server running locally', async() => { + const result = await Connection.from('mongodb://localhost'); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); }); - it('admin database', (done) => { - Connection.from('mongodb://sysop:moon@localhost', (error, result) => { - expect(error).to.not.exist; - expect(result.mongodbUsername).to.be.equal('sysop'); - expect(result.mongodbPassword).to.be.equal('moon'); - done(); - }); + it('admin database', async() => { + const result = await Connection.from('mongodb://sysop:moon@localhost'); + expect(result.mongodbUsername).to.be.equal('sysop'); + expect(result.mongodbPassword).to.be.equal('moon'); }); - it('records database', (done) => { - Connection.from( + it('records database', async() => { + const result = await Connection.from( 'mongodb://sysop:moon@localhost/records', - (error, result) => { - expect(error).to.not.exist; - expect(result.mongodbUsername).to.be.equal('sysop'); - expect(result.mongodbPassword).to.be.equal('moon'); - expect(result.ns).to.be.equal('records'); - done(); - } ); + expect(result.mongodbUsername).to.be.equal('sysop'); + expect(result.mongodbPassword).to.be.equal('moon'); + expect(result.ns).to.be.equal('records'); }); - it('replica set with members on localhost', (done) => { - Connection.from( + it('replica set with members on localhost', async() => { + const result = await Connection.from( 'mongodb://localhost,localhost:27018,localhost:27019/?replicaSet=test', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('test'); - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'localhost', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'localhost', - port: 27018 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'localhost', - port: 27019 - }); - done(); - } ); + expect(result.replicaSet).to.be.equal('test'); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'localhost', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'localhost', + port: 27018 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'localhost', + port: 27019 + }); }); - it('with explicit authSource', (done) => { - Connection.from( + it('with explicit authSource', async() => { + const result = await Connection.from( 'mongodb://%40rlo:w%40of@localhost:27017/dogdb?authMechanism=SCRAM-SHA-1&authSource=catdb', - (error, result) => { - expect(error).to.not.exist; - expect(result.ns).to.be.equal('dogdb'); - expect(result.mongodbDatabaseName).to.be.equal('catdb'); - done(); - } ); + expect(result.ns).to.be.equal('dogdb'); + expect(result.mongodbDatabaseName).to.be.equal('catdb'); }); - it('when authSource is not specified should fall back to dbName', (done) => { - Connection.from( + it('when authSource is not specified should fall back to dbName', async() => { + const result = await Connection.from( 'mongodb://%40rlo:w%40of@localhost:27017/dogdb?authMechanism=SCRAM-SHA-1', - (error, result) => { - expect(error).to.not.exist; - expect(result.ns).to.be.equal('dogdb'); - expect(result.mongodbDatabaseName).to.be.equal('admin'); - done(); - } ); + expect(result.ns).to.be.equal('dogdb'); + expect(result.mongodbDatabaseName).to.be.equal('admin'); }); - it('when using MONGODB auth', (done) => { - Connection.from( + it('when using MONGODB auth', async() => { + const result = await Connection.from( 'mongodb://%40rlo:w%40of@localhost:27017/?authSource=%40dmin', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - expect(result.authStrategy).to.be.equal('MONGODB'); - expect(result.mongodbUsername).to.be.equal('@rlo'); - expect(result.mongodbPassword).to.be.equal('w@of'); - expect(result.mongodbDatabaseName).to.be.equal('@dmin'); // this is the authSource, not dbName! - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); + expect(result.authStrategy).to.be.equal('MONGODB'); + expect(result.mongodbUsername).to.be.equal('@rlo'); + expect(result.mongodbPassword).to.be.equal('w@of'); + expect(result.mongodbDatabaseName).to.be.equal('@dmin'); // this is the authSource, not dbName! }); - it('when using LDAP auth', (done) => { - Connection.from( + it('when using LDAP auth', async() => { + const result = await Connection.from( 'mongodb://arlo:w%40of@localhost:27017/ldap?authMechanism=PLAIN', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - expect(result.authStrategy).to.be.equal('LDAP'); - expect(result.ldapUsername).to.be.equal('arlo'); - expect(result.ldapPassword).to.be.equal('w@of'); - expect(result.ns).to.be.equal('ldap'); - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); + expect(result.authStrategy).to.be.equal('LDAP'); + expect(result.ldapUsername).to.be.equal('arlo'); + expect(result.ldapPassword).to.be.equal('w@of'); + expect(result.ns).to.be.equal('ldap'); }); - it('when using X509 auth with a username', (done) => { - Connection.from( + it('when using X509 auth with a username', async() => { + const result = await Connection.from( 'mongodb://CN%3Dclient%2COU%3Darlo%2CO%3DMongoDB%2CL%3DPhiladelphia%2CST%3DPennsylvania%2CC%3DUS@localhost:27017/x509?authMechanism=MONGODB-X509', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - expect(result.authStrategy).to.be.equal('X509'); - expect(result.x509Username).to.be.equal( - 'CN=client,OU=arlo,O=MongoDB,L=Philadelphia,ST=Pennsylvania,C=US' - ); - expect(result.ns).to.be.equal('x509'); - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); + expect(result.authStrategy).to.be.equal('X509'); + expect(result.x509Username).to.be.equal( + 'CN=client,OU=arlo,O=MongoDB,L=Philadelphia,ST=Pennsylvania,C=US' + ); + expect(result.ns).to.be.equal('x509'); }); - it('when using X509 auth without a username', (done) => { - Connection.from( + it('when using X509 auth without a username', async() => { + const result = await Connection.from( 'mongodb://localhost:27017/x509?authMechanism=MONGODB-X509', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - expect(result.authStrategy).to.be.equal('X509'); - expect(result.x509Username).to.be.equal(undefined); - expect(result.ns).to.be.equal('x509'); - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); + expect(result.authStrategy).to.be.equal('X509'); + expect(result.x509Username).to.be.equal(undefined); + expect(result.ns).to.be.equal('x509'); }); - it('when using KERBEROS auth', (done) => { - Connection.from( + it('when using KERBEROS auth', async() => { + const result = await Connection.from( 'mongodb://arlo%2Fdog%40krb5.mongodb.parts:w%40%40f@localhost:27017/kerberos?gssapiServiceName=mongodb&authMechanism=GSSAPI', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.port).to.be.equal(27017); - expect(result.authStrategy).to.be.equal('KERBEROS'); - expect(result.kerberosPrincipal).to.be.equal( - 'arlo/dog@krb5.mongodb.parts' - ); - expect(result.ns).to.be.equal('kerberos'); - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result.port).to.be.equal(27017); + expect(result.authStrategy).to.be.equal('KERBEROS'); + expect(result.kerberosPrincipal).to.be.equal( + 'arlo/dog@krb5.mongodb.parts' + ); + expect(result.ns).to.be.equal('kerberos'); }); }); context('remote host', () => { - it('UNIX domain socket', (done) => { - Connection.from( + it('UNIX domain socket', async() => { + const result = await Connection.from( 'mongodb://%2Ftmp%2Fmongodb-27017.sock', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('/tmp/mongodb-27017.sock'); - expect(result.port).to.be.equal(27017); - done(); - } ); + expect(result.hostname).to.be.equal('/tmp/mongodb-27017.sock'); + expect(result.port).to.be.equal(27017); }); - it('replica set with members on different machines', (done) => { - Connection.from( + it('replica set with members on different machines', async() => { + const result = await Connection.from( 'mongodb://db1.example.net,db2.example.com/?replicaSet=test', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('test'); - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(2); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'db1.example.net', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'db2.example.com', - port: 27017 - }); - done(); - } ); + expect(result.replicaSet).to.be.equal('test'); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(2); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'db1.example.net', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'db2.example.com', + port: 27017 + }); }); - it('replica set with read distribution', (done) => { - Connection.from( + it('replica set with read distribution', async() => { + const result = await Connection.from( 'mongodb://example1.com,example2.com,example3.com/?replicaSet=test&readPreference=secondary', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('test'); - expect(result.readPreference).to.be.equal('secondary'); - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'example1.com', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'example2.com', - port: 27017 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'example3.com', - port: 27017 - }); - done(); - } ); + expect(result.replicaSet).to.be.equal('test'); + expect(result.readPreference).to.be.equal('secondary'); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'example1.com', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'example2.com', + port: 27017 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'example3.com', + port: 27017 + }); }); - it('replica set with a high level of write concern', (done) => { - Connection.from( + it('replica set with a high level of write concern', async() => { + const result = await Connection.from( 'mongodb://example1.com,example2.com,example3.com/?replicaSet=test&w=2&wtimeoutMS=2000', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('test'); - expect(result.w).to.be.equal(2); - expect(result.wTimeoutMS).to.be.equal(2000); - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'example1.com', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'example2.com', - port: 27017 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'example3.com', - port: 27017 - }); - done(); - } ); + expect(result.replicaSet).to.be.equal('test'); + expect(result.w).to.be.equal(2); + expect(result.wTimeoutMS).to.be.equal(2000); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'example1.com', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'example2.com', + port: 27017 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'example3.com', + port: 27017 + }); }); - it('sharded cluster', (done) => { - Connection.from( - 'mongodb://router1.example.com:27017,router2.example2.com:27017,router3.example3.com:27017/', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'router1.example.com', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'router2.example2.com', - port: 27017 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'router3.example3.com', - port: 27017 - }); - done(); - } + it('sharded cluster', async() => { + const result = await Connection.from( + 'mongodb://router1.example.com:27017,router2.example2.com:27017,router3.example3.com:27017/' ); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'router1.example.com', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'router2.example2.com', + port: 27017 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'router3.example3.com', + port: 27017 + }); }); - it('sharded cluster and admin database', (done) => { - Connection.from( - 'mongodb://mongos0.example.com:27017,mongos1.example.com:27017,mongos2.example.com:27017/admin', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'mongos0.example.com', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'mongos1.example.com', - port: 27017 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'mongos2.example.com', - port: 27017 - }); - expect(result.ns).to.be.equal('admin'); - done(); - } + it('sharded cluster and admin database', async() => { + const result = await Connection.from( + 'mongodb://mongos0.example.com:27017,mongos1.example.com:27017,mongos2.example.com:27017/admin' ); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'mongos0.example.com', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'mongos1.example.com', + port: 27017 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'mongos2.example.com', + port: 27017 + }); + expect(result.ns).to.be.equal('admin'); }); - it('sharded cluster that enforces access control, include user credentials', (done) => { - Connection.from( + it('sharded cluster that enforces access control, include user credentials', async() => { + const result = await Connection.from( 'mongodb://myDBReader:D1fficultP%40ssw0rd@mongos0.example.com:27017,mongos1.example.com:27017,mongos2.example.com:27017/admin', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('hosts'); - expect(result.hosts).to.have.lengthOf(3); - expect(result.hosts[0]).to.be.deep.equal({ - host: 'mongos0.example.com', - port: 27017 - }); - expect(result.hosts[1]).to.be.deep.equal({ - host: 'mongos1.example.com', - port: 27017 - }); - expect(result.hosts[2]).to.be.deep.equal({ - host: 'mongos2.example.com', - port: 27017 - }); - expect(result.mongodbUsername).to.be.equal('myDBReader'); - expect(result.mongodbPassword).to.be.equal('D1fficultP@ssw0rd'); - expect(result.ns).to.be.equal('admin'); - expect(result.authStrategy).to.be.equal('MONGODB'); - done(); - } ); + expect(result).to.have.property('hosts'); + expect(result.hosts).to.have.lengthOf(3); + expect(result.hosts[0]).to.be.deep.equal({ + host: 'mongos0.example.com', + port: 27017 + }); + expect(result.hosts[1]).to.be.deep.equal({ + host: 'mongos1.example.com', + port: 27017 + }); + expect(result.hosts[2]).to.be.deep.equal({ + host: 'mongos2.example.com', + port: 27017 + }); + expect(result.mongodbUsername).to.be.equal('myDBReader'); + expect(result.mongodbPassword).to.be.equal('D1fficultP@ssw0rd'); + expect(result.ns).to.be.equal('admin'); + expect(result.authStrategy).to.be.equal('MONGODB'); }); - it('when host and port are specified', (done) => { - Connection.from('mongodb://krb5.mongodb.parts:1234', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('krb5.mongodb.parts'); - expect(result.port).to.be.equal(1234); - done(); - }); + it('when host and port are specified', async() => { + const result = await Connection.from('mongodb://krb5.mongodb.parts:1234'); + expect(result.hostname).to.be.equal('krb5.mongodb.parts'); + expect(result.port).to.be.equal(1234); }); - it('when port is not specified', (done) => { - Connection.from('mongodb://data.mongodb.com/', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('data.mongodb.com'); - expect(result.port).to.be.equal(27017); - done(); - }); + it('when port is not specified', async() => { + const result = await Connection.from('mongodb://data.mongodb.com/'); + expect(result.hostname).to.be.equal('data.mongodb.com'); + expect(result.port).to.be.equal(27017); }); }); }); diff --git a/test/parse-uri-components.test.js b/test/parse-uri-components.test.js index 825bdd33..9f57b5f1 100644 --- a/test/parse-uri-components.test.js +++ b/test/parse-uri-components.test.js @@ -21,624 +21,455 @@ const stubedConnection = proxyquire('../', stubs); chai.use(require('chai-subset')); -describe('connection model partser should parse URI components such as', () => { +describe('connection model parser should parse URI components such as', () => { describe('prefix', () => { - it('should set isSrvRecord to false', (done) => { - Connection.from( + it('should set isSrvRecord to false', async() => { + const result = await Connection.from( 'mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?replicaSet=mySet&authSource=authDB', - (error, result) => { - expect(error).to.not.exist; - expect(result.isSrvRecord).to.be.equal(false); - done(); - } ); + expect(result.isSrvRecord).to.be.equal(false); }); - it('should set isSrvRecord to true', (done) => { + it('should set isSrvRecord to true', async() => { stubHostname = 'server.example.com'; - stubedConnection.from( + const result = await stubedConnection.from( `mongodb+srv://${stubHostname}/?connectTimeoutMS=300000&authSource=aDifferentAuthDB`, - (error, result) => { - expect(error).to.not.exist; - expect(result.isSrvRecord).to.be.equal(true); - done(); - } ); + expect(result.isSrvRecord).to.be.equal(true); }); - it('should catch ampersand validation errors', (done) => { + it('should catch ampersand validation errors', async() => { stubHostname = 'server.example.com'; - stubedConnection.from( - // note: socketTimeoutMS=1&socketTimeoutMS=2 will cause the validation to fail, - // as socketTimeoutMS is expected to be a number, instead will be parsed as an array: - `mongodb+srv://${stubHostname}/?connectTimeoutMS=300000&authSource=aDifferentAuthDB&socketTimeoutMS=1&socketTimeoutMS=2`, - (error) => { - expect(error).to.exist; - expect(error.message).to.contain('Property \'socketTimeoutMS\' must be of type number'); - done(); - } - ); + let error; + try { + // note: socketTimeoutMS=1&socketTimeoutMS=2 will cause the validation to fail. + await stubedConnection.from( + `mongodb+srv://${stubHostname}/?connectTimeoutMS=300000&authSource=aDifferentAuthDB&socketTimeoutMS=1&socketTimeoutMS=2`, + ); + } catch (err) { + error = err; + } + expect(error).to.exist; + expect(error.message).to.contain('Property \'socketTimeoutMS\' must be of type number'); }); - it('should set only one hostname without decorating it with the replica set info', (done) => { + it('should set only one hostname without decorating it with the replica set info', async() => { stubHostname = 'test.mongodb.net'; - stubedConnection.from( + const result = await stubedConnection.from( `mongodb+srv://admin:qwerty@${stubHostname}/admin`, - (error, result) => { - expect(error).to.not.exist; - expect(result.isSrvRecord).to.be.equal(true); - expect(result.hostname).to.be.equal('test.mongodb.net'); - done(); - } ); + expect(result.isSrvRecord).to.be.equal(true); + expect(result.hostname).to.be.equal('test.mongodb.net'); }); }); describe('authentication credentials', () => { - it('should parse username and password', (done) => { - Connection.from( + it('should parse username and password', async() => { + const result = await Connection.from( 'mongodb://someUsername:testPassword@localhost', - (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result).to.have.property('auth'); - expect(result.mongodbUsername).to.be.equal('someUsername'); - expect(result.mongodbPassword).to.be.equal('testPassword'); - expect(result.ns).to.be.equal('test'); - expect(result.authStrategy).to.be.equal('MONGODB'); - done(); - } ); + expect(result.hostname).to.be.equal('localhost'); + expect(result).to.have.property('auth'); + expect(result.mongodbUsername).to.be.equal('someUsername'); + expect(result.mongodbPassword).to.be.equal('testPassword'); + expect(result.ns).to.be.equal('test'); + expect(result.authStrategy).to.be.equal('MONGODB'); }); - it('should not return authentication info', (done) => { - Connection.from('mongodb://localhost', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('localhost'); - expect(result.authStrategy).to.be.equal('NONE'); - done(); - }); + it('should not return authentication info', async() => { + const result = await Connection.from('mongodb://localhost'); + expect(result.hostname).to.be.equal('localhost'); + expect(result.authStrategy).to.be.equal('NONE'); }); }); describe('the host and optional port number', () => { - it('should parse host and port', (done) => { - Connection.from('mongodb://host:27018', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('host'); - expect(result.hosts[0].host).to.equal('host'); - expect(result.hosts[0].port).to.equal(27018); - done(); - }); + it('should parse host and port', async() => { + const result = await Connection.from('mongodb://host:27018'); + expect(result.hostname).to.be.equal('host'); + expect(result.hosts[0].host).to.equal('host'); + expect(result.hosts[0].port).to.equal(27018); }); - it('should provide a default port if one is not provided', (done) => { - Connection.from('mongodb://host', (error, result) => { - expect(error).to.not.exist; - expect(result.hostname).to.be.equal('host'); - expect(result.hosts[0].host).to.equal('host'); - expect(result.hosts[0].port).to.equal(27017); - done(); - }); + it('should provide a default port if one is not provided', async() => { + const result = await Connection.from('mongodb://host'); + expect(result.hostname).to.be.equal('host'); + expect(result.hosts[0].host).to.equal('host'); + expect(result.hosts[0].port).to.equal(27017); }); }); describe('the name of the database to authenticate', () => { - it('should parse a database name', (done) => { - Connection.from( + it('should parse a database name', async() => { + const result = await Connection.from( 'mongodb://root:password123@localhost:27017/databasename', - (error, result) => { - expect(error).to.not.exist; - expect(result.mongodbUsername).to.equal('root'); - expect(result.mongodbPassword).to.equal('password123'); - done(); - } ); + expect(result.mongodbUsername).to.equal('root'); + expect(result.mongodbPassword).to.equal('password123'); }); }); describe('connection string options that include', () => { describe('replica set options', () => { - it('should parse replicaSet', (done) => { - Connection.from( + it('should parse replicaSet', async() => { + const result = await Connection.from( 'mongodb://db0.example.com:27017,db1.example.com:27017,db2.example.com:27017/admin?replicaSet=myRepl', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('myRepl'); - expect(result.hostname).to.be.equal('db0.example.com'); - expect(result.port).to.be.equal(27017); - expect(result.ns).to.be.equal('admin'); - done(); - } ); + expect(result.replicaSet).to.be.equal('myRepl'); + expect(result.hostname).to.be.equal('db0.example.com'); + expect(result.port).to.be.equal(27017); + expect(result.ns).to.be.equal('admin'); }); }); describe('connection options', () => { - it('should parse ssl', (done) => { - Connection.from( + it('should parse ssl', async() => { + const result = await Connection.from( 'mongodb://db0.example.com,db1.example.com,db2.example.com/?replicaSet=myReplOther&ssl=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.replicaSet).to.be.equal('myReplOther'); - expect(result.ssl).to.be.equal(true); - done(); - } ); + expect(result.replicaSet).to.be.equal('myReplOther'); + expect(result.ssl).to.be.equal(true); }); - it('should parse connectTimeoutMS', (done) => { - Connection.from( + it('should parse connectTimeoutMS', async() => { + const result = await Connection.from( 'mongodb://mongodb1.example.com:27317,mongodb2.example.com:27017/?connectTimeoutMS=300000&replicaSet=mySet&authSource=aDifferentAuthDB', - (error, result) => { - expect(error).to.not.exist; - expect(result.connectTimeoutMS).to.be.equal(300000); - done(); - } ); + expect(result.connectTimeoutMS).to.be.equal(300000); }); - it('should parse socketTimeoutMS with w', (done) => { - Connection.from( + it('should parse socketTimeoutMS with w', async() => { + const result = await Connection.from( 'mongodb://localhost:27017/sampleDb?socketTimeoutMS=30000&w=majority', - (error, result) => { - expect(error).to.not.exist; - expect(result.socketTimeoutMS).to.be.equal(30000); - done(); - } ); + expect(result.socketTimeoutMS).to.be.equal(30000); }); - it('should parse socketTimeoutMS with multiple servers', (done) => { - Connection.from( + it('should parse socketTimeoutMS with multiple servers', async() => { + const result = await Connection.from( 'mongodb://localhost:27017,localhost:27018,localhost:27019/sampleDb?replicaSet=rs0&socketTimeoutMS=5000', - (error, result) => { - expect(error).to.not.exist; - expect(result.socketTimeoutMS).to.be.equal(5000); - done(); - } ); + expect(result.socketTimeoutMS).to.be.equal(5000); }); - it('should parse compressors with snappy value', (done) => { - Connection.from( + it('should parse compressors with snappy value', async() => { + const result = await Connection.from( 'mongodb://localhost/?compressors=snappy', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('compression'); - expect(result.compression.compressors).to.have.lengthOf(1); - expect(result.compression.compressors).to.include('snappy'); - done(); - } ); + expect(result).to.have.property('compression'); + expect(result.compression.compressors).to.have.lengthOf(1); + expect(result.compression.compressors).to.include('snappy'); }); - it('should parse compressors with zlib value', (done) => { - Connection.from( + it('should parse compressors with zlib value', async() => { + const result = await Connection.from( 'mongodb://localhost/?compressors=zlib', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('compression'); - expect(result.compression.compressors).to.have.lengthOf(1); - expect(result.compression.compressors).to.include('zlib'); - done(); - } ); + expect(result).to.have.property('compression'); + expect(result.compression.compressors).to.have.lengthOf(1); + expect(result.compression.compressors).to.include('zlib'); }); - it('should throw the error if compressors contain invalid value', (done) => { - Connection.from('mongodb://localhost/?compressors=bunnies', (error) => { - expect(error).to.exist; - done(); - }); + it('should throw the error if compressors contain invalid value', async() => { + let error; + try { + await Connection.from('mongodb://localhost/?compressors=bunnies'); + } catch (err) { + error = err; + } + + expect(error).to.exist; }); - it('should parse compressors with snappy and zlib values', (done) => { - Connection.from( + it('should parse compressors with snappy and zlib values', async() => { + const result = await Connection.from( 'mongodb://localhost/?compressors=snappy,zlib', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('compression'); - expect(result.compression.compressors).to.have.lengthOf(2); - expect(result.compression.compressors).to.include('zlib'); - expect(result.compression.compressors).to.include('snappy'); - done(); - } ); + expect(result).to.have.property('compression'); + expect(result.compression.compressors).to.have.lengthOf(2); + expect(result.compression.compressors).to.include('zlib'); + expect(result.compression.compressors).to.include('snappy'); }); - it('should parse zlibCompressionLevel', (done) => { - Connection.from( + it('should parse zlibCompressionLevel', async() => { + const result = await Connection.from( 'mongodb://localhost/?compressors=zlib&zlibCompressionLevel=4', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('compression'); - expect(result.compression).to.eql({ - compressors: ['zlib'], - zlibCompressionLevel: 4 - }); - done(); - } ); + expect(result).to.have.property('compression'); + expect(result.compression).to.eql({ + compressors: ['zlib'], + zlibCompressionLevel: 4 + }); }); - it('should throw the error if zlibCompressionLevel has invalid value', (done) => { - Connection.from( - 'mongodb://localhost/?zlibCompressionLevel=15', - (error) => { - expect(error).to.exist; - done(); - } - ); + it('should throw the error if zlibCompressionLevel has invalid value', async() => { + let error; + try { + await Connection.from( + 'mongodb://localhost/?zlibCompressionLevel=15', + ); + } catch (err) { + error = err; + } + expect(error).to.exist; }); }); describe('connection pool options', () => { - it('should parse minPoolSize and maxPoolSize', (done) => { - Connection.from( + it('should parse minPoolSize and maxPoolSize', async() => { + const result = await Connection.from( 'mongodb://localhost:27017,localhost:27018,localhost:27019/databasename?replicaSet=rs01&ssl=false&connectTimeoutMS=100000&minPoolSize=5&maxPoolSize=10', - (error, result) => { - expect(error).to.not.exist; - expect(result.minPoolSize).to.be.equal(5); - expect(result.maxPoolSize).to.be.equal(10); - done(); - } ); + expect(result.minPoolSize).to.be.equal(5); + expect(result.maxPoolSize).to.be.equal(10); }); - it('should parse maxIdleTimeMS', (done) => { - Connection.from( + it('should parse maxIdleTimeMS', async() => { + const result = await Connection.from( 'mongodb://localhost/test?maxIdleTimeMS=30000', - (error, result) => { - expect(error).to.not.exist; - expect(result.maxIdleTimeMS).to.be.equal(30000); - done(); - } ); + expect(result.maxIdleTimeMS).to.be.equal(30000); }); - it('should parse waitQueueMultiple', (done) => { - Connection.from( + it('should parse waitQueueMultiple', async() => { + const result = await Connection.from( 'mongodb://user:password@ip:27017/?waitQueueMultiple=10', - (error, result) => { - expect(error).to.not.exist; - expect(result.waitQueueMultiple).to.be.equal(10); - done(); - } ); + expect(result.waitQueueMultiple).to.be.equal(10); }); - it('should parse escaped URI with maxIdleTimeMS, waitQueueTimeoutMS, waitQueueTimeoutMS and journal', (done) => { - Connection.from( + it('should parse escaped URI with maxIdleTimeMS, waitQueueTimeoutMS, waitQueueTimeoutMS and journal', async() => { + const result = await Connection.from( 'mongodb://localhost/test?readPreference=primary&maxPoolSize=50&minPoolSize=5&maxIdleTimeMS=1000&waitQueueMultiple=200&waitQueueTimeoutMS=100&w=1&journal=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.journal).to.be.equal(true); - expect(result.maxIdleTimeMS).to.be.equal(1000); - expect(result.waitQueueMultiple).to.be.equal(200); - expect(result.waitQueueTimeoutMS).to.be.equal(100); - done(); - } ); + expect(result.journal).to.be.equal(true); + expect(result.maxIdleTimeMS).to.be.equal(1000); + expect(result.waitQueueMultiple).to.be.equal(200); + expect(result.waitQueueTimeoutMS).to.be.equal(100); }); }); describe('write concern options', () => { - it('should parse write concern w option with number value', (done) => { - Connection.from( + it('should parse write concern w option with number value', async() => { + const result = await Connection.from( 'mongodb://localhost/DBName?replicaSet=xxxx&w=1&readPreference=nearest&maxPoolSize=50', - (error, result) => { - expect(error).to.not.exist; - expect(result.w).to.be.equal(1); - done(); - } ); + expect(result.w).to.be.equal(1); }); - it('should parse write concern w option with majority value', (done) => { - Connection.from( + it('should parse write concern w option with majority value', async() => { + const result = await Connection.from( 'mongodb://localhost/DBName?replicaSet=xxxx&w=majority', - (error, result) => { - expect(error).to.not.exist; - expect(result.w).to.be.equal('majority'); - done(); - } ); + expect(result.w).to.be.equal('majority'); }); - it('should parse write concern w option with tag set value', (done) => { - Connection.from( + it('should parse write concern w option with tag set value', async() => { + const result = await Connection.from( 'mongodb://localhost/DBName?w=MultipleDC', - (error, result) => { - expect(error).to.not.exist; - expect(result.w).to.be.equal('MultipleDC'); - done(); - } ); + expect(result.w).to.be.equal('MultipleDC'); }); - it('should parse wTimeoutMS', (done) => { - Connection.from( + it('should parse wTimeoutMS', async() => { + const result = await Connection.from( 'mongodb://host1:port1,host2:port2/?ssl=1&wtimeoutMS=1000', // Note the difference `wtimeoutMS` and `wTimeoutMS` - (error, result) => { - expect(error).to.not.exist; - expect(result.wTimeoutMS).to.be.equal(1000); // Returned value was camelCased - done(); - } ); + expect(result.wTimeoutMS).to.be.equal(1000); // Returned value was camelCased }); - it('should parse journal', (done) => { - Connection.from( + it('should parse journal', async() => { + const result = await Connection.from( 'mongodb://localhost/test?readPreference=primary&w=1&journal=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.journal).to.be.equal(true); - done(); - } ); + expect(result.journal).to.be.equal(true); }); - it('should parse j option', (done) => { - Connection.from( + it('should parse j option', async() => { + const result = await Connection.from( 'mongodb://localhost/test?readPreference=primary&w=1&j=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.journal).to.be.equal(true); // Converts j=true to journal=true - done(); - } ); + expect(result.journal).to.be.equal(true); // Converts j=true to journal=true }); - it('should parse wtimeout', (done) => { - Connection.from( + it('should parse wtimeout', async() => { + const result = await Connection.from( 'mongodb://localhost/test?w=1&wtimeout=2500', - (error, result) => { - expect(error).to.not.exist; - expect(result.wTimeoutMS).to.be.equal(2500); // Converts jwtimeout to wTimeoutMS - done(); - } ); + expect(result.wTimeoutMS).to.be.equal(2500); // Converts jwtimeout to wTimeoutMS }); }); describe('read concern options', () => { - it('should parse readConcernLevel with local value', (done) => { - Connection.from( + it('should parse readConcernLevel with local value', async() => { + const result = await Connection.from( 'mongodb://localhost/?readConcernLevel=local', - (error, result) => { - expect(error).to.not.exist; - expect(result.readConcernLevel).to.be.equal('local'); - done(); - } ); + expect(result.readConcernLevel).to.be.equal('local'); }); - it('should parse readConcernLevel with majority value', (done) => { - Connection.from( + it('should parse readConcernLevel with majority value', async() => { + const result = await Connection.from( 'mongodb://db0.example.com,db1.example.com,db2.example.com/?replicaSet=myRepl&readConcernLevel=majority', - (error, result) => { - expect(error).to.not.exist; - expect(result.readConcernLevel).to.be.equal('majority'); - done(); - } ); + expect(result.readConcernLevel).to.be.equal('majority'); }); }); describe('read preference options', () => { - it('should parse readPreference and maxStalenessSeconds', (done) => { - Connection.from( + it('should parse readPreference and maxStalenessSeconds', async() => { + const result = await Connection.from( 'mongodb://mongos1.example.com,mongos2.example.com/?readPreference=secondary&maxStalenessSeconds=120', - (error, result) => { - expect(error).to.not.exist; - expect(result.readPreference).to.be.equal('secondary'); - expect(result.maxStalenessSeconds).to.be.equal(120); - done(); - } ); + expect(result.readPreference).to.be.equal('secondary'); + expect(result.maxStalenessSeconds).to.be.equal(120); }); - it('should throw the error if readPreference has invalid value', (done) => { - Connection.from( - 'mongodb://localhost/?readPreference=llamasPreferred', - (error) => { - expect(error).to.exist; - done(); - } - ); + it('should throw the error if readPreference has invalid value', async() => { + let error; + try { + await Connection.from( + 'mongodb://localhost/?readPreference=llamasPreferred', + ); + } catch (err) { + error = err; + } + expect(error).to.exist; }); - it('should parse readPreference and readPreferenceTags', (done) => { - Connection.from( + it('should parse readPreference and readPreferenceTags', async() => { + const result = await Connection.from( 'mongodb://mongos1.example.com,mongos2.example.com/?readPreference=secondary&readPreferenceTags=dc:ny,rack:1', - (error, result) => { - expect(error).to.not.exist; - expect(result.readPreference).to.be.equal('secondary'); - expect(result).to.have.property('readPreferenceTags'); - expect(result.readPreferenceTags).to.eql([{ dc: 'ny', rack: 1 }]); - done(); - } ); + expect(result.readPreference).to.be.equal('secondary'); + expect(result).to.have.property('readPreferenceTags'); + expect(result.readPreferenceTags).to.eql([{ dc: 'ny', rack: 1 }]); }); }); describe('authentication options', () => { - it('should parse authSource', (done) => { - Connection.from( + it('should parse authSource', async() => { + const result = await Connection.from( 'mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017,mongodb1.example.com:27017,mongodb2.example.com:27017/test?replicaSet=myRepl&authSource=admin', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.have.property('authSource'); - expect(result.authSource).to.equal('admin'); - done(); - } ); + expect(result).to.have.property('authSource'); + expect(result.authSource).to.equal('admin'); }); - it('should parse authSource and authMechanism', (done) => { - Connection.from( + it('should parse authSource and authMechanism', async() => { + const result = await Connection.from( 'mongodb://user:password@example.com/?authSource=theDatabase&authMechanism=SCRAM-SHA-256', - (error, result) => { - expect(error).to.not.exist; - expect(result.authSource).to.be.equal('theDatabase'); - expect(result.authMechanism).to.be.equal('SCRAM-SHA-256'); - done(); - } ); + expect(result.authSource).to.be.equal('theDatabase'); + expect(result.authMechanism).to.be.equal('SCRAM-SHA-256'); }); - it('should throw the error if authMechanism has invalid value', (done) => { - Connection.from('mongodb://localhost/?authMechanism=DOGS', (error) => { - expect(error).to.exist; - done(); - }); + it('should throw the error if authMechanism has invalid value', async() => { + let error; + try { + await Connection.from('mongodb://localhost/?authMechanism=DOGS'); + } catch (err) { + error = err; + } + + expect(error).to.exist; }); - it('should parse authMechanismProperties', (done) => { - Connection.from( + it('should parse authMechanismProperties', async() => { + const result = await Connection.from( 'mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,SERVICE_REALM:blah,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI', - (error, result) => { - expect(error).to.not.exist; - expect(result).to.deep.include({ - gssapiServiceName: 'other', - gssapiServiceRealm: 'blah', - gssapiCanonicalizeHostName: true - }); - expect(result).to.have.property('authMechanism'); - expect(result.authMechanism).to.equal('GSSAPI'); - done(); - } - ); - }); - - it('should parse authMechanismProperties', (done) => { - Connection.from( + ); + expect(result).to.deep.include({ + gssapiServiceName: 'other', + gssapiServiceRealm: 'blah', + gssapiCanonicalizeHostName: true + }); + expect(result).to.have.property('authMechanism'); + expect(result.authMechanism).to.equal('GSSAPI'); + }); + + it('should parse authMechanismProperties', async() => { + const result = await Connection.from( 'mongodb://user:password@example.com/?authMechanism=GSSAPI&authSource=$external&gssapiServiceName=mongodb', - (error, result) => { - expect(error).to.not.exist; - expect(result.gssapiServiceName).to.be.equal('mongodb'); - done(); - } ); + expect(result.gssapiServiceName).to.be.equal('mongodb'); }); }); describe('server selection and discovery options', () => { - it('should parse multiple options including localThresholdMS, serverSelectionTimeoutMS and heartbeatFrequencyMS', (done) => { - Connection.from( + it('should parse multiple options including localThresholdMS, serverSelectionTimeoutMS and heartbeatFrequencyMS', async() => { + const result = await Connection.from( 'mongodb://localhost/?replicaSet=test&w=1&ssl=true&readPreference=secondary&serverSelectionTimeoutMS=25000&localThresholdMS=30&heartbeatFrequencyMS=20000', - (error, result) => { - expect(error).to.not.exist; - expect(result.localThresholdMS).to.be.equal(30); - expect(result.serverSelectionTimeoutMS).to.be.equal(25000); - expect(result.heartbeatFrequencyMS).to.be.equal(20000); - done(); - } ); + expect(result.localThresholdMS).to.be.equal(30); + expect(result.serverSelectionTimeoutMS).to.be.equal(25000); + expect(result.heartbeatFrequencyMS).to.be.equal(20000); }); - it('should parse serverSelectionTryOnce', (done) => { - Connection.from( + it('should parse serverSelectionTryOnce', async() => { + const result = await Connection.from( 'mongodb://a/?serverSelectionTryOnce=false', - (error, result) => { - expect(error).to.not.exist; - expect(result.serverSelectionTryOnce).to.be.equal(false); - done(); - } ); + expect(result.serverSelectionTryOnce).to.be.equal(false); }); - it('defaults directConnection undefined', (done) => { - Connection.from( + it('defaults directConnection undefined', async() => { + const result = await Connection.from( 'mongodb://localhost:27017', - (error, result) => { - expect(error).to.not.exist; - expect(result.directConnection).to.be.equal(undefined); - done(); - } ); + expect(result.directConnection).to.be.equal(undefined); }); - it('saves directConnection true', (done) => { - Connection.from( + it('saves directConnection true', async() => { + const result = await Connection.from( 'mongodb://localhost:27017/?directConnection=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.directConnection).to.be.equal(true); - done(); - } ); + expect(result.directConnection).to.be.equal(true); }); - it('saves directConnection false', (done) => { - Connection.from( + it('saves directConnection false', async() => { + const result = await Connection.from( 'mongodb://localhost:27017/?directConnection=false', - (error, result) => { - expect(error).to.not.exist; - expect(result.directConnection).to.be.equal(false); - done(); - } ); + expect(result.directConnection).to.be.equal(false); }); }); describe('miscellaneous configuration', () => { - it('should parse appname', (done) => { - Connection.from('mongodb://localhost/?appname=foo', (error, result) => { - expect(error).to.not.exist; - expect(result.appname).to.be.equal('foo'); - done(); - }); + it('should parse appname', async() => { + const result = await Connection.from('mongodb://localhost/?appname=foo'); + expect(result.appname).to.be.equal('foo'); }); - it('should parse retryWrites with invalid value eql 1', (done) => { - Connection.from('mongodb://hostname?retryWrites=1', (error, result) => { - expect(error).to.not.exist; - expect(result.retryWrites).to.be.equal(false); // retryWrites expects a bool value. Other values are being treated as false - done(); - }); + it('should parse retryWrites with invalid value eql 1', async() => { + const result = await Connection.from('mongodb://hostname?retryWrites=1'); + expect(result.retryWrites).to.be.equal(false); // retryWrites expects a bool value. Other values are being treated as false }); - it('should parse retryWrites with invalid value eql 3', (done) => { - Connection.from('mongodb://hostname?retryWrites=1', (error, result) => { - expect(error).to.not.exist; - expect(result.retryWrites).to.be.equal(false); - done(); - }); + it('should parse retryWrites with invalid value eql 3', async() => { + const result = await Connection.from('mongodb://hostname?retryWrites=1'); + expect(result.retryWrites).to.be.equal(false); }); - it('should parse retryWrites with false value', (done) => { - Connection.from( + it('should parse retryWrites with false value', async() => { + const result = await Connection.from( 'mongodb://hostname?retryWrites=false', - (error, result) => { - expect(error).to.not.exist; - expect(result.retryWrites).to.be.equal(false); - done(); - } ); + expect(result.retryWrites).to.be.equal(false); }); - it('should parse retryWrites with true value', (done) => { - Connection.from( + it('should parse retryWrites with true value', async() => { + const result = await Connection.from( 'mongodb://hostname?retryWrites=true', - (error, result) => { - expect(error).to.not.exist; - expect(result.retryWrites).to.be.equal(true); - done(); - } ); + expect(result.retryWrites).to.be.equal(true); }); - it('should parse uuidRepresentation', (done) => { - Connection.from( + it('should parse uuidRepresentation', async() => { + const result = await Connection.from( 'mongodb://foo/?uuidrepresentation=csharpLegacy', - (error, result) => { - expect(error).to.not.exist; - expect(result.uuidRepresentation).to.be.equal('csharpLegacy'); - done(); - } ); + expect(result.uuidRepresentation).to.be.equal('csharpLegacy'); }); }); }); diff --git a/test/ssh-tunnel.test.js b/test/ssh-tunnel.test.js index d30e3580..ab918a61 100644 --- a/test/ssh-tunnel.test.js +++ b/test/ssh-tunnel.test.js @@ -285,18 +285,15 @@ describe('sshTunnel', function () { assert(c.isValid()); }); - it('should inject ssh tunnel port', (done) => { + it('should inject ssh tunnel port', async() => { assert.equal( c.driverUrl, 'mongodb://mongodb.my-internal-host.com:27000/?readPreference=primary&ssl=false' ); - Connection.from(c.driverUrlWithSsh, (error, sshModel) => { - assert(!error); - assert.equal(sshModel.hostname, '127.0.0.1'); - assert.notEqual(c.port, sshModel.port); - done(); - }); + const sshModel = await Connection.from(c.driverUrlWithSsh); + assert.equal(sshModel.hostname, '127.0.0.1'); + assert.notEqual(c.port, sshModel.port); }); describe('sshTunnelOptions', () => { @@ -364,7 +361,7 @@ describe('sshTunnel', function () { fs.unlink(identityFilePath, done); }); - it('should connect successfully', function (done) { + it('should connect successfully', async function () { if (!process.env.AWS_SSH_TUNNEL_HOSTNAME) { return this.skip( 'Set the `AWS_SSH_TUNNEL_HOSTNAME` environment variable' @@ -384,7 +381,7 @@ describe('sshTunnel', function () { sshTunnelIdentityFile: [identityFilePath] }); - Connection.connect(c, setupListeners, done); + await Connection.connect(c, setupListeners); }); });