diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 8808527b15f..2d50b86e8e9 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -81,6 +81,7 @@ functions: MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} \ AUTH=${AUTH} SSL=${SSL} \ ORCHESTRATION_FILE=${ORCHESTRATION_FILE} \ + REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \ bash ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh - command: expansions.update params: @@ -125,8 +126,10 @@ functions: rm -f ./prepare_client_encryption.sh fi - AUTH=${AUTH} SSL=${SSL} UNIFIED=${UNIFIED} MONGODB_URI="${MONGODB_URI}" \ - NODE_VERSION=${NODE_VERSION} SKIP_DEPS=1 NO_EXIT=1 \ + MONGODB_URI="${MONGODB_URI}" \ + AUTH=${AUTH} SSL=${SSL} UNIFIED=${UNIFIED} \ + MONGODB_API_VERSION="${MONGODB_API_VERSION}" \ + NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \ bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh run checks: - command: shell.exec @@ -780,6 +783,22 @@ tasks: VERSION: '2.6' TOPOLOGY: sharded_cluster - func: run tests + - name: test-latest-server-v1-api + tags: + - latest + - server + - v1-api + commands: + - func: install dependencies + - func: bootstrap mongo-orchestration + vars: + VERSION: latest + TOPOLOGY: server + REQUIRE_API_VERSION: '1' + - func: run tests + vars: + MONGODB_API_VERSION: '1' + NO_EXIT: '' - name: test-atlas-connectivity tags: - atlas-connect @@ -1192,6 +1211,7 @@ buildvariants: - test-2.6-server - test-2.6-replica_set - test-2.6-sharded_cluster + - test-latest-server-v1-api - test-atlas-connectivity - test-atlas-data-lake - test-auth-kerberos @@ -1258,6 +1278,7 @@ buildvariants: - test-2.6-server - test-2.6-replica_set - test-2.6-sharded_cluster + - test-latest-server-v1-api - test-atlas-connectivity - test-atlas-data-lake - test-auth-kerberos @@ -1353,6 +1374,7 @@ buildvariants: - test-3.2-server - test-3.2-replica_set - test-3.2-sharded_cluster + - test-latest-server-v1-api - test-atlas-connectivity - test-atlas-data-lake - test-auth-kerberos diff --git a/.evergreen/config.yml.in b/.evergreen/config.yml.in index 4558913f996..2e72318aefe 100644 --- a/.evergreen/config.yml.in +++ b/.evergreen/config.yml.in @@ -98,6 +98,7 @@ functions: MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} \ AUTH=${AUTH} SSL=${SSL} \ ORCHESTRATION_FILE=${ORCHESTRATION_FILE} \ + REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \ bash ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh # run-orchestration generates expansion file with the MONGODB_URI for the cluster - command: expansions.update @@ -145,8 +146,10 @@ functions: rm -f ./prepare_client_encryption.sh fi - AUTH=${AUTH} SSL=${SSL} UNIFIED=${UNIFIED} MONGODB_URI="${MONGODB_URI}" \ - NODE_VERSION=${NODE_VERSION} SKIP_DEPS=1 NO_EXIT=1 \ + MONGODB_URI="${MONGODB_URI}" \ + AUTH=${AUTH} SSL=${SSL} UNIFIED=${UNIFIED} \ + MONGODB_API_VERSION="${MONGODB_API_VERSION}" \ + NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \ bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh "run checks": diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 786dde9675d..f93aa6e5b76 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -60,6 +60,7 @@ const OPERATING_SYSTEMS = [ ) ); +// TODO: NODE-3060: enable skipped tests on windows const WINDOWS_SKIP_TAGS = new Set(['atlas-connect', 'auth']); const TASKS = []; @@ -87,6 +88,28 @@ const BASE_TASKS = []; MONGODB_VERSIONS.forEach(mongoVersion => { TOPOLOGIES.forEach(topology => BASE_TASKS.push(makeTask({ mongoVersion, topology }))); }); +BASE_TASKS.push({ + name: `test-latest-server-v1-api`, + tags: ['latest', 'server', 'v1-api'], + commands: [ + { func: 'install dependencies' }, + { + func: 'bootstrap mongo-orchestration', + vars: { + VERSION: 'latest', + TOPOLOGY: 'server', + REQUIRE_API_VERSION: '1' + } + }, + { + func: 'run tests', + vars: { + MONGODB_API_VERSION: '1', + NO_EXIT: '' + } + } + ] +}); // manually added tasks Array.prototype.push.apply(TASKS, [ diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index f01a1f6479a..162d07f3b10 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -56,4 +56,4 @@ else npm install mongodb-client-encryption@">=1.2.1" fi -MONGODB_UNIFIED_TOPOLOGY=${UNIFIED} MONGODB_URI=${MONGODB_URI} npm run ${TEST_NPM_SCRIPT} +MONGODB_API_VERSION=${MONGODB_API_VERSION} MONGODB_UNIFIED_TOPOLOGY=${UNIFIED} MONGODB_URI=${MONGODB_URI} npm run ${TEST_NPM_SCRIPT} diff --git a/src/cmap/auth/scram.ts b/src/cmap/auth/scram.ts index 8953a0232b7..f2dd08e5fb6 100644 --- a/src/cmap/auth/scram.ts +++ b/src/cmap/auth/scram.ts @@ -2,7 +2,7 @@ import * as crypto from 'crypto'; import { Binary, Document } from '../../bson'; import { MongoError, AnyError } from '../../error'; import { AuthProvider, AuthContext } from './auth_provider'; -import { Callback, emitWarning, ns } from '../../utils'; +import { Callback, ns, emitWarning } from '../../utils'; import type { MongoCredentials } from './mongo_credentials'; import type { HandshakeDocument } from '../connect'; diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index bc6189e651c..73496a53adb 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -38,7 +38,7 @@ import { applyCommonQueryOptions, getReadPreference, isSharded } from './wire_pr import { ReadPreference, ReadPreferenceLike } from '../read_preference'; import { isTransactionCommand } from '../transactions'; import type { W, WriteConcern, WriteConcernOptions } from '../write_concern'; -import type { SupportedNodeConnectionOptions } from '../mongo_client'; +import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client'; const kStream = Symbol('stream'); const kQueue = Symbol('queue'); @@ -107,6 +107,7 @@ export interface ConnectionOptions hostAddress: HostAddress; // Settings autoEncrypter?: AutoEncrypter; + serverApi?: ServerApi; monitorCommands: boolean; connectionType: typeof Connection; credentials?: MongoCredentials; @@ -136,6 +137,7 @@ export class Connection extends EventEmitter { closed: boolean; destroyed: boolean; lastIsMasterMS?: number; + serverApi?: ServerApi; /** @internal */ [kDescription]: StreamDescription; /** @internal */ @@ -168,6 +170,7 @@ export class Connection extends EventEmitter { this.address = streamIdentifier(stream); this.socketTimeout = options.socketTimeout ?? 0; this.monitorCommands = options.monitorCommands; + this.serverApi = options.serverApi; this.closed = false; this.destroyed = false; @@ -317,6 +320,15 @@ export class Connection extends EventEmitter { let clusterTime = this.clusterTime; let finalCmd = Object.assign({}, cmd); + const inTransaction = session && (session.inTransaction() || isTransactionCommand(finalCmd)); + + if (this.serverApi && supportsVersionedApi(cmd, session)) { + const { version, strict, deprecationErrors } = this.serverApi; + finalCmd.apiVersion = version; + if (strict != null) finalCmd.apiStrict = strict; + if (deprecationErrors != null) finalCmd.apiDeprecationErrors = deprecationErrors; + } + if (hasSessionSupport(this) && session) { if ( session.clusterTime && @@ -361,7 +373,6 @@ export class Connection extends EventEmitter { ? new Msg(cmdNs, finalCmd, commandOptions) : new Query(cmdNs, finalCmd, commandOptions); - const inTransaction = session && (session.inTransaction() || isTransactionCommand(finalCmd)); const commandResponseHandler = inTransaction ? (err?: AnyError, ...args: Document[]) => { // We need to add a TransientTransactionError errorLabel, as stated in the transaction spec. @@ -630,6 +641,16 @@ function supportsOpMsg(conn: Connection) { return maxWireVersion(conn) >= 6 && !description.__nodejs_mock_server__; } +function supportsVersionedApi(cmd: Document, session?: ClientSession) { + const inTransaction = session && (session.inTransaction() || isTransactionCommand(cmd)); + // if an API version was declared, add the apiVersion option to every command, except: + // a. only in the initial command of a transaction + // b. only in a Cursor's initiating command, not subsequent getMore commands + return ( + (!inTransaction || session?.transaction.isStarting) && !cmd.commitTransaction && !cmd.getMore + ); +} + function messageHandler(conn: Connection) { return function messageHandler(message: BinMsg | Response) { // always emit the message, in case we are streaming diff --git a/src/connection_string.ts b/src/connection_string.ts index 0a4ad1e998d..a119be4ca3c 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -22,7 +22,8 @@ import { MongoClient, MongoClientOptions, MongoOptions, - PkFactory + PkFactory, + ServerApi } from './mongo_client'; import { MongoCredentials } from './cmap/auth/mongo_credentials'; import type { TagSet } from './sdam/server_description'; @@ -323,6 +324,12 @@ export function parseOptions( throw new MongoParseError('URI cannot contain options with no value'); } + if (key.toLowerCase() === 'serverapi') { + throw new MongoParseError( + 'URI cannot contain `serverApi`, it can only be passed to the client' + ); + } + if (key.toLowerCase() === 'authsource' && urlOptions.has('authSource')) { // If authSource is an explicit key in the urlOptions we need to remove the implicit dbName urlOptions.delete('authSource'); @@ -572,6 +579,15 @@ export const OPTIONS = { autoEncryption: { type: 'record' }, + serverApi: { + target: 'serverApi', + transform({ values: [version] }): ServerApi { + if (typeof version === 'string') { + return { version }; + } + return version as ServerApi; + } + }, checkKeys: { type: 'boolean' }, diff --git a/src/index.ts b/src/index.ts index 3717087fe8f..e6c39b6aa04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,6 +85,7 @@ export { Compressor } from './cmap/wire_protocol/compression'; export { ExplainVerbosity } from './explain'; export { ReadConcernLevel } from './read_concern'; export { ReadPreferenceMode } from './read_preference'; +export { ServerApiVersion } from './mongo_client'; // events export { @@ -211,6 +212,8 @@ export type { Auth, DriverInfo, MongoOptions, + ServerApi, + ServerApiVersionId, SupportedNodeConnectionOptions, SupportedTLSConnectionOptions, SupportedTLSSocketOptions, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 725dcfc070a..034e305ca26 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -32,6 +32,21 @@ import type { SrvPoller } from './sdam/srv_polling'; import type { Connection } from './cmap/connection'; import type { LEGAL_TLS_SOCKET_OPTIONS, LEGAL_TCP_SOCKET_OPTIONS } from './cmap/connect'; +/** @public */ +export const ServerApiVersion = { + v1: '1' +}; + +/** @public */ +export type ServerApiVersionId = typeof ServerApiVersion[keyof typeof ServerApiVersion]; + +/** @public */ +export interface ServerApi { + version: string | ServerApiVersionId; + strict?: boolean; + deprecationErrors?: boolean; +} + /** @public */ export interface DriverInfo { name?: string; @@ -198,6 +213,8 @@ export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeC logger?: Logger; /** Enable command monitoring for this client */ monitorCommands?: boolean; + /** Server API version */ + serverApi?: ServerApi | ServerApiVersionId; /** Optionally enable client side auto encryption */ autoEncryption?: AutoEncryptionOptions; /** Allows a wrapping driver to amend the client metadata generated by the driver to include information about the wrapping driver */ @@ -306,6 +323,10 @@ export class MongoClient extends EventEmitter { return Object.freeze({ ...this[kOptions] }); } + get serverApi(): Readonly { + return this[kOptions].serverApi && Object.freeze({ ...this[kOptions].serverApi }); + } + get autoEncrypter(): AutoEncrypter | undefined { return this[kOptions].autoEncrypter; } @@ -597,6 +618,7 @@ export interface MongoOptions credentials?: MongoCredentials; readPreference: ReadPreference; readConcern: ReadConcern; + serverApi: ServerApi; writeConcern: WriteConcern; dbName: string; metadata: ClientMetadata; diff --git a/src/operations/update.ts b/src/operations/update.ts index 197c934ab8f..b3057ebaebe 100644 --- a/src/operations/update.ts +++ b/src/operations/update.ts @@ -274,7 +274,7 @@ export function makeUpdateStatement( op.upsert = options.upsert; } - if (typeof options.multi === 'boolean') { + if (options.multi) { op.multi = options.multi; } diff --git a/src/sdam/server.ts b/src/sdam/server.ts index a9ccfb01fd6..374d1faee9a 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -44,6 +44,7 @@ import type { ServerHeartbeatSucceededEvent } from './events'; import type { ClientSession } from '../sessions'; import type { Document, Long } from '../bson'; import type { AutoEncrypter } from '../deps'; +import type { ServerApi } from '../mongo_client'; // Used for filtering out fields for logging const DEBUG_FIELDS = [ @@ -100,12 +101,15 @@ export interface ServerPrivate { topology: Topology; /** A connection pool for this server */ pool: ConnectionPool; + /** MongoDB server API version */ + serverApi?: ServerApi; } /** @public */ export class Server extends EventEmitter { /** @internal */ s: ServerPrivate; + serverApi?: ServerApi; clusterTime?: ClusterTime; ismaster?: Document; [kMonitor]: Monitor; @@ -131,6 +135,8 @@ export class Server extends EventEmitter { constructor(topology: Topology, description: ServerDescription, options: ServerOptions) { super(); + this.serverApi = options.serverApi; + const poolOptions = { hostAddress: description.hostAddress, ...options }; this.s = { diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 0ee086c5a6a..93b348aee1c 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -50,7 +50,7 @@ import type { MongoCredentials } from '../cmap/auth/mongo_credentials'; import type { Transaction } from '../transactions'; import type { CloseOptions } from '../cmap/connection_pool'; import { DestroyOptions, Connection } from '../cmap/connection'; -import type { MongoClientOptions } from '../mongo_client'; +import type { MongoClientOptions, ServerApi } from '../mongo_client'; import { DEFAULT_OPTIONS } from '../connection_string'; import { serialize, deserialize } from '../bson'; @@ -139,6 +139,8 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions { /** Indicates that a client should directly connect to a node without attempting to discover its topology type */ directConnection: boolean; metadata: ClientMetadata; + /** MongoDB server API version */ + serverApi?: ServerApi; } /** @public */ diff --git a/src/transactions.ts b/src/transactions.ts index 05908f1601d..c3fae4efa58 100644 --- a/src/transactions.ts +++ b/src/transactions.ts @@ -116,6 +116,11 @@ export class Transaction { return !!this.server; } + /** @returns Whether the transaction has started */ + get isStarting(): boolean { + return this.state === TxnState.STARTING_TRANSACTION; + } + /** * @returns Whether this session is presently in a transaction */ diff --git a/test/functional/bulk.test.js b/test/functional/bulk.test.js index a0201543f31..3e0545fb9e3 100644 --- a/test/functional/bulk.test.js +++ b/test/functional/bulk.test.js @@ -68,7 +68,7 @@ describe('Bulk', function () { var op = error.getOperation(); test.equal(2, op.q.b); test.equal(1, op.u['$set'].a); - test.equal(false, op.multi); + expect(op.multi).to.not.be.true; test.equal(true, op.upsert); // Get the first error @@ -324,7 +324,7 @@ describe('Bulk', function () { test.ok(error.errmsg != null); test.equal(2, error.getOperation().q.b); test.equal(1, error.getOperation().u['$set'].a); - test.equal(false, error.getOperation().multi); + expect(error.getOperation().multi).to.not.be.true; test.equal(true, error.getOperation().upsert); // Finish up test @@ -698,7 +698,7 @@ describe('Bulk', function () { var op = error.getOperation(); test.equal(2, op.q.b); test.equal(1, op.u['$set'].a); - test.equal(false, op.multi); + expect(op.multi).to.not.be.true; test.equal(true, op.upsert); // Get the first error @@ -1180,7 +1180,6 @@ describe('Bulk', function () { expect(batches[1].operations[0]).to.containSubset({ q: { b: 2 }, u: { $set: { a: 1 } }, - multi: false, upsert: true }); expect(batches[2].operations[0]).to.containSubset({ b: 3, a: 2 }); @@ -1283,7 +1282,6 @@ describe('Bulk', function () { expect(batches[1].operations[0]).to.containSubset({ q: { b: 2 }, u: { $set: { a: 1 } }, - multi: false, upsert: true }); client.close(done); diff --git a/test/functional/cmap/connection.test.js b/test/functional/cmap/connection.test.js index 9001738c7fc..6f310d4115b 100644 --- a/test/functional/cmap/connection.test.js +++ b/test/functional/cmap/connection.test.js @@ -11,48 +11,54 @@ describe('Connection - functional/cmap', function () { return setupDatabase(this.configuration); }); - it('should execute a command against a server', function (done) { - const connectOptions = Object.assign( - { connectionType: Connection }, - this.configuration.options - ); - - connect(connectOptions, (err, conn) => { - expect(err).to.not.exist; - this.defer(_done => conn.destroy(_done)); + it('should execute a command against a server', { + metadata: { requires: { apiVersion: false } }, + test: function (done) { + const connectOptions = Object.assign( + { connectionType: Connection }, + this.configuration.options + ); - conn.command(ns('admin.$cmd'), { ismaster: 1 }, undefined, (err, ismaster) => { + connect(connectOptions, (err, conn) => { expect(err).to.not.exist; - expect(ismaster).to.exist; - expect(ismaster.ok).to.equal(1); - done(); + this.defer(_done => conn.destroy(_done)); + + conn.command(ns('admin.$cmd'), { ismaster: 1 }, undefined, (err, ismaster) => { + expect(err).to.not.exist; + expect(ismaster).to.exist; + expect(ismaster.ok).to.equal(1); + done(); + }); }); - }); + } }); - it('should emit command monitoring events', function (done) { - const connectOptions = Object.assign( - { connectionType: Connection, monitorCommands: true }, - this.configuration.options - ); - - connect(connectOptions, (err, conn) => { - expect(err).to.not.exist; - this.defer(_done => conn.destroy(_done)); - - const events = []; - conn.on('commandStarted', event => events.push(event)); - conn.on('commandSucceeded', event => events.push(event)); - conn.on('commandFailed', event => events.push(event)); + it('should emit command monitoring events', { + metadata: { requires: { apiVersion: false } }, + test: function (done) { + const connectOptions = Object.assign( + { connectionType: Connection, monitorCommands: true }, + this.configuration.options + ); - conn.command(ns('admin.$cmd'), { ismaster: 1 }, undefined, (err, ismaster) => { + connect(connectOptions, (err, conn) => { expect(err).to.not.exist; - expect(ismaster).to.exist; - expect(ismaster.ok).to.equal(1); - expect(events).to.have.length(2); - done(); + this.defer(_done => conn.destroy(_done)); + + const events = []; + conn.on('commandStarted', event => events.push(event)); + conn.on('commandSucceeded', event => events.push(event)); + conn.on('commandFailed', event => events.push(event)); + + conn.command(ns('admin.$cmd'), { ismaster: 1 }, undefined, (err, ismaster) => { + expect(err).to.not.exist; + expect(ismaster).to.exist; + expect(ismaster.ok).to.equal(1); + expect(events).to.have.length(2); + done(); + }); }); - }); + } }); it.skip('should support socket timeouts', { @@ -78,7 +84,7 @@ describe('Connection - functional/cmap', function () { }); it('should support calling back multiple times on exhaust commands', { - metadata: { requires: { mongodb: '>=4.2.0', topology: ['single'] } }, + metadata: { requires: { apiVersion: false, mongodb: '>=4.2.0', topology: ['single'] } }, test: function (done) { const namespace = ns(`${this.configuration.db}.$cmd`); const connectOptions = Object.assign( diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index 10863ee58da..5b3f97f1f54 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -646,7 +646,7 @@ describe('Collation', function () { .collection('test') .createIndex({ a: 1 }, { collation: { caseLevel: true } }) .then(() => { - expect(commandResult).to.eql({ + expect(commandResult).to.containSubset({ createIndexes: 'test', indexes: [{ name: 'a_1', key: { a: 1 }, collation: { caseLevel: true } }] }); diff --git a/test/functional/core/topology.test.js b/test/functional/core/topology.test.js index 5c53ab239cf..677052c309f 100644 --- a/test/functional/core/topology.test.js +++ b/test/functional/core/topology.test.js @@ -2,18 +2,22 @@ const expect = require('chai').expect; describe('Topology', function () { - it('should correctly track states of a topology', function (done) { - const topology = this.configuration.newTopology(); + it('should correctly track states of a topology', { + metadata: { requires: { apiVersion: false } }, // apiVersion not supported by newTopology() + test: function (done) { + const topology = this.configuration.newTopology(); - const states = []; - topology.on('stateChanged', (_, newState) => states.push(newState)); - topology.connect(err => { - expect(err).to.not.exist; - topology.close(err => { + const states = []; + topology.on('stateChanged', (_, newState) => states.push(newState)); + topology.connect(err => { expect(err).to.not.exist; - expect(states).to.eql(['connecting', 'connected', 'closing', 'closed']); - done(); + topology.close(err => { + expect(err).to.not.exist; + expect(topology.isDestroyed()).to.be.true; + expect(states).to.eql(['connecting', 'connected', 'closing', 'closed']); + done(); + }); }); - }); + } }); }); diff --git a/test/functional/gridfs_stream.test.js b/test/functional/gridfs_stream.test.js index dc3d7938638..1423feb77b4 100644 --- a/test/functional/gridfs_stream.test.js +++ b/test/functional/gridfs_stream.test.js @@ -601,7 +601,7 @@ describe('GridFS Stream', function () { * @example-method abort */ it('Destroying a download stream', { - metadata: { requires: { topology: ['single'] } }, + metadata: { requires: { topology: ['single'], apiVersion: false } }, test: function (done) { var configuration = this.configuration; @@ -669,7 +669,9 @@ describe('GridFS Stream', function () { * @example-method delete */ it('Deleting a file using promises', { - metadata: { requires: { topology: ['single'], node: '>12.0.0' } }, + metadata: { + requires: { topology: ['single'], node: '>12.0.0', sessions: { skipLeakTests: true } } + }, test: function (done) { var configuration = this.configuration; @@ -718,7 +720,7 @@ describe('GridFS Stream', function () { }); it('find()', { - metadata: { requires: { topology: ['single'] } }, + metadata: { requires: { topology: ['single'], sessions: { skipLeakTests: true } } }, test: function (done) { var configuration = this.configuration; @@ -759,7 +761,7 @@ describe('GridFS Stream', function () { * @example-method drop */ it('drop example', { - metadata: { requires: { topology: ['single'] } }, + metadata: { requires: { topology: ['single'], sessions: { skipLeakTests: true } } }, test: function (done) { var configuration = this.configuration; diff --git a/test/functional/max_staleness.test.js b/test/functional/max_staleness.test.js index d87f4336ad0..3de4377fd98 100644 --- a/test/functional/max_staleness.test.js +++ b/test/functional/max_staleness.test.js @@ -63,7 +63,7 @@ describe('Max Staleness', function () { .find({}) .toArray(function (err) { expect(err).to.not.exist; - expect(test.checkCommand).to.eql({ + expect(test.checkCommand).to.containSubset({ $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -98,7 +98,7 @@ describe('Max Staleness', function () { .find({}) .toArray(function (err) { expect(err).to.not.exist; - expect(test.checkCommand).to.eql({ + expect(test.checkCommand).to.containSubset({ $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -134,7 +134,7 @@ describe('Max Staleness', function () { .find({}) .toArray(function (err) { expect(err).to.not.exist; - expect(test.checkCommand).to.eql({ + expect(test.checkCommand).to.containSubset({ $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -169,7 +169,7 @@ describe('Max Staleness', function () { .withReadPreference(readPreference) .toArray(function (err) { expect(err).to.not.exist; - expect(test.checkCommand).to.eql({ + expect(test.checkCommand).to.containSubset({ $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); diff --git a/test/functional/spec-runner/index.js b/test/functional/spec-runner/index.js index 90b334ea80c..6828db318e0 100644 --- a/test/functional/spec-runner/index.js +++ b/test/functional/spec-runner/index.js @@ -114,7 +114,8 @@ function generateTopologyTests(testSuites, testContext, filter) { environmentRequirementList.forEach(requires => { const suiteName = `${testSuite.name} - ${requires.topology.join()}`; describe(suiteName, { - metadata: { requires }, + // FIXME: calling this.skip() inside tests triggers the leak checker, disable until fixed + metadata: { requires, sessions: { skipLeakTests: true } }, test: function () { beforeEach(() => prepareDatabaseForSuite(testSuite, testContext)); afterEach(() => testContext.cleanupAfterSuite()); diff --git a/test/functional/unified-spec-runner/entities.ts b/test/functional/unified-spec-runner/entities.ts index 11027541bf0..79f42f2b31c 100644 --- a/test/functional/unified-spec-runner/entities.ts +++ b/test/functional/unified-spec-runner/entities.ts @@ -37,7 +37,15 @@ export class UnifiedMongoClient extends MongoClient { } as const; constructor(url: string, description: ClientEntity) { - super(url, { monitorCommands: true, ...description.uriOptions }); + super(url, { + monitorCommands: true, + ...description.uriOptions, + serverApi: description.serverApi + ? description.serverApi + : process.env.MONGODB_API_VERSION + ? { version: process.env.MONGODB_API_VERSION } + : null + }); this.events = []; this.failPoints = []; this.ignoredEvents = [ diff --git a/test/functional/unified-spec-runner/runner.ts b/test/functional/unified-spec-runner/runner.ts index d4dfc378f6a..1d3e5b9cbab 100644 --- a/test/functional/unified-spec-runner/runner.ts +++ b/test/functional/unified-spec-runner/runner.ts @@ -76,11 +76,12 @@ export async function runUnifiedTest( ...(test.runOnRequirements ?? []) ]; - let doesNotMeetRunOnRequirement = allRequirements.length > 0; + let doesNotMeetRunOnRequirement = false; for (const requirement of allRequirements) { - if (await topologySatisfies(ctx.configuration, requirement, utilClient)) { - doesNotMeetRunOnRequirement = false; // it does meet a run on requirement! + const met = await topologySatisfies(ctx.configuration, requirement, utilClient); + if (!met) { + doesNotMeetRunOnRequirement = true; // it doesn't meet a run on requirement break; } } diff --git a/test/functional/unified-spec-runner/unified-utils.ts b/test/functional/unified-spec-runner/unified-utils.ts index e55ea468812..420df85cbbb 100644 --- a/test/functional/unified-spec-runner/unified-utils.ts +++ b/test/functional/unified-spec-runner/unified-utils.ts @@ -33,9 +33,9 @@ export async function topologySatisfies( Sharded: 'sharded' }[config.topologyType]; - if (r.topologies.includes('sharded-replicaset')) { + if (r.topologies.includes('sharded-replicaset') && topologyType === 'sharded') { const shards = await utilClient.db('config').collection('shards').find({}).toArray(); - ok &&= shards.every(shard => shard.host.split(',').length > 1); + ok &&= shards.length > 0 && shards.every(shard => shard.host.split(',').length > 1); } else { if (!topologyType) throw new Error(`Topology undiscovered: ${config.topologyType}`); ok &&= r.topologies.includes(topologyType); diff --git a/test/functional/versioned-api.test.js b/test/functional/versioned-api.test.js new file mode 100644 index 00000000000..e16bd772121 --- /dev/null +++ b/test/functional/versioned-api.test.js @@ -0,0 +1,40 @@ +'use strict'; + +const { expect } = require('chai'); +const { loadSpecTests } = require('../spec/index'); +const { runUnifiedTest } = require('./unified-spec-runner/runner'); + +describe('Versioned API', function () { + it('should throw an error if serverApi version is provided via the uri', { + metadata: { topology: 'single' }, + test: function () { + expect(() => this.configuration.newClient({ serverApi: '1' })).to.throw( + /URI cannot contain `serverApi`, it can only be passed to the client/ + ); + } + }); + + for (const versionedApiTest of loadSpecTests('versioned-api')) { + expect(versionedApiTest).to.exist; + context(String(versionedApiTest.description), function () { + for (const test of versionedApiTest.tests) { + it(String(test.description), { + metadata: { sessions: { skipLeakTests: true } }, + test: async function () { + try { + await runUnifiedTest(this, versionedApiTest, test); + } catch (error) { + if (error.message.includes('not implemented.')) { + console.log(`${test.description}: was skipped due to missing functionality`); + console.log(error.stack); + this.skip(); + } else { + throw error; + } + } + } + }); + } + }); + } +}); diff --git a/test/functional/view.test.js b/test/functional/view.test.js index d853a7812c3..4fee4d454da 100644 --- a/test/functional/view.test.js +++ b/test/functional/view.test.js @@ -58,7 +58,7 @@ describe('Views', function () { ) { expect(r).to.exist; expect(err).to.not.exist; - expect(commandResult).to.eql({ + expect(commandResult).to.containSubset({ create: 'test', viewOn: 'users', pipeline: [{ $match: {} }] diff --git a/test/spec/versioned-api/crud-api-version-1-strict.json b/test/spec/versioned-api/crud-api-version-1-strict.json new file mode 100644 index 00000000000..a2eb02e4321 --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1-strict.json @@ -0,0 +1,1076 @@ +{ + "description": "CRUD Api Version 1 (strict)", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.7" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "database": { + "id": "adminDatabase", + "client": "client", + "databaseName": "admin" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "aggregate on collection appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "aggregate on database appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "adminDatabase", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + }, + "expectError": { + "errorCodeName": "APIStrictError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "bulkWrite appends declared API version", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 6, + "x": 66 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 7 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "ordered": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "countDocuments appends declared API version", + "operations": [ + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$gt": 11 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "x": { + "$gt": 11 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "deleteMany appends declared API version", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "deleteOne appends declared API version", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 7 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "distinct appends declared API version", + "operations": [ + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + }, + "expectError": { + "isError": true, + "errorContains": "command distinct is not in API Version 1", + "errorCodeName": "APIStrictError" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "x", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount appends declared API version", + "skipReason": "DRIVERS-1437 count was removed from API version 1", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "test", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "find command with declared API version appends to the command, but getMore does not", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "batchSize": 3 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndDelete appends declared API version", + "operations": [ + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "remove": true, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndReplace appends declared API version", + "operations": [ + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndUpdate appends declared API version", + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "insertMany appends declared API version", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "insertOne appends declared API version", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 6, + "x": 66 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "replaceOne appends declared API version", + "operations": [ + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateMany appends declared API version", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateOne appends declared API version", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/versioned-api/crud-api-version-1-strict.yml b/test/spec/versioned-api/crud-api-version-1-strict.yml new file mode 100644 index 00000000000..48aa14540e7 --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1-strict.yml @@ -0,0 +1,395 @@ +description: "CRUD Api Version 1 (strict)" + +schemaVersion: "1.1" + +runOnRequirements: + - minServerVersion: "4.7" + +createEntities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + serverApi: + version: "1" + strict: true + - database: + id: &database database + client: *client + databaseName: &databaseName versioned-api-tests + - database: + id: &adminDatabase adminDatabase + client: *client + databaseName: &adminDatabaseName admin + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + +_yamlAnchors: + versions: + - &expectedApiVersion + apiVersion: "1" + apiStrict: true + apiDeprecationErrors: { $$unsetOrMatches: false } + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + +tests: + - description: "aggregate on collection appends declared API version" + operations: + - name: aggregate + object: *collection + arguments: + pipeline: &pipeline + - $sort: { x : 1 } + - $match: { _id: { $gt: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: *pipeline + <<: *expectedApiVersion + + - description: "aggregate on database appends declared API version" + operations: + - name: aggregate + object: *adminDatabase + arguments: + pipeline: &pipeline + - $listLocalSessions: {} + - $limit: 1 + expectError: + errorCodeName: "APIStrictError" + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: 1 + pipeline: *pipeline + <<: *expectedApiVersion + + - description: "bulkWrite appends declared API version" + operations: + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 6, x: 66 } + - updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - deleteMany: + filter: { x: { $nin: [ 24, 34 ] } } + - updateMany: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + - deleteOne: + filter: { _id: 7 } + - replaceOne: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + upsert: true + ordered: true + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 2 }, u: { $inc: { x: 1 } } } + <<: *expectedApiVersion + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { x: { $nin: [ 24, 34 ] } }, limit: 0 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: { $gt: 1 } }, u: { $inc: { x: 1 } }, multi: true } + <<: *expectedApiVersion + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { _id: 7 }, limit: 1 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 4 }, u: { _id: 4, x: 44 }, upsert: true } + <<: *expectedApiVersion + + - description: "countDocuments appends declared API version" + operations: + - name: countDocuments + object: *collection + arguments: + filter: &filter + x : { $gt: 11 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: + - { $match: *filter } + - { $group: { _id: 1, n: { $sum: 1 } } } + <<: *expectedApiVersion + + - description: "deleteMany appends declared API version" + operations: + - name: deleteMany + object: *collection + arguments: + filter: { x: { $nin: [ 24, 34 ] } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { x: { $nin: [ 24, 34 ] } }, limit: 0 } + <<: *expectedApiVersion + + - description: "deleteOne appends declared API version" + operations: + - name: deleteOne + object: *collection + arguments: + filter: { _id: 7 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { _id: 7 }, limit: 1 } + <<: *expectedApiVersion + + # distinct will fail until drivers replace it with an alternative + # implementation + - description: "distinct appends declared API version" + operations: + - name: distinct + object: *collection + arguments: + fieldName: x + filter: {} + expectError: + isError: true + errorContains: "command distinct is not in API Version 1" + errorCodeName: "APIStrictError" + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + distinct: *collectionName + key: x + <<: *expectedApiVersion + + - description: "estimatedDocumentCount appends declared API version" + skipReason: "DRIVERS-1437 count was removed from API version 1" + operations: + - name: estimatedDocumentCount + object: *collection + arguments: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + count: *collectionName + <<: *expectedApiVersion + + - description: "find command with declared API version appends to the command, but getMore does not" + operations: + - name: find + object: *collection + arguments: + filter: {} + sort: { _id: 1 } + batchSize: 3 + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + find: *collectionName + <<: *expectedApiVersion + - commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + apiVersion: { $$exists: false } + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } + + - description: "findOneAndDelete appends declared API version" + operations: + - name: findOneAndDelete + object: *collection + arguments: + filter: &filter { _id: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + remove: true + <<: *expectedApiVersion + + - description: "findOneAndReplace appends declared API version" + operations: + - name: findOneAndReplace + object: *collection + arguments: + filter: &filter { _id: 1 } + replacement: &replacement { x: 33 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + update: *replacement + <<: *expectedApiVersion + + - description: "findOneAndUpdate appends declared API version" + operations: + - name: findOneAndUpdate + object: collection + arguments: + filter: &filter { _id: 1 } + update: &update { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + update: *update + <<: *expectedApiVersion + + - description: "insertMany appends declared API version" + operations: + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + <<: *expectedApiVersion + + - description: "insertOne appends declared API version" + operations: + - name: insertOne + object: *collection + arguments: + document: { _id: 6, x: 66 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + <<: *expectedApiVersion + + - description: "replaceOne appends declared API version" + operations: + - name: replaceOne + object: *collection + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + upsert: true + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 4 }, u: { _id: 4, x: 44 }, upsert: true } + <<: *expectedApiVersion + + - description: "updateMany appends declared API version" + operations: + - name: updateMany + object: *collection + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: { $gt: 1 } }, u: { $inc: { x: 1 } }, multi: true } + <<: *expectedApiVersion + + - description: "updateOne appends declared API version" + operations: + - name: updateOne + object: *collection + arguments: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 2 }, u: { $inc: { x: 1 } } } + <<: *expectedApiVersion diff --git a/test/spec/versioned-api/crud-api-version-1.json b/test/spec/versioned-api/crud-api-version-1.json new file mode 100644 index 00000000000..6584d8d2aea --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1.json @@ -0,0 +1,1067 @@ +{ + "description": "CRUD Api Version 1", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.7" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "deprecationErrors": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "database": { + "id": "adminDatabase", + "client": "client", + "databaseName": "admin" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "aggregate on collection appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "collection", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "aggregate on database appends declared API version", + "operations": [ + { + "name": "aggregate", + "object": "adminDatabase", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "bulkWrite appends declared API version", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 6, + "x": 66 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 7 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "ordered": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "countDocuments appends declared API version", + "operations": [ + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$gt": 11 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "x": { + "$gt": 11 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "deleteMany appends declared API version", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "x": { + "$nin": [ + 24, + 34 + ] + } + }, + "limit": 0 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "deleteOne appends declared API version", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 7 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 7 + }, + "limit": 1 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "distinct appends declared API version", + "operations": [ + { + "name": "distinct", + "object": "collection", + "arguments": { + "fieldName": "x", + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "x", + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount appends declared API version", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "test", + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "find command with declared API version appends to the command, but getMore does not", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "batchSize": 3 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "findOneAndDelete appends declared API version", + "operations": [ + { + "name": "findOneAndDelete", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "remove": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "findOneAndReplace appends declared API version", + "operations": [ + { + "name": "findOneAndReplace", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "findOneAndUpdate appends declared API version", + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "insertMany appends declared API version", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "insertOne appends declared API version", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 6, + "x": 66 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "replaceOne appends declared API version", + "operations": [ + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "updateMany appends declared API version", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "updateOne appends declared API version", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$inc": { + "x": 1 + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/versioned-api/crud-api-version-1.yml b/test/spec/versioned-api/crud-api-version-1.yml new file mode 100644 index 00000000000..250f29ba40f --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1.yml @@ -0,0 +1,388 @@ +description: "CRUD Api Version 1" + +schemaVersion: "1.1" + +runOnRequirements: + - minServerVersion: "4.7" + +createEntities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + serverApi: + version: "1" + # Deprecation errors is set to true to ensure that drivers don't use any + # deprecated server API in their logic. + deprecationErrors: true + - database: + id: &database database + client: *client + databaseName: &databaseName versioned-api-tests + - database: + id: &adminDatabase adminDatabase + client: *client + databaseName: &adminDatabaseName admin + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + +_yamlAnchors: + versions: + - &expectedApiVersion + apiVersion: "1" + apiStrict: { $$unsetOrMatches: false } + apiDeprecationErrors: true + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + +tests: + - description: "aggregate on collection appends declared API version" + operations: + - name: aggregate + object: *collection + arguments: + pipeline: &pipeline + - $sort: { x : 1 } + - $match: { _id: { $gt: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: *pipeline + <<: *expectedApiVersion + + - description: "aggregate on database appends declared API version" + operations: + - name: aggregate + object: *adminDatabase + arguments: + pipeline: &pipeline + - $listLocalSessions: {} + - $limit: 1 + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: 1 + pipeline: *pipeline + <<: *expectedApiVersion + + - description: "bulkWrite appends declared API version" + operations: + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 6, x: 66 } + - updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - deleteMany: + filter: { x: { $nin: [ 24, 34 ] } } + - updateMany: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + - deleteOne: + filter: { _id: 7 } + - replaceOne: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + upsert: true + ordered: true + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 2 }, u: { $inc: { x: 1 } } } + <<: *expectedApiVersion + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { x: { $nin: [ 24, 34 ] } }, limit: 0 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: { $gt: 1 } }, u: { $inc: { x: 1 } }, multi: true } + <<: *expectedApiVersion + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { _id: 7 }, limit: 1 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 4 }, u: { _id: 4, x: 44 }, upsert: true } + <<: *expectedApiVersion + + - description: "countDocuments appends declared API version" + operations: + - name: countDocuments + object: *collection + arguments: + filter: &filter + x : { $gt: 11 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: + - { $match: *filter } + - { $group: { _id: 1, n: { $sum: 1 } } } + <<: *expectedApiVersion + + - description: "deleteMany appends declared API version" + operations: + - name: deleteMany + object: *collection + arguments: + filter: { x: { $nin: [ 24, 34 ] } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { x: { $nin: [ 24, 34 ] } }, limit: 0 } + <<: *expectedApiVersion + + - description: "deleteOne appends declared API version" + operations: + - name: deleteOne + object: *collection + arguments: + filter: { _id: 7 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + delete: *collectionName + deletes: + - { q: { _id: 7 }, limit: 1 } + <<: *expectedApiVersion + + - description: "distinct appends declared API version" + operations: + - name: distinct + object: *collection + arguments: + fieldName: x + filter: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + distinct: *collectionName + key: x + <<: *expectedApiVersion + + - description: "estimatedDocumentCount appends declared API version" + operations: + - name: estimatedDocumentCount + object: *collection + arguments: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + count: *collectionName + <<: *expectedApiVersion + + - description: "find command with declared API version appends to the command, but getMore does not" + operations: + - name: find + object: *collection + arguments: + filter: {} + sort: { _id: 1 } + batchSize: 3 + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + find: *collectionName + <<: *expectedApiVersion + - commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + apiVersion: { $$exists: false } + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } + + - description: "findOneAndDelete appends declared API version" + operations: + - name: findOneAndDelete + object: *collection + arguments: + filter: &filter { _id: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + remove: true + <<: *expectedApiVersion + + - description: "findOneAndReplace appends declared API version" + operations: + - name: findOneAndReplace + object: *collection + arguments: + filter: &filter { _id: 1 } + replacement: &replacement { x: 33 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + update: *replacement + <<: *expectedApiVersion + + - description: "findOneAndUpdate appends declared API version" + operations: + - name: findOneAndUpdate + object: collection + arguments: + filter: &filter { _id: 1 } + update: &update { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + findAndModify: *collectionName + query: *filter + update: *update + <<: *expectedApiVersion + + - description: "insertMany appends declared API version" + operations: + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + <<: *expectedApiVersion + + - description: "insertOne appends declared API version" + operations: + - name: insertOne + object: *collection + arguments: + document: { _id: 6, x: 66 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 6, x: 66 } + <<: *expectedApiVersion + + - description: "replaceOne appends declared API version" + operations: + - name: replaceOne + object: *collection + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + upsert: true + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 4 }, u: { _id: 4, x: 44 }, upsert: true } + <<: *expectedApiVersion + + - description: "updateMany appends declared API version" + operations: + - name: updateMany + object: *collection + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: { $gt: 1 } }, u: { $inc: { x: 1 } }, multi: true } + <<: *expectedApiVersion + + - description: "updateOne appends declared API version" + operations: + - name: updateOne + object: *collection + arguments: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + update: *collectionName + updates: + - { q: { _id: 2 }, u: { $inc: { x: 1 } } } + <<: *expectedApiVersion diff --git a/test/spec/versioned-api/runcommand-helper-no-api-version-declared.json b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.json new file mode 100644 index 00000000000..65c24ef4604 --- /dev/null +++ b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.json @@ -0,0 +1,117 @@ +{ + "description": "RunCommand helper: No API version declared", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.7", + "serverParameters": { + "requireApiVersion": false + } + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + } + ], + "tests": [ + { + "description": "runCommand does not inspect or change the command document", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1, + "apiVersion": "server_will_never_support_this_api_version" + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "apiVersion": "server_will_never_support_this_api_version", + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + }, + "commandName": "ping", + "databaseName": "versioned-api-tests" + } + } + ] + } + ] + }, + { + "description": "runCommand does not prevent sending invalid API version declarations", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1, + "apiStrict": true + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "apiVersion": { + "$$exists": false + }, + "apiStrict": true, + "apiDeprecationErrors": { + "$$exists": false + } + }, + "commandName": "ping", + "databaseName": "versioned-api-tests" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/versioned-api/runcommand-helper-no-api-version-declared.yml b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.yml new file mode 100644 index 00000000000..dc31897e2e0 --- /dev/null +++ b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.yml @@ -0,0 +1,67 @@ +description: "RunCommand helper: No API version declared" + +schemaVersion: "1.1" + +runOnRequirements: + - minServerVersion: "4.7" + serverParameters: + requireApiVersion: false + +createEntities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + - database: + id: &database database + client: *client + databaseName: &databaseName versioned-api-tests + +tests: + - description: "runCommand does not inspect or change the command document" + operations: + - name: runCommand + object: *database + arguments: + commandName: ping + command: + ping: 1 + apiVersion: "server_will_never_support_this_api_version" + expectError: + isError: true + isClientError: false + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + apiVersion: "server_will_never_support_this_api_version" + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } + commandName: ping + databaseName: *databaseName + + - description: "runCommand does not prevent sending invalid API version declarations" + operations: + - name: runCommand + object: *database + arguments: + commandName: ping + command: + ping: 1 + apiStrict: true + expectError: + isError: true + isClientError: false + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + apiVersion: { $$exists: false } + apiStrict: true + apiDeprecationErrors: { $$exists: false } + commandName: ping + databaseName: *databaseName diff --git a/test/spec/versioned-api/transaction-handling.json b/test/spec/versioned-api/transaction-handling.json new file mode 100644 index 00000000000..64e9706b5ef --- /dev/null +++ b/test/spec/versioned-api/transaction-handling.json @@ -0,0 +1,388 @@ +{ + "description": "Transaction handling", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.7", + "topologies": [ + "replicaset", + "sharded-replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "versioned-api-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + }, + { + "session": { + "id": "session", + "client": "client" + } + } + ], + "_yamlAnchors": { + "versions": [ + { + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + }, + { + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + ] + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "versioned-api-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "Only the first command in a transaction declares an API version", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 6, + "x": 66 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 7, + "x": 77 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "startTransaction": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 7, + "x": 77 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "Committing a transaction twice does not append server API options", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset" + ] + } + ], + "operations": [ + { + "name": "startTransaction", + "object": "session" + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 6, + "x": 66 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "session": "session", + "document": { + "_id": 7, + "x": 77 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session" + }, + { + "name": "commitTransaction", + "object": "session" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 6, + "x": 66 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "startTransaction": true, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 7, + "x": 77 + } + ], + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": { + "$$exists": false + }, + "apiStrict": { + "$$exists": false + }, + "apiDeprecationErrors": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} + diff --git a/test/spec/versioned-api/transaction-handling.yml b/test/spec/versioned-api/transaction-handling.yml new file mode 100644 index 00000000000..97616f6f7a4 --- /dev/null +++ b/test/spec/versioned-api/transaction-handling.yml @@ -0,0 +1,140 @@ +description: "Transaction handling" + +schemaVersion: "1.1" + +runOnRequirements: + - minServerVersion: "4.7" + topologies: [ replicaset, sharded-replicaset ] + +createEntities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + serverApi: + version: "1" + - database: + id: &database database + client: *client + databaseName: &databaseName versioned-api-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + - session: + id: &session session + client: *client + +_yamlAnchors: + versions: + - &expectedApiVersion + apiVersion: "1" + apiStrict: { $$unsetOrMatches: false } + apiDeprecationErrors: { $$unsetOrMatches: false } + - &noApiVersion + apiVersion: { $$exists: false } + apiStrict: { $$exists: false } + apiDeprecationErrors: { $$exists: false } + + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + +tests: + - description: "Only the first command in a transaction declares an API version" + runOnRequirements: + - topologies: [ replicaset, sharded-replicaset ] + operations: + - name: startTransaction + object: *session + - name: insertOne + object: *collection + arguments: + session: *session + document: { _id: 6, x: 66 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 6 } } } + - name: insertOne + object: *collection + arguments: + session: *session + document: { _id: 7, x: 77 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 7 } } } + - name: commitTransaction + object: *session + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: [ { _id: 6, x: 66 } ] + lsid: { $$sessionLsid: *session } + startTransaction: true + <<: *expectedApiVersion + - commandStartedEvent: + command: + insert: *collectionName + documents: [ { _id: 7, x: 77 } ] + lsid: { $$sessionLsid: *session } + <<: *noApiVersion + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + <<: *noApiVersion + - description: "Committing a transaction twice does not append server API options" + runOnRequirements: + - topologies: [ replicaset, sharded-replicaset ] + operations: + - name: startTransaction + object: *session + - name: insertOne + object: *collection + arguments: + session: *session + document: { _id: 6, x: 66 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 6 } } } + - name: insertOne + object: *collection + arguments: + session: *session + document: { _id: 7, x: 77 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 7 } } } + - name: commitTransaction + object: *session + - name: commitTransaction + object: *session + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: [ { _id: 6, x: 66 } ] + lsid: { $$sessionLsid: *session } + startTransaction: true + <<: *expectedApiVersion + - commandStartedEvent: + command: + insert: *collectionName + documents: [ { _id: 7, x: 77 } ] + lsid: { $$sessionLsid: *session } + <<: *noApiVersion + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + <<: *noApiVersion + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + <<: *noApiVersion + diff --git a/test/tools/runner/config.js b/test/tools/runner/config.js index 15d171895af..34b2435fe99 100644 --- a/test/tools/runner/config.js +++ b/test/tools/runner/config.js @@ -40,6 +40,7 @@ class TestConfiguration { this.topologyType = context.topologyType; this.version = context.version; this.clientSideEncryption = context.clientSideEncryption; + this.serverApi = context.serverApi; this.parameters = undefined; this.options = { hosts, @@ -96,16 +97,17 @@ class TestConfiguration { } newClient(dbOptions, serverOptions) { + const defaultOptions = { minHeartbeatFrequencyMS: 100 }; + if (this.serverApi) { + Object.assign(defaultOptions, { serverApi: this.serverApi }); + } // support MongoClient constructor form (url, options) for `newClient` if (typeof dbOptions === 'string') { - return new MongoClient( - dbOptions, - Object.assign({ minHeartbeatFrequencyMS: 100 }, serverOptions) - ); + return new MongoClient(dbOptions, Object.assign(defaultOptions, serverOptions)); } dbOptions = dbOptions || {}; - serverOptions = Object.assign({}, { minHeartbeatFrequencyMS: 100 }, serverOptions); + serverOptions = Object.assign({}, defaultOptions, serverOptions); // Fall back let dbHost = (serverOptions && serverOptions.host) || this.options.host; @@ -164,6 +166,7 @@ class TestConfiguration { if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) { throw new Error(`Cannot use options to specify host/port, must be in ${connectionString}`); } + return new MongoClient(connectionString, serverOptions); } diff --git a/test/tools/runner/filters/api_version_filter.js b/test/tools/runner/filters/api_version_filter.js new file mode 100755 index 00000000000..83c00b20b70 --- /dev/null +++ b/test/tools/runner/filters/api_version_filter.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Filter for the MongoDB API Version required for the test + * + * example: + * metadata: { + * requires: { + * apiVersion: '1' + * } + * } + */ +class ApiVersionFilter { + constructor() { + // Get environmental variables that are known + this.apiVersion = process.env.MONGODB_API_VERSION; + } + + filter(test) { + if (!test.metadata) return true; + if (!test.metadata.requires) return true; + const apiVersion = test.metadata.requires.apiVersion; + + // setting to false skips this test when an apiVersion is required + if (apiVersion === false) return !this.apiVersion; + // setting to true requires some apiVersion be specified + if (apiVersion === true) return !!this.apiVersion; + + // if there's no metadata requirement, always run + if (apiVersion == null) return true; + + // otherwise attempt a direct match + return apiVersion === this.apiVersion; + } +} + +module.exports = ApiVersionFilter; diff --git a/test/tools/runner/index.js b/test/tools/runner/index.js index 02f125a2607..313f72688e1 100644 --- a/test/tools/runner/index.js +++ b/test/tools/runner/index.js @@ -9,6 +9,7 @@ const mock = require('../mock'); const wtfnode = require('wtfnode'); const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017'; +const MONGODB_API_VERSION = process.env.MONGODB_API_VERSION; const filters = []; function initializeFilters(client, callback) { @@ -60,7 +61,8 @@ before(function (_done) { // )} topology` // ); - const client = new MongoClient(MONGODB_URI); + const options = MONGODB_API_VERSION ? { serverApi: MONGODB_API_VERSION } : {}; + const client = new MongoClient(MONGODB_URI, options); const done = err => client.close(err2 => _done(err || err2)); client.connect(err => { @@ -75,6 +77,11 @@ before(function (_done) { return; } + // Ensure test MongoClients set a serverApi parameter when required + if (MONGODB_API_VERSION) { + context.serverApi = MONGODB_API_VERSION; + } + // replace this when mocha supports dynamic skipping with `afterEach` filterOutTests(this._runnable.parent); this.configuration = new TestConfiguration(MONGODB_URI, context); diff --git a/test/unit/legacy_compat.test.js b/test/unit/legacy_compat.test.js index 8d7a4e0f8d9..d8fd20a6c31 100644 --- a/test/unit/legacy_compat.test.js +++ b/test/unit/legacy_compat.test.js @@ -1,11 +1,10 @@ 'use strict'; const { expect } = require('chai'); -const { MongoClient } = require('../../src'); describe('Legacy 3.x features', function () { it('Should have bson defined on topology', function () { - const client = new MongoClient(this.configuration.url()); + const client = this.configuration.newClient(this.configuration.url()); return client .connect() .then(client => { @@ -18,13 +17,13 @@ describe('Legacy 3.x features', function () { it('Should allow legacy option useUnifiedTopology', function () { const url = this.configuration.url(); - expect(() => new MongoClient(url, { useUnifiedTopology: true })).to.not.throw; - expect(() => new MongoClient(url, { useUnifiedTopology: false })).to.not.throw; + expect(() => this.configuration.newClient(url, { useUnifiedTopology: true })).to.not.throw; + expect(() => this.configuration.newClient(url, { useUnifiedTopology: false })).to.not.throw; }); it('Should allow legacy option useNewUrlParser', function () { const url = this.configuration.url(); - expect(() => new MongoClient(url, { useNewUrlParser: true })).to.not.throw; - expect(() => new MongoClient(url, { useNewUrlParser: false })).to.not.throw; + expect(() => this.configuration.newClient(url, { useNewUrlParser: true })).to.not.throw; + expect(() => this.configuration.newClient(url, { useNewUrlParser: false })).to.not.throw; }); });