diff --git a/lib/instrumentation/mongodb/common.js b/lib/instrumentation/mongodb/common.js index 3bec0d4da3..50e1ac7069 100644 --- a/lib/instrumentation/mongodb/common.js +++ b/lib/instrumentation/mongodb/common.js @@ -72,9 +72,13 @@ common.instrumentBulkOperation = function instrumentBulkOperation(shim, BulkOper common.instrumentDb = function instrumentDb(shim, Db) { if (Db && Db.prototype) { const proto = Db.prototype - shim.recordOperation(proto, DB_OPS, new OperationSpec({ callback: shim.LAST, opaque: true })) + shim.recordOperation( + proto, + DB_OPS, + new OperationSpec({ callback: shim.LAST, opaque: true, promise: true }) + ) // link to client.connect(removed in v4.0) - shim.recordOperation(Db, 'connect', new OperationSpec({ callback: shim.LAST })) + shim.recordOperation(Db, 'connect', new OperationSpec({ callback: shim.LAST, promise: true })) } } @@ -133,7 +137,7 @@ common.makeBulkDescFunc = function makeBulkDescFunc(shim) { * * @param {Shim} shim instance of shim * @param {object} instrumenter instance of mongo APM class - * @param {object} [options={}] provide command names to skip updating host/port as they are unrelated to the active query. This is only in v3 because after every command is runs `endSessions` which runs on the admin database + * @param {object} [options] provide command names to skip updating host/port as they are unrelated to the active query. This is only in v3 because after every command is runs `endSessions` which runs on the admin database */ common.captureAttributesOnStarted = function captureAttributesOnStarted( shim, @@ -214,7 +218,6 @@ function setHostPort(shim, connStr, db, client) { */ function getInstanceAttributeParameters(shim, mongo) { let params - if (mongo?.s?.topology) { shim.logger.trace('Adding datastore instance attributes from mongo.s.db + mongo.s.topology') const databaseName = mongo?.s?.db?.databaseName || mongo?.s?.namespace?.db || null @@ -224,6 +227,10 @@ function getInstanceAttributeParameters(shim, mongo) { const databaseName = mongo?.s?.db?.databaseName || null const hosts = mongo.s.db.s.client.s.options.hosts params = getParametersFromHosts(hosts, databaseName) + } else if (mongo?.s?.db?.client?.topology) { + const databaseName = mongo?.s?.namespace?.db + const topology = mongo.s.db.client.topology + params = getParametersFromTopology(topology, databaseName) } else { shim.logger.trace('Could not find datastore instance attributes.') params = new DatastoreParameters() @@ -272,6 +279,11 @@ function getParametersFromTopology(conf, database) { ;[{ host, port }] = conf.s.options.servers } + // hosts is an array but we will always pull the first for consistency + if (conf?.s?.options?.hosts?.length) { + ;[{ host, port }] = conf.s.options.hosts + } + // host is a domain socket. set host as localhost and use the domain // socket host as the port if (host && host.endsWith('.sock')) { @@ -280,7 +292,7 @@ function getParametersFromTopology(conf, database) { } return new DatastoreParameters({ - host: host, + host, port_path_or_id: port, database_name: database }) diff --git a/test/versioned/mongodb/collection-common.js b/test/versioned/mongodb/collection-common.js index 5d4c5878b5..d339cda2b2 100644 --- a/test/versioned/mongodb/collection-common.js +++ b/test/versioned/mongodb/collection-common.js @@ -14,16 +14,12 @@ const { version: pkgVersion } = require('mongodb/package') let METRIC_HOST_NAME = null let METRIC_HOST_PORT = null -exports.MONGO_SEGMENT_RE = common.MONGO_SEGMENT_RE -exports.TRANSACTION_NAME = common.TRANSACTION_NAME -exports.DB_NAME = common.DB_NAME - exports.connect = common.connect exports.close = common.close -exports.populate = populate exports.test = collectionTest - exports.dropTestCollections = dropTestCollections +exports.populate = populate + const { COLLECTIONS } = common function collectionTest(name, run) { @@ -36,30 +32,25 @@ function collectionTest(name, run) { t.test('remote connection', function (t) { t.autoend() - t.beforeEach(function () { + t.beforeEach(async function () { agent = helper.instrumentMockedAgent() const mongodb = require('mongodb') - return dropTestCollections(mongodb) - .then(() => { - METRIC_HOST_NAME = common.getHostName(agent) - METRIC_HOST_PORT = common.getPort() - return common.connect(mongodb) - }) - .then((res) => { - client = res.client - db = res.db - collection = db.collection(COLLECTIONS.collection1) - return populate(db, collection) - }) + await dropTestCollections(mongodb) + METRIC_HOST_NAME = common.getHostName(agent) + METRIC_HOST_PORT = common.getPort() + const res = await common.connect(mongodb) + client = res.client + db = res.db + collection = db.collection(COLLECTIONS.collection1) + await populate(collection) }) - t.afterEach(function () { - return common.close(client, db).then(() => { - helper.unloadAgent(agent) - agent = null - }) + t.afterEach(async function () { + await common.close(client, db) + helper.unloadAgent(agent) + agent = null }) t.test('should not error outside of a transaction', function (t) { @@ -200,30 +191,25 @@ function collectionTest(name, run) { const shouldTestDomain = domainPath t.test('domain socket', { skip: !shouldTestDomain }, function (t) { t.autoend() - t.beforeEach(function () { + t.beforeEach(async function () { agent = helper.instrumentMockedAgent() METRIC_HOST_NAME = agent.config.getHostnameSafe() METRIC_HOST_PORT = domainPath const mongodb = require('mongodb') - return dropTestCollections(mongodb) - .then(() => { - return common.connect(mongodb, domainPath) - }) - .then((res) => { - client = res.client - db = res.db - collection = db.collection(COLLECTIONS.collection1) - return populate(db, collection) - }) + await dropTestCollections(mongodb) + const res = await common.connect(mongodb, domainPath) + client = res.client + db = res.db + collection = db.collection(COLLECTIONS.collection1) + await populate(collection) }) - t.afterEach(function () { - return common.close(client, db).then(() => { - helper.unloadAgent(agent) - agent = null - }) + t.afterEach(async function () { + await common.close(client, db) + helper.unloadAgent(agent) + agent = null }) t.test('should have domain socket in metrics', function (t) { @@ -247,30 +233,25 @@ function collectionTest(name, run) { t.test('domain socket replica set', { skip: !shouldTestDomain }, function (t) { t.autoend() - t.beforeEach(function () { + t.beforeEach(async function () { agent = helper.instrumentMockedAgent() METRIC_HOST_NAME = agent.config.getHostnameSafe() METRIC_HOST_PORT = domainPath const mongodb = require('mongodb') - return dropTestCollections(mongodb) - .then(() => { - return common.connect(mongodb, domainPath, true) - }) - .then((res) => { - client = res.client - db = res.db - collection = db.collection(COLLECTIONS.collection1) - return populate(db, collection) - }) + await dropTestCollections(mongodb) + const res = await common.connect(mongodb, domainPath) + client = res.client + db = res.db + collection = db.collection(COLLECTIONS.collection1) + await populate(collection) }) - t.afterEach(function () { - return common.close(client, db).then(() => { - helper.unloadAgent(agent) - agent = null - }) + t.afterEach(async function () { + await common.close(client, db) + helper.unloadAgent(agent) + agent = null }) t.test('should have domain socket in metrics', function (t) { @@ -297,30 +278,25 @@ function collectionTest(name, run) { if (semver.satisfies(pkgVersion, '>=3.6.0')) { t.test('replica set string remote connection', function (t) { t.autoend() - t.beforeEach(function () { + t.beforeEach(async function () { agent = helper.instrumentMockedAgent() const mongodb = require('mongodb') - return dropTestCollections(mongodb) - .then(() => { - METRIC_HOST_NAME = common.getHostName(agent) - METRIC_HOST_PORT = common.getPort() - return common.connect(mongodb, null, true) - }) - .then((res) => { - client = res.client - db = res.db - collection = db.collection(COLLECTIONS.collection1) - return populate(db, collection) - }) + await dropTestCollections(mongodb) + METRIC_HOST_NAME = common.getHostName(agent) + METRIC_HOST_PORT = common.getPort() + const res = await common.connect(mongodb, null, true) + client = res.client + db = res.db + collection = db.collection(COLLECTIONS.collection1) + await populate(collection) }) - t.afterEach(function () { - return common.close(client, db).then(() => { - helper.unloadAgent(agent) - agent = null - }) + t.afterEach(async function () { + await common.close(client, db) + helper.unloadAgent(agent) + agent = null }) t.test('should generate the correct metrics and segments', function (t) { @@ -410,29 +386,20 @@ function checkSegmentParams(t, segment) { t.equal(attributes.port_path_or_id, METRIC_HOST_PORT, 'should have correct port') } -function populate(db, collection) { - return new Promise((resolve, reject) => { - const items = [] - for (let i = 0; i < 30; ++i) { - items.push({ - i: i, - next3: [i + 1, i + 2, i + 3], - data: Math.random().toString(36).slice(2), - mod10: i % 10, - // spiral out - loc: [i % 4 && (i + 1) % 4 ? i : -i, (i + 1) % 4 && (i + 2) % 4 ? i : -i] - }) - } - - db.collection(COLLECTIONS.collection2).drop(function () { - collection.deleteMany({}, function (err) { - if (err) { - reject(err) - } - collection.insert(items, resolve) - }) +async function populate(collection) { + const items = [] + for (let i = 0; i < 30; ++i) { + items.push({ + i: i, + next3: [i + 1, i + 2, i + 3], + data: Math.random().toString(36).slice(2), + mod10: i % 10, + // spiral out + loc: [i % 4 && (i + 1) % 4 ? i : -i, (i + 1) % 4 && (i + 2) % 4 ? i : -i] }) - }) + } + + await collection.insertMany(items) } /** diff --git a/test/versioned/mongodb/collection-find.tap.js b/test/versioned/mongodb/collection-find.tap.js index d5ba018efa..85931c8aef 100644 --- a/test/versioned/mongodb/collection-find.tap.js +++ b/test/versioned/mongodb/collection-find.tap.js @@ -5,73 +5,32 @@ 'use strict' const common = require('./collection-common') -const semver = require('semver') -const { pkgVersion, STATEMENT_PREFIX } = require('./common') +const { STATEMENT_PREFIX } = require('./common') +const findOpt = { returnDocument: 'after' } -let findOpt = { returnOriginal: false } -// 4.0.0 changed this opt https://github.com/mongodb/node-mongodb-native/pull/2803/files -if (semver.satisfies(pkgVersion, '>=4')) { - findOpt = { returnDocument: 'after' } -} - -if (semver.satisfies(pkgVersion, '<4')) { - common.test('findAndModify', function findAndModifyTest(t, collection, verify) { - collection.findAndModify({ i: 1 }, [['i', 1]], { $set: { a: 15 } }, { new: true }, done) - - function done(err, data) { - t.error(err) - t.equal(data.value.a, 15) - t.equal(data.value.i, 1) - t.equal(data.ok, 1) - verify(null, [`${STATEMENT_PREFIX}/findAndModify`, 'Callback: done'], ['findAndModify']) - } - }) - - common.test('findAndRemove', function findAndRemoveTest(t, collection, verify) { - collection.findAndRemove({ i: 1 }, [['i', 1]], function done(err, data) { - t.error(err) - t.equal(data.value.i, 1) - t.equal(data.ok, 1) - verify(null, [`${STATEMENT_PREFIX}/findAndRemove`, 'Callback: done'], ['findAndRemove']) - }) - }) -} - -common.test('findOne', function findOneTest(t, collection, verify) { - collection.findOne({ i: 15 }, function done(err, data) { - t.error(err) - t.equal(data.i, 15) - verify(null, [`${STATEMENT_PREFIX}/findOne`, 'Callback: done'], ['findOne']) - }) +common.test('findOne', async function findOneTest(t, collection, verify) { + const data = await collection.findOne({ i: 15 }) + t.equal(data.i, 15) + verify(null, [`${STATEMENT_PREFIX}/findOne`], ['findOne'], { strict: false }) }) -common.test('findOneAndDelete', function findOneAndDeleteTest(t, collection, verify) { - collection.findOneAndDelete({ i: 15 }, function done(err, data) { - t.error(err) - t.equal(data.ok, 1) - t.equal(data.value.i, 15) - verify(null, [`${STATEMENT_PREFIX}/findOneAndDelete`, 'Callback: done'], ['findOneAndDelete']) - }) +common.test('findOneAndDelete', async function findOneAndDeleteTest(t, collection, verify) { + const data = await collection.findOneAndDelete({ i: 15 }) + const response = data?.value?.i || data.i + t.equal(response, 15) + verify(null, [`${STATEMENT_PREFIX}/findOneAndDelete`], ['findOneAndDelete'], { strict: false }) }) -common.test('findOneAndReplace', function findAndReplaceTest(t, collection, verify) { - collection.findOneAndReplace({ i: 15 }, { b: 15 }, findOpt, done) - - function done(err, data) { - t.error(err) - t.equal(data.value.b, 15) - t.equal(data.ok, 1) - verify(null, [`${STATEMENT_PREFIX}/findOneAndReplace`, 'Callback: done'], ['findOneAndReplace']) - } +common.test('findOneAndReplace', async function findAndReplaceTest(t, collection, verify) { + const data = await collection.findOneAndReplace({ i: 15 }, { b: 15 }, findOpt) + const response = data?.value?.b || data.b + t.equal(response, 15) + verify(null, [`${STATEMENT_PREFIX}/findOneAndReplace`], ['findOneAndReplace'], { strict: false }) }) -common.test('findOneAndUpdate', function findOneAndUpdateTest(t, collection, verify) { - collection.findOneAndUpdate({ i: 15 }, { $set: { a: 15 } }, findOpt, done) - - function done(err, data) { - t.error(err) - t.equal(data.value.a, 15) - t.equal(data.ok, 1) - verify(null, [`${STATEMENT_PREFIX}/findOneAndUpdate`, 'Callback: done'], ['findOneAndUpdate']) - } +common.test('findOneAndUpdate', async function findOneAndUpdateTest(t, collection, verify) { + const data = await collection.findOneAndUpdate({ i: 15 }, { $set: { a: 15 } }, findOpt) + const response = data?.value?.a || data.a + t.equal(response, 15) + verify(null, [`${STATEMENT_PREFIX}/findOneAndUpdate`], ['findOneAndUpdate'], { strict: false }) }) diff --git a/test/versioned/mongodb/collection-index.tap.js b/test/versioned/mongodb/collection-index.tap.js index dbe6ab3edf..6014a9cc66 100644 --- a/test/versioned/mongodb/collection-index.tap.js +++ b/test/versioned/mongodb/collection-index.tap.js @@ -7,101 +7,58 @@ const common = require('./collection-common') const semver = require('semver') -const { pkgVersion, STATEMENT_PREFIX, DB_NAME, COLLECTIONS } = require('./common') +const { COLLECTIONS, DB_NAME, pkgVersion, STATEMENT_PREFIX } = require('./common') -common.test('createIndex', function createIndexTest(t, collection, verify) { - collection.createIndex('i', function onIndex(err, data) { - t.error(err) - t.equal(data, 'i_1') - verify(null, [`${STATEMENT_PREFIX}/createIndex`, 'Callback: onIndex'], ['createIndex']) - }) +common.test('createIndex', async function createIndexTest(t, collection, verify) { + const data = await collection.createIndex('i') + t.equal(data, 'i_1') + verify(null, [`${STATEMENT_PREFIX}/createIndex`], ['createIndex'], { strict: false }) }) -common.test('dropIndex', function dropIndexTest(t, collection, verify) { - collection.createIndex('i', function onIndex(err) { - t.error(err) - collection.dropIndex('i_1', function done(err, data) { - t.error(err) - t.equal(data.ok, 1) - verify( - null, - [ - `${STATEMENT_PREFIX}/createIndex`, - 'Callback: onIndex', - `${STATEMENT_PREFIX}/dropIndex`, - 'Callback: done' - ], - ['createIndex', 'dropIndex'] - ) - }) - }) +common.test('dropIndex', async function dropIndexTest(t, collection, verify) { + await collection.createIndex('i') + const data = await collection.dropIndex('i_1') + t.equal(data.ok, 1) + verify( + null, + [`${STATEMENT_PREFIX}/createIndex`, `${STATEMENT_PREFIX}/dropIndex`], + ['createIndex', 'dropIndex'], + { strict: false, childrenLength: 2 } + ) }) -common.test('indexes', function indexesTest(t, collection, verify) { - collection.indexes(function done(err, data) { - t.error(err) - const result = data && data[0] - const expectedResult = { - v: result && result.v, - key: { _id: 1 }, - name: '_id_' - } - - // this will fail if running a mongodb server > 4.3.1 - // https://jira.mongodb.org/browse/SERVER-41696 - // we only connect to a server > 4.3.1 when using the mongodb - // driver of 4.2.0+ - if (semver.satisfies(pkgVersion, '<4.2.0')) { - expectedResult.ns = `${DB_NAME}.${COLLECTIONS.collection1}` - } - - t.same(result, expectedResult, 'should have expected results') - - verify(null, [`${STATEMENT_PREFIX}/indexes`, 'Callback: done'], ['indexes']) - }) +common.test('indexes', async function indexesTest(t, collection, verify) { + const data = await collection.indexes() + const result = data && data[0] + const expectedResult = { + v: result && result.v, + key: { _id: 1 }, + name: '_id_' + } + + // this will fail if running a mongodb server > 4.3.1 + // https://jira.mongodb.org/browse/SERVER-41696 + // we only connect to a server > 4.3.1 when using the mongodb + // driver of 4.2.0+ + if (semver.satisfies(pkgVersion, '<4.2.0')) { + expectedResult.ns = `${DB_NAME}.${COLLECTIONS.collection1}` + } + + t.same(result, expectedResult, 'should have expected results') + + verify(null, [`${STATEMENT_PREFIX}/indexes`], ['indexes'], { strict: false }) }) -common.test('indexExists', function indexExistsTest(t, collection, verify) { - collection.indexExists(['_id_'], function done(err, data) { - t.error(err) - t.equal(data, true) +common.test('indexExists', async function indexExistsTest(t, collection, verify) { + const data = await collection.indexExists(['_id_']) + t.equal(data, true) - verify(null, [`${STATEMENT_PREFIX}/indexExists`, 'Callback: done'], ['indexExists']) - }) + verify(null, [`${STATEMENT_PREFIX}/indexExists`], ['indexExists'], { strict: false }) }) -common.test('indexInformation', function indexInformationTest(t, collection, verify) { - collection.indexInformation(function done(err, data) { - t.error(err) - t.same(data && data._id_, [['_id', 1]], 'should have expected results') +common.test('indexInformation', async function indexInformationTest(t, collection, verify) { + const data = await collection.indexInformation() + t.same(data && data._id_, [['_id', 1]], 'should have expected results') - verify(null, [`${STATEMENT_PREFIX}/indexInformation`, 'Callback: done'], ['indexInformation']) - }) + verify(null, [`${STATEMENT_PREFIX}/indexInformation`], ['indexInformation'], { strict: false }) }) - -if (semver.satisfies(pkgVersion, '<4')) { - common.test('dropAllIndexes', function dropAllIndexesTest(t, collection, verify) { - collection.dropAllIndexes(function done(err, data) { - t.error(err) - t.equal(data, true) - verify(null, [`${STATEMENT_PREFIX}/dropAllIndexes`, 'Callback: done'], ['dropAllIndexes']) - }) - }) - - common.test('ensureIndex', function ensureIndexTest(t, collection, verify) { - collection.ensureIndex('i', function done(err, data) { - t.error(err) - t.equal(data, 'i_1') - verify(null, [`${STATEMENT_PREFIX}/ensureIndex`, 'Callback: done'], ['ensureIndex']) - }) - }) - - common.test('reIndex', function reIndexTest(t, collection, verify) { - collection.reIndex(function done(err, data) { - t.error(err) - t.equal(data, true) - - verify(null, [`${STATEMENT_PREFIX}/reIndex`, 'Callback: done'], ['reIndex']) - }) - }) -} diff --git a/test/versioned/mongodb/collection-misc.tap.js b/test/versioned/mongodb/collection-misc.tap.js index 097b8e75dd..7ad73de48a 100644 --- a/test/versioned/mongodb/collection-misc.tap.js +++ b/test/versioned/mongodb/collection-misc.tap.js @@ -14,131 +14,94 @@ function verifyAggregateData(t, data) { t.same(data, [{ value: 5 }, { value: 15 }, { value: 25 }], 'should have expected results') } -if (semver.satisfies(pkgVersion, '<4')) { - common.test('aggregate', function aggregateTest(t, collection, verify) { - const cursor = collection.aggregate([ +common.test('aggregate', async function aggregateTest(t, collection, verify) { + const data = await collection + .aggregate([ { $sort: { i: 1 } }, { $match: { mod10: 5 } }, { $limit: 3 }, { $project: { value: '$i', _id: 0 } } ]) + .toArray() + verifyAggregateData(t, data) + verify( + null, + [`${STATEMENT_PREFIX}/aggregate`, `${STATEMENT_PREFIX}/toArray`], + ['aggregate', 'toArray'], + { childrenLength: 2 } + ) +}) - cursor.toArray(function onResult(err, data) { - verifyAggregateData(t, data) - verify( - err, - [`${STATEMENT_PREFIX}/aggregate`, `${STATEMENT_PREFIX}/toArray`], - ['aggregate', 'toArray'], - { childrenLength: 2, strict: false } - ) - }) - }) -} else { - common.test('aggregate v4', async function aggregateTest(t, collection, verify) { - const data = await collection - .aggregate([ - { $sort: { i: 1 } }, - { $match: { mod10: 5 } }, - { $limit: 3 }, - { $project: { value: '$i', _id: 0 } } - ]) - .toArray() - verifyAggregateData(t, data) - verify( - null, - [`${STATEMENT_PREFIX}/aggregate`, `${STATEMENT_PREFIX}/toArray`], - ['aggregate', 'toArray'], - { childrenLength: 2 } - ) - }) -} - -common.test('bulkWrite', function bulkWriteTest(t, collection, verify) { - collection.bulkWrite( +common.test('bulkWrite', async function bulkWriteTest(t, collection, verify) { + const data = await collection.bulkWrite( [{ deleteMany: { filter: {} } }, { insertOne: { document: { a: 1 } } }], - { ordered: true, w: 1 }, - onWrite + { ordered: true, w: 1 } ) - function onWrite(err, data) { - t.error(err) - t.equal(data.insertedCount, 1) - t.equal(data.deletedCount, 30) - verify(null, [`${STATEMENT_PREFIX}/bulkWrite`, 'Callback: onWrite'], ['bulkWrite']) - } + t.equal(data.insertedCount, 1) + t.equal(data.deletedCount, 30) + verify(null, [`${STATEMENT_PREFIX}/bulkWrite`], ['bulkWrite'], { strict: false }) }) -common.test('count', function countTest(t, collection, verify) { - collection.count(function onCount(err, data) { - t.error(err) - t.equal(data, 30) - verify(null, [`${STATEMENT_PREFIX}/count`, 'Callback: onCount'], ['count']) - }) +common.test('count', async function countTest(t, collection, verify) { + const data = await collection.count() + t.equal(data, 30) + verify(null, [`${STATEMENT_PREFIX}/count`], ['count'], { strict: false }) }) -common.test('distinct', function distinctTest(t, collection, verify) { - collection.distinct('mod10', function done(err, data) { - t.error(err) - t.same(data.sort(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - verify(null, [`${STATEMENT_PREFIX}/distinct`, 'Callback: done'], ['distinct']) - }) +common.test('distinct', async function distinctTest(t, collection, verify) { + const data = await collection.distinct('mod10') + t.same(data.sort(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + verify(null, [`${STATEMENT_PREFIX}/distinct`], ['distinct'], { strict: false }) }) -common.test('drop', function dropTest(t, collection, verify) { - collection.drop(function done(err, data) { - t.error(err) - t.equal(data, true) - verify(null, [`${STATEMENT_PREFIX}/drop`, 'Callback: done'], ['drop']) - }) +common.test('drop', async function dropTest(t, collection, verify) { + const data = await collection.drop() + t.equal(data, true) + verify(null, [`${STATEMENT_PREFIX}/drop`], ['drop'], { strict: false }) }) -if (semver.satisfies(pkgVersion, '<3')) { - common.test('geoNear', function geoNearTest(t, collection, verify) { - collection.ensureIndex({ loc: '2d' }, { bucketSize: 1 }, indexed) +common.test('isCapped', async function isCappedTest(t, collection, verify) { + const data = await collection.isCapped() + t.notOk(data) - function indexed(err) { - t.error(err) - collection.geoNear(20, 20, { maxDistance: 5 }, done) - } + verify(null, [`${STATEMENT_PREFIX}/isCapped`], ['isCapped'], { strict: false }) +}) - function done(err, data) { - t.error(err) - t.equal(data.ok, 1) - t.equal(data.results.length, 2) - t.equal(data.results[0].obj.i, 21) - t.equal(data.results[1].obj.i, 17) - t.same(data.results[0].obj.loc, [21, 21]) - t.same(data.results[1].obj.loc, [17, 17]) - t.equal(data.results[0].dis, 1.4142135623730951) - t.equal(data.results[1].dis, 4.242640687119285) - verify( - null, - [ - `${STATEMENT_PREFIX}/ensureIndex`, - 'Callback: indexed', - `${STATEMENT_PREFIX}/geoNear`, - 'Callback: done' - ], - ['ensureIndex', 'geoNear'] - ) - } - }) -} +common.test('options', async function optionsTest(t, collection, verify) { + const data = await collection.options() + + // Depending on the version of the mongo server this will change. + if (data) { + t.same(data, {}, 'should have expected results') + } else { + t.notOk(data, 'should have expected results') + } + + verify(null, [`${STATEMENT_PREFIX}/options`], ['options'], { strict: false }) +}) -common.test('isCapped', function isCappedTest(t, collection, verify) { - collection.isCapped(function done(err, data) { - t.error(err) - t.notOk(data) +common.test('rename', async function renameTest(t, collection, verify) { + await collection.rename(COLLECTIONS.collection2) - verify(null, [`${STATEMENT_PREFIX}/isCapped`, 'Callback: done'], ['isCapped']) - }) + verify(null, [`${STATEMENT_PREFIX}/rename`], ['rename'], { strict: false }) }) -common.test('mapReduce', function mapReduceTest(t, collection, verify) { - collection.mapReduce(map, reduce, { out: { inline: 1 } }, done) +if (semver.satisfies(pkgVersion, '<6.0.0')) { + common.test('stats', async function statsTest(t, collection, verify) { + const data = await collection.stats({ i: 5 }) + t.equal(data.ns, `${DB_NAME}.${COLLECTIONS.collection1}`) + t.equal(data.count, 30) + t.equal(data.ok, 1) + + verify(null, [`${STATEMENT_PREFIX}/stats`], ['stats'], { strict: false }) + }) +} + +if (semver.satisfies(pkgVersion, '<5.0.0')) { + common.test('mapReduce', async function mapReduceTest(t, collection, verify) { + const data = await collection.mapReduce(map, reduce, { out: { inline: 1 } }) - function done(err, data) { - t.error(err) const expectedData = [ { _id: 0, value: 30 }, { _id: 1, value: 33 }, @@ -157,140 +120,18 @@ common.test('mapReduce', function mapReduceTest(t, collection, verify) { data.sort((a, b) => a._id - b._id) t.same(data, expectedData) - verify(null, [`${STATEMENT_PREFIX}/mapReduce`, 'Callback: done'], ['mapReduce']) - } - - /* eslint-disable */ - function map(obj) { - emit(this.mod10, this.i) - } - /* eslint-enable */ - - function reduce(key, vals) { - return vals.reduce(function sum(prev, val) { - return prev + val - }, 0) - } -}) - -common.test('options', function optionsTest(t, collection, verify) { - collection.options(function done(err, data) { - t.error(err) - - // Depending on the version of the mongo server this will change. - if (data) { - t.same(data, {}, 'should have expected results') - } else { - t.notOk(data, 'should have expected results') - } - - verify(null, [`${STATEMENT_PREFIX}/options`, 'Callback: done'], ['options']) - }) -}) - -if (semver.satisfies(pkgVersion, '<4')) { - common.test('parallelCollectionScan', function (t, collection, verify) { - collection.parallelCollectionScan({ numCursors: 1 }, function done(err, cursors) { - t.error(err) - - cursors[0].toArray(function toArray(err, items) { - t.error(err) - t.equal(items.length, 30) - - const total = items.reduce(function sum(prev, item) { - return item.i + prev - }, 0) - - t.equal(total, 435) - verify( - null, - [ - `${STATEMENT_PREFIX}/parallelCollectionScan`, - 'Callback: done', - `${STATEMENT_PREFIX}/toArray`, - 'Callback: toArray' - ], - ['parallelCollectionScan', 'toArray'] - ) - }) - }) - }) - - common.test('geoHaystackSearch', function haystackSearchTest(t, collection, verify) { - collection.ensureIndex({ loc: 'geoHaystack', type: 1 }, { bucketSize: 1 }, indexed) - - function indexed(err) { - t.error(err) - collection.geoHaystackSearch(15, 15, { maxDistance: 5, search: {} }, done) - } - - function done(err, data) { - t.error(err) - t.equal(data.ok, 1) - t.equal(data.results.length, 2) - t.equal(data.results[0].i, 13) - t.equal(data.results[1].i, 17) - t.same(data.results[0].loc, [13, 13]) - t.same(data.results[1].loc, [17, 17]) - verify( - null, - [ - `${STATEMENT_PREFIX}/ensureIndex`, - 'Callback: indexed', - `${STATEMENT_PREFIX}/geoHaystackSearch`, - 'Callback: done' - ], - ['ensureIndex', 'geoHaystackSearch'] - ) - } - }) - - common.test('group', function groupTest(t, collection, verify) { - collection.group(['mod10'], {}, { count: 0, total: 0 }, count, done) - - function done(err, data) { - t.error(err) - t.same(data.sort(sort), [ - { mod10: 0, count: 3, total: 30 }, - { mod10: 1, count: 3, total: 33 }, - { mod10: 2, count: 3, total: 36 }, - { mod10: 3, count: 3, total: 39 }, - { mod10: 4, count: 3, total: 42 }, - { mod10: 5, count: 3, total: 45 }, - { mod10: 6, count: 3, total: 48 }, - { mod10: 7, count: 3, total: 51 }, - { mod10: 8, count: 3, total: 54 }, - { mod10: 9, count: 3, total: 57 } - ]) - verify(null, [`${STATEMENT_PREFIX}/group`, 'Callback: done'], ['group']) - } + verify(null, [`${STATEMENT_PREFIX}/mapReduce`], ['mapReduce'], { strict: false }) - function count(obj, prev) { - prev.total += obj.i - prev.count++ + /* eslint-disable */ + function map() { + emit(this.mod10, this.i) } + /* eslint-enable */ - function sort(a, b) { - return a.mod10 - b.mod10 + function reduce(_key, vals) { + return vals.reduce(function sum(prev, val) { + return prev + val + }, 0) } }) } - -common.test('rename', function renameTest(t, collection, verify) { - collection.rename(COLLECTIONS.collection2, function done(err) { - t.error(err) - - verify(null, [`${STATEMENT_PREFIX}/rename`, 'Callback: done'], ['rename']) - }) -}) - -common.test('stats', function statsTest(t, collection, verify) { - collection.stats({ i: 5 }, function done(err, data) { - t.error(err) - t.equal(data.ns, `${DB_NAME}.${COLLECTIONS.collection1}`) - t.equal(data.count, 30) - t.equal(data.ok, 1) - - verify(null, [`${STATEMENT_PREFIX}/stats`, 'Callback: done'], ['stats']) - }) -}) diff --git a/test/versioned/mongodb/collection-update.tap.js b/test/versioned/mongodb/collection-update.tap.js index aa2e90f97a..78d60e602b 100644 --- a/test/versioned/mongodb/collection-update.tap.js +++ b/test/versioned/mongodb/collection-update.tap.js @@ -19,209 +19,156 @@ const { pkgVersion, STATEMENT_PREFIX } = require('./common') * @param {Number} params.count, optional * @param {string} params.keyPrefix prefix where the count exists * @param {Object} params.extraValues extra fields to assert on >=4.0.0 version of module - * @param {Object} params.legaycValues extra fields to assert on <4.0.0 version of module */ -function assertExpectedResult({ t, data, count, keyPrefix, extraValues, legacyValues }) { - if (semver.satisfies(pkgVersion, '<4')) { - const expectedResult = { ok: 1, ...legacyValues } - if (count) { - expectedResult.n = count - } - t.same(data.result, expectedResult) - } else { - const expectedResult = { acknowledged: true, ...extraValues } - if (count) { - expectedResult[`${keyPrefix}Count`] = count - } - t.same(data, expectedResult) +function assertExpectedResult({ t, data, count, keyPrefix, extraValues }) { + const expectedResult = { acknowledged: true, ...extraValues } + if (count) { + expectedResult[`${keyPrefix}Count`] = count } + t.same(data, expectedResult) } -common.test('deleteMany', function deleteManyTest(t, collection, verify) { - collection.deleteMany({ mod10: 5 }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 3, - keyPrefix: 'deleted' - }) - verify(null, [`${STATEMENT_PREFIX}/deleteMany`, 'Callback: done'], ['deleteMany']) +common.test('deleteMany', async function deleteManyTest(t, collection, verify) { + const data = await collection.deleteMany({ mod10: 5 }) + assertExpectedResult({ + t, + data, + count: 3, + keyPrefix: 'deleted' }) + verify(null, [`${STATEMENT_PREFIX}/deleteMany`], ['deleteMany'], { strict: false }) }) -common.test('deleteOne', function deleteOneTest(t, collection, verify) { - collection.deleteOne({ mod10: 5 }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 1, - keyPrefix: 'deleted' - }) - verify(null, [`${STATEMENT_PREFIX}/deleteOne`, 'Callback: done'], ['deleteOne']) +common.test('deleteOne', async function deleteOneTest(t, collection, verify) { + const data = await collection.deleteOne({ mod10: 5 }) + assertExpectedResult({ + t, + data, + count: 1, + keyPrefix: 'deleted' }) + verify(null, [`${STATEMENT_PREFIX}/deleteOne`], ['deleteOne'], { strict: false }) }) -common.test('insert', function insertTest(t, collection, verify) { - collection.insert({ foo: 'bar' }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 1, - keyPrefix: 'inserted', - extraValues: { - insertedIds: { - 0: {} - } +common.test('insertMany', async function insertManyTest(t, collection, verify) { + const data = await collection.insertMany([{ foo: 'bar' }, { foo: 'bar2' }]) + assertExpectedResult({ + t, + data, + count: 2, + keyPrefix: 'inserted', + extraValues: { + insertedIds: { + 0: data.insertedIds[0], + 1: data.insertedIds[1] } - }) - - verify(null, [`${STATEMENT_PREFIX}/insert`, 'Callback: done'], ['insert']) + } }) -}) -common.test('insertMany', function insertManyTest(t, collection, verify) { - collection.insertMany([{ foo: 'bar' }, { foo: 'bar2' }], function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 2, - keyPrefix: 'inserted', - extraValues: { - insertedIds: { - 0: {}, - 1: {} - } - } - }) - - verify(null, [`${STATEMENT_PREFIX}/insertMany`, 'Callback: done'], ['insertMany']) - }) + verify(null, [`${STATEMENT_PREFIX}/insertMany`], ['insertMany'], { strict: false }) }) -common.test('insertOne', function insertOneTest(t, collection, verify) { - collection.insertOne({ foo: 'bar' }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - legacyValues: { - n: 1 - }, - extraValues: { - insertedId: {} - } - }) - - verify(null, [`${STATEMENT_PREFIX}/insertOne`, 'Callback: done'], ['insertOne']) +common.test('insertOne', async function insertOneTest(t, collection, verify) { + const data = await collection.insertOne({ foo: 'bar' }) + assertExpectedResult({ + t, + data, + extraValues: { + insertedId: data.insertedId + } }) -}) -common.test('remove', function removeTest(t, collection, verify) { - collection.remove({ mod10: 5 }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 3, - keyPrefix: 'deleted' - }) + verify(null, [`${STATEMENT_PREFIX}/insertOne`], ['insertOne'], { strict: false }) +}) - verify(null, [`${STATEMENT_PREFIX}/remove`, 'Callback: done'], ['remove']) +common.test('replaceOne', async function replaceOneTest(t, collection, verify) { + const data = await collection.replaceOne({ i: 5 }, { foo: 'bar' }) + assertExpectedResult({ + t, + data, + count: 1, + keyPrefix: 'modified', + extraValues: { + matchedCount: 1, + upsertedCount: 0, + upsertedId: null + } }) -}) -common.test('replaceOne', function replaceOneTest(t, collection, verify) { - collection.replaceOne({ i: 5 }, { foo: 'bar' }, function done(err, data) { - t.error(err) - assertExpectedResult({ - t, - data, - count: 1, - keyPrefix: 'modified', - legacyValues: { - nModified: 1 - }, - extraValues: { - matchedCount: 1, - upsertedCount: 0, - upsertedId: null - } - }) + verify(null, [`${STATEMENT_PREFIX}/replaceOne`], ['replaceOne'], { strict: false }) +}) - verify(null, [`${STATEMENT_PREFIX}/replaceOne`, 'Callback: done'], ['replaceOne']) +common.test('updateMany', async function updateManyTest(t, collection, verify) { + const data = await collection.updateMany({ mod10: 5 }, { $set: { a: 5 } }) + assertExpectedResult({ + t, + data, + count: 3, + keyPrefix: 'modified', + extraValues: { + matchedCount: 3, + upsertedCount: 0, + upsertedId: null + } }) -}) -if (semver.satisfies(pkgVersion, '<4')) { - common.test('save', function saveTest(t, collection, verify) { - collection.save({ foo: 'bar' }, function done(err, data) { - t.error(err) - t.same(data.result, { ok: 1, n: 1 }) + verify(null, [`${STATEMENT_PREFIX}/updateMany`], ['updateMany'], { strict: false }) +}) - verify(null, [`${STATEMENT_PREFIX}/save`, 'Callback: done'], ['save']) - }) +common.test('updateOne', async function updateOneTest(t, collection, verify) { + const data = await collection.updateOne({ i: 5 }, { $set: { a: 5 } }) + assertExpectedResult({ + t, + data, + count: 1, + keyPrefix: 'modified', + extraValues: { + matchedCount: 1, + upsertedCount: 0, + upsertedId: null + } }) -} -common.test('update', function updateTest(t, collection, verify) { - collection.update({ i: 5 }, { $set: { foo: 'bar' } }, function done(err, data) { - t.error(err) + verify(null, [`${STATEMENT_PREFIX}/updateOne`], ['updateOne'], { strict: false }) +}) + +if (semver.satisfies(pkgVersion, '<5.0.0')) { + common.test('insert', async function insertTest(t, collection, verify) { + const data = await collection.insert({ foo: 'bar' }) assertExpectedResult({ t, data, count: 1, - keyPrefix: 'modified', - legacyValues: { - nModified: 1 - }, + keyPrefix: 'inserted', extraValues: { - matchedCount: 1, - upsertedCount: 0, - upsertedId: null + insertedIds: { + 0: {} + } } }) - verify(null, [`${STATEMENT_PREFIX}/update`, 'Callback: done'], ['update']) + verify(null, [`${STATEMENT_PREFIX}/insert`], ['insert'], { strict: false }) }) -}) -common.test('updateMany', function updateManyTest(t, collection, verify) { - collection.updateMany({ mod10: 5 }, { $set: { a: 5 } }, function done(err, data) { - t.error(err) + common.test('remove', async function removeTest(t, collection, verify) { + const data = await collection.remove({ mod10: 5 }) assertExpectedResult({ t, data, count: 3, - keyPrefix: 'modified', - legacyValues: { - nModified: 3 - }, - extraValues: { - matchedCount: 3, - upsertedCount: 0, - upsertedId: null - } + keyPrefix: 'deleted' }) - verify(null, [`${STATEMENT_PREFIX}/updateMany`, 'Callback: done'], ['updateMany']) + verify(null, [`${STATEMENT_PREFIX}/remove`], ['remove'], { strict: false }) }) -}) -common.test('updateOne', function updateOneTest(t, collection, verify) { - collection.updateOne({ i: 5 }, { $set: { a: 5 } }, function done(err, data) { - t.notOk(err, 'should not error') + common.test('update', async function updateTest(t, collection, verify) { + const data = await collection.update({ i: 5 }, { $set: { foo: 'bar' } }) assertExpectedResult({ t, data, count: 1, keyPrefix: 'modified', - legacyValues: { - nModified: 1 - }, extraValues: { matchedCount: 1, upsertedCount: 0, @@ -229,6 +176,6 @@ common.test('updateOne', function updateOneTest(t, collection, verify) { } }) - verify(null, [`${STATEMENT_PREFIX}/updateOne`, 'Callback: done'], ['updateOne']) + verify(null, [`${STATEMENT_PREFIX}/update`], ['update'], { strict: false }) }) -}) +} diff --git a/test/versioned/mongodb/common.js b/test/versioned/mongodb/common.js index a9b53035e2..8b9f61d97a 100644 --- a/test/versioned/mongodb/common.js +++ b/test/versioned/mongodb/common.js @@ -34,8 +34,14 @@ exports.connect = function connect() { return connectV4.apply(this, arguments) } +exports.close = function close() { + if (semver.satisfies(mongoPackage.version, '<4')) { + return closeLegacy.apply(this, arguments) + } + return closeAsync.apply(this, arguments) +} + exports.checkMetrics = checkMetrics -exports.close = close exports.getHostName = getHostName exports.getPort = getPort exports.getDomainSocketPath = getDomainSocketPath @@ -95,33 +101,26 @@ function connectV3(mongodb, host, replicaSet = false) { // This is same as connectV3 except it uses a different // set of params to connect to the mongodb_v4 container // it is actually just using the `mongodb:5` image -function connectV4(mongodb, host, replicaSet = false) { - return new Promise((resolve, reject) => { - if (host) { - host = encodeURIComponent(host) - } else { - host = params.mongodb_v4_host + ':' + params.mongodb_v4_port - } - - let connString = `mongodb://${host}` - let options = {} +async function connectV4(mongodb, host, replicaSet = false) { + if (host) { + host = encodeURIComponent(host) + } else { + host = params.mongodb_v4_host + ':' + params.mongodb_v4_port + } - if (replicaSet) { - connString = `mongodb://${host},${host},${host}` - options = { useNewUrlParser: true, useUnifiedTopology: true } - } - mongodb.MongoClient.connect(connString, options, function (err, client) { - if (err) { - reject(err) - } + let connString = `mongodb://${host}` + let options = {} - const db = client.db(DB_NAME) - resolve({ db, client }) - }) - }) + if (replicaSet) { + connString = `mongodb://${host},${host},${host}` + options = { useNewUrlParser: true, useUnifiedTopology: true } + } + const client = await mongodb.MongoClient.connect(connString, options) + const db = client.db(DB_NAME) + return { db, client } } -function close(client, db) { +function closeLegacy(client, db) { return new Promise((resolve) => { if (db && typeof db.close === 'function') { db.close(resolve) @@ -133,6 +132,14 @@ function close(client, db) { }) } +async function closeAsync(client, db) { + if (db && typeof db.close === 'function') { + await db.close() + } else if (client) { + await client.close(true) + } +} + function getHostName(agent) { const host = semver.satisfies(mongoPackage.version, '>=4.2.0') ? params.mongodb_v4_host diff --git a/test/versioned/mongodb/cursor.tap.js b/test/versioned/mongodb/cursor.tap.js index d7cbd58205..9e4e57692c 100644 --- a/test/versioned/mongodb/cursor.tap.js +++ b/test/versioned/mongodb/cursor.tap.js @@ -6,110 +6,28 @@ 'use strict' const common = require('./collection-common') -const concat = require('concat-stream') -const helper = require('../../lib/agent_helper') -const semver = require('semver') -const tap = require('tap') -const { pkgVersion, STATEMENT_PREFIX, COLLECTIONS } = require('./common') +const { STATEMENT_PREFIX } = require('./common') -common.test('count', function countTest(t, collection, verify) { - collection.find({}).count(function onCount(err, data) { - t.notOk(err, 'should not error') - t.equal(data, 30, 'should have correct result') - verify(null, [`${STATEMENT_PREFIX}/count`, 'Callback: onCount'], ['count']) - }) +common.test('count', async function countTest(t, collection, verify) { + const data = await collection.find({}).count() + t.equal(data, 30, 'should have correct result') + verify(null, [`${STATEMENT_PREFIX}/count`], ['count'], { strict: false }) }) -common.test('explain', function explainTest(t, collection, verify) { - collection.find({}).explain(function onExplain(err, data) { - t.error(err) - // Depending on the version of the mongo server the explain plan is different. - if (data.hasOwnProperty('cursor')) { - t.equal(data.cursor, 'BasicCursor', 'should have correct response') - } else { - t.ok(data.hasOwnProperty('queryPlanner'), 'should have correct response') - } - verify(null, [`${STATEMENT_PREFIX}/explain`, 'Callback: onExplain'], ['explain']) - }) +common.test('explain', async function explainTest(t, collection, verify) { + const data = await collection.find({}).explain() + t.ok(data.hasOwnProperty('queryPlanner'), 'should have correct response') + verify(null, [`${STATEMENT_PREFIX}/explain`], ['explain'], { strict: false }) }) -if (semver.satisfies(pkgVersion, '<3')) { - common.test('nextObject', function nextObjectTest(t, collection, verify) { - collection.find({}).nextObject(function onNextObject(err, data) { - t.notOk(err) - t.equal(data.i, 0) - verify(null, [`${STATEMENT_PREFIX}/nextObject`, 'Callback: onNextObject'], ['nextObject']) - }) - }) -} - -common.test('next', function nextTest(t, collection, verify) { - collection.find({}).next(function onNext(err, data) { - t.notOk(err) - t.equal(data.i, 0) - verify(null, [`${STATEMENT_PREFIX}/next`, 'Callback: onNext'], ['next']) - }) +common.test('next', async function nextTest(t, collection, verify) { + const data = await collection.find({}).next() + t.equal(data.i, 0) + verify(null, [`${STATEMENT_PREFIX}/next`], ['next'], { strict: false }) }) -common.test('toArray', function toArrayTest(t, collection, verify) { - collection.find({}).toArray(function onToArray(err, data) { - t.notOk(err) - t.equal(data[0].i, 0) - verify(null, [`${STATEMENT_PREFIX}/toArray`, 'Callback: onToArray'], ['toArray']) - }) +common.test('toArray', async function toArrayTest(t, collection, verify) { + const data = await collection.find({}).toArray() + t.equal(data[0].i, 0) + verify(null, [`${STATEMENT_PREFIX}/toArray`], ['toArray'], { strict: false }) }) - -if (semver.satisfies(pkgVersion, '<4')) { - tap.test('piping cursor stream hides internal calls', function (t) { - let agent = helper.instrumentMockedAgent() - let client = null - let db = null - let collection = null - - t.teardown(function () { - return common.close(client, db).then(() => { - helper.unloadAgent(agent) - agent = null - }) - }) - - const mongodb = require('mongodb') - common - .dropTestCollections(mongodb) - .then(() => { - return common.connect(mongodb) - }) - .then((res) => { - client = res.client - db = res.db - - collection = db.collection(COLLECTIONS.collection1) - return common.populate(db, collection) - }) - .then(runTest) - - function runTest() { - helper.runInTransaction(agent, function (transaction) { - transaction.name = common.TRANSACTION_NAME - const destination = concat(function () {}) - - destination.on('finish', function () { - transaction.end() - t.equal( - transaction.trace.root.children[0].name, - 'Datastore/operation/MongoDB/pipe', - 'should have pipe segment' - ) - t.equal( - 0, - transaction.trace.root.children[0].children.length, - 'pipe should not have any children' - ) - t.end() - }) - - collection.find({}).pipe(destination) - }) - } - }) -} diff --git a/test/versioned/mongodb/db-common.js b/test/versioned/mongodb/db-common.js new file mode 100644 index 0000000000..221692afdb --- /dev/null +++ b/test/versioned/mongodb/db-common.js @@ -0,0 +1,186 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const common = require('./common') +const semver = require('semver') +const collectionCommon = require('./collection-common') +const helper = require('../../lib/agent_helper') +const tap = require('tap') + +let MONGO_HOST = null +let MONGO_PORT = null +const BAD_MONGO_COMMANDS = ['collection'] +if (semver.satisfies(common.pkgVersion, '2.2.x')) { + BAD_MONGO_COMMANDS.push('authenticate', 'logout') +} + +function dbTest(name, run) { + mongoTest(name, function init(t, agent) { + const LOCALHOST = agent.config.getHostnameSafe() + const domainPath = common.getDomainSocketPath() + const mongodb = require('mongodb') + let db = null + let client = null + + t.autoend() + + t.test('remote connection', function (t) { + t.autoend() + t.beforeEach(async function () { + // mongo >= 3.6.9 fails if you try to create an existing collection + // drop before executing tests + if (name === 'createCollection') { + await collectionCommon.dropTestCollections(mongodb) + } + MONGO_HOST = common.getHostName(agent) + MONGO_PORT = common.getPort() + + const res = await common.connect(mongodb) + client = res.client + db = res.db + }) + + t.afterEach(async function () { + await common.close(client, db) + }) + + t.test('without transaction', function (t) { + run(t, db, function () { + t.notOk(agent.getTransaction(), 'should not have transaction') + t.end() + }) + }) + + t.test('with transaction', function (t) { + t.notOk(agent.getTransaction(), 'should not have transaction') + helper.runInTransaction(agent, function (transaction) { + run(t, db, function (names, opts = {}) { + verifyMongoSegments(t, agent, transaction, names, opts) + transaction.end() + t.end() + }) + }) + }) + }) + + // The domain socket tests should only be run if there is a domain socket + // to connect to, which only happens if there is a Mongo instance running on + // the same box as these tests. + const shouldTestDomain = domainPath + + t.test('domain socket', { skip: !shouldTestDomain }, function (t) { + t.autoend() + t.beforeEach(async function () { + // mongo >= 3.6.9 fails if you try to create an existing collection + // drop before executing tests + if (name === 'createCollection') { + await collectionCommon.dropTestCollections(mongodb) + } + MONGO_HOST = LOCALHOST + MONGO_PORT = domainPath + + const res = await common.connect(mongodb, domainPath) + client = res.client + db = res.db + }) + + t.afterEach(function () { + return common.close(client, db) + }) + + t.test('with transaction', function (t) { + t.notOk(agent.getTransaction(), 'should not have transaction') + helper.runInTransaction(agent, function (transaction) { + run(t, db, function (names, opts = {}) { + verifyMongoSegments(t, agent, transaction, names, opts) + transaction.end() + t.end() + }) + }) + }) + }) + }) +} + +function mongoTest(name, run) { + tap.test(name, function testWrap(t) { + const mongodb = require('mongodb') + collectionCommon.dropTestCollections(mongodb).then(() => { + run(t, helper.loadTestAgent(t)) + }) + }) +} + +function verifyMongoSegments(t, agent, transaction, names, opts) { + t.ok(agent.getTransaction(), 'should not lose transaction state') + t.equal(agent.getTransaction().id, transaction.id, 'transaction is correct') + + const segment = agent.tracer.getSegment() + let current = transaction.trace.root + let child + + for (let i = 0, l = names.length; i < l; ++i) { + if (opts.legacy) { + // Filter out net.createConnection segments as they could occur during execution, which is fine + // but breaks out assertion function + current.children = current.children.filter((c) => c.name !== 'net.createConnection') + t.equal(current.children.length, 1, 'should have one child segment') + child = current.children[0] + current = current.children[0] + } else { + child = current.children[i] + } + t.equal(child.name, names[i], 'segment should be named ' + names[i]) + + // If this is a Mongo operation/statement segment then it should have the + // datastore instance attributes. + if (/^Datastore\/.*?\/MongoDB/.test(child.name)) { + if (isBadSegment(child)) { + t.comment('Skipping attributes check for ' + child.name) + continue + } + + // Commands known as "admin commands" always happen against the "admin" + // database regardless of the DB the connection is actually connected to. + // This is apparently by design. + // https://jira.mongodb.org/browse/NODE-827 + let dbName = common.DB_NAME + if (/\/renameCollection$/.test(child.name)) { + dbName = 'admin' + } + + const attributes = child.getAttributes() + t.equal(attributes.database_name, dbName, 'should have correct db name') + t.equal(attributes.host, MONGO_HOST, 'should have correct host name') + t.equal(attributes.port_path_or_id, MONGO_PORT, 'should have correct port') + t.equal(attributes.product, 'MongoDB', 'should have correct product attribute') + } + } + + if (opts.legacy) { + // Do not use `t.equal` for this comparison. When it is false tap would dump + // way too much information to be useful. + t.ok(current === segment, 'current segment is ' + segment.name) + } +} + +function isBadSegment(segment) { + const nameParts = segment.name.split('/') + const command = nameParts[nameParts.length - 1] + const attributes = segment.getAttributes() + + return ( + BAD_MONGO_COMMANDS.indexOf(command) !== -1 && // Is in the list of bad commands + !attributes.database_name && // and does not have any of the + !attributes.host && // instance attributes. + !attributes.port_path_or_id + ) +} + +module.exports = { + dbTest, + mongoTest +} diff --git a/test/versioned/mongodb/db.tap.js b/test/versioned/mongodb/db.tap.js index 38b890d6df..0fba9be66c 100644 --- a/test/versioned/mongodb/db.tap.js +++ b/test/versioned/mongodb/db.tap.js @@ -4,419 +4,99 @@ */ 'use strict' +const { COLLECTIONS } = require('./common') +const { dbTest } = require('./db-common') -const common = require('./common') -const collectionCommon = require('./collection-common') -const helper = require('../../lib/agent_helper') -const params = require('../../lib/params') -const semver = require('semver') -const tap = require('tap') - -let MONGO_HOST = null -let MONGO_PORT = null -const BAD_MONGO_COMMANDS = ['collection'] -const { pkgVersion, COLLECTIONS } = require('./common') - -if (semver.satisfies(pkgVersion, '<3')) { - mongoTest('open', function openTest(t, agent) { - const mongodb = require('mongodb') - const server = new mongodb.Server(params.mongodb_host, params.mongodb_port) - const db = new mongodb.Db(common.DB_NAME, server) - - if (semver.satisfies(pkgVersion, '2.2.x')) { - BAD_MONGO_COMMANDS.push('authenticate', 'logout') - } - - helper.runInTransaction(agent, function inTransaction(transaction) { - db.open(function onOpen(err, _db) { - const segment = agent.tracer.getSegment() - t.error(err, 'db.open should not error') - t.equal(db, _db, 'should pass through the arguments correctly') - t.equal(agent.getTransaction(), transaction, 'should not lose tx state') - t.equal(segment.name, 'Callback: onOpen', 'should create segments') - t.equal(transaction.trace.root.children.length, 1, 'should only create one') - const parent = transaction.trace.root.children[0] - t.equal(parent.name, 'Datastore/operation/MongoDB/open', 'should name segment correctly') - t.not(parent.children.indexOf(segment), -1, 'should have callback as child') - db.close() - t.end() - }) - }) - }) - - dbTest('logout', function logoutTest(t, db, verify) { - db.logout({}, function loggedOut(err) { - t.error(err, 'should not have error') - verify(['Datastore/operation/MongoDB/logout', 'Callback: loggedOut']) - }) - }) -} - -dbTest('addUser, authenticate, removeUser', function addUserTest(t, db, verify) { +dbTest('addUser, removeUser', async function addUserTest(t, db, verify) { const userName = 'user-test' const userPass = 'user-test-pass' - db.removeUser(userName, function preRemove() { + try { + await db.removeUser(userName) + } catch { // Don't care if this first remove fails, it's just to ensure a clean slate. - db.addUser(userName, userPass, { roles: ['readWrite'] }, added) - }) - - function added(err) { - if (!t.error(err, 'addUser should not have error')) { - return t.end() - } - - if (typeof db.authenticate === 'function') { - db.authenticate(userName, userPass, authed) - } else { - t.comment('Skipping authentication test, not supported on db') - db.removeUser(userName, removedNoAuth) - } - } - - function authed(err) { - if (!t.error(err, 'authenticate should not have error')) { - return t.end() - } - db.removeUser(userName, removed) - } - - function removed(err) { - if (!t.error(err, 'removeUser should not have error')) { - return t.end() - } - verify([ - 'Datastore/operation/MongoDB/removeUser', - 'Callback: preRemove', - 'Datastore/operation/MongoDB/addUser', - 'Callback: added', - 'Datastore/operation/MongoDB/authenticate', - 'Callback: authed', - 'Datastore/operation/MongoDB/removeUser', - 'Callback: removed' - ]) - } - - function removedNoAuth(err) { - if (!t.error(err, 'removeUser should not have error')) { - return t.end() - } - verify([ - 'Datastore/operation/MongoDB/removeUser', - 'Callback: preRemove', - 'Datastore/operation/MongoDB/addUser', - 'Callback: added', - 'Datastore/operation/MongoDB/removeUser', - 'Callback: removedNoAuth' - ]) } + await db.command({ + createUser: userName, + pwd: userPass, + roles: ['readWrite'] + }) + + await db.removeUser(userName) + verify([ + 'Datastore/operation/MongoDB/removeUser', + 'Datastore/operation/MongoDB/command', + 'Datastore/operation/MongoDB/removeUser' + ]) }) -// removed in v4 https://github.com/mongodb/node-mongodb-native/pull/2817 -if (semver.satisfies(pkgVersion, '<4')) { - dbTest('collection', function collectionTest(t, db, verify) { - db.collection(COLLECTIONS.collection1, function gotCollection(err, collection) { - t.error(err, 'should not have error') - t.ok(collection, 'collection is not null') - verify(['Datastore/operation/MongoDB/collection', 'Callback: gotCollection']) - }) - }) - - dbTest('eval', function evalTest(t, db, verify) { - db.eval('function (x) {return x;}', [3], function evaled(err, result) { - t.error(err, 'should not have error') - t.equal(3, result, 'should produce the right result') - verify(['Datastore/operation/MongoDB/eval', 'Callback: evaled']) - }) - }) -} - -dbTest('collections', function collectionTest(t, db, verify) { - db.collections(function gotCollections(err2, collections) { - t.error(err2, 'should not have error') - t.ok(Array.isArray(collections), 'got array of collections') - verify(['Datastore/operation/MongoDB/collections', 'Callback: gotCollections']) - }) +dbTest('collections', async function collectionTest(t, db, verify) { + const collections = await db.collections() + t.ok(Array.isArray(collections), 'got array of collections') + verify(['Datastore/operation/MongoDB/collections']) }) -dbTest('command', function commandTest(t, db, verify) { - db.command({ ping: 1 }, function onCommand(err, result) { - t.error(err, 'should not have error') - t.same(result, { ok: 1 }, 'got correct result') - verify(['Datastore/operation/MongoDB/command', 'Callback: onCommand']) - }) +dbTest('command', async function commandTest(t, db, verify) { + const result = await db.command({ ping: 1 }) + t.same(result, { ok: 1 }, 'got correct result') + verify(['Datastore/operation/MongoDB/command']) }) -dbTest('createCollection', function createTest(t, db, verify) { - db.createCollection(COLLECTIONS.collection1, function gotCollection(err, collection) { - t.error(err, 'should not have error') - t.equal( - collection.collectionName || collection.s.name, - COLLECTIONS.collection1, - 'new collection should have the right name' - ) - verify(['Datastore/operation/MongoDB/createCollection', 'Callback: gotCollection']) - }) +dbTest('createCollection', async function createTest(t, db, verify) { + const collection = await db.createCollection(COLLECTIONS.collection1) + t.equal( + collection.collectionName || collection.s.name, + COLLECTIONS.collection1, + 'new collection should have the right name' + ) + verify(['Datastore/operation/MongoDB/createCollection']) }) -dbTest('createIndex', function createIndexTest(t, db, verify) { - db.createIndex(COLLECTIONS.collection1, 'foo', function createdIndex(err, result) { - t.error(err, 'should not have error') - t.equal(result, 'foo_1', 'should have the right result') - verify(['Datastore/operation/MongoDB/createIndex', 'Callback: createdIndex']) - }) +dbTest('createIndex', async function createIndexTest(t, db, verify) { + const result = await db.createIndex(COLLECTIONS.collection1, 'foo') + t.equal(result, 'foo_1', 'should have the right result') + verify(['Datastore/operation/MongoDB/createIndex']) }) -dbTest('dropCollection', function dropTest(t, db, verify) { - db.createCollection(COLLECTIONS.collection1, function gotCollection(err) { - t.error(err, 'should not have error getting collection') - - db.dropCollection(COLLECTIONS.collection1, function droppedCollection(err, result) { - t.error(err, 'should not have error dropping collection') - t.ok(result === true, 'result should be boolean true') - verify([ - 'Datastore/operation/MongoDB/createCollection', - 'Callback: gotCollection', - 'Datastore/operation/MongoDB/dropCollection', - 'Callback: droppedCollection' - ]) - }) - }) +dbTest('dropCollection', async function dropTest(t, db, verify) { + await db.createCollection(COLLECTIONS.collection1) + const result = await db.dropCollection(COLLECTIONS.collection1) + t.ok(result === true, 'result should be boolean true') + verify([ + 'Datastore/operation/MongoDB/createCollection', + 'Datastore/operation/MongoDB/dropCollection' + ]) }) -dbTest('dropDatabase', function dropDbTest(t, db, verify) { - db.dropDatabase(function droppedDatabase(err, result) { - t.error(err, 'should not have error') - t.ok(result, 'result should be truthy') - verify(['Datastore/operation/MongoDB/dropDatabase', 'Callback: droppedDatabase']) - }) +dbTest('dropDatabase', async function dropDbTest(t, db, verify) { + const result = await db.dropDatabase() + t.ok(result, 'result should be truthy') + verify(['Datastore/operation/MongoDB/dropDatabase']) }) -if (semver.satisfies(pkgVersion, '<4')) { - dbTest('ensureIndex', function ensureIndexTest(t, db, verify) { - db.ensureIndex(COLLECTIONS.collection1, 'foo', function ensuredIndex(err, result) { - t.error(err, 'should not have error') - t.equal(result, 'foo_1') - verify(['Datastore/operation/MongoDB/ensureIndex', 'Callback: ensuredIndex']) - }) - }) - - dbTest('indexInformation', function indexInfoTest(t, db, verify) { - db.ensureIndex(COLLECTIONS.collection1, 'foo', function ensuredIndex(err) { - t.error(err, 'ensureIndex should not have error') - db.indexInformation(COLLECTIONS.collection1, function gotInfo(err2, result) { - t.error(err2, 'indexInformation should not have error') - t.same(result, { _id_: [['_id', 1]], foo_1: [['foo', 1]] }, 'result is the expected object') - verify([ - 'Datastore/operation/MongoDB/ensureIndex', - 'Callback: ensuredIndex', - 'Datastore/operation/MongoDB/indexInformation', - 'Callback: gotInfo' - ]) - }) - }) - }) -} else { - dbTest('indexInformation', function indexInfoTest(t, db, verify) { - db.createIndex(COLLECTIONS.collection1, 'foo', function createdIndex(err) { - t.error(err, 'createIndex should not have error') - db.indexInformation(COLLECTIONS.collection1, function gotInfo(err2, result) { - t.error(err2, 'indexInformation should not have error') - t.same(result, { _id_: [['_id', 1]], foo_1: [['foo', 1]] }, 'result is the expected object') - verify([ - 'Datastore/operation/MongoDB/createIndex', - 'Callback: createdIndex', - 'Datastore/operation/MongoDB/indexInformation', - 'Callback: gotInfo' - ]) - }) - }) - }) -} - -dbTest('renameCollection', function (t, db, verify) { - db.createCollection(COLLECTIONS.collection1, function gotCollection(err) { - t.error(err, 'should not have error getting collection') - db.renameCollection( - COLLECTIONS.collection1, - COLLECTIONS.collection2, - function renamedCollection(err2) { - t.error(err2, 'should not have error renaming collection') - db.dropCollection(COLLECTIONS.collection2, function droppedCollection(err3) { - t.error(err3) - verify([ - 'Datastore/operation/MongoDB/createCollection', - 'Callback: gotCollection', - 'Datastore/operation/MongoDB/renameCollection', - 'Callback: renamedCollection', - 'Datastore/operation/MongoDB/dropCollection', - 'Callback: droppedCollection' - ]) - }) - } - ) - }) +dbTest('indexInformation', async function indexInfoTest(t, db, verify) { + await db.createIndex(COLLECTIONS.collection1, 'foo') + const result = await db.indexInformation(COLLECTIONS.collection1) + t.same(result, { _id_: [['_id', 1]], foo_1: [['foo', 1]] }, 'result is the expected object') + verify([ + 'Datastore/operation/MongoDB/createIndex', + 'Datastore/operation/MongoDB/indexInformation' + ]) }) -dbTest('stats', function statsTest(t, db, verify) { - db.stats({}, function gotStats(err, stats) { - t.error(err, 'should not have error') - t.ok(stats, 'got stats') - verify(['Datastore/operation/MongoDB/stats', 'Callback: gotStats']) - }) +dbTest('renameCollection', async function (t, db, verify) { + await db.createCollection(COLLECTIONS.collection1) + await db.renameCollection(COLLECTIONS.collection1, COLLECTIONS.collection2) + await db.dropCollection(COLLECTIONS.collection2) + verify([ + 'Datastore/operation/MongoDB/createCollection', + 'Datastore/operation/MongoDB/renameCollection', + 'Datastore/operation/MongoDB/dropCollection' + ]) }) -function dbTest(name, run) { - mongoTest(name, function init(t, agent) { - const LOCALHOST = agent.config.getHostnameSafe() - const domainPath = common.getDomainSocketPath() - const mongodb = require('mongodb') - let db = null - let client = null - - t.autoend() - - t.test('remote connection', function (t) { - t.autoend() - t.beforeEach(async function () { - // mongo >= 3.6.9 fails if you try to create an existing collection - // drop before executing tests - if (name === 'createCollection') { - await collectionCommon.dropTestCollections(mongodb) - } - MONGO_HOST = common.getHostName(agent) - MONGO_PORT = common.getPort() - - const res = await common.connect(mongodb) - client = res.client - db = res.db - }) - - t.afterEach(function () { - return common.close(client, db) - }) - - t.test('without transaction', function (t) { - run(t, db, function () { - t.notOk(agent.getTransaction(), 'should not have transaction') - t.end() - }) - }) - - t.test('with transaction', function (t) { - t.notOk(agent.getTransaction(), 'should not have transaction') - helper.runInTransaction(agent, function (transaction) { - run(t, db, function (names) { - verifyMongoSegments(t, agent, transaction, names) - transaction.end() - t.end() - }) - }) - }) - }) - - // The domain socket tests should only be run if there is a domain socket - // to connect to, which only happens if there is a Mongo instance running on - // the same box as these tests. - const shouldTestDomain = domainPath - - t.test('domain socket', { skip: !shouldTestDomain }, function (t) { - t.autoend() - t.beforeEach(async function () { - // mongo >= 3.6.9 fails if you try to create an existing collection - // drop before executing tests - if (name === 'createCollection') { - await collectionCommon.dropTestCollections(mongodb) - } - MONGO_HOST = LOCALHOST - MONGO_PORT = domainPath - - const res = await common.connect(mongodb, domainPath) - client = res.client - db = res.db - }) - - t.afterEach(function () { - return common.close(client, db) - }) - - t.test('with transaction', function (t) { - t.notOk(agent.getTransaction(), 'should not have transaction') - helper.runInTransaction(agent, function (transaction) { - run(t, db, function (names) { - verifyMongoSegments(t, agent, transaction, names) - transaction.end() - t.end() - }) - }) - }) - }) - }) -} - -function mongoTest(name, run) { - tap.test(name, function testWrap(t) { - const mongodb = require('mongodb') - collectionCommon.dropTestCollections(mongodb).then(() => { - run(t, helper.loadTestAgent(t)) - }) - }) -} - -function verifyMongoSegments(t, agent, transaction, names) { - t.ok(agent.getTransaction(), 'should not lose transaction state') - t.equal(agent.getTransaction().id, transaction.id, 'transaction is correct') - - const segment = agent.tracer.getSegment() - let current = transaction.trace.root - - for (let i = 0, l = names.length; i < l; ++i) { - // Filter out net.createConnection segments as they could occur during execution, which is fine - // but breaks out assertion function - current.children = current.children.filter((child) => child.name !== 'net.createConnection') - t.equal(current.children.length, 1, 'should have one child segment') - current = current.children[0] - t.equal(current.name, names[i], 'segment should be named ' + names[i]) - - // If this is a Mongo operation/statement segment then it should have the - // datastore instance attributes. - if (/^Datastore\/.*?\/MongoDB/.test(current.name)) { - if (isBadSegment(current)) { - t.comment('Skipping attributes check for ' + current.name) - continue - } - - // Commands known as "admin commands" always happen against the "admin" - // database regardless of the DB the connection is actually connected to. - // This is apparently by design. - // https://jira.mongodb.org/browse/NODE-827 - let dbName = common.DB_NAME - if (/\/renameCollection$/.test(current.name)) { - dbName = 'admin' - } - - const attributes = current.getAttributes() - t.equal(attributes.database_name, dbName, 'should have correct db name') - t.equal(attributes.host, MONGO_HOST, 'should have correct host name') - t.equal(attributes.port_path_or_id, MONGO_PORT, 'should have correct port') - t.equal(attributes.product, 'MongoDB', 'should have correct product attribute') - } - } - - // Do not use `t.equal` for this comparison. When it is false tap would dump - // way too much information to be useful. - t.ok(current === segment, 'current segment is ' + segment.name) -} - -function isBadSegment(segment) { - const nameParts = segment.name.split('/') - const command = nameParts[nameParts.length - 1] - const attributes = segment.getAttributes() - - return ( - BAD_MONGO_COMMANDS.indexOf(command) !== -1 && // Is in the list of bad commands - !attributes.database_name && // and does not have any of the - !attributes.host && // instance attributes. - !attributes.port_path_or_id - ) -} +dbTest('stats', async function statsTest(t, db, verify) { + const stats = await db.stats({}) + t.ok(stats, 'got stats') + verify(['Datastore/operation/MongoDB/stats']) +}) diff --git a/test/versioned/mongodb/bulk.tap.js b/test/versioned/mongodb/legacy/bulk.tap.js similarity index 88% rename from test/versioned/mongodb/bulk.tap.js rename to test/versioned/mongodb/legacy/bulk.tap.js index 61241ce03e..662e9b54ea 100644 --- a/test/versioned/mongodb/bulk.tap.js +++ b/test/versioned/mongodb/legacy/bulk.tap.js @@ -5,12 +5,12 @@ 'use strict' -const common = require('./collection-common') +const common = require('../collection-common') const semver = require('semver') -const { pkgVersion, STATEMENT_PREFIX } = require('./common') +const { pkgVersion, STATEMENT_PREFIX } = require('../common') // see test/versioned/mongodb/common.js -if (semver.satisfies(pkgVersion, '>=3.2.4 <4.1.4')) { +if (semver.satisfies(pkgVersion, '>=3.2.4')) { common.test('unorderedBulkOp', function unorderedBulkOpTest(t, collection, verify) { const bulk = collection.initializeUnorderedBulkOp() bulk diff --git a/test/versioned/mongodb/legacy/cursor.tap.js b/test/versioned/mongodb/legacy/cursor.tap.js new file mode 100644 index 0000000000..70623aed14 --- /dev/null +++ b/test/versioned/mongodb/legacy/cursor.tap.js @@ -0,0 +1,112 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const common = require('../collection-common') +const concat = require('concat-stream') +const helper = require('../../../lib/agent_helper') +const semver = require('semver') +const tap = require('tap') +const { pkgVersion, STATEMENT_PREFIX, COLLECTIONS } = require('../common') + +common.test('count', function countTest(t, collection, verify) { + collection.find({}).count(function onCount(err, data) { + t.notOk(err, 'should not error') + t.equal(data, 30, 'should have correct result') + verify(null, [`${STATEMENT_PREFIX}/count`, 'Callback: onCount'], ['count']) + }) +}) + +common.test('explain', function explainTest(t, collection, verify) { + collection.find({}).explain(function onExplain(err, data) { + t.error(err) + // Depending on the version of the mongo server the explain plan is different. + if (data.hasOwnProperty('cursor')) { + t.equal(data.cursor, 'BasicCursor', 'should have correct response') + } else { + t.ok(data.hasOwnProperty('queryPlanner'), 'should have correct response') + } + verify(null, [`${STATEMENT_PREFIX}/explain`, 'Callback: onExplain'], ['explain']) + }) +}) + +if (semver.satisfies(pkgVersion, '<3')) { + common.test('nextObject', function nextObjectTest(t, collection, verify) { + collection.find({}).nextObject(function onNextObject(err, data) { + t.notOk(err) + t.equal(data.i, 0) + verify(null, [`${STATEMENT_PREFIX}/nextObject`, 'Callback: onNextObject'], ['nextObject']) + }) + }) +} + +common.test('next', function nextTest(t, collection, verify) { + collection.find({}).next(function onNext(err, data) { + t.notOk(err) + t.equal(data.i, 0) + verify(null, [`${STATEMENT_PREFIX}/next`, 'Callback: onNext'], ['next']) + }) +}) + +common.test('toArray', function toArrayTest(t, collection, verify) { + collection.find({}).toArray(function onToArray(err, data) { + t.notOk(err) + t.equal(data[0].i, 0) + verify(null, [`${STATEMENT_PREFIX}/toArray`, 'Callback: onToArray'], ['toArray']) + }) +}) + +tap.test('piping cursor stream hides internal calls', function (t) { + let agent = helper.instrumentMockedAgent() + let client = null + let db = null + let collection = null + + t.teardown(async function () { + await common.close(client, db) + helper.unloadAgent(agent) + agent = null + }) + + const mongodb = require('mongodb') + common + .dropTestCollections(mongodb) + .then(() => { + return common.connect(mongodb) + }) + .then((res) => { + client = res.client + db = res.db + + collection = db.collection(COLLECTIONS.collection1) + return common.populate(collection) + }) + .then(runTest) + + function runTest() { + helper.runInTransaction(agent, function (transaction) { + transaction.name = common.TRANSACTION_NAME + const destination = concat(function () {}) + + destination.on('finish', function () { + transaction.end() + t.equal( + transaction.trace.root.children[0].name, + 'Datastore/operation/MongoDB/pipe', + 'should have pipe segment' + ) + t.equal( + 0, + transaction.trace.root.children[0].children.length, + 'pipe should not have any children' + ) + t.end() + }) + + collection.find({}).pipe(destination) + }) + } +}) diff --git a/test/versioned/mongodb/legacy/db.tap.js b/test/versioned/mongodb/legacy/db.tap.js new file mode 100644 index 0000000000..e05ded4acc --- /dev/null +++ b/test/versioned/mongodb/legacy/db.tap.js @@ -0,0 +1,256 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const semver = require('semver') +const { dbTest, mongoTest } = require('../db-common') +const params = require('../../../lib/params') +const helper = require('../../../lib/agent_helper') +const { pkgVersion, COLLECTIONS, DB_NAME } = require('../common') + +if (semver.satisfies(pkgVersion, '<3')) { + mongoTest('open', function openTest(t, agent) { + const mongodb = require('mongodb') + const server = new mongodb.Server(params.mongodb_host, params.mongodb_port) + const db = new mongodb.Db(DB_NAME, server) + + helper.runInTransaction(agent, function inTransaction(transaction) { + db.open(function onOpen(err, _db) { + const segment = agent.tracer.getSegment() + t.error(err, 'db.open should not error') + t.equal(db, _db, 'should pass through the arguments correctly') + t.equal(agent.getTransaction(), transaction, 'should not lose tx state') + t.equal(segment.name, 'Callback: onOpen', 'should create segments') + t.equal(transaction.trace.root.children.length, 1, 'should only create one') + const parent = transaction.trace.root.children[0] + t.equal(parent.name, 'Datastore/operation/MongoDB/open', 'should name segment correctly') + t.not(parent.children.indexOf(segment), -1, 'should have callback as child') + db.close() + t.end() + }) + }) + }) + + dbTest('logout', function logoutTest(t, db, verify) { + db.logout({}, function loggedOut(err) { + t.error(err, 'should not have error') + verify(['Datastore/operation/MongoDB/logout', 'Callback: loggedOut'], { legacy: true }) + }) + }) +} + +dbTest('addUser, authenticate, removeUser', function addUserTest(t, db, verify) { + const userName = 'user-test' + const userPass = 'user-test-pass' + + db.removeUser(userName, function preRemove() { + // Don't care if this first remove fails, it's just to ensure a clean slate. + db.addUser(userName, userPass, { roles: ['readWrite'] }, added) + }) + + function added(err) { + if (!t.error(err, 'addUser should not have error')) { + return t.end() + } + + if (typeof db.authenticate === 'function') { + db.authenticate(userName, userPass, authed) + } else { + t.comment('Skipping authentication test, not supported on db') + db.removeUser(userName, removedNoAuth) + } + } + + function authed(err) { + if (!t.error(err, 'authenticate should not have error')) { + return t.end() + } + db.removeUser(userName, removed) + } + + function removed(err) { + if (!t.error(err, 'removeUser should not have error')) { + return t.end() + } + verify( + [ + 'Datastore/operation/MongoDB/removeUser', + 'Callback: preRemove', + 'Datastore/operation/MongoDB/addUser', + 'Callback: added', + 'Datastore/operation/MongoDB/authenticate', + 'Callback: authed', + 'Datastore/operation/MongoDB/removeUser', + 'Callback: removed' + ], + { legacy: true } + ) + } + + function removedNoAuth(err) { + if (!t.error(err, 'removeUser should not have error')) { + return t.end() + } + verify( + [ + 'Datastore/operation/MongoDB/removeUser', + 'Callback: preRemove', + 'Datastore/operation/MongoDB/addUser', + 'Callback: added', + 'Datastore/operation/MongoDB/removeUser', + 'Callback: removedNoAuth' + ], + { legacy: true } + ) + } +}) + +dbTest('collection', function collectionTest(t, db, verify) { + db.collection(COLLECTIONS.collection1, function gotCollection(err, collection) { + t.error(err, 'should not have error') + t.ok(collection, 'collection is not null') + verify(['Datastore/operation/MongoDB/collection', 'Callback: gotCollection'], { legacy: true }) + }) +}) + +dbTest('eval', function evalTest(t, db, verify) { + db.eval('function (x) {return x;}', [3], function evaled(err, result) { + t.error(err, 'should not have error') + t.equal(3, result, 'should produce the right result') + verify(['Datastore/operation/MongoDB/eval', 'Callback: evaled'], { legacy: true }) + }) +}) + +dbTest('collections', function collectionTest(t, db, verify) { + db.collections(function gotCollections(err2, collections) { + t.error(err2, 'should not have error') + t.ok(Array.isArray(collections), 'got array of collections') + verify(['Datastore/operation/MongoDB/collections', 'Callback: gotCollections'], { + legacy: true + }) + }) +}) + +dbTest('command', function commandTest(t, db, verify) { + db.command({ ping: 1 }, function onCommand(err, result) { + t.error(err, 'should not have error') + t.same(result, { ok: 1 }, 'got correct result') + verify(['Datastore/operation/MongoDB/command', 'Callback: onCommand'], { legacy: true }) + }) +}) + +dbTest('createCollection', function createTest(t, db, verify) { + db.createCollection(COLLECTIONS.collection1, function gotCollection(err, collection) { + t.error(err, 'should not have error') + t.equal( + collection.collectionName || collection.s.name, + COLLECTIONS.collection1, + 'new collection should have the right name' + ) + verify(['Datastore/operation/MongoDB/createCollection', 'Callback: gotCollection'], { + legacy: true + }) + }) +}) + +dbTest('createIndex', function createIndexTest(t, db, verify) { + db.createIndex(COLLECTIONS.collection1, 'foo', function createdIndex(err, result) { + t.error(err, 'should not have error') + t.equal(result, 'foo_1', 'should have the right result') + verify(['Datastore/operation/MongoDB/createIndex', 'Callback: createdIndex'], { legacy: true }) + }) +}) + +dbTest('dropCollection', function dropTest(t, db, verify) { + db.createCollection(COLLECTIONS.collection1, function gotCollection(err) { + t.error(err, 'should not have error getting collection') + + db.dropCollection(COLLECTIONS.collection1, function droppedCollection(err, result) { + t.error(err, 'should not have error dropping collection') + t.ok(result === true, 'result should be boolean true') + verify( + [ + 'Datastore/operation/MongoDB/createCollection', + 'Callback: gotCollection', + 'Datastore/operation/MongoDB/dropCollection', + 'Callback: droppedCollection' + ], + { legacy: true } + ) + }) + }) +}) + +dbTest('dropDatabase', function dropDbTest(t, db, verify) { + db.dropDatabase(function droppedDatabase(err, result) { + t.error(err, 'should not have error') + t.ok(result, 'result should be truthy') + verify(['Datastore/operation/MongoDB/dropDatabase', 'Callback: droppedDatabase'], { + legacy: true + }) + }) +}) + +dbTest('ensureIndex', function ensureIndexTest(t, db, verify) { + db.ensureIndex(COLLECTIONS.collection1, 'foo', function ensuredIndex(err, result) { + t.error(err, 'should not have error') + t.equal(result, 'foo_1') + verify(['Datastore/operation/MongoDB/ensureIndex', 'Callback: ensuredIndex'], { legacy: true }) + }) +}) + +dbTest('indexInformation', function indexInfoTest(t, db, verify) { + db.ensureIndex(COLLECTIONS.collection1, 'foo', function ensuredIndex(err) { + t.error(err, 'ensureIndex should not have error') + db.indexInformation(COLLECTIONS.collection1, function gotInfo(err2, result) { + t.error(err2, 'indexInformation should not have error') + t.same(result, { _id_: [['_id', 1]], foo_1: [['foo', 1]] }, 'result is the expected object') + verify( + [ + 'Datastore/operation/MongoDB/ensureIndex', + 'Callback: ensuredIndex', + 'Datastore/operation/MongoDB/indexInformation', + 'Callback: gotInfo' + ], + { legacy: true } + ) + }) + }) +}) + +dbTest('renameCollection', function (t, db, verify) { + db.createCollection(COLLECTIONS.collection1, function gotCollection(err) { + t.error(err, 'should not have error getting collection') + db.renameCollection( + COLLECTIONS.collection1, + COLLECTIONS.collection2, + function renamedCollection(err2) { + t.error(err2, 'should not have error renaming collection') + db.dropCollection(COLLECTIONS.collection2, function droppedCollection(err3) { + t.error(err3) + verify( + [ + 'Datastore/operation/MongoDB/createCollection', + 'Callback: gotCollection', + 'Datastore/operation/MongoDB/renameCollection', + 'Callback: renamedCollection', + 'Datastore/operation/MongoDB/dropCollection', + 'Callback: droppedCollection' + ], + { legacy: true } + ) + }) + } + ) + }) +}) + +dbTest('stats', function statsTest(t, db, verify) { + db.stats({}, function gotStats(err, stats) { + t.error(err, 'should not have error') + t.ok(stats, 'got stats') + verify(['Datastore/operation/MongoDB/stats', 'Callback: gotStats'], { legacy: true }) + }) +}) diff --git a/test/versioned/mongodb/legacy/find.tap.js b/test/versioned/mongodb/legacy/find.tap.js new file mode 100644 index 0000000000..fc51d1b81c --- /dev/null +++ b/test/versioned/mongodb/legacy/find.tap.js @@ -0,0 +1,70 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const common = require('../collection-common') +const { STATEMENT_PREFIX } = require('../common') + +const findOpt = { returnOriginal: false } + +common.test('findAndModify', function findAndModifyTest(t, collection, verify) { + collection.findAndModify({ i: 1 }, [['i', 1]], { $set: { a: 15 } }, { new: true }, done) + + function done(err, data) { + t.error(err) + t.equal(data.value.a, 15) + t.equal(data.value.i, 1) + t.equal(data.ok, 1) + verify(null, [`${STATEMENT_PREFIX}/findAndModify`, 'Callback: done'], ['findAndModify']) + } +}) + +common.test('findAndRemove', function findAndRemoveTest(t, collection, verify) { + collection.findAndRemove({ i: 1 }, [['i', 1]], function done(err, data) { + t.error(err) + t.equal(data.value.i, 1) + t.equal(data.ok, 1) + verify(null, [`${STATEMENT_PREFIX}/findAndRemove`, 'Callback: done'], ['findAndRemove']) + }) +}) + +common.test('findOne', function findOneTest(t, collection, verify) { + collection.findOne({ i: 15 }, function done(err, data) { + t.error(err) + t.equal(data.i, 15) + verify(null, [`${STATEMENT_PREFIX}/findOne`, 'Callback: done'], ['findOne']) + }) +}) + +common.test('findOneAndDelete', function findOneAndDeleteTest(t, collection, verify) { + collection.findOneAndDelete({ i: 15 }, function done(err, data) { + t.error(err) + t.equal(data.ok, 1) + t.equal(data.value.i, 15) + verify(null, [`${STATEMENT_PREFIX}/findOneAndDelete`, 'Callback: done'], ['findOneAndDelete']) + }) +}) + +common.test('findOneAndReplace', function findAndReplaceTest(t, collection, verify) { + collection.findOneAndReplace({ i: 15 }, { b: 15 }, findOpt, done) + + function done(err, data) { + t.error(err) + t.equal(data.value.b, 15) + t.equal(data.ok, 1) + verify(null, [`${STATEMENT_PREFIX}/findOneAndReplace`, 'Callback: done'], ['findOneAndReplace']) + } +}) + +common.test('findOneAndUpdate', function findOneAndUpdateTest(t, collection, verify) { + collection.findOneAndUpdate({ i: 15 }, { $set: { a: 15 } }, findOpt, done) + + function done(err, data) { + t.error(err) + t.equal(data.value.a, 15) + t.equal(data.ok, 1) + verify(null, [`${STATEMENT_PREFIX}/findOneAndUpdate`, 'Callback: done'], ['findOneAndUpdate']) + } +}) diff --git a/test/versioned/mongodb/legacy/index.tap.js b/test/versioned/mongodb/legacy/index.tap.js new file mode 100644 index 0000000000..60f168b78d --- /dev/null +++ b/test/versioned/mongodb/legacy/index.tap.js @@ -0,0 +1,97 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const common = require('../collection-common') +const { STATEMENT_PREFIX, DB_NAME, COLLECTIONS } = require('../common') + +common.test('createIndex', function createIndexTest(t, collection, verify) { + collection.createIndex('i', function onIndex(err, data) { + t.error(err) + t.equal(data, 'i_1') + verify(null, [`${STATEMENT_PREFIX}/createIndex`, 'Callback: onIndex'], ['createIndex']) + }) +}) + +common.test('dropIndex', function dropIndexTest(t, collection, verify) { + collection.createIndex('i', function onIndex(err) { + t.error(err) + collection.dropIndex('i_1', function done(err, data) { + t.error(err) + t.equal(data.ok, 1) + verify( + null, + [ + `${STATEMENT_PREFIX}/createIndex`, + 'Callback: onIndex', + `${STATEMENT_PREFIX}/dropIndex`, + 'Callback: done' + ], + ['createIndex', 'dropIndex'] + ) + }) + }) +}) + +common.test('indexes', function indexesTest(t, collection, verify) { + collection.indexes(function done(err, data) { + t.error(err) + const result = data && data[0] + const expectedResult = { + v: result && result.v, + key: { _id: 1 }, + name: '_id_', + ns: `${DB_NAME}.${COLLECTIONS.collection1}` + } + + t.same(result, expectedResult, 'should have expected results') + + verify(null, [`${STATEMENT_PREFIX}/indexes`, 'Callback: done'], ['indexes']) + }) +}) + +common.test('indexExists', function indexExistsTest(t, collection, verify) { + collection.indexExists(['_id_'], function done(err, data) { + t.error(err) + t.equal(data, true) + + verify(null, [`${STATEMENT_PREFIX}/indexExists`, 'Callback: done'], ['indexExists']) + }) +}) + +common.test('indexInformation', function indexInformationTest(t, collection, verify) { + collection.indexInformation(function done(err, data) { + t.error(err) + t.same(data && data._id_, [['_id', 1]], 'should have expected results') + + verify(null, [`${STATEMENT_PREFIX}/indexInformation`, 'Callback: done'], ['indexInformation']) + }) +}) + +common.test('dropAllIndexes', function dropAllIndexesTest(t, collection, verify) { + collection.dropAllIndexes(function done(err, data) { + t.error(err) + t.equal(data, true) + verify(null, [`${STATEMENT_PREFIX}/dropAllIndexes`, 'Callback: done'], ['dropAllIndexes']) + }) +}) + +common.test('ensureIndex', function ensureIndexTest(t, collection, verify) { + collection.ensureIndex('i', function done(err, data) { + t.error(err) + t.equal(data, 'i_1') + verify(null, [`${STATEMENT_PREFIX}/ensureIndex`, 'Callback: done'], ['ensureIndex']) + }) +}) + +common.test('reIndex', function reIndexTest(t, collection, verify) { + collection.reIndex(function done(err, data) { + t.error(err) + t.equal(data, true) + + verify(null, [`${STATEMENT_PREFIX}/reIndex`, 'Callback: done'], ['reIndex']) + }) +}) diff --git a/test/versioned/mongodb/legacy/misc.tap.js b/test/versioned/mongodb/legacy/misc.tap.js new file mode 100644 index 0000000000..b1cd35b3c7 --- /dev/null +++ b/test/versioned/mongodb/legacy/misc.tap.js @@ -0,0 +1,274 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const common = require('../collection-common') +const semver = require('semver') +const { pkgVersion, STATEMENT_PREFIX, COLLECTIONS, DB_NAME } = require('../common') + +function verifyAggregateData(t, data) { + t.equal(data.length, 3, 'should have expected amount of results') + t.same(data, [{ value: 5 }, { value: 15 }, { value: 25 }], 'should have expected results') +} + +common.test('aggregate', function aggregateTest(t, collection, verify) { + const cursor = collection.aggregate([ + { $sort: { i: 1 } }, + { $match: { mod10: 5 } }, + { $limit: 3 }, + { $project: { value: '$i', _id: 0 } } + ]) + + cursor.toArray(function onResult(err, data) { + verifyAggregateData(t, data) + verify( + err, + [`${STATEMENT_PREFIX}/aggregate`, `${STATEMENT_PREFIX}/toArray`], + ['aggregate', 'toArray'], + { childrenLength: 2, strict: false } + ) + }) +}) + +common.test('bulkWrite', function bulkWriteTest(t, collection, verify) { + collection.bulkWrite( + [{ deleteMany: { filter: {} } }, { insertOne: { document: { a: 1 } } }], + { ordered: true, w: 1 }, + onWrite + ) + + function onWrite(err, data) { + t.error(err) + t.equal(data.insertedCount, 1) + t.equal(data.deletedCount, 30) + verify(null, [`${STATEMENT_PREFIX}/bulkWrite`, 'Callback: onWrite'], ['bulkWrite']) + } +}) + +common.test('count', function countTest(t, collection, verify) { + collection.count(function onCount(err, data) { + t.error(err) + t.equal(data, 30) + verify(null, [`${STATEMENT_PREFIX}/count`, 'Callback: onCount'], ['count']) + }) +}) + +common.test('distinct', function distinctTest(t, collection, verify) { + collection.distinct('mod10', function done(err, data) { + t.error(err) + t.same(data.sort(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + verify(null, [`${STATEMENT_PREFIX}/distinct`, 'Callback: done'], ['distinct']) + }) +}) + +common.test('drop', function dropTest(t, collection, verify) { + collection.drop(function done(err, data) { + t.error(err) + t.equal(data, true) + verify(null, [`${STATEMENT_PREFIX}/drop`, 'Callback: done'], ['drop']) + }) +}) + +if (semver.satisfies(pkgVersion, '<3')) { + common.test('geoNear', function geoNearTest(t, collection, verify) { + collection.ensureIndex({ loc: '2d' }, { bucketSize: 1 }, indexed) + + function indexed(err) { + t.error(err) + collection.geoNear(20, 20, { maxDistance: 5 }, done) + } + + function done(err, data) { + t.error(err) + t.equal(data.ok, 1) + t.equal(data.results.length, 2) + t.equal(data.results[0].obj.i, 21) + t.equal(data.results[1].obj.i, 17) + t.same(data.results[0].obj.loc, [21, 21]) + t.same(data.results[1].obj.loc, [17, 17]) + t.equal(data.results[0].dis, 1.4142135623730951) + t.equal(data.results[1].dis, 4.242640687119285) + verify( + null, + [ + `${STATEMENT_PREFIX}/ensureIndex`, + 'Callback: indexed', + `${STATEMENT_PREFIX}/geoNear`, + 'Callback: done' + ], + ['ensureIndex', 'geoNear'] + ) + } + }) +} + +common.test('isCapped', function isCappedTest(t, collection, verify) { + collection.isCapped(function done(err, data) { + t.error(err) + t.notOk(data) + + verify(null, [`${STATEMENT_PREFIX}/isCapped`, 'Callback: done'], ['isCapped']) + }) +}) + +common.test('mapReduce', function mapReduceTest(t, collection, verify) { + collection.mapReduce(map, reduce, { out: { inline: 1 } }, done) + + function done(err, data) { + t.error(err) + const expectedData = [ + { _id: 0, value: 30 }, + { _id: 1, value: 33 }, + { _id: 2, value: 36 }, + { _id: 3, value: 39 }, + { _id: 4, value: 42 }, + { _id: 5, value: 45 }, + { _id: 6, value: 48 }, + { _id: 7, value: 51 }, + { _id: 8, value: 54 }, + { _id: 9, value: 57 } + ] + + // data is not sorted depending on speed of + // db calls, sort to compare vs expected collection + data.sort((a, b) => a._id - b._id) + t.same(data, expectedData) + + verify(null, [`${STATEMENT_PREFIX}/mapReduce`, 'Callback: done'], ['mapReduce']) + } + + /* eslint-disable */ + function map(obj) { + emit(this.mod10, this.i) + } + /* eslint-enable */ + + function reduce(key, vals) { + return vals.reduce(function sum(prev, val) { + return prev + val + }, 0) + } +}) + +common.test('options', function optionsTest(t, collection, verify) { + collection.options(function done(err, data) { + t.error(err) + + // Depending on the version of the mongo server this will change. + if (data) { + t.same(data, {}, 'should have expected results') + } else { + t.notOk(data, 'should have expected results') + } + + verify(null, [`${STATEMENT_PREFIX}/options`, 'Callback: done'], ['options']) + }) +}) + +common.test('parallelCollectionScan', function (t, collection, verify) { + collection.parallelCollectionScan({ numCursors: 1 }, function done(err, cursors) { + t.error(err) + + cursors[0].toArray(function toArray(err, items) { + t.error(err) + t.equal(items.length, 30) + + const total = items.reduce(function sum(prev, item) { + return item.i + prev + }, 0) + + t.equal(total, 435) + verify( + null, + [ + `${STATEMENT_PREFIX}/parallelCollectionScan`, + 'Callback: done', + `${STATEMENT_PREFIX}/toArray`, + 'Callback: toArray' + ], + ['parallelCollectionScan', 'toArray'] + ) + }) + }) +}) + +common.test('geoHaystackSearch', function haystackSearchTest(t, collection, verify) { + collection.ensureIndex({ loc: 'geoHaystack', type: 1 }, { bucketSize: 1 }, indexed) + + function indexed(err) { + t.error(err) + collection.geoHaystackSearch(15, 15, { maxDistance: 5, search: {} }, done) + } + + function done(err, data) { + t.error(err) + t.equal(data.ok, 1) + t.equal(data.results.length, 2) + t.equal(data.results[0].i, 13) + t.equal(data.results[1].i, 17) + t.same(data.results[0].loc, [13, 13]) + t.same(data.results[1].loc, [17, 17]) + verify( + null, + [ + `${STATEMENT_PREFIX}/ensureIndex`, + 'Callback: indexed', + `${STATEMENT_PREFIX}/geoHaystackSearch`, + 'Callback: done' + ], + ['ensureIndex', 'geoHaystackSearch'] + ) + } +}) + +common.test('group', function groupTest(t, collection, verify) { + collection.group(['mod10'], {}, { count: 0, total: 0 }, count, done) + + function done(err, data) { + t.error(err) + t.same(data.sort(sort), [ + { mod10: 0, count: 3, total: 30 }, + { mod10: 1, count: 3, total: 33 }, + { mod10: 2, count: 3, total: 36 }, + { mod10: 3, count: 3, total: 39 }, + { mod10: 4, count: 3, total: 42 }, + { mod10: 5, count: 3, total: 45 }, + { mod10: 6, count: 3, total: 48 }, + { mod10: 7, count: 3, total: 51 }, + { mod10: 8, count: 3, total: 54 }, + { mod10: 9, count: 3, total: 57 } + ]) + verify(null, [`${STATEMENT_PREFIX}/group`, 'Callback: done'], ['group']) + } + + function count(obj, prev) { + prev.total += obj.i + prev.count++ + } + + function sort(a, b) { + return a.mod10 - b.mod10 + } +}) + +common.test('rename', function renameTest(t, collection, verify) { + collection.rename(COLLECTIONS.collection2, function done(err) { + t.error(err) + + verify(null, [`${STATEMENT_PREFIX}/rename`, 'Callback: done'], ['rename']) + }) +}) + +common.test('stats', function statsTest(t, collection, verify) { + collection.stats({ i: 5 }, function done(err, data) { + t.error(err) + t.equal(data.ns, `${DB_NAME}.${COLLECTIONS.collection1}`) + t.equal(data.count, 30) + t.equal(data.ok, 1) + + verify(null, [`${STATEMENT_PREFIX}/stats`, 'Callback: done'], ['stats']) + }) +}) diff --git a/test/versioned/mongodb/legacy/update.tap.js b/test/versioned/mongodb/legacy/update.tap.js new file mode 100644 index 0000000000..49fb445805 --- /dev/null +++ b/test/versioned/mongodb/legacy/update.tap.js @@ -0,0 +1,178 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const common = require('../collection-common') +const { STATEMENT_PREFIX } = require('../common') + +/** + * The response from the methods in this file differ between versions + * This helper decides which pieces to assert + * + * @param {Object} params fn params + * @param {Tap.Test} params.t tap instance + * @param {Object} params.data result from callback used to assert + * @param {Number} [params.count] count of results + * @param {Object} params.extraValues extra fields to assert + */ +function assertExpectedResult({ t, data, count, extraValues }) { + const expectedResult = { ok: 1, ...extraValues } + if (count) { + expectedResult.n = count + } + t.same(data.result, expectedResult) +} + +common.test('deleteMany', function deleteManyTest(t, collection, verify) { + collection.deleteMany({ mod10: 5 }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 3 + }) + verify(null, [`${STATEMENT_PREFIX}/deleteMany`, 'Callback: done'], ['deleteMany']) + }) +}) + +common.test('deleteOne', function deleteOneTest(t, collection, verify) { + collection.deleteOne({ mod10: 5 }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 1 + }) + verify(null, [`${STATEMENT_PREFIX}/deleteOne`, 'Callback: done'], ['deleteOne']) + }) +}) + +common.test('insert', function insertTest(t, collection, verify) { + collection.insert({ foo: 'bar' }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 1 + }) + + verify(null, [`${STATEMENT_PREFIX}/insert`, 'Callback: done'], ['insert']) + }) +}) + +common.test('insertMany', function insertManyTest(t, collection, verify) { + collection.insertMany([{ foo: 'bar' }, { foo: 'bar2' }], function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 2 + }) + + verify(null, [`${STATEMENT_PREFIX}/insertMany`, 'Callback: done'], ['insertMany']) + }) +}) + +common.test('insertOne', function insertOneTest(t, collection, verify) { + collection.insertOne({ foo: 'bar' }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + extraValues: { + n: 1 + } + }) + + verify(null, [`${STATEMENT_PREFIX}/insertOne`, 'Callback: done'], ['insertOne']) + }) +}) + +common.test('remove', function removeTest(t, collection, verify) { + collection.remove({ mod10: 5 }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 3 + }) + + verify(null, [`${STATEMENT_PREFIX}/remove`, 'Callback: done'], ['remove']) + }) +}) + +common.test('replaceOne', function replaceOneTest(t, collection, verify) { + collection.replaceOne({ i: 5 }, { foo: 'bar' }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 1, + extraValues: { + nModified: 1 + } + }) + + verify(null, [`${STATEMENT_PREFIX}/replaceOne`, 'Callback: done'], ['replaceOne']) + }) +}) + +common.test('save', function saveTest(t, collection, verify) { + collection.save({ foo: 'bar' }, function done(err, data) { + t.error(err) + t.same(data.result, { ok: 1, n: 1 }) + + verify(null, [`${STATEMENT_PREFIX}/save`, 'Callback: done'], ['save']) + }) +}) + +common.test('update', function updateTest(t, collection, verify) { + collection.update({ i: 5 }, { $set: { foo: 'bar' } }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 1, + extraValues: { + nModified: 1 + } + }) + + verify(null, [`${STATEMENT_PREFIX}/update`, 'Callback: done'], ['update']) + }) +}) + +common.test('updateMany', function updateManyTest(t, collection, verify) { + collection.updateMany({ mod10: 5 }, { $set: { a: 5 } }, function done(err, data) { + t.error(err) + assertExpectedResult({ + t, + data, + count: 3, + extraValues: { + nModified: 3 + } + }) + + verify(null, [`${STATEMENT_PREFIX}/updateMany`, 'Callback: done'], ['updateMany']) + }) +}) + +common.test('updateOne', function updateOneTest(t, collection, verify) { + collection.updateOne({ i: 5 }, { $set: { a: 5 } }, function done(err, data) { + t.notOk(err, 'should not error') + assertExpectedResult({ + t, + data, + count: 1, + extraValues: { + nModified: 1 + } + }) + + verify(null, [`${STATEMENT_PREFIX}/updateOne`, 'Callback: done'], ['updateOne']) + }) +}) diff --git a/test/versioned/mongodb/package.json b/test/versioned/mongodb/package.json index 5ead09fb4d..3448130d43 100644 --- a/test/versioned/mongodb/package.json +++ b/test/versioned/mongodb/package.json @@ -8,10 +8,29 @@ "node": ">=16" }, "dependencies": { - "mongodb": ">=2.1 < 4.0.0 || >= 4.1.4 < 5" + "mongodb": { + "versions": ">=2.1 < 4.0.0", + "samples": "2" + } + }, + "files": [ + "legacy/bulk.tap.js", + "legacy/cursor.tap.js", + "legacy/db.tap.js", + "legacy/find.tap.js", + "legacy/index.tap.js", + "legacy/misc.tap.js", + "legacy/update.tap.js" + ] + }, + { + "engines": { + "node": ">=16" + }, + "dependencies": { + "mongodb": ">=4.1.4" }, "files": [ - "bulk.tap.js", "collection-find.tap.js", "collection-index.tap.js", "collection-misc.tap.js",