From 0136cd12c71caa4eb53d2c0644c7dcf693b5064b Mon Sep 17 00:00:00 2001 From: emadum Date: Wed, 6 May 2020 16:00:17 -0400 Subject: [PATCH 01/10] add test and fix --- lib/operations/connect.js | 12 ++++++++++++ package.json | 2 +- test/functional/connection.test.js | 30 +++++++++++++++++++++++++++--- test/functional/shared.js | 13 ++++++++++++- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/operations/connect.js b/lib/operations/connect.js index 7378367b4a0..08a69d291e4 100644 --- a/lib/operations/connect.js +++ b/lib/operations/connect.js @@ -260,6 +260,18 @@ function connect(mongoClient, url, options, callback) { throw new Error('no callback function provided'); } + // Has a connection already been established? + if (mongoClient.topology) { + if (mongoClient.topology.isConnected()) { + throw new Error('already connected'); + } + // this is a reconnect, clear out old state + mongoClient.topology = undefined; + mongoClient.s.options = {}; + mongoClient.s.dbCache = new Map(); + mongoClient.s.sessions = new Set(); + } + let didRequestAuthentication = false; const logger = Logger('MongoClient', options); diff --git a/package.json b/package.json index 56e7576de74..1ef4959cb55 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "test": "npm run lint && mocha --recursive test/functional test/unit test/core", "test-nolint": "mocha --recursive test/functional test/unit test/core", "coverage": "istanbul cover mongodb-test-runner -- -t 60000 test/core test/unit test/functional", - "lint": "eslint lib test", + "lint": "eslint -v && eslint lib test", "format": "prettier --print-width 100 --tab-width 2 --single-quote --write 'test/**/*.js' 'lib/**/*.js'", "bench": "node test/benchmarks/driverBench/", "generate-evergreen": "node .evergreen/generate_evergreen_tasks.js", diff --git a/test/functional/connection.test.js b/test/functional/connection.test.js index a75916eeb6f..f4a9ade250f 100644 --- a/test/functional/connection.test.js +++ b/test/functional/connection.test.js @@ -1,7 +1,8 @@ 'use strict'; -const test = require('./shared').assert, - setupDatabase = require('./shared').setupDatabase, - expect = require('chai').expect; +const test = require('./shared').assert; +const setupDatabase = require('./shared').setupDatabase; +const withClient = require('./shared').withClient; +const expect = require('chai').expect; describe('Connection', function() { before(function() { @@ -514,4 +515,27 @@ describe('Connection', function() { }); } }); + + it('should be able to connect again after close', function() { + return withClient(this.configuration.newClient(), client => done => { + expect(client.isConnected()).to.be.true; + const collection = () => client.db('testReconnect').collection('test'); + collection().insertOne({ a: 1 }, (err, result) => { + expect(err).to.not.exist; + expect(result).to.exist; + client.close(err => { + expect(err).to.not.exist; + client.connect(err => { + expect(err).to.not.exist; + collection().insertOne({ b: 2 }, (err, result) => { + expect(err).to.not.exist; + expect(result).to.exist; + expect(client.topology.isDestroyed()).to.be.false; + done(); + }); + }); + }); + }); + }); + }); }); diff --git a/test/functional/shared.js b/test/functional/shared.js index 65023355140..d3c59094517 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -78,9 +78,20 @@ function makeCleanupFn(client) { function withClient(client, operation, errorHandler) { const cleanup = makeCleanupFn(client); + function operationHandler(client) { + // run the operation + const result = operation(client); + // if it returns a callback, wrap it in a Promise + if (typeof result === 'function') { + return new Promise(done => result(done)); + } + // otherwise assume it returned a Promise + return result; + } + return client .connect() - .then(operation, errorHandler) + .then(operationHandler, errorHandler) .then(() => cleanup(), cleanup); } From 3be1b95cf432f62774016ef46e5051bdc6ea8aa4 Mon Sep 17 00:00:00 2001 From: emadum Date: Fri, 8 May 2020 09:32:51 -0400 Subject: [PATCH 02/10] clean up evg logs --- .evergreen/install-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index acfd7f2fe11..d2edf9b8e83 100644 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -18,7 +18,7 @@ mkdir -p "${NPM_TMP_DIR}" # install Node.js curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash [ -s "${NVM_DIR}/nvm.sh" ] && \. "${NVM_DIR}/nvm.sh" -nvm install --lts=${NODE_LTS_NAME} +nvm install --no-progress --lts=${NODE_LTS_NAME} # setup npm cache in a local directory cat < .npmrc From 865d8b597d892a693dc41324e2eb56d96bf4b827 Mon Sep 17 00:00:00 2001 From: emadum Date: Fri, 8 May 2020 09:46:11 -0400 Subject: [PATCH 03/10] update nvm to support --no-progress --- .evergreen/install-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index d2edf9b8e83..29ee53841cb 100644 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -16,7 +16,7 @@ mkdir -p ${NPM_CACHE_DIR} mkdir -p "${NPM_TMP_DIR}" # install Node.js -curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash [ -s "${NVM_DIR}/nvm.sh" ] && \. "${NVM_DIR}/nvm.sh" nvm install --no-progress --lts=${NODE_LTS_NAME} From 854c72f1129c2124a2f599632b02e91d91c56191 Mon Sep 17 00:00:00 2001 From: emadum Date: Fri, 8 May 2020 09:53:58 -0400 Subject: [PATCH 04/10] add XFG_CONFIG_HOME for new nvm --- .evergreen/install-dependencies.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index 29ee53841cb..e856ee76f48 100644 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -9,6 +9,7 @@ NPM_TMP_DIR="${NODE_ARTIFACTS_PATH}/tmp" # this needs to be explicitly exported for the nvm install below export NVM_DIR="${NODE_ARTIFACTS_PATH}/nvm" +export XDG_CONFIG_HOME=${NODE_ARTIFACTS_PATH} # create node artifacts path if needed mkdir -p ${NODE_ARTIFACTS_PATH} From 5043ca3f6d8c4a8751c5aed5045bfdb85c130925 Mon Sep 17 00:00:00 2001 From: emadum Date: Mon, 11 May 2020 09:45:01 -0400 Subject: [PATCH 05/10] incorporate review feedback --- lib/mongo_client.js | 27 ++++++++----- lib/operations/connect.js | 11 +----- test/functional/connection.test.js | 2 +- test/functional/shared.js | 61 ++++++++++++++++-------------- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 2d58247788b..8be08637778 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -167,6 +167,8 @@ function MongoClient(url, options) { writeConcern: WriteConcern.fromOptions(options), namespace: new MongoDBNamespace('admin') }; + + this.originalOptions = Object.assign({}, this.s.options); } /** @@ -241,28 +243,35 @@ MongoClient.prototype.close = function(force, callback) { force = false; } - const client = this; return maybePromise(this, callback, cb => { const completeClose = err => { - client.emit('close', client); + this.emit('close', this); - if (!(client.topology instanceof NativeTopology)) { - for (const item of client.s.dbCache) { - item[1].emit('close', client); + if (!(this.topology instanceof NativeTopology)) { + for (const item of this.s.dbCache) { + item[1].emit('close', this); } } - client.removeAllListeners('close'); + this.removeAllListeners('close'); + + // clear out references to old topology + this.topology = undefined; + this.s.dbCache = new Map(); + + // restore options + this.s.options = Object.assign({}, this.originalOptions); + cb(err); }; - if (client.topology == null) { + if (this.topology == null) { completeClose(); return; } - client.topology.close(force, err => { - const autoEncrypter = client.topology.s.options.autoEncrypter; + this.topology.close(force, err => { + const autoEncrypter = this.topology.s.options.autoEncrypter; if (!autoEncrypter) { completeClose(err); return; diff --git a/lib/operations/connect.js b/lib/operations/connect.js index 08a69d291e4..5d5c2ba3b25 100644 --- a/lib/operations/connect.js +++ b/lib/operations/connect.js @@ -261,15 +261,8 @@ function connect(mongoClient, url, options, callback) { } // Has a connection already been established? - if (mongoClient.topology) { - if (mongoClient.topology.isConnected()) { - throw new Error('already connected'); - } - // this is a reconnect, clear out old state - mongoClient.topology = undefined; - mongoClient.s.options = {}; - mongoClient.s.dbCache = new Map(); - mongoClient.s.sessions = new Set(); + if (mongoClient.topology && mongoClient.topology.isConnected()) { + throw new Error(`'connect' cannot be called when already connected`); } let didRequestAuthentication = false; diff --git a/test/functional/connection.test.js b/test/functional/connection.test.js index f4a9ade250f..a4ce997d64f 100644 --- a/test/functional/connection.test.js +++ b/test/functional/connection.test.js @@ -517,7 +517,7 @@ describe('Connection', function() { }); it('should be able to connect again after close', function() { - return withClient(this.configuration.newClient(), client => done => { + return withClient.call(this, (client, done) => { expect(client.isConnected()).to.be.true; const collection = () => client.db('testReconnect').collection('test'); collection().insertOne({ a: 1 }, (err, result) => { diff --git a/test/functional/shared.js b/test/functional/shared.js index 85e07f54e11..79443b59a7f 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -57,8 +57,37 @@ function setupDatabase(configuration, dbsToClean) { ); } -function makeCleanupFn(client) { - return function(err) { +function withTempDb(name, options, client, operation, errorHandler) { + return withClient( + client, + client => done => { + const db = client.db(name, options); + operation.call(this, db)(() => db.dropDatabase(done)); + }, + errorHandler + ); +} + +/** + * Safely perform a test with provided MongoClient, ensuring client won't leak. + * + * @param {MongoClient} [client] if not provided, withClient must be bound to test function `this` + * @param {Function} operation (client):Promise or (client, done):void + * @param {Function} [errorHandler] + */ +function withClient(client, operation, errorHandler) { + if (!(client instanceof MongoClient)) { + errorHandler = operation; + operation = client; + client = this.configuration.newClient(); + } + + if (operation.length === 2) { + const callback = operation; + operation = client => new Promise(resolve => callback(client, resolve)); + } + + function cleanup(err) { return new Promise((resolve, reject) => { try { client.close(closeErr => { @@ -72,37 +101,11 @@ function makeCleanupFn(client) { return reject(err || e); } }); - }; -} - -function withTempDb(name, options, client, operation, errorHandler) { - return withClient( - client, - client => done => { - const db = client.db(name, options); - operation.call(this, db)(() => db.dropDatabase(done)); - }, - errorHandler - ); -} - -function withClient(client, operation, errorHandler) { - const cleanup = makeCleanupFn(client); - - function operationHandler(client) { - // run the operation - const result = operation(client); - // if it returns a callback, wrap it in a Promise - if (typeof result === 'function') { - return new Promise(done => result(done)); - } - // otherwise assume it returned a Promise - return result; } return client .connect() - .then(operationHandler, errorHandler) + .then(operation, errorHandler) .then(() => cleanup(), cleanup); } From 5763728f310129a6095702ae5081c5f1e171bb93 Mon Sep 17 00:00:00 2001 From: emadum Date: Mon, 11 May 2020 09:59:28 -0400 Subject: [PATCH 06/10] refactor withTempDb to withDb --- test/functional/change_stream.test.js | 38 +++++++++++++-------------- test/functional/shared.js | 28 +++++++++++++------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index 020de3d6c44..f8ad85a1273 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -4,7 +4,8 @@ var Transform = require('stream').Transform; const MongoError = require('../../lib/core').MongoError; var MongoNetworkError = require('../../lib/core').MongoNetworkError; var setupDatabase = require('./shared').setupDatabase; -var withTempDb = require('./shared').withTempDb; +var withClient = require('./shared').withClient; +var withDb = require('./shared').withDb; var delay = require('./shared').delay; var co = require('co'); var mock = require('mongodb-mock-server'); @@ -2635,19 +2636,21 @@ describe('Change Streams', function() { it('should return null on single iteration of empty cursor', { metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { - return withTempDb( - 'testTryNext', - { w: 'majority' }, - this.configuration.newClient(), - db => done => { - const changeStream = db.collection('test').watch(); - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.not.exist; + return withClient.bind(this)( + withDb( + 'testTryNext', + { w: 'majority' }, + (db, done) => { + const changeStream = db.collection('test').watch(); + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; - changeStream.close(done); - }); - } + changeStream.close(done); + }); + }, + true + ) ); } }); @@ -2655,11 +2658,8 @@ describe('Change Streams', function() { it('should iterate a change stream until first empty batch', { metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { - return withTempDb( - 'testTryNext', - { w: 'majority' }, - this.configuration.newClient(), - db => done => { + return withClient.bind(this)( + withDb('testTryNext', { w: 'majority' }, (db, done) => { const collection = db.collection('test'); const changeStream = collection.watch(); waitForStarted(changeStream, () => { @@ -2688,7 +2688,7 @@ describe('Change Streams', function() { }); }); }); - } + }) ); } }); diff --git a/test/functional/shared.js b/test/functional/shared.js index 79443b59a7f..24906972ec8 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -57,15 +57,25 @@ function setupDatabase(configuration, dbsToClean) { ); } -function withTempDb(name, options, client, operation, errorHandler) { - return withClient( - client, - client => done => { +/** + * use as the `operation` of `withClient` + * + * @param {string} name database name + * @param {object} [options] database options + * @param {Function} testFn test function to execute + * @param {boolean} [drop] drop database after test + */ +function withDb(name, options, testFn, drop) { + if (typeof options === 'function') { + drop = testFn; + testFn = options; + options = {}; + } + return client => + new Promise(resolve => { const db = client.db(name, options); - operation.call(this, db)(() => db.dropDatabase(done)); - }, - errorHandler - ); + testFn(db, drop ? () => db.dropDatabase(resolve) : resolve); + }); } /** @@ -243,7 +253,7 @@ module.exports = { delay, withClient, withMonitoredClient, - withTempDb, + withDb, filterForCommands, filterOutCommands, ignoreNsNotFound, From c4eb0fa429440ac3d2b664be338b7747c8a7476c Mon Sep 17 00:00:00 2001 From: emadum Date: Mon, 11 May 2020 10:36:55 -0400 Subject: [PATCH 07/10] dont save options, show warnings instead --- lib/mongo_client.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 8be08637778..f5a6b072d8e 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -167,8 +167,6 @@ function MongoClient(url, options) { writeConcern: WriteConcern.fromOptions(options), namespace: new MongoDBNamespace('admin') }; - - this.originalOptions = Object.assign({}, this.s.options); } /** @@ -259,9 +257,6 @@ MongoClient.prototype.close = function(force, callback) { this.topology = undefined; this.s.dbCache = new Map(); - // restore options - this.s.options = Object.assign({}, this.originalOptions); - cb(err); }; From 32c1ce807976916f34fa5f1c2e77c937c670e164 Mon Sep 17 00:00:00 2001 From: emadum Date: Mon, 11 May 2020 12:19:46 -0400 Subject: [PATCH 08/10] fix for sessions --- lib/mongo_client.js | 1 + test/functional/sessions.test.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/mongo_client.js b/lib/mongo_client.js index f5a6b072d8e..13fc4622ac9 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -256,6 +256,7 @@ MongoClient.prototype.close = function(force, callback) { // clear out references to old topology this.topology = undefined; this.s.dbCache = new Map(); + this.s.sessions = new Set(); cb(err); }; diff --git a/test/functional/sessions.test.js b/test/functional/sessions.test.js index c79510001d2..b4f88b68e6f 100644 --- a/test/functional/sessions.test.js +++ b/test/functional/sessions.test.js @@ -125,7 +125,7 @@ describe('Sessions', function() { // verify that the `endSessions` command was sent const lastCommand = test.commands.started[test.commands.started.length - 1]; expect(lastCommand.commandName).to.equal('endSessions'); - expect(client.topology.s.sessionPool.sessions).to.have.length(0); + expect(client.topology).to.not.exist; }); }); }); @@ -149,7 +149,7 @@ describe('Sessions', function() { // verify that the `endSessions` command was sent const lastCommand = test.commands.started[test.commands.started.length - 1]; expect(lastCommand.commandName).to.equal('endSessions'); - expect(client.topology.s.sessionPool.sessions).to.have.length(0); + expect(client.topology).to.not.exist; }); }); } From dafab246372d4a5245befb5518c6b91000c07fd4 Mon Sep 17 00:00:00 2001 From: emadum Date: Mon, 11 May 2020 12:50:38 -0400 Subject: [PATCH 09/10] save reference to topology --- lib/mongo_client.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 13fc4622ac9..7b0c603a64f 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -241,11 +241,13 @@ MongoClient.prototype.close = function(force, callback) { force = false; } + const topology = this.topology; + return maybePromise(this, callback, cb => { const completeClose = err => { this.emit('close', this); - if (!(this.topology instanceof NativeTopology)) { + if (!(topology instanceof NativeTopology)) { for (const item of this.s.dbCache) { item[1].emit('close', this); } @@ -261,13 +263,13 @@ MongoClient.prototype.close = function(force, callback) { cb(err); }; - if (this.topology == null) { + if (topology == null) { completeClose(); return; } - this.topology.close(force, err => { - const autoEncrypter = this.topology.s.options.autoEncrypter; + topology.close(force, err => { + const autoEncrypter = topology.s.options.autoEncrypter; if (!autoEncrypter) { completeClose(err); return; From 414e091d71161fbffb4ebf8692d151b9467512fb Mon Sep 17 00:00:00 2001 From: emadum Date: Tue, 12 May 2020 15:02:17 -0400 Subject: [PATCH 10/10] fix flaky tryNext tests --- test/functional/change_stream.test.js | 96 +++++++++++++++------------ test/functional/shared.js | 72 +++++++++++++++++--- 2 files changed, 115 insertions(+), 53 deletions(-) diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index f8ad85a1273..5a834a92feb 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -6,6 +6,7 @@ var MongoNetworkError = require('../../lib/core').MongoNetworkError; var setupDatabase = require('./shared').setupDatabase; var withClient = require('./shared').withClient; var withDb = require('./shared').withDb; +var withCollection = require('./shared').withCollection; var delay = require('./shared').delay; var co = require('co'); var mock = require('mongodb-mock-server'); @@ -60,8 +61,12 @@ function tryNext(changeStream, callback) { let complete = false; function done(err, result) { if (complete) return; - // if the arity is 1 then this a callback for `more` + // if the arity is 1 then this is a callback for `more` if (arguments.length === 1) { + if (err instanceof Error) { + callback(err); + return; + } result = err; const batch = result.cursor.firstBatch || result.cursor.nextBatch; if (batch.length === 0) { @@ -2633,64 +2638,67 @@ describe('Change Streams', function() { }); describe('tryNext', function() { - it('should return null on single iteration of empty cursor', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, - test: function() { + function withTemporaryCollectionOnDb(database, testFn) { + return function() { return withClient.bind(this)( withDb( - 'testTryNext', - { w: 'majority' }, - (db, done) => { - const changeStream = db.collection('test').watch(); - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.not.exist; - - changeStream.close(done); - }); - }, - true + database, + { helper: { drop: true } }, + withCollection( + { + collection: { w: 'majority' }, + helper: { create: true } + }, + testFn + ) ) ); - } + }; + } + it('should return null on single iteration of empty cursor', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, + test: withTemporaryCollectionOnDb('test_try_next_empty', (collection, done) => { + const changeStream = collection.watch(); + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; + + changeStream.close(done); + }); + }) }); it('should iterate a change stream until first empty batch', { metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, - test: function() { - return withClient.bind(this)( - withDb('testTryNext', { w: 'majority' }, (db, done) => { - const collection = db.collection('test'); - const changeStream = collection.watch(); - waitForStarted(changeStream, () => { - collection.insertOne({ a: 42 }, err => { - expect(err).to.not.exist; + test: withTemporaryCollectionOnDb('test_try_next_results', (collection, done) => { + const changeStream = collection.watch(); + waitForStarted(changeStream, () => { + collection.insertOne({ a: 42 }, err => { + expect(err).to.not.exist; - collection.insertOne({ b: 24 }, err => { - expect(err).to.not.exist; - }); - }); + collection.insertOne({ b: 24 }, err => { + expect(err).to.not.exist; }); + }); + }); - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.not.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; - changeStream.close(done); - }); - }); + changeStream.close(done); }); - }) - ); - } + }); + }); + }) }); }); diff --git a/test/functional/shared.js b/test/functional/shared.js index 2d53e677c9e..5ee460f526d 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -57,24 +57,77 @@ function setupDatabase(configuration, dbsToClean) { ); } +/** + * use as the `testFn` of `withDb` + * + * @param {string} [name='test'] database name + * @param {object} [options] options + * @param {object} [options.collection={}] collection options + * @param {object} [options.helper={}] helper options + * @param {boolean} [options.helper.create] create collection before test + * @param {boolean} [options.helper.drop] drop collection after test + * @param {Function} testFn test function to execute + */ +function withCollection(name, options, testFn) { + if (arguments.length === 1) { + testFn = name; + name = 'test'; + options = { collection: {}, helper: {} }; + } else if (arguments.length === 2) { + testFn = options; + if (typeof name === 'string') { + options = { collection: {}, helper: {} }; + } else { + options = name; + name = 'test'; + } + } + function runTest(collection, done) { + testFn(collection, options.helper.drop ? () => collection.drop(done) : done); + } + if (options.helper.create) { + return (db, done) => + db.createCollection(name, options, (err, collection) => { + if (err) return done(err); + runTest(collection, done); + }); + } + return (db, done) => { + const collection = db.collection(name, options.collection); + runTest(collection, done); + }; +} + + /** * use as the `operation` of `withClient` * - * @param {string} name database name - * @param {object} [options] database options + * @param {string} [name='test'] database name + * @param {object} [options] options + * @param {object} [options.db={}] database options + * @param {object} [options.helper={}] helper options + * @param {boolean} [options.helper.drop] drop database after test * @param {Function} testFn test function to execute - * @param {boolean} [drop] drop database after test + */ -function withDb(name, options, testFn, drop) { - if (typeof options === 'function') { - drop = testFn; +function withDb(name, options, testFn) { + if (arguments.length === 1) { + testFn = name; + name = 'test'; + options = { db: {}, helper: {} }; + } else if (arguments.length === 2) { testFn = options; - options = {}; + if (typeof name === 'string') { + options = { db: {}, helper: {} }; + } else { + options = name; + name = 'test'; + } } return client => new Promise(resolve => { - const db = client.db(name, options); - testFn(db, drop ? () => db.dropDatabase(resolve) : resolve); + const db = client.db(name, options.db); + testFn(db, options.helper.drop ? () => db.dropDatabase(resolve) : resolve); }); } @@ -277,6 +330,7 @@ module.exports = { withClient, withMonitoredClient, withDb, + withCollection, filterForCommands, filterOutCommands, ignoreNsNotFound,