From 2862914e2dfcbd64cb2100a233faae7a42cbe8d3 Mon Sep 17 00:00:00 2001 From: emadum Date: Wed, 16 Jun 2021 15:58:22 -0400 Subject: [PATCH 1/7] feat(NODE-3191): backport versioned api --- .evergreen/config.yml | 27 +- .evergreen/config.yml.in | 7 +- .evergreen/generate_evergreen_tasks.js | 24 + .evergreen/run-tests.sh | 2 +- lib/cmap/connection.js | 41 +- lib/cmap/connection_pool.js | 1 + lib/collection.js | 2 +- lib/core/connection/connect.js | 8 +- lib/core/sdam/monitor.js | 8 +- lib/core/sdam/server.js | 2 + lib/core/sdam/topology.js | 7 +- lib/core/uri_parser.js | 5 + lib/core/wireprotocol/command.js | 12 + lib/core/wireprotocol/kill_cursors.js | 5 +- lib/mongo_client.js | 1 + lib/operations/connect.js | 1 + lib/operations/estimated_document_count.js | 50 +- lib/topologies/native_topology.js | 2 + package.json | 4 +- test/functional/apm.test.js | 1 + test/functional/authentication.test.js | 5 +- test/functional/buffering_proxy.test.js | 12 +- test/functional/change_stream.test.js | 12 +- test/functional/cmap/connection.test.js | 83 +- test/functional/collations.test.js | 39 +- test/functional/collection.test.js | 2 +- test/functional/command_write_concern.test.js | 6 +- test/functional/connection.test.js | 4 +- test/functional/core/pool.test.js | 81 +- .../core/single_mocks/compression.test.js | 5 + test/functional/crud_spec.test.js | 19 + test/functional/cursor.test.js | 98 +- test/functional/deprecate_warning.test.js | 3 +- test/functional/max_staleness.test.js | 12 +- test/functional/multiple_db.test.js | 3 +- test/functional/replicaset_mock.test.js | 4 +- test/functional/retryable_reads.test.js | 3 +- test/functional/transactions.test.js | 1 + .../unified-spec-runner/entities.ts | 19 +- test/functional/unified-spec-runner/match.ts | 10 +- test/functional/versioned-api.test.js | 40 + test/functional/view.test.js | 3 +- .../crud/unified/estimatedDocumentCount.json | 562 +++++++++ .../crud/unified/estimatedDocumentCount.yml | 267 ++++ .../retryable-reads/count-serverErrors.json | 10 +- .../retryable-reads/count-serverErrors.yml | 10 +- .../countDocuments-serverErrors.json | 10 +- .../countDocuments-serverErrors.yml | 10 +- .../estimatedDocumentCount-4.9.json | 246 ++++ .../estimatedDocumentCount-4.9.yml | 60 + ...son => estimatedDocumentCount-pre4.9.json} | 2 + ....yml => estimatedDocumentCount-pre4.9.yml} | 2 + ...timatedDocumentCount-serverErrors-4.9.json | 911 ++++++++++++++ ...stimatedDocumentCount-serverErrors-4.9.yml | 146 +++ ...tedDocumentCount-serverErrors-pre4.9.json} | 12 +- ...atedDocumentCount-serverErrors-pre4.9.yml} | 12 +- .../integration/connectTimeoutMS.json | 3 +- .../integration/connectTimeoutMS.yml | 2 +- .../integration/isMaster-command-error.json | 16 +- .../integration/isMaster-command-error.yml | 11 +- .../integration/isMaster-network-error.json | 16 +- .../integration/isMaster-network-error.yml | 11 +- .../integration/isMaster-timeout.json | 14 +- .../integration/isMaster-timeout.yml | 9 +- .../crud-api-version-1-strict.json | 1115 +++++++++++++++++ .../crud-api-version-1-strict.yml | 417 ++++++ .../versioned-api/crud-api-version-1.json | 1107 ++++++++++++++++ .../spec/versioned-api/crud-api-version-1.yml | 411 ++++++ ...ommand-helper-no-api-version-declared.json | 127 ++ ...command-helper-no-api-version-declared.yml | 75 ++ .../versioned-api/transaction-handling.json | 348 +++++ .../versioned-api/transaction-handling.yml | 128 ++ test/tools/runner/config.js | 15 +- .../runner/filters/api_version_filter.js | 37 + test/tools/runner/index.js | 16 +- test/unit/bulk_write.test.js | 2 +- test/unit/bypass_validation.test.js | 8 +- test/unit/client.test.js | 2 +- test/unit/client_metadata.test.js | 2 +- test/unit/cmap/connection.test.js | 6 +- test/unit/cmap/connection_pool.test.js | 16 +- test/unit/core/common.js | 6 +- test/unit/core/connect.test.js | 4 +- test/unit/core/response_test.js.test.js | 2 +- test/unit/core/scram_iterations.test.js | 6 +- test/unit/core/sessions.test.js | 4 +- test/unit/core/single/sessions.test.js | 3 +- test/unit/core/write_concern_error.test.js | 4 +- test/unit/create_index_error.test.js | 2 +- test/unit/db_list_collections.test.js | 2 +- test/unit/sdam/monitoring.test.js | 12 +- test/unit/sdam/srv_polling.test.js | 2 +- test/unit/sdam/topology.test.js | 8 +- test/unit/sessions/client.test.js | 12 +- test/unit/sessions/collection.test.js | 8 +- 95 files changed, 6556 insertions(+), 367 deletions(-) create mode 100644 test/functional/versioned-api.test.js create mode 100644 test/spec/crud/unified/estimatedDocumentCount.json create mode 100644 test/spec/crud/unified/estimatedDocumentCount.yml create mode 100644 test/spec/retryable-reads/estimatedDocumentCount-4.9.json create mode 100644 test/spec/retryable-reads/estimatedDocumentCount-4.9.yml rename test/spec/retryable-reads/{estimatedDocumentCount.json => estimatedDocumentCount-pre4.9.json} (97%) rename test/spec/retryable-reads/{estimatedDocumentCount.yml => estimatedDocumentCount-pre4.9.yml} (96%) create mode 100644 test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.json create mode 100644 test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.yml rename test/spec/retryable-reads/{estimatedDocumentCount-serverErrors.json => estimatedDocumentCount-serverErrors-pre4.9.json} (96%) rename test/spec/retryable-reads/{estimatedDocumentCount-serverErrors.yml => estimatedDocumentCount-serverErrors-pre4.9.yml} (92%) create mode 100644 test/spec/versioned-api/crud-api-version-1-strict.json create mode 100644 test/spec/versioned-api/crud-api-version-1-strict.yml create mode 100644 test/spec/versioned-api/crud-api-version-1.json create mode 100644 test/spec/versioned-api/crud-api-version-1.yml create mode 100644 test/spec/versioned-api/runcommand-helper-no-api-version-declared.json create mode 100644 test/spec/versioned-api/runcommand-helper-no-api-version-declared.yml create mode 100644 test/spec/versioned-api/transaction-handling.json create mode 100644 test/spec/versioned-api/transaction-handling.yml create mode 100755 test/tools/runner/filters/api_version_filter.js diff --git a/.evergreen/config.yml b/.evergreen/config.yml index e1d85d77c7d..63d337408c4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -79,6 +79,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: @@ -111,8 +112,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 @@ -1144,6 +1147,23 @@ tasks: - func: run tests vars: UNIFIED: 1 + - 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' + UNIFIED: 1 + NO_EXIT: '' - name: test-atlas-connectivity tags: - atlas-connect @@ -1616,6 +1636,7 @@ buildvariants: - test-2.6-server-unified - test-2.6-replica_set-unified - test-2.6-sharded_cluster-unified + - test-latest-server-v1-api - test-atlas-connectivity - test-auth-kerberos-legacy - test-auth-kerberos-unified @@ -1726,6 +1747,7 @@ buildvariants: - test-2.6-server-unified - test-2.6-replica_set-unified - test-2.6-sharded_cluster-unified + - test-latest-server-v1-api - test-atlas-connectivity - test-auth-kerberos-legacy - test-auth-kerberos-unified @@ -1901,6 +1923,7 @@ buildvariants: - test-3.2-server-unified - test-3.2-replica_set-unified - test-3.2-sharded_cluster-unified + - test-latest-server-v1-api - test-atlas-connectivity - test-auth-kerberos-legacy - test-auth-kerberos-unified diff --git a/.evergreen/config.yml.in b/.evergreen/config.yml.in index 5aa2c08f8eb..7c5787066ab 100644 --- a/.evergreen/config.yml.in +++ b/.evergreen/config.yml.in @@ -96,6 +96,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 @@ -130,8 +131,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 9fee00942a1..597c62d934f 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -74,6 +74,7 @@ const OPERATING_SYSTEMS = [ ) ); +// TODO: NODE-3060: enable skipped tests on windows const WINDOWS_SKIP_TAGS = new Set([ 'atlas-connect', 'auth' @@ -113,6 +114,29 @@ MONGODB_VERSIONS.forEach(mongoVersion => { 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', + UNIFIED: 1, + NO_EXIT: '' + } + } + ] +}); TASKS.push( { diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 09cfe8f1731..75cda76cd9c 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -59,4 +59,4 @@ nvm use 12 npm run build:unified nvm use "$NODE_VERSION" -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/lib/cmap/connection.js b/lib/cmap/connection.js index 81925b96218..612252c86b9 100644 --- a/lib/cmap/connection.js +++ b/lib/cmap/connection.js @@ -37,6 +37,8 @@ class Connection extends EventEmitter { this.port = options.port || 27017; this.monitorCommands = typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false; + this.serverApi = options.serverApi; + this.closed = false; this.destroyed = false; @@ -170,33 +172,58 @@ class Connection extends EventEmitter { }); } + applyApiVersion(options) { + if (this.serverApi) { + options.serverApi = this.serverApi; + } + return options; + } + // Wire protocol methods command(ns, cmd, options, callback) { - wp.command(makeServerTrampoline(this), ns, cmd, options, callback); + if (typeof options === 'function') { + callback = options; + options = {}; + } + wp.command(makeServerTrampoline(this), ns, cmd, this.applyApiVersion(options), callback); } query(ns, cmd, cursorState, options, callback) { - wp.query(makeServerTrampoline(this), ns, cmd, cursorState, options, callback); + wp.query( + makeServerTrampoline(this), + ns, + cmd, + cursorState, + this.applyApiVersion(options), + callback + ); } getMore(ns, cursorState, batchSize, options, callback) { - wp.getMore(makeServerTrampoline(this), ns, cursorState, batchSize, options, callback); + wp.getMore( + makeServerTrampoline(this), + ns, + cursorState, + batchSize, + this.applyApiVersion(options), + callback + ); } killCursors(ns, cursorState, callback) { - wp.killCursors(makeServerTrampoline(this), ns, cursorState, callback); + wp.killCursors(makeServerTrampoline(this), ns, cursorState, this.applyApiVersion({}), callback); } insert(ns, ops, options, callback) { - wp.insert(makeServerTrampoline(this), ns, ops, options, callback); + wp.insert(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback); } update(ns, ops, options, callback) { - wp.update(makeServerTrampoline(this), ns, ops, options, callback); + wp.update(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback); } remove(ns, ops, options, callback) { - wp.remove(makeServerTrampoline(this), ns, ops, options, callback); + wp.remove(makeServerTrampoline(this), ns, ops, this.applyApiVersion(options), callback); } } diff --git a/lib/cmap/connection_pool.js b/lib/cmap/connection_pool.js index 4500d9a280f..e11148cfd57 100644 --- a/lib/cmap/connection_pool.js +++ b/lib/cmap/connection_pool.js @@ -41,6 +41,7 @@ const VALID_POOL_OPTIONS = new Set([ 'ssl', 'bson', 'connectionType', + 'serverApi', 'monitorCommands', 'socketTimeout', 'credentials', diff --git a/lib/collection.js b/lib/collection.js index 09b8304b684..157bcdeb2c4 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1524,7 +1524,7 @@ Collection.prototype.count = deprecate(function(query, options, callback) { return executeOperation( this.s.topology, - new EstimatedDocumentCountOperation(this, query, options), + new CountDocumentsOperation(this, query, options), callback ); }, 'collection.count is deprecated, and will be removed in a future version.' + diff --git a/lib/core/connection/connect.js b/lib/core/connection/connect.js index bd10535466e..9442c70b68f 100644 --- a/lib/core/connection/connect.js +++ b/lib/core/connection/connect.js @@ -108,6 +108,11 @@ function performInitialHandshake(conn, options, _callback) { return; } + if ('isWritablePrimary' in response) { + // Provide pre-hello-style response document. + response.ismaster = response.isWritablePrimary; + } + const supportedServerErr = checkSupportedServer(response, options); if (supportedServerErr) { callback(supportedServerErr); @@ -158,11 +163,12 @@ function performInitialHandshake(conn, options, _callback) { function prepareHandshakeDocument(authContext, callback) { const options = authContext.options; + const serverApi = authContext.connection.serverApi; const compressors = options.compression && options.compression.compressors ? options.compression.compressors : []; const handshakeDoc = { - ismaster: true, + [serverApi ? 'hello' : 'ismaster']: true, client: options.metadata || makeClientMetadata(options), compression: compressors }; diff --git a/lib/core/sdam/monitor.js b/lib/core/sdam/monitor.js index 701fb59b28a..d87f64787bd 100644 --- a/lib/core/sdam/monitor.js +++ b/lib/core/sdam/monitor.js @@ -203,8 +203,9 @@ function checkServer(monitor, callback) { const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS; const topologyVersion = monitor[kServer].description.topologyVersion; const isAwaitable = topologyVersion != null; + const serverApi = monitor[kConnection].serverApi; - const cmd = { ismaster: true }; + const cmd = { [serverApi ? 'hello' : 'ismaster']: true }; const options = { socketTimeout: connectTimeoutMS }; if (isAwaitable) { @@ -228,6 +229,11 @@ function checkServer(monitor, callback) { const isMaster = result.result; const rttPinger = monitor[kRTTPinger]; + if ('isWritablePrimary' in isMaster) { + // Provide pre-hello-style response document. + isMaster.ismaster = isMaster.isWritablePrimary; + } + const duration = isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start); diff --git a/lib/core/sdam/server.js b/lib/core/sdam/server.js index 26aeb5ed234..e015578b65d 100644 --- a/lib/core/sdam/server.js +++ b/lib/core/sdam/server.js @@ -112,6 +112,7 @@ class Server extends EventEmitter { credentials: options.credentials, topology }; + this.serverApi = options.serverApi; // create the connection pool // NOTE: this used to happen in `connect`, we supported overriding pool options there @@ -245,6 +246,7 @@ class Server extends EventEmitter { if (typeof options === 'function') { (callback = options), (options = {}), (options = options || {}); } + options.serverApi = this.serverApi; if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) { callback(new MongoError('server is closed')); diff --git a/lib/core/sdam/topology.js b/lib/core/sdam/topology.js index 1ca73ee48ab..697a59e6b91 100644 --- a/lib/core/sdam/topology.js +++ b/lib/core/sdam/topology.js @@ -201,6 +201,7 @@ class Topology extends EventEmitter { // timer management connectionTimers: new Set() }; + this.serverApi = options.serverApi; if (options.srvHost) { this.s.srvPoller = @@ -495,7 +496,11 @@ class Topology extends EventEmitter { this.command( 'admin.$cmd', { endSessions: sessions }, - { readPreference: ReadPreference.primaryPreferred, noResponse: true }, + { + readPreference: ReadPreference.primaryPreferred, + noResponse: true, + serverApi: this.serverApi + }, () => { // intentionally ignored, per spec if (typeof callback === 'function') callback(); diff --git a/lib/core/uri_parser.js b/lib/core/uri_parser.js index 46cc74eb418..1d3980858b6 100644 --- a/lib/core/uri_parser.js +++ b/lib/core/uri_parser.js @@ -432,6 +432,11 @@ function parseQueryString(query, options) { } const normalizedKey = key.toLowerCase(); + if (normalizedKey === 'serverapi') { + throw new MongoParseError( + 'URI cannot contain `serverApi`, it can only be passed to the client' + ); + } const parsedValue = FILE_PATH_OPTIONS.has(normalizedKey) ? value : parseQueryStringItemValue(normalizedKey, value); diff --git a/lib/core/wireprotocol/command.js b/lib/core/wireprotocol/command.js index 9e687f8c957..94f010eed89 100644 --- a/lib/core/wireprotocol/command.js +++ b/lib/core/wireprotocol/command.js @@ -48,6 +48,18 @@ function _command(server, ns, cmd, options, callback) { const serverClusterTime = server.clusterTime; let clusterTime = serverClusterTime; let finalCmd = Object.assign({}, cmd); + + const serverApi = options.serverApi; + if (serverApi) { + finalCmd.apiVersion = serverApi.version || serverApi; + if (serverApi.strict != null) { + finalCmd.apiStrict = serverApi.strict; + } + if (serverApi.deprecationErrors != null) { + finalCmd.apiDeprecationErrors = serverApi.deprecationErrors; + } + } + if (hasSessionSupport(server) && session) { const sessionClusterTime = session.clusterTime; if ( diff --git a/lib/core/wireprotocol/kill_cursors.js b/lib/core/wireprotocol/kill_cursors.js index 22744794f90..cefebdf6817 100644 --- a/lib/core/wireprotocol/kill_cursors.js +++ b/lib/core/wireprotocol/kill_cursors.js @@ -8,8 +8,10 @@ const maxWireVersion = require('../utils').maxWireVersion; const emitWarning = require('../utils').emitWarning; const command = require('./command'); -function killCursors(server, ns, cursorState, callback) { +function killCursors(server, ns, cursorState, options, callback) { callback = typeof callback === 'function' ? callback : () => {}; + options = options || {}; + const cursorId = cursorState.cursorId; if (maxWireVersion(server) < 4) { @@ -45,7 +47,6 @@ function killCursors(server, ns, cursorState, callback) { cursors: [cursorId] }; - const options = {}; if (typeof cursorState.session === 'object') options.session = cursorState.session; command(server, ns, killCursorCmd, options, (err, result) => { diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 7e11512bf15..46f8a854284 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -155,6 +155,7 @@ const validOptions = require('./operations/connect').validOptions; * @property {number} [numberOfRetries] (**default**: 5) The number of retries for a tailable cursor * @property {boolean} [auto_reconnect] (**default**: true) Enable auto reconnecting for single server instances * @property {boolean} [monitorCommands] (**default**: false) Enable command monitoring for this client + * @property {string|ServerApi} [serverApi] (**default**: undefined) The server API version * @property {number} [minSize] If present, the connection pool will be initialized with minSize connections, and will never dip below minSize connections * @property {boolean} [useNewUrlParser] (**default**: true) Determines whether or not to use the new url parser. Enables the new, spec-compliant, url parser shipped in the core driver. This url parser fixes a number of problems with the original parser, and aims to outright replace that parser in the near future. Defaults to true, and must be explicitly set to false to use the legacy url parser. * @property {boolean} [useUnifiedTopology] Enables the new unified topology layer diff --git a/lib/operations/connect.js b/lib/operations/connect.js index 4533d2847be..47635bf0598 100644 --- a/lib/operations/connect.js +++ b/lib/operations/connect.js @@ -137,6 +137,7 @@ const validOptionNames = [ 'auto_reconnect', 'minSize', 'monitorCommands', + 'serverApi', 'retryWrites', 'retryReads', 'useNewUrlParser', diff --git a/lib/operations/estimated_document_count.js b/lib/operations/estimated_document_count.js index e2d65563d65..55f71aad330 100644 --- a/lib/operations/estimated_document_count.js +++ b/lib/operations/estimated_document_count.js @@ -3,39 +3,47 @@ const Aspect = require('./operation').Aspect; const defineAspects = require('./operation').defineAspects; const CommandOperationV2 = require('./command_v2'); +const maxWireVersion = require('../core/utils').maxWireVersion; class EstimatedDocumentCountOperation extends CommandOperationV2 { - constructor(collection, query, options) { - if (typeof options === 'undefined') { - options = query; - query = undefined; - } - + constructor(collection, options) { super(collection, options); this.collectionName = collection.s.namespace.collection; - if (query) { - this.query = query; - } } execute(server, callback) { - const options = this.options; - const cmd = { count: this.collectionName }; - - if (this.query) { - cmd.query = this.query; + if (maxWireVersion(server) < 12) { + return this.executeLegacy(server, callback); } + const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }]; + const cmd = { aggregate: this.collectionName, pipeline, cursor: {} }; - if (typeof options.skip === 'number') { - cmd.skip = options.skip; + if (typeof this.options.maxTimeMS === 'number') { + cmd.maxTimeMS = this.options.maxTimeMS; } - if (typeof options.limit === 'number') { - cmd.limit = options.limit; - } + super.executeCommand(server, cmd, (err, response) => { + if (err && err.code !== 26) { + callback(err); + return; + } + + callback( + undefined, + (response && + response.cursor && + response.cursor.firstBatch && + response.cursor.firstBatch[0].n) || + 0 + ); + }); + } + + executeLegacy(server, callback) { + const cmd = { count: this.collectionName }; - if (options.hint) { - cmd.hint = options.hint; + if (typeof this.options.maxTimeMS === 'number') { + cmd.maxTimeMS = this.options.maxTimeMS; } super.executeCommand(server, cmd, (err, response) => { diff --git a/lib/topologies/native_topology.js b/lib/topologies/native_topology.js index cb7d91dcdea..227cad3f1cf 100644 --- a/lib/topologies/native_topology.js +++ b/lib/topologies/native_topology.js @@ -44,6 +44,8 @@ class NativeTopology extends Topology { // Translate all the options to the core types clonedOptions = translateOptions(clonedOptions, socketOptions); + clonedOptions.serverApi = options.serverApi; + super(servers, clonedOptions); } diff --git a/package.json b/package.json index ae22bfaab2c..035f9d6d9ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mongodb", - "version": "3.6.9", + "version": "3.7.0", "description": "The official MongoDB driver for Node.js", "main": "index.js", "files": [ @@ -98,7 +98,7 @@ "check:tls": "mocha --opts '{}' test/manual/tls_support.test.js", "format": "npm run check:lint -- --fix", "release": "standard-version -i HISTORY.md", - "test": "npm run lint && mocha --recursive test/functional test/unit" + "test": "npm run check:lint && mocha --recursive test/functional test/unit" }, "homepage": "https://github.com/mongodb/node-mongodb-native", "optionalDependencies": { diff --git a/test/functional/apm.test.js b/test/functional/apm.test.js index 5a7350c02f6..465ac6edc3d 100644 --- a/test/functional/apm.test.js +++ b/test/functional/apm.test.js @@ -1089,6 +1089,7 @@ describe('APM', function() { } loadSpecTests('command-monitoring/legacy').forEach(scenario => { + if (scenario.name === 'command') return; // FIXME(NODE-3074): remove when `count` spec tests have been fixed describe(scenario.name, function() { scenario.tests.forEach(test => { const requirements = { topology: ['single', 'replicaset', 'sharded'] }; diff --git a/test/functional/authentication.test.js b/test/functional/authentication.test.js index 3c23063c13b..26c6cf7d313 100644 --- a/test/functional/authentication.test.js +++ b/test/functional/authentication.test.js @@ -10,7 +10,7 @@ describe('Authentication', function() { }); it('should still work for auth when using new url parser and no database is in url', { - metadata: { requires: { topology: ['single'] } }, + metadata: { requires: { topology: ['single'], apiVersion: false } }, // FIXME(NODE-3191) test: function(done) { const configuration = this.configuration; const username = 'testUser'; @@ -146,8 +146,9 @@ describe('Authentication', function() { * * @ignore */ + // FIXME(NODE-3191) it('should correctly call validateCollection using authenticatedMode', { - metadata: { requires: { topology: ['single', 'heap', 'wiredtiger'] } }, + metadata: { requires: { topology: ['single', 'heap', 'wiredtiger'], apiVersion: false } }, // The actual test we wish to run test: function(done) { diff --git a/test/functional/buffering_proxy.test.js b/test/functional/buffering_proxy.test.js index 933f3653d0a..33490eafd43 100644 --- a/test/functional/buffering_proxy.test.js +++ b/test/functional/buffering_proxy.test.js @@ -138,7 +138,7 @@ describe.skip('Buffering Proxy', function() { if (die) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[currentIsMasterIndex]); } else if (doc.insert) { request.reply({ ok: 1, n: 1 }); @@ -156,7 +156,7 @@ describe.skip('Buffering Proxy', function() { if (die || dieSecondary) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(firstSecondary[currentIsMasterIndex]); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -170,7 +170,7 @@ describe.skip('Buffering Proxy', function() { if (die || dieSecondary) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(secondSecondary[currentIsMasterIndex]); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -359,7 +359,7 @@ describe.skip('Buffering Proxy', function() { if (die || diePrimary) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[currentIsMasterIndex]); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -373,7 +373,7 @@ describe.skip('Buffering Proxy', function() { if (die) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(firstSecondary[currentIsMasterIndex]); } else if (doc.count) { request.reply({ ok: 1, n: 10 }); @@ -391,7 +391,7 @@ describe.skip('Buffering Proxy', function() { if (die) { request.connection.destroy(); } else { - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(secondSecondary[currentIsMasterIndex]); } else if (doc.count) { request.reply({ ok: 1, n: 10 }); diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index 458e7651c90..b2b558cde5e 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -1046,7 +1046,7 @@ describe('Change Streams', function() { const doc = request.document; // Create a server that responds to the initial aggregation to connect to the server, but not to subsequent getMore requests - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign( { @@ -1466,7 +1466,7 @@ describe('Change Streams', function() { const doc = request.document; // Create a server that responds to the initial aggregation to connect to the server, but not to subsequent getMore requests - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign( { @@ -1988,7 +1988,7 @@ describe('Change Streams', function() { class MockServerManager { constructor(config, commandIterators) { this.config = config; - this.cmdList = new Set(['ismaster', 'endSessions', 'aggregate', 'getMore']); + this.cmdList = new Set(['ismaster', 'hello', 'endSessions', 'aggregate', 'getMore']); this.database = 'test_db'; this.collection = 'test_coll'; this.ns = `${this.database}.${this.collection}`; @@ -2086,6 +2086,10 @@ describe('Change Streams', function() { }); } + hello() { + return this.ismaster(); + } + endSessions() { return { ok: 1 }; } @@ -2788,7 +2792,7 @@ context('NODE-2626', function() { it('changeStream should close if cursor id for initial aggregate is Long.ZERO', function(done) { mockServer.setMessageHandler(req => { const doc = req.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return req.reply(mock.DEFAULT_ISMASTER_36); } if (doc.aggregate) { diff --git a/test/functional/cmap/connection.test.js b/test/functional/cmap/connection.test.js index d86c476c079..cdffd8a04e8 100644 --- a/test/functional/cmap/connection.test.js +++ b/test/functional/cmap/connection.test.js @@ -11,59 +11,66 @@ describe('Connection', function() { return setupDatabase(this.configuration); }); - it('should execute a command against a server', function(done) { - const connectOptions = Object.assign( - { connectionType: Connection, bson: new BSON() }, - this.configuration.options - ); + it('should execute a command against a server', { + metadata: { requires: { apiVersion: false } }, + test: function(done) { + const connectOptions = Object.assign( + { connectionType: Connection, bson: new BSON() }, + this.configuration.options + ); - connect(connectOptions, (err, conn) => { - expect(err).to.not.exist; - this.defer(_done => conn.destroy(_done)); + connect(connectOptions, (err, conn) => { + expect(err).to.not.exist; + this.defer(_done => conn.destroy(_done)); - conn.command('admin.$cmd', { ismaster: 1 }, (err, result) => { - // NODE-2382: remove `result.result` when command returns just a raw response - const ismaster = result.result; + conn.command('admin.$cmd', { ismaster: 1 }, (err, result) => { + // NODE-2382: remove `result.result` when command returns just a raw response + const ismaster = result.result; - expect(err).to.not.exist; - expect(ismaster).to.exist; - expect(ismaster.ok).to.equal(1); - done(); + 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, bson: new BSON(), monitorCommands: true }, - this.configuration.options - ); + it('should emit command monitoring events', { + metadata: { requires: { apiVersion: false } }, + test: function(done) { + const connectOptions = Object.assign( + { connectionType: Connection, bson: new BSON(), monitorCommands: true }, + this.configuration.options + ); - connect(connectOptions, (err, conn) => { - expect(err).to.not.exist; - this.defer(_done => conn.destroy(_done)); + 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)); + 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('admin.$cmd', { ismaster: 1 }, (err, result) => { - // NODE-2382: remove `result.result` when command returns just a raw response - const ismaster = result.result; + conn.command('admin.$cmd', { ismaster: 1 }, (err, result) => { + // NODE-2382: remove `result.result` when command returns just a raw response + const ismaster = result.result; - expect(err).to.not.exist; - expect(ismaster).to.exist; - expect(ismaster.ok).to.equal(1); - expect(events).to.have.length(2); - done(); + 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', { metadata: { requires: { + apiVersion: false, os: '!win32' // NODE-2941: 240.0.0.1 doesnt work for windows } }, @@ -84,7 +91,7 @@ describe('Connection', function() { }); it('should support calling back multiple times on exhaust commands', { - metadata: { requires: { mongodb: '>=4.2.0', topology: ['single'] } }, + metadata: { requires: { mongodb: '>=4.2.0', topology: ['single'], apiVersion: false } }, test: function(done) { const ns = `${this.configuration.db}.$cmd`; const connectOptions = Object.assign( diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index 62dcbfb3760..743dc8646b3 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -25,7 +25,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.findAndModify) { commandResult = doc; @@ -66,7 +66,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.count) { commandResult = doc; @@ -102,7 +102,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.aggregate) { commandResult = doc; @@ -140,7 +140,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.distinct) { commandResult = doc; @@ -176,7 +176,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.group) { commandResult = doc; @@ -221,7 +221,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.mapReduce) { commandResult = doc; @@ -262,7 +262,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.delete) { commandResult = doc; @@ -298,7 +298,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.update) { commandResult = doc; @@ -336,7 +336,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.find) { commandResult = doc; @@ -372,7 +372,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.find) { commandResult = doc; @@ -410,7 +410,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.find) { commandResult = doc; @@ -447,7 +447,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.listCollections) { request.reply({ @@ -489,7 +489,7 @@ describe('Collation', function() { testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.find) { request.reply({ ok: 1 }); @@ -523,7 +523,7 @@ describe('Collation', function() { testContext.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.find) { request.reply({ ok: 1 }); @@ -560,7 +560,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.update) { commandResult = doc; @@ -613,7 +613,7 @@ describe('Collation', function() { testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.update) { request.reply({ ok: 1 }); @@ -661,7 +661,7 @@ describe('Collation', function() { let commandResult; testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.createIndexes) { commandResult = doc; @@ -678,6 +678,7 @@ describe('Collation', function() { .collection('test') .createIndex({ a: 1 }, { collation: { caseLevel: true } }) .then(() => { + delete commandResult.apiVersion; expect(commandResult).to.eql({ createIndexes: 'test', indexes: [{ name: 'a_1', key: { a: 1 }, collation: { caseLevel: true } }] @@ -699,7 +700,7 @@ describe('Collation', function() { testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.createIndexes) { request.reply({ ok: 1 }); @@ -734,7 +735,7 @@ describe('Collation', function() { testContext.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.createIndexes) { request.reply({ ok: 1 }); diff --git a/test/functional/collection.test.js b/test/functional/collection.test.js index 3cfe482cf3b..d2fa272d944 100644 --- a/test/functional/collection.test.js +++ b/test/functional/collection.test.js @@ -1079,7 +1079,7 @@ describe('Collection', function() { } } - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); diff --git a/test/functional/command_write_concern.test.js b/test/functional/command_write_concern.test.js index 7f3f5797658..89c2c59bf9a 100644 --- a/test/functional/command_write_concern.test.js +++ b/test/functional/command_write_concern.test.js @@ -56,7 +56,7 @@ class WriteConcernTest { primaryServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(self.serverStates.primary[0]); } else if (doc[resultKey]) { self.commandResult = doc; @@ -68,7 +68,7 @@ class WriteConcernTest { firstSecondaryServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(self.serverStates.firstSecondary[0]); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -77,7 +77,7 @@ class WriteConcernTest { arbiterServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(self.serverStates.arbiter[0]); } else if (doc.endSessions) { request.reply({ ok: 1 }); diff --git a/test/functional/connection.test.js b/test/functional/connection.test.js index 2f56b47ce66..8a3ed00ff88 100644 --- a/test/functional/connection.test.js +++ b/test/functional/connection.test.js @@ -325,7 +325,7 @@ describe('Connection', function() { * @ignore */ it('test connect good auth', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, // FIXME(NODE-3191) // The actual test we wish to run test: function(done) { @@ -361,7 +361,7 @@ describe('Connection', function() { * @ignore */ it('test connect good auth as option', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, // FIXME(NODE-3191) // The actual test we wish to run test: function(done) { diff --git a/test/functional/core/pool.test.js b/test/functional/core/pool.test.js index 77658999183..8c5ae9e9335 100644 --- a/test/functional/core/pool.test.js +++ b/test/functional/core/pool.test.js @@ -27,7 +27,7 @@ describe('Pool tests', function() { }); it('should correctly connect pool to single server', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { // Attempt to connect @@ -50,7 +50,7 @@ describe('Pool tests', function() { }); it('Should only listen on connect once', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { const pool = new Pool(null, { host: this.configuration.host, @@ -75,7 +75,7 @@ describe('Pool tests', function() { }); it('should correctly write ismaster operation to the server', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { // Attempt to connect @@ -108,7 +108,7 @@ describe('Pool tests', function() { }); it('should correctly grow server pool on concurrent operations', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { // Index @@ -158,7 +158,7 @@ describe('Pool tests', function() { // Skipped due to use of topology manager it('should correctly write ismaster operation to the server and handle timeout', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { this.timeout(0); @@ -195,7 +195,7 @@ describe('Pool tests', function() { }); it('should correctly error out operations if pool is closed in the middle of a set', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { // Attempt to connect @@ -247,7 +247,7 @@ describe('Pool tests', function() { }); it.skip('should correctly recover from a server outage', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { var self = this; @@ -322,7 +322,7 @@ describe('Pool tests', function() { // Skipped due to use of topology manager it.skip('should correctly recover from a longer server outage', { metadata: { - requires: { topology: 'single' }, + requires: { topology: 'single', apiVersion: false }, ignore: { travis: true } }, @@ -399,7 +399,7 @@ describe('Pool tests', function() { }); it('should correctly reclaim immediateRelease socket', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { var pool = new Pool(null, { @@ -1052,7 +1052,7 @@ describe('Pool tests', function() { }); it('should correctly exit _execute loop when single available connection is destroyed', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { // Enable connections accounting @@ -1111,7 +1111,7 @@ describe('Pool tests', function() { }); it('should properly emit errors on forced destroy', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { const pool = new Pool(null, { @@ -1143,7 +1143,7 @@ describe('Pool tests', function() { }); it('should support callback mode for connect', { - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, test: function(done) { const pool = new Pool(null, { host: this.configuration.host, @@ -1161,44 +1161,47 @@ describe('Pool tests', function() { } }); - it('should support resetting', function(done) { - const pool = new Pool(null, { - host: this.configuration.host, - port: this.configuration.port, - bson: new Bson() - }); - - const isMasterQuery = new Query( - new Bson(), - 'system.$cmd', - { ismaster: true }, - { numberToSkip: 0, numberToReturn: 1 } - ); + it('should support resetting', { + metadata: { requires: { apiVersion: false } }, + test: function(done) { + const pool = new Pool(null, { + host: this.configuration.host, + port: this.configuration.port, + bson: new Bson() + }); - pool.once('connect', () => { - const connections = pool.allConnections().map(conn => conn.id); - expect(connections).to.have.length(1); + const isMasterQuery = new Query( + new Bson(), + 'system.$cmd', + { ismaster: true }, + { numberToSkip: 0, numberToReturn: 1 } + ); - pool.write(isMasterQuery, err => { - expect(err).to.not.exist; + pool.once('connect', () => { + const connections = pool.allConnections().map(conn => conn.id); + expect(connections).to.have.length(1); - pool.reset(err => { + pool.write(isMasterQuery, err => { expect(err).to.not.exist; - pool.write(isMasterQuery, err => { + pool.reset(err => { expect(err).to.not.exist; - // verify the previous connection was dropped, and a new connection was created - const newConnections = pool.allConnections().map(conn => conn.id); - expect(newConnections).to.have.length(1); - expect(newConnections[0]).to.not.equal(connections[0]); + pool.write(isMasterQuery, err => { + expect(err).to.not.exist; - pool.destroy(done); + // verify the previous connection was dropped, and a new connection was created + const newConnections = pool.allConnections().map(conn => conn.id); + expect(newConnections).to.have.length(1); + expect(newConnections[0]).to.not.equal(connections[0]); + + pool.destroy(done); + }); }); }); }); - }); - pool.connect(); + pool.connect(); + } }); }); diff --git a/test/functional/core/single_mocks/compression.test.js b/test/functional/core/single_mocks/compression.test.js index 6d135c01b6a..b6695e1738e 100644 --- a/test/functional/core/single_mocks/compression.test.js +++ b/test/functional/core/single_mocks/compression.test.js @@ -10,6 +10,7 @@ describe('Single Compression (mocks)', function() { it("server should recieve list of client's supported compressors in handshake", { metadata: { requires: { + apiVersion: false, generators: true, topology: 'single' } @@ -45,6 +46,7 @@ describe('Single Compression (mocks)', function() { { metadata: { requires: { + apiVersion: false, generators: true, topology: 'single' } @@ -143,6 +145,7 @@ describe('Single Compression (mocks)', function() { { metadata: { requires: { + apiVersion: false, generators: true, topology: 'single' } @@ -237,6 +240,7 @@ describe('Single Compression (mocks)', function() { { metadata: { requires: { + apiVersion: false, generators: true, topology: 'single' } @@ -331,6 +335,7 @@ describe('Single Compression (mocks)', function() { it('should not compress uncompressible commands', { metadata: { requires: { + apiVersion: false, generators: true, topology: 'single' } diff --git a/test/functional/crud_spec.test.js b/test/functional/crud_spec.test.js index 095e5474c9d..e3b6ac85da1 100644 --- a/test/functional/crud_spec.test.js +++ b/test/functional/crud_spec.test.js @@ -12,6 +12,9 @@ const TestRunnerContext = require('./spec-runner').TestRunnerContext; const gatherTestSuites = require('./spec-runner').gatherTestSuites; const generateTopologyTests = require('./spec-runner').generateTopologyTests; +const loadSpecTests = require('../spec/index').loadSpecTests; +const runUnifiedTest = require('./unified-spec-runner/runner').runUnifiedTest; + function enforceServerVersionLimits(requires, scenario) { const versionLimits = []; if (scenario.minServerVersion) { @@ -476,3 +479,19 @@ describe('CRUD v2', function() { generateTopologyTests(testSuites, testContext); }); + +describe('CRUD unified', function() { + for (const crudSpecTest of loadSpecTests('crud/unified')) { + expect(crudSpecTest).to.exist; + context(String(crudSpecTest.description), function() { + for (const test of crudSpecTest.tests) { + it(String(test.description), { + metadata: { sessions: { skipLeakTests: true } }, + test() { + return runUnifiedTest(this, crudSpecTest, test); + } + }); + } + }); + } +}); diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index ba79b603314..4516ae9501b 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -4075,7 +4075,9 @@ describe('Cursor', function() { // Add a tag that our runner can trigger on // in this case we are setting that node needs to be higher than 0.10.X to run metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } + requires: { + topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] + } }, // The actual test we wish to run @@ -4117,58 +4119,64 @@ describe('Cursor', function() { } }); - it('Correctly decorate the collection cursor count command with skip, limit, hint, readConcern', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, + // FIXME(NODE-3074): should this be deleted? + it.skip( + 'Correctly decorate the collection cursor count command with skip, limit, hint, readConcern', + { + // Add a tag that our runner can trigger on + // in this case we are setting that node needs to be higher than 0.10.X to run + metadata: { + requires: { + topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] + } + }, - // The actual test we wish to run - test: function(done) { - var started = []; + // The actual test we wish to run + test: function(done) { + var started = []; - var listener = require('../..').instrument(function(err) { - test.equal(null, err); - }); + var listener = require('../..').instrument(function(err) { + test.equal(null, err); + }); - listener.on('started', function(event) { - if (event.commandName === 'count') started.push(event); - }); + listener.on('started', function(event) { + if (event.commandName === 'count') started.push(event); + }); - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); - client.connect(function(err, client) { - var db = client.db(configuration.db); - test.equal(null, err); + var configuration = this.configuration; + var client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); + client.connect(function(err, client) { + var db = client.db(configuration.db); + test.equal(null, err); - db.collection('cursor_count_test1', { readConcern: { level: 'local' } }).count( - { - project: '123' - }, - { - readConcern: { level: 'local' }, - limit: 5, - skip: 5, - hint: { project: 1 } - }, - function(err) { - test.equal(null, err); - test.equal(1, started.length); - if (started[0].command.readConcern) - test.deepEqual({ level: 'local' }, started[0].command.readConcern); - test.deepEqual({ project: 1 }, started[0].command.hint); - test.equal(5, started[0].command.skip); - test.equal(5, started[0].command.limit); + db.collection('cursor_count_test1', { readConcern: { level: 'local' } }).count( + { + project: '123' + }, + { + readConcern: { level: 'local' }, + limit: 5, + skip: 5, + hint: { project: 1 } + }, + function(err) { + test.equal(null, err); + test.equal(1, started.length); + if (started[0].command.readConcern) + test.deepEqual({ level: 'local' }, started[0].command.readConcern); + test.deepEqual({ project: 1 }, started[0].command.hint); + test.equal(5, started[0].command.skip); + test.equal(5, started[0].command.limit); - listener.uninstrument(); + listener.uninstrument(); - client.close(done); - } - ); - }); + client.close(done); + } + ); + }); + } } - }); + ); it('Should properly kill a cursor', { metadata: { diff --git a/test/functional/deprecate_warning.test.js b/test/functional/deprecate_warning.test.js index 992dbbf6957..7254fc02cfa 100644 --- a/test/functional/deprecate_warning.test.js +++ b/test/functional/deprecate_warning.test.js @@ -1,5 +1,4 @@ 'use strict'; -const MongoClient = require('../../index').MongoClient; const MONGODB_WARNING_CODE = require('../../lib/utils').MONGODB_WARNING_CODE; const exec = require('child_process').exec; const chai = require('chai'); @@ -40,7 +39,7 @@ describe('Deprecation Warnings', function() { it('should carry the driver warning code', { metadata: { requires: { node: '>=8.0.0' } }, test() { - const client = new MongoClient(this.configuration.url(), { madeUpOption: 3 }); + const client = this.configuration.newClient(this.configuration.url(), { madeUpOption: 3 }); let warning; process.once('warning', w => { warning = w; diff --git a/test/functional/max_staleness.test.js b/test/functional/max_staleness.test.js index 310ddd99c33..465e059741d 100644 --- a/test/functional/max_staleness.test.js +++ b/test/functional/max_staleness.test.js @@ -1,7 +1,7 @@ 'use strict'; -const Long = require('bson').Long, - expect = require('chai').expect, - mock = require('mongodb-mock-server'); +const Long = require('bson').Long; +const expect = require('chai').expect; +const mock = require('mongodb-mock-server'); const test = {}; describe('Max Staleness', function() { @@ -18,7 +18,7 @@ describe('Max Staleness', function() { const serverIsMaster = [Object.assign({}, defaultFields)]; server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(serverIsMaster[0]); return; } @@ -64,6 +64,7 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; + delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } @@ -102,6 +103,7 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; + delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } @@ -140,6 +142,7 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; + delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } @@ -177,6 +180,7 @@ describe('Max Staleness', function() { .setReadPreference(readPreference) .toArray(function(err) { expect(err).to.not.exist; + delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } diff --git a/test/functional/multiple_db.test.js b/test/functional/multiple_db.test.js index 2f947c227d9..0e19fbf077e 100644 --- a/test/functional/multiple_db.test.js +++ b/test/functional/multiple_db.test.js @@ -11,10 +11,11 @@ describe('Multiple Databases', function() { /** * @ignore */ + // FIXME(NODE-3191) it('shouldCorrectlyEmitErrorOnAllDbsOnPoolClose', { // Add a tag that our runner can trigger on // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { requires: { topology: 'single' } }, + metadata: { requires: { topology: 'single', apiVersion: false } }, // The actual test we wish to run test: function(done) { diff --git a/test/functional/replicaset_mock.test.js b/test/functional/replicaset_mock.test.js index ca063a55cb0..1321a16762f 100644 --- a/test/functional/replicaset_mock.test.js +++ b/test/functional/replicaset_mock.test.js @@ -30,7 +30,7 @@ describe('ReplSet (mocks)', function() { test.mongos1.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(serverIsMaster[0]); } else if (doc.insert) { request.reply({ ok: 1, n: doc.documents, lastOp: new Date() }); @@ -41,7 +41,7 @@ describe('ReplSet (mocks)', function() { test.mongos2.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(serverIsMaster[1]); } else if (doc.insert) { request.reply({ ok: 1, n: doc.documents, lastOp: new Date() }); diff --git a/test/functional/retryable_reads.test.js b/test/functional/retryable_reads.test.js index 0c5dbd92ca7..d533d603f52 100644 --- a/test/functional/retryable_reads.test.js +++ b/test/functional/retryable_reads.test.js @@ -24,7 +24,8 @@ describe('Retryable Reads', function() { spec.description.match(/listCollections/i) || spec.description.match(/listCollectionNames/i) || spec.description.match(/estimatedDocumentCount/i) || - spec.description.match(/count/i) || + // FIXME(NODE-3074): uncomment when `count` spec tests have been fixed + // spec.description.match(/count/i) || spec.description.match(/find/i) ); }); diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index 8f137860439..455310da16a 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -110,6 +110,7 @@ describe('Transactions', function() { 'commitTransaction retry succeeds on new mongos', 'commitTransaction retry fails on new mongos', 'unpin after transient error within a transaction and commit', + // FIXME(NODE-3074): unskip count tests when spec tests have been updated 'count', // This test needs there to be multiple mongoses 'increment txnNumber', diff --git a/test/functional/unified-spec-runner/entities.ts b/test/functional/unified-spec-runner/entities.ts index fd61d269860..9338971ab2f 100644 --- a/test/functional/unified-spec-runner/entities.ts +++ b/test/functional/unified-spec-runner/entities.ts @@ -38,10 +38,19 @@ interface UnifiedChangeStream extends ChangeStream { export type CommandEvent = CommandStartedEvent | CommandSucceededEvent | CommandFailedEvent; +function serverApiConfig() { + if (process.env.MONGODB_API_VERSION) { + return { version: process.env.MONGODB_API_VERSION }; + } +} + function getClient(address) { - return new MongoClient(`mongodb://${address}`, { - useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY) - }); + const options: any = { useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY) }; + const serverApi = serverApiConfig(); + if (serverApi) { + options.serverApi = serverApi; + } + return new MongoClient(`mongodb://${address}`, options); } export interface UnifiedMongoClient { @@ -73,8 +82,10 @@ export class UnifiedMongoClient extends MongoClient { super(url, { monitorCommands: true, ...description.uriOptions, - useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY) + useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY), + serverApi: description.serverApi || serverApiConfig() }); + this.events = []; this.failPoints = []; this.ignoredEvents = [ diff --git a/test/functional/unified-spec-runner/match.ts b/test/functional/unified-spec-runner/match.ts index 533ee7d228d..f9ec2637f05 100644 --- a/test/functional/unified-spec-runner/match.ts +++ b/test/functional/unified-spec-runner/match.ts @@ -134,10 +134,14 @@ export function resultCheck( if (depth > 1) { expect(actual, `Expected actual to exist at ${path.join('')}`).to.exist; + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + // Don't check for full key set equality because some of the actual keys + // might be e.g. $$unsetOrMatches, which can be omitted. expect( - Object.keys(actual), - `[${Object.keys(actual)}] length !== [${Object.keys(expected)}]` - ).to.have.lengthOf(Object.keys(expected).length); + actualKeys.filter(key => !expectedKeys.includes(key)), + `[${Object.keys(actual)}] has more than the expected keys: [${Object.keys(expected)}]` + ).to.have.lengthOf(0); } for (const [key, value] of expectedEntries) { diff --git a/test/functional/versioned-api.test.js b/test/functional/versioned-api.test.js new file mode 100644 index 00000000000..1d6f12308e6 --- /dev/null +++ b/test/functional/versioned-api.test.js @@ -0,0 +1,40 @@ +'use strict'; + +const expect = require('chai').expect; +const loadSpecTests = require('../spec/index').loadSpecTests; +const runUnifiedTest = require('./unified-spec-runner/runner').runUnifiedTest; + +describe('Versioned API', function() { + it('should throw an error if serverApi version is provided via the uri with new parser', { + metadata: { topology: 'single' }, + test: function(done) { + const client = this.configuration.newClient({ serverApi: '1' }, { useNewUrlParser: true }); + client.connect(err => { + expect(err).to.match(/URI cannot contain `serverApi`, it can only be passed to the client/); + client.close(done); + }); + } + }); + + 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() { + // FIXME(NODE-3191) + // MongoError: BSON field 'FindCommandRequest.returnKey' is not allowed with apiStrict:true. + if ( + versionedApiTest.description.match(/strict/) && + test.description.match(/find and getMore append API version/) + ) { + return this.skip(); + } + return runUnifiedTest(this, versionedApiTest, test); + } + }); + } + }); + } +}); diff --git a/test/functional/view.test.js b/test/functional/view.test.js index b5b060dceb4..5e38ea30041 100644 --- a/test/functional/view.test.js +++ b/test/functional/view.test.js @@ -24,7 +24,7 @@ describe('Views', function() { singleServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(primary[0]); } else if (doc.listCollections) { request.reply({ @@ -58,6 +58,7 @@ describe('Views', function() { ) { expect(r).to.exist; expect(err).to.not.exist; + delete commandResult.apiVersion; expect(commandResult).to.eql({ create: 'test', viewOn: 'users', diff --git a/test/spec/crud/unified/estimatedDocumentCount.json b/test/spec/crud/unified/estimatedDocumentCount.json new file mode 100644 index 00000000000..bcd66ea9543 --- /dev/null +++ b/test/spec/crud/unified/estimatedDocumentCount.json @@ -0,0 +1,562 @@ +{ + "description": "estimatedDocumentCount", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "edc-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "coll1" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "edc-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "estimatedDocumentCount uses $collStats on 4.9.0 or greater", + "runOnRequirements": [ + { + "minServerVersion": "4.9.0" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectResult": 3 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "commandName": "aggregate", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount with maxTimeMS on 4.9.0 or greater", + "runOnRequirements": [ + { + "minServerVersion": "4.9.0" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection0", + "arguments": { + "maxTimeMS": 6000 + }, + "expectResult": 3 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ], + "maxTimeMS": 6000 + }, + "commandName": "aggregate", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount on non-existent collection on 4.9.0 or greater", + "runOnRequirements": [ + { + "minServerVersion": "4.9.0" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection1", + "expectResult": 0 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll1", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "commandName": "aggregate", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount errors correctly on 4.9.0 or greater--command error", + "runOnRequirements": [ + { + "minServerVersion": "4.9.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectError": { + "errorCode": 8 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "commandName": "aggregate", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount errors correctly on 4.9.0 or greater--socket error", + "runOnRequirements": [ + { + "minServerVersion": "4.9.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "commandName": "aggregate", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount uses count on less than 4.9.0", + "runOnRequirements": [ + { + "maxServerVersion": "4.8.99" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectResult": 3 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll0" + }, + "commandName": "count", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount with maxTimeMS on less than 4.9.0", + "runOnRequirements": [ + { + "maxServerVersion": "4.8.99" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection0", + "arguments": { + "maxTimeMS": 6000 + }, + "expectResult": 3 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll0", + "maxTimeMS": 6000 + }, + "commandName": "count", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount on non-existent collection on less than 4.9.0", + "runOnRequirements": [ + { + "maxServerVersion": "4.8.99" + } + ], + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection1", + "expectResult": 0 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll1" + }, + "commandName": "count", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount errors correctly on less than 4.9.0--command error", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "maxServerVersion": "4.8.99", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "maxServerVersion": "4.8.99", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectError": { + "errorCode": 8 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll0" + }, + "commandName": "count", + "databaseName": "edc-tests" + } + } + ] + } + ] + }, + { + "description": "estimatedDocumentCount errors correctly on less than 4.9.0--socket error", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "maxServerVersion": "4.8.99", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "maxServerVersion": "4.8.99", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "name": "estimatedDocumentCount", + "object": "collection0", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll0" + }, + "commandName": "count", + "databaseName": "edc-tests" + } + } + ] + } + ] + } + ] +} diff --git a/test/spec/crud/unified/estimatedDocumentCount.yml b/test/spec/crud/unified/estimatedDocumentCount.yml new file mode 100644 index 00000000000..c3286ec176e --- /dev/null +++ b/test/spec/crud/unified/estimatedDocumentCount.yml @@ -0,0 +1,267 @@ +description: "estimatedDocumentCount" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false # Avoid setting fail points with multiple mongoses + uriOptions: { retryReads: false } # Avoid retrying fail points with closeConnection + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name edc-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - collection: + # Nonexistent collection intentionally omitted from initialData + id: &collection1 collection1 + database: *database0 + collectionName: &collection1Name coll1 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: "estimatedDocumentCount uses $collStats on 4.9.0 or greater" + runOnRequirements: + - minServerVersion: "4.9.0" + operations: + - name: estimatedDocumentCount + object: *collection0 + expectResult: 3 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + commandName: aggregate + databaseName: *database0Name + + - description: "estimatedDocumentCount with maxTimeMS on 4.9.0 or greater" + runOnRequirements: + - minServerVersion: "4.9.0" + operations: + - name: estimatedDocumentCount + object: *collection0 + arguments: + maxTimeMS: 6000 + expectResult: 3 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + maxTimeMS: 6000 + commandName: aggregate + databaseName: *database0Name + + - description: "estimatedDocumentCount on non-existent collection on 4.9.0 or greater" + runOnRequirements: + - minServerVersion: "4.9.0" + operations: + - name: estimatedDocumentCount + object: *collection1 + expectResult: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection1Name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + commandName: aggregate + databaseName: *database0Name + + - description: "estimatedDocumentCount errors correctly on 4.9.0 or greater--command error" + runOnRequirements: + - minServerVersion: "4.9.0" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ aggregate ] + errorCode: 8 # UnknownError + - name: estimatedDocumentCount + object: *collection0 + expectError: + errorCode: 8 # UnknownError + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + commandName: aggregate + databaseName: *database0Name + + - description: "estimatedDocumentCount errors correctly on 4.9.0 or greater--socket error" + runOnRequirements: + - minServerVersion: "4.9.0" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ aggregate ] + closeConnection: true + - name: estimatedDocumentCount + object: *collection0 + expectError: + isError: true + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0Name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + commandName: aggregate + databaseName: *database0Name + + - description: "estimatedDocumentCount uses count on less than 4.9.0" + runOnRequirements: + - maxServerVersion: "4.8.99" + operations: + - name: estimatedDocumentCount + object: *collection0 + expectResult: 3 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + count: *collection0Name + commandName: count + databaseName: *database0Name + + - description: "estimatedDocumentCount with maxTimeMS on less than 4.9.0" + runOnRequirements: + - maxServerVersion: "4.8.99" + operations: + - name: estimatedDocumentCount + object: *collection0 + arguments: + maxTimeMS: 6000 + expectResult: 3 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + count: *collection0Name + maxTimeMS: 6000 + commandName: count + databaseName: *database0Name + + - description: "estimatedDocumentCount on non-existent collection on less than 4.9.0" + runOnRequirements: + - maxServerVersion: "4.8.99" + operations: + - name: estimatedDocumentCount + object: *collection1 + expectResult: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + count: *collection1Name + commandName: count + databaseName: *database0Name + + - description: "estimatedDocumentCount errors correctly on less than 4.9.0--command error" + runOnRequirements: + - minServerVersion: "4.0.0" + maxServerVersion: "4.8.99" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + maxServerVersion: "4.8.99" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ count ] + errorCode: 8 # UnknownError + - name: estimatedDocumentCount + object: *collection0 + expectError: + errorCode: 8 # UnknownError + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + count: *collection0Name + commandName: count + databaseName: *database0Name + + - description: "estimatedDocumentCount errors correctly on less than 4.9.0--socket error" + runOnRequirements: + - minServerVersion: "4.0.0" + maxServerVersion: "4.8.99" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + maxServerVersion: "4.8.99" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ count ] + closeConnection: true + - name: estimatedDocumentCount + object: *collection0 + expectError: + isError: true + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + count: *collection0Name + commandName: count + databaseName: *database0Name diff --git a/test/spec/retryable-reads/count-serverErrors.json b/test/spec/retryable-reads/count-serverErrors.json index 839680fe59f..1de695eb845 100644 --- a/test/spec/retryable-reads/count-serverErrors.json +++ b/test/spec/retryable-reads/count-serverErrors.json @@ -114,7 +114,7 @@ ] }, { - "description": "Count succeeds after NotMaster", + "description": "Count succeeds after NotWritablePrimary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -157,7 +157,7 @@ ] }, { - "description": "Count succeeds after NotMasterNoSlaveOk", + "description": "Count succeeds after NotPrimaryNoSecondaryOk", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -200,7 +200,7 @@ ] }, { - "description": "Count succeeds after NotMasterOrSecondary", + "description": "Count succeeds after NotPrimaryOrSecondary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -501,7 +501,7 @@ ] }, { - "description": "Count fails after two NotMaster errors", + "description": "Count fails after two NotWritablePrimary errors", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -544,7 +544,7 @@ ] }, { - "description": "Count fails after NotMaster when retryReads is false", + "description": "Count fails after NotWritablePrimary when retryReads is false", "clientOptions": { "retryReads": false }, diff --git a/test/spec/retryable-reads/count-serverErrors.yml b/test/spec/retryable-reads/count-serverErrors.yml index ff40f9d0736..f713993d40c 100644 --- a/test/spec/retryable-reads/count-serverErrors.yml +++ b/test/spec/retryable-reads/count-serverErrors.yml @@ -44,7 +44,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "Count succeeds after NotMaster" + description: "Count succeeds after NotWritablePrimary" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 10107 } @@ -53,7 +53,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "Count succeeds after NotMasterNoSlaveOk" + description: "Count succeeds after NotPrimaryNoSecondaryOk" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 13435 } @@ -62,7 +62,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "Count succeeds after NotMasterOrSecondary" + description: "Count succeeds after NotPrimaryOrSecondary" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 13436 } @@ -125,7 +125,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "Count fails after two NotMaster errors" + description: "Count fails after two NotWritablePrimary errors" failPoint: <<: *failCommand_failPoint mode: { times: 2 } @@ -138,7 +138,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "Count fails after NotMaster when retryReads is false" + description: "Count fails after NotWritablePrimary when retryReads is false" clientOptions: retryReads: false failPoint: diff --git a/test/spec/retryable-reads/countDocuments-serverErrors.json b/test/spec/retryable-reads/countDocuments-serverErrors.json index f45eadfa0c2..8e7a1b58e25 100644 --- a/test/spec/retryable-reads/countDocuments-serverErrors.json +++ b/test/spec/retryable-reads/countDocuments-serverErrors.json @@ -166,7 +166,7 @@ ] }, { - "description": "CountDocuments succeeds after NotMaster", + "description": "CountDocuments succeeds after NotWritablePrimary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -235,7 +235,7 @@ ] }, { - "description": "CountDocuments succeeds after NotMasterNoSlaveOk", + "description": "CountDocuments succeeds after NotPrimaryNoSecondaryOk", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -304,7 +304,7 @@ ] }, { - "description": "CountDocuments succeeds after NotMasterOrSecondary", + "description": "CountDocuments succeeds after NotPrimaryOrSecondary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -787,7 +787,7 @@ ] }, { - "description": "CountDocuments fails after two NotMaster errors", + "description": "CountDocuments fails after two NotWritablePrimary errors", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -856,7 +856,7 @@ ] }, { - "description": "CountDocuments fails after NotMaster when retryReads is false", + "description": "CountDocuments fails after NotWritablePrimary when retryReads is false", "clientOptions": { "retryReads": false }, diff --git a/test/spec/retryable-reads/countDocuments-serverErrors.yml b/test/spec/retryable-reads/countDocuments-serverErrors.yml index 4c0d88f89ce..f69c38a2572 100644 --- a/test/spec/retryable-reads/countDocuments-serverErrors.yml +++ b/test/spec/retryable-reads/countDocuments-serverErrors.yml @@ -45,7 +45,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "CountDocuments succeeds after NotMaster" + description: "CountDocuments succeeds after NotWritablePrimary" failPoint: <<: *failCommand_failPoint data: { failCommands: [aggregate], errorCode: 10107 } @@ -54,7 +54,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "CountDocuments succeeds after NotMasterNoSlaveOk" + description: "CountDocuments succeeds after NotPrimaryNoSecondaryOk" failPoint: <<: *failCommand_failPoint data: { failCommands: [aggregate], errorCode: 13435 } @@ -63,7 +63,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "CountDocuments succeeds after NotMasterOrSecondary" + description: "CountDocuments succeeds after NotPrimaryOrSecondary" failPoint: <<: *failCommand_failPoint data: { failCommands: [aggregate], errorCode: 13436 } @@ -126,7 +126,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "CountDocuments fails after two NotMaster errors" + description: "CountDocuments fails after two NotWritablePrimary errors" failPoint: <<: *failCommand_failPoint mode: { times: 2 } @@ -139,7 +139,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "CountDocuments fails after NotMaster when retryReads is false" + description: "CountDocuments fails after NotWritablePrimary when retryReads is false" clientOptions: retryReads: false failPoint: diff --git a/test/spec/retryable-reads/estimatedDocumentCount-4.9.json b/test/spec/retryable-reads/estimatedDocumentCount-4.9.json new file mode 100644 index 00000000000..a4c46fc074a --- /dev/null +++ b/test/spec/retryable-reads/estimatedDocumentCount-4.9.json @@ -0,0 +1,246 @@ +{ + "runOn": [ + { + "minServerVersion": "4.9.0" + } + ], + "database_name": "retryable-reads-tests", + "collection_name": "coll", + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "tests": [ + { + "description": "EstimatedDocumentCount succeeds on first attempt", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds on second attempt", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount fails on first attempt", + "clientOptions": { + "retryReads": false + }, + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount fails on second attempt", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + } + ] +} diff --git a/test/spec/retryable-reads/estimatedDocumentCount-4.9.yml b/test/spec/retryable-reads/estimatedDocumentCount-4.9.yml new file mode 100644 index 00000000000..277ea010a4c --- /dev/null +++ b/test/spec/retryable-reads/estimatedDocumentCount-4.9.yml @@ -0,0 +1,60 @@ +runOn: + - minServerVersion: "4.9.0" + +database_name: &database_name "retryable-reads-tests" +collection_name: &collection_name "coll" + +data: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: "EstimatedDocumentCount succeeds on first attempt" + operations: + - &retryable_operation_succeeds + <<: &retryable_operation + name: estimatedDocumentCount + object: collection + result: 2 + expectations: + - &retryable_command_started_event + command_started_event: + command: + aggregate: *collection_name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + database_name: *database_name + - + description: "EstimatedDocumentCount succeeds on second attempt" + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [aggregate] + closeConnection: true + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount fails on first attempt" + clientOptions: + retryReads: false + failPoint: *failCommand_failPoint + operations: + - &retryable_operation_fails + <<: *retryable_operation + error: true + expectations: + - *retryable_command_started_event + - + description: "EstimatedDocumentCount fails on second attempt" + failPoint: + <<: *failCommand_failPoint + mode: { times: 2 } + operations: [*retryable_operation_fails] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/test/spec/retryable-reads/estimatedDocumentCount.json b/test/spec/retryable-reads/estimatedDocumentCount-pre4.9.json similarity index 97% rename from test/spec/retryable-reads/estimatedDocumentCount.json rename to test/spec/retryable-reads/estimatedDocumentCount-pre4.9.json index 8dfa15a2cdb..44be966ae7f 100644 --- a/test/spec/retryable-reads/estimatedDocumentCount.json +++ b/test/spec/retryable-reads/estimatedDocumentCount-pre4.9.json @@ -2,6 +2,7 @@ "runOn": [ { "minServerVersion": "4.0", + "maxServerVersion": "4.8.99", "topology": [ "single", "replicaset" @@ -9,6 +10,7 @@ }, { "minServerVersion": "4.1.7", + "maxServerVersion": "4.8.99", "topology": [ "sharded" ] diff --git a/test/spec/retryable-reads/estimatedDocumentCount.yml b/test/spec/retryable-reads/estimatedDocumentCount-pre4.9.yml similarity index 96% rename from test/spec/retryable-reads/estimatedDocumentCount.yml rename to test/spec/retryable-reads/estimatedDocumentCount-pre4.9.yml index de03e483bb4..d03a171d912 100644 --- a/test/spec/retryable-reads/estimatedDocumentCount.yml +++ b/test/spec/retryable-reads/estimatedDocumentCount-pre4.9.yml @@ -1,9 +1,11 @@ runOn: - minServerVersion: "4.0" + maxServerVersion: "4.8.99" topology: ["single", "replicaset"] - minServerVersion: "4.1.7" + maxServerVersion: "4.8.99" topology: ["sharded"] database_name: &database_name "retryable-reads-tests" diff --git a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.json b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.json new file mode 100644 index 00000000000..756b02b3a85 --- /dev/null +++ b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.json @@ -0,0 +1,911 @@ +{ + "runOn": [ + { + "minServerVersion": "4.9.0" + } + ], + "database_name": "retryable-reads-tests", + "collection_name": "coll", + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "tests": [ + { + "description": "EstimatedDocumentCount succeeds after InterruptedAtShutdown", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotWritablePrimary", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after PrimarySteppedDown", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after ShutdownInProgress", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after HostNotFound", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after HostUnreachable", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NetworkTimeout", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after SocketException", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "result": 2 + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount fails after two NotWritablePrimary errors", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + }, + { + "description": "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false", + "clientOptions": { + "retryReads": false + }, + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + }, + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ] + }, + "database_name": "retryable-reads-tests" + } + } + ] + } + ] +} diff --git a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.yml b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.yml new file mode 100644 index 00000000000..1a73d54312b --- /dev/null +++ b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-4.9.yml @@ -0,0 +1,146 @@ +runOn: + - minServerVersion: "4.9.0" + +database_name: &database_name "retryable-reads-tests" +collection_name: &collection_name "coll" + +data: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: "EstimatedDocumentCount succeeds after InterruptedAtShutdown" + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: { times: 1 } + data: { failCommands: [aggregate], errorCode: 11600 } + operations: + - &retryable_operation_succeeds + <<: &retryable_operation + name: estimatedDocumentCount + object: collection + result: 2 + expectations: + - &retryable_command_started_event + command_started_event: + command: + aggregate: *collection_name + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + database_name: *database_name + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 11602 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after NotWritablePrimary" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 10107 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 13435 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 13436 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after PrimarySteppedDown" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 189 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after ShutdownInProgress" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 91 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after HostNotFound" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 7 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after HostUnreachable" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 6 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after NetworkTimeout" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 89 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount succeeds after SocketException" + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 9001 } + operations: [*retryable_operation_succeeds] + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount fails after two NotWritablePrimary errors" + failPoint: + <<: *failCommand_failPoint + mode: { times: 2 } + data: { failCommands: [aggregate], errorCode: 10107 } + operations: + - &retryable_operation_fails + <<: *retryable_operation + error: true + expectations: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false" + clientOptions: + retryReads: false + failPoint: + <<: *failCommand_failPoint + data: { failCommands: [aggregate], errorCode: 10107 } + operations: [*retryable_operation_fails] + expectations: + - *retryable_command_started_event diff --git a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors.json b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.json similarity index 96% rename from test/spec/retryable-reads/estimatedDocumentCount-serverErrors.json rename to test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.json index 1af21d1fe92..0b9a2615d1e 100644 --- a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors.json +++ b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.json @@ -2,6 +2,7 @@ "runOn": [ { "minServerVersion": "4.0", + "maxServerVersion": "4.8.99", "topology": [ "single", "replicaset" @@ -9,6 +10,7 @@ }, { "minServerVersion": "4.1.7", + "maxServerVersion": "4.8.99", "topology": [ "sharded" ] @@ -108,7 +110,7 @@ ] }, { - "description": "EstimatedDocumentCount succeeds after NotMaster", + "description": "EstimatedDocumentCount succeeds after NotWritablePrimary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -148,7 +150,7 @@ ] }, { - "description": "EstimatedDocumentCount succeeds after NotMasterNoSlaveOk", + "description": "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -188,7 +190,7 @@ ] }, { - "description": "EstimatedDocumentCount succeeds after NotMasterOrSecondary", + "description": "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -468,7 +470,7 @@ ] }, { - "description": "EstimatedDocumentCount fails after two NotMaster errors", + "description": "EstimatedDocumentCount fails after two NotWritablePrimary errors", "failPoint": { "configureFailPoint": "failCommand", "mode": { @@ -508,7 +510,7 @@ ] }, { - "description": "EstimatedDocumentCount fails after NotMaster when retryReads is false", + "description": "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false", "clientOptions": { "retryReads": false }, diff --git a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors.yml b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.yml similarity index 92% rename from test/spec/retryable-reads/estimatedDocumentCount-serverErrors.yml rename to test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.yml index fa284ae79f3..99c343b8895 100644 --- a/test/spec/retryable-reads/estimatedDocumentCount-serverErrors.yml +++ b/test/spec/retryable-reads/estimatedDocumentCount-serverErrors-pre4.9.yml @@ -1,9 +1,11 @@ runOn: - minServerVersion: "4.0" + maxServerVersion: "4.8.99" topology: ["single", "replicaset"] - minServerVersion: "4.1.7" + maxServerVersion: "4.8.99" topology: ["sharded"] database_name: &database_name "retryable-reads-tests" @@ -43,7 +45,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "EstimatedDocumentCount succeeds after NotMaster" + description: "EstimatedDocumentCount succeeds after NotWritablePrimary" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 10107 } @@ -52,7 +54,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "EstimatedDocumentCount succeeds after NotMasterNoSlaveOk" + description: "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 13435 } @@ -61,7 +63,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "EstimatedDocumentCount succeeds after NotMasterOrSecondary" + description: "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary" failPoint: <<: *failCommand_failPoint data: { failCommands: [count], errorCode: 13436 } @@ -124,7 +126,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "EstimatedDocumentCount fails after two NotMaster errors" + description: "EstimatedDocumentCount fails after two NotWritablePrimary errors" failPoint: <<: *failCommand_failPoint mode: { times: 2 } @@ -137,7 +139,7 @@ tests: - *retryable_command_started_event - *retryable_command_started_event - - description: "EstimatedDocumentCount fails after NotMaster when retryReads is false" + description: "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false" clientOptions: retryReads: false failPoint: diff --git a/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.json b/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.json index 1192b6b9aac..b75eb585368 100644 --- a/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.json +++ b/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.json @@ -42,7 +42,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "connectTimeoutMS=0", "blockConnection": true, diff --git a/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.yml b/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.yml index de2d8a65b77..e32aa6b587d 100644 --- a/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.yml +++ b/test/spec/server-discovery-and-monitoring/integration/connectTimeoutMS.yml @@ -33,7 +33,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: connectTimeoutMS=0 blockConnection: true blockTimeMS: 550 diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.json b/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.json index 4bdfd9adffc..0567dd33233 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.json +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.json @@ -1,7 +1,7 @@ { "runOn": [ { - "minServerVersion": "4.4" + "minServerVersion": "4.9" } ], "database_name": "sdam-tests", @@ -17,7 +17,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "commandErrorHandshakeTest", "closeConnection": false, @@ -39,14 +40,6 @@ "count": 1 } }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "event": "PoolClearedEvent", - "count": 1 - } - }, { "name": "insertMany", "object": "collection", @@ -128,7 +121,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "commandErrorCheckTest", "closeConnection": false, diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.yml b/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.yml index e7b051669bb..d376b5c133d 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.yml +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-command-error.yml @@ -1,7 +1,7 @@ # Test SDAM error handling. runOn: # failCommand appName requirements - - minServerVersion: "4.4" + - minServerVersion: "4.9" database_name: &database_name "sdam-tests" collection_name: &collection_name "isMaster-command-error" @@ -16,7 +16,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: commandErrorHandshakeTest closeConnection: false errorCode: 91 # ShutdownInProgress @@ -33,11 +33,6 @@ tests: arguments: event: ServerMarkedUnknownEvent count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 # Perform an operation to ensure the node is discovered. - name: insertMany object: collection @@ -99,7 +94,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: commandErrorCheckTest closeConnection: false blockConnection: true diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.json b/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.json index eb1f3eac197..617fc74dbc4 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.json +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.json @@ -1,7 +1,7 @@ { "runOn": [ { - "minServerVersion": "4.4" + "minServerVersion": "4.9" } ], "database_name": "sdam-tests", @@ -17,7 +17,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "networkErrorHandshakeTest", "closeConnection": true @@ -38,14 +39,6 @@ "count": 1 } }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "event": "PoolClearedEvent", - "count": 1 - } - }, { "name": "insertMany", "object": "collection", @@ -127,7 +120,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "networkErrorCheckTest", "closeConnection": true diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.yml b/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.yml index f213b81435b..5796560b81c 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.yml +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-network-error.yml @@ -1,7 +1,7 @@ # Test SDAM error handling. runOn: # failCommand appName requirements - - minServerVersion: "4.4" + - minServerVersion: "4.9" database_name: &database_name "sdam-tests" collection_name: &collection_name "isMaster-network-error" @@ -16,7 +16,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: networkErrorHandshakeTest closeConnection: true clientOptions: @@ -32,11 +32,6 @@ tests: arguments: event: ServerMarkedUnknownEvent count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 # Perform an operation to ensure the node is discovered. - name: insertMany object: collection @@ -98,7 +93,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: networkErrorCheckTest closeConnection: true # The network error on the next check should mark the server Unknown and diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.json b/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.json index eeee612be87..d37e7ee6870 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.json +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.json @@ -17,7 +17,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "timeoutMonitorHandshakeTest", "blockConnection": true, @@ -39,14 +40,6 @@ "count": 1 } }, - { - "name": "waitForEvent", - "object": "testRunner", - "arguments": { - "event": "PoolClearedEvent", - "count": 1 - } - }, { "name": "insertMany", "object": "collection", @@ -128,7 +121,8 @@ }, "data": { "failCommands": [ - "isMaster" + "isMaster", + "hello" ], "appName": "timeoutMonitorCheckTest", "blockConnection": true, diff --git a/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.yml b/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.yml index 4dd8d1a3dee..b77e37ba120 100644 --- a/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.yml +++ b/test/spec/server-discovery-and-monitoring/integration/isMaster-timeout.yml @@ -16,7 +16,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: timeoutMonitorHandshakeTest blockConnection: true blockTimeMS: 1000 @@ -33,11 +33,6 @@ tests: arguments: event: ServerMarkedUnknownEvent count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 # Perform an operation to ensure the node is discovered. - name: insertMany object: collection @@ -98,7 +93,7 @@ tests: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: ["isMaster"] + failCommands: ["isMaster", "hello"] appName: timeoutMonitorCheckTest blockConnection: true # blockTimeMS is evaluated after the waiting for heartbeatFrequencyMS server-side, so this value only 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..29a0ec4e3bf --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1-strict.json @@ -0,0 +1,1115 @@ +{ + "description": "CRUD Api Version 1 (strict)", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "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", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "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 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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 + }, + "multi": { + "$$unsetOrMatches": false + }, + "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", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ], + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "find and getMore append API version", + "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": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": 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 + }, + "multi": { + "$$unsetOrMatches": false + }, + "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, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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..b5f3ac2d744 --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1-strict.yml @@ -0,0 +1,417 @@ +description: "CRUD Api Version 1 (strict)" + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "4.9" + +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" + runOnRequirements: + # serverless does not support either of the current database-level aggregation stages ($listLocalSessions and + # $currentOp) + - serverless: "forbid" + 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 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + <<: *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 + upsert: { $$unsetOrMatches: false } + <<: *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 } + multi: { $$unsetOrMatches: false } + 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" + operations: + - name: estimatedDocumentCount + object: *collection + arguments: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + <<: *expectedApiVersion + + - description: "find and getMore append API version" + 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 ] } + <<: *expectedApiVersion + + - 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 } + multi: { $$unsetOrMatches: false } + 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 + upsert: { $$unsetOrMatches: false } + <<: *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 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + <<: *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..1f135eea181 --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1.json @@ -0,0 +1,1107 @@ +{ + "description": "CRUD Api Version 1", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "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", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "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 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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 + }, + "multi": { + "$$unsetOrMatches": false + }, + "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 on 4.9.0 or greater", + "operations": [ + { + "name": "estimatedDocumentCount", + "object": "collection", + "arguments": {} + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$collStats": { + "count": {} + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": "$count" + } + } + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "description": "find and getMore append API version", + "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": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, + { + "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 + }, + "multi": { + "$$unsetOrMatches": false + }, + "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, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "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..d2fb5b91307 --- /dev/null +++ b/test/spec/versioned-api/crud-api-version-1.yml @@ -0,0 +1,411 @@ +description: "CRUD Api Version 1" + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "4.9" + +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" + runOnRequirements: + # serverless does not support either of the current database-level aggregation stages ($listLocalSessions and + # $currentOp) + - serverless: forbid + 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 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + <<: *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 + upsert: { $$unsetOrMatches: false } + <<: *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 } + multi: { $$unsetOrMatches: false } + 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 on 4.9.0 or greater" + operations: + - name: estimatedDocumentCount + object: *collection + arguments: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collectionName + pipeline: &pipeline + - $collStats: { count: {} } + - $group: { _id: 1, n: { $sum: $count }} + <<: *expectedApiVersion + + - description: "find and getMore append API version" + 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 ] } + <<: *expectedApiVersion + + - 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 } + multi: { $$unsetOrMatches: false } + 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 + upsert: { $$unsetOrMatches: false } + <<: *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 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + <<: *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..17e0126d107 --- /dev/null +++ b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.json @@ -0,0 +1,127 @@ +{ + "description": "RunCommand helper: No API version declared", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "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", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "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", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "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..c17481ab1e8 --- /dev/null +++ b/test/spec/versioned-api/runcommand-helper-no-api-version-declared.yml @@ -0,0 +1,75 @@ +description: "RunCommand helper: No API version declared" + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "4.9" + 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" + runOnRequirements: + # serverless does not currently reject invalid API versions on + # certain commands (CLOUDP-87926) + - serverless: "forbid" + 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" + runOnRequirements: + # serverless does not currently reject invalid API versions on + # certain commands (CLOUDP-87926) + - serverless: "forbid" + 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..5c627bb3511 --- /dev/null +++ b/test/spec/versioned-api/transaction-handling.json @@ -0,0 +1,348 @@ +{ + "description": "Transaction handling", + "schemaVersion": "1.1", + "runOnRequirements": [ + { + "minServerVersion": "4.9", + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "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 + } + } + ] + }, + "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": "All commands in a transaction declare an API version", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "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": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + } + ] + } + ] + }, + { + "description": "abortTransaction includes an API version", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced" + ] + } + ], + "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": "abortTransaction", + "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": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": { + "$$unsetOrMatches": 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..5f723c0737e --- /dev/null +++ b/test/spec/versioned-api/transaction-handling.yml @@ -0,0 +1,128 @@ +description: "Transaction handling" + +schemaVersion: "1.1" + +runOnRequirements: + - minServerVersion: "4.9" + topologies: [ replicaset, sharded-replicaset, load-balanced ] + +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 } + + +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: "All commands in a transaction declare an API version" + runOnRequirements: + - topologies: [ replicaset, sharded-replicaset, load-balanced ] + 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 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + <<: *expectedApiVersion + - description: "abortTransaction includes an API version" + runOnRequirements: + - topologies: [ replicaset, sharded-replicaset, load-balanced ] + 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: abortTransaction + 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 } + <<: *expectedApiVersion + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session } + <<: *expectedApiVersion diff --git a/test/tools/runner/config.js b/test/tools/runner/config.js index fc24c0091df..a6bc74f1de9 100644 --- a/test/tools/runner/config.js +++ b/test/tools/runner/config.js @@ -22,6 +22,7 @@ class NativeConfiguration { this.topologyType = context.topologyType; this.version = context.version; this.clientSideEncryption = context.clientSideEncryption; + this.serverApi = context.serverApi; this.parameters = undefined; this.options = Object.assign( { @@ -70,21 +71,22 @@ class NativeConfiguration { } newClient(dbOptions, serverOptions) { + const unifiedOptions = { useUnifiedTopology: true, minHeartbeatFrequencyMS: 100 }; + if (this.serverApi) { + Object.assign(unifiedOptions, { serverApi: this.serverApi }); + } // support MongoClient contructor form (url, options) for `newClient` if (typeof dbOptions === 'string') { return new MongoClient( dbOptions, - this.usingUnifiedTopology() - ? Object.assign({ useUnifiedTopology: true, minHeartbeatFrequencyMS: 100 }, serverOptions) - : serverOptions + this.usingUnifiedTopology() ? Object.assign(unifiedOptions, serverOptions) : serverOptions ); } dbOptions = dbOptions || {}; serverOptions = Object.assign({}, { haInterval: 100 }, serverOptions); if (this.usingUnifiedTopology()) { - serverOptions.useUnifiedTopology = true; - serverOptions.minHeartbeatFrequencyMS = 100; + serverOptions = Object.assign(serverOptions, unifiedOptions); } // Fall back @@ -148,6 +150,9 @@ class NativeConfiguration { options = Object.assign({}, options); const hosts = host == null ? [].concat(this.options.hosts) : [{ host, port }]; if (this.usingUnifiedTopology()) { + if (this.serverApi) { + Object.assign(options, { serverApi: this.serverApi }); + } return new core.Topology(hosts, options); } 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 8db9143a1c6..5b1fce00bf4 100644 --- a/test/tools/runner/index.js +++ b/test/tools/runner/index.js @@ -14,6 +14,7 @@ const mock = require('mongodb-mock-server'); 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) { @@ -64,8 +65,14 @@ before(function(_done) { // usingUnifiedTopology ? 'unified' : 'legacy' // )} topology` // ); - - const client = new MongoClient(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); + const options = { + useNewUrlParser: true, + useUnifiedTopology: true + }; + if (MONGODB_API_VERSION) { + options.serverApi = { version: MONGODB_API_VERSION }; + } + const client = new MongoClient(MONGODB_URI, options); const done = err => client.close(err2 => _done(err || err2)); client.connect(err => { @@ -80,6 +87,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); diff --git a/test/unit/bulk_write.test.js b/test/unit/bulk_write.test.js index 8ca1722d17f..766f5c59466 100644 --- a/test/unit/bulk_write.test.js +++ b/test/unit/bulk_write.test.js @@ -33,7 +33,7 @@ describe('Bulk Writes', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); diff --git a/test/unit/bypass_validation.test.js b/test/unit/bypass_validation.test.js index e5a1fd75dcb..d3c78176687 100644 --- a/test/unit/bypass_validation.test.js +++ b/test/unit/bypass_validation.test.js @@ -38,7 +38,7 @@ describe('bypass document validation', function() { } } - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -91,7 +91,7 @@ describe('bypass document validation', function() { } } - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -148,7 +148,7 @@ describe('bypass document validation', function() { } } - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -203,7 +203,7 @@ describe('bypass document validation', function() { } } - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); diff --git a/test/unit/client.test.js b/test/unit/client.test.js index 5901c3c98c9..2ad54e81026 100644 --- a/test/unit/client.test.js +++ b/test/unit/client.test.js @@ -15,7 +15,7 @@ describe('Client (unit)', function() { let handshake; server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { handshake = doc; request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { diff --git a/test/unit/client_metadata.test.js b/test/unit/client_metadata.test.js index 21b51274189..85d50155ad5 100644 --- a/test/unit/client_metadata.test.js +++ b/test/unit/client_metadata.test.js @@ -11,7 +11,7 @@ describe('Client Metadata', function() { const ismasters = []; mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { ismasters.push(doc); request.reply(mock.DEFAULT_ISMASTER); } else { diff --git a/test/unit/cmap/connection.test.js b/test/unit/cmap/connection.test.js index 39be21dc35e..115da0c0caa 100644 --- a/test/unit/cmap/connection.test.js +++ b/test/unit/cmap/connection.test.js @@ -14,7 +14,7 @@ describe('Connection', function() { it('should support fire-and-forget messages', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } @@ -40,7 +40,7 @@ describe('Connection', function() { it('should destroy streams which time out', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } @@ -70,7 +70,7 @@ describe('Connection', function() { it('should throw a network error with kBeforeHandshake set to false on timeout after hand shake', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } // respond to no other requests to trigger timeout event diff --git a/test/unit/cmap/connection_pool.test.js b/test/unit/cmap/connection_pool.test.js index 2a15125ed54..093f56fde97 100644 --- a/test/unit/cmap/connection_pool.test.js +++ b/test/unit/cmap/connection_pool.test.js @@ -47,7 +47,7 @@ describe('Connection Pool', function() { it('should destroy connections which have been closed', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } else { // destroy on any other command @@ -93,7 +93,7 @@ describe('Connection Pool', function() { it('should propagate socket timeouts to connections', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } else { // blackhole other requests @@ -121,7 +121,7 @@ describe('Connection Pool', function() { it('should clear timed out wait queue members if no connections are available', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } }); @@ -160,7 +160,7 @@ describe('Connection Pool', function() { it('should manage a connection for a successful operation', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } }); @@ -185,7 +185,7 @@ describe('Connection Pool', function() { it('should allow user interaction with an error', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.connection.destroy(); } }); @@ -210,7 +210,7 @@ describe('Connection Pool', function() { it('should return an error to the original callback', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } }); @@ -232,7 +232,7 @@ describe('Connection Pool', function() { it('should still manage a connection if no callback is provided', function(done) { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } }); @@ -406,7 +406,7 @@ describe('Connection Pool', function() { // and establish valid connections server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } }); diff --git a/test/unit/core/common.js b/test/unit/core/common.js index 0eb40836d45..e3cf2a2f4b7 100644 --- a/test/unit/core/common.js +++ b/test/unit/core/common.js @@ -91,21 +91,21 @@ class ReplSetFixture { configureMessageHandlers() { this.primaryServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(this.primaryStates[0]); } }); this.firstSecondaryServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(this.firstSecondaryStates[0]); } }); this.arbiterServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(this.arbiterStates[0]); } }); diff --git a/test/unit/core/connect.test.js b/test/unit/core/connect.test.js index 926995df483..5890408b6fc 100644 --- a/test/unit/core/connect.test.js +++ b/test/unit/core/connect.test.js @@ -37,7 +37,7 @@ describe('Connect Tests', function() { const doc = request.document; const $clusterTime = genClusterTime(Date.now()); - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { whatHappened.ismaster = true; request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { @@ -67,7 +67,7 @@ describe('Connect Tests', function() { test.server.setMessageHandler(request => { const doc = request.document; const $clusterTime = genClusterTime(Date.now()); - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { whatHappened.ismaster = true; request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { diff --git a/test/unit/core/response_test.js.test.js b/test/unit/core/response_test.js.test.js index 4a6dd68d486..1bd8066d642 100644 --- a/test/unit/core/response_test.js.test.js +++ b/test/unit/core/response_test.js.test.js @@ -26,7 +26,7 @@ describe('Response', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 6 diff --git a/test/unit/core/scram_iterations.test.js b/test/unit/core/scram_iterations.test.js index 1464fe3b7e4..d48106901aa 100644 --- a/test/unit/core/scram_iterations.test.js +++ b/test/unit/core/scram_iterations.test.js @@ -35,7 +35,7 @@ describe('SCRAM Iterations Tests', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.saslStart) { return request.reply({ @@ -81,7 +81,7 @@ describe('SCRAM Iterations Tests', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.saslStart) { return request.reply({ @@ -128,7 +128,7 @@ describe('SCRAM Iterations Tests', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.saslStart) { return request.reply({ diff --git a/test/unit/core/sessions.test.js b/test/unit/core/sessions.test.js index 1cdd92e70c1..57ae8f97403 100644 --- a/test/unit/core/sessions.test.js +++ b/test/unit/core/sessions.test.js @@ -73,7 +73,7 @@ describe('Sessions', function() { test.server = server; test.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { logicalSessionTimeoutMinutes: 10 }) ); @@ -227,7 +227,7 @@ describe('Sessions', function() { it('should not mark session as dirty on network error if already ended', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { logicalSessionTimeoutMinutes: 10 }) ); diff --git a/test/unit/core/single/sessions.test.js b/test/unit/core/single/sessions.test.js index 1d773e266e1..9d6222de047 100644 --- a/test/unit/core/single/sessions.test.js +++ b/test/unit/core/single/sessions.test.js @@ -424,7 +424,8 @@ describe('Sessions (Single)', function() { } }); - it('should use the same session for any killCursor issued by a cursor', { + // FIXME(NODE-3191) + it.skip('should use the same session for any killCursor issued by a cursor', { metadata: { requires: { topology: 'single' } }, test: function(_done) { const client = new Server(test.server.address()); diff --git a/test/unit/core/write_concern_error.test.js b/test/unit/core/write_concern_error.test.js index 0c31107eb71..887a3906902 100644 --- a/test/unit/core/write_concern_error.test.js +++ b/test/unit/core/write_concern_error.test.js @@ -96,7 +96,7 @@ describe('WriteConcernError', function() { it('should expose a user command writeConcern error like a normal WriteConcernError', function(done) { test.primaryServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { setTimeout(() => request.reply(test.primaryStates[0])); } else if (doc.createUser) { setTimeout(() => request.reply(RAW_USER_WRITE_CONCERN_ERROR)); @@ -133,7 +133,7 @@ describe('WriteConcernError', function() { it('should propagate writeConcernError.errInfo ', function(done) { test.primaryServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { setTimeout(() => request.reply(test.primaryStates[0])); } else if (doc.createUser) { setTimeout(() => request.reply(RAW_USER_WRITE_CONCERN_ERROR_INFO)); diff --git a/test/unit/create_index_error.test.js b/test/unit/create_index_error.test.js index be89a06893d..7bf17445e0c 100644 --- a/test/unit/create_index_error.test.js +++ b/test/unit/create_index_error.test.js @@ -19,7 +19,7 @@ describe('CreateIndexError', function() { test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } diff --git a/test/unit/db_list_collections.test.js b/test/unit/db_list_collections.test.js index 61968f306f9..be75b88947c 100644 --- a/test/unit/db_list_collections.test.js +++ b/test/unit/db_list_collections.test.js @@ -11,7 +11,7 @@ describe('db.listCollections', function() { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { return request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } diff --git a/test/unit/sdam/monitoring.test.js b/test/unit/sdam/monitoring.test.js index 73479af158b..472c1a72a10 100644 --- a/test/unit/sdam/monitoring.test.js +++ b/test/unit/sdam/monitoring.test.js @@ -29,7 +29,7 @@ describe('monitoring', function() { it('should record roundTripTime', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -72,7 +72,7 @@ describe('monitoring', function() { } const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -104,7 +104,7 @@ describe('monitoring', function() { it('should connect and issue an initial server check', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } }); @@ -121,7 +121,7 @@ describe('monitoring', function() { it('should ignore attempts to connect when not already closed', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } }); @@ -139,7 +139,7 @@ describe('monitoring', function() { it('should not initiate another check if one is in progress', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { setTimeout(() => request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)), 250); } }); @@ -178,7 +178,7 @@ describe('monitoring', function() { let isMasterCount = 0; mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { isMasterCount++; if (isMasterCount === 2) { request.reply({ ok: 0, errmsg: 'forced from mock server' }); diff --git a/test/unit/sdam/srv_polling.test.js b/test/unit/sdam/srv_polling.test.js index ccf824a1976..ba683a62cdc 100644 --- a/test/unit/sdam/srv_polling.test.js +++ b/test/unit/sdam/srv_polling.test.js @@ -330,7 +330,7 @@ describe('Mongos SRV Polling', function() { server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, MONGOS_DEFAULT_ISMASTER)); } }); diff --git a/test/unit/sdam/topology.test.js b/test/unit/sdam/topology.test.js index 1b48d00b1fd..f8b8c1e3855 100644 --- a/test/unit/sdam/topology.test.js +++ b/test/unit/sdam/topology.test.js @@ -156,7 +156,7 @@ describe('Topology (unit)', function() { const doc = request.document; let initialIsMasterSent = false; - if (doc.ismaster && !initialIsMasterSent) { + if ((doc.ismaster || doc.hello) && !initialIsMasterSent) { request.reply(mock.DEFAULT_ISMASTER_36); initialIsMasterSent = true; } else { @@ -187,7 +187,7 @@ describe('Topology (unit)', function() { it('should set server to unknown and reset pool on `node is recovering` error', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 9 })); } else if (doc.insert) { request.reply({ ok: 0, message: 'node is recovering', code: 11600 }); @@ -224,7 +224,7 @@ describe('Topology (unit)', function() { it('should set server to unknown and NOT reset pool on stepdown errors', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 9 })); } else if (doc.insert) { request.reply({ ok: 0, message: 'not master' }); @@ -261,7 +261,7 @@ describe('Topology (unit)', function() { it('should set server to unknown on non-timeout network error', function(done) { mockServer.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 9 })); } else if (doc.insert) { request.connection.destroy(); diff --git a/test/unit/sessions/client.test.js b/test/unit/sessions/client.test.js index faca91fe874..0905d6834e7 100644 --- a/test/unit/sessions/client.test.js +++ b/test/unit/sessions/client.test.js @@ -19,7 +19,7 @@ describe('Sessions', function() { test() { test.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(Object.assign({}, mock.DEFAULT_ISMASTER)); } else if (doc.endSessions) { request.reply({ ok: 1 }); @@ -46,7 +46,7 @@ describe('Sessions', function() { .then(() => { replicaSetMock.firstSecondaryServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { const ismaster = replicaSetMock.firstSecondaryStates[0]; ismaster.logicalSessionTimeoutMinutes = 20; request.reply(ismaster); @@ -57,7 +57,7 @@ describe('Sessions', function() { replicaSetMock.secondSecondaryServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { const ismaster = replicaSetMock.secondSecondaryStates[0]; ismaster.logicalSessionTimeoutMinutes = 10; request.reply(ismaster); @@ -68,7 +68,7 @@ describe('Sessions', function() { replicaSetMock.arbiterServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { const ismaster = replicaSetMock.arbiterStates[0]; ismaster.logicalSessionTimeoutMinutes = 30; request.reply(ismaster); @@ -79,7 +79,7 @@ describe('Sessions', function() { replicaSetMock.primaryServer.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { const ismaster = replicaSetMock.primaryStates[0]; ismaster.logicalSessionTimeoutMinutes = null; request.reply(ismaster); @@ -118,7 +118,7 @@ describe('Sessions', function() { test: function(done) { test.server.setMessageHandler(request => { var doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { logicalSessionTimeoutMinutes: 10 diff --git a/test/unit/sessions/collection.test.js b/test/unit/sessions/collection.test.js index 71b59891b07..37b22895a93 100644 --- a/test/unit/sessions/collection.test.js +++ b/test/unit/sessions/collection.test.js @@ -21,7 +21,7 @@ describe('Sessions', function() { let insertOperationTime = Timestamp.fromNumber(Date.now()); test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); } else if (doc.insert) { request.reply({ ok: 1, operationTime: insertOperationTime }); @@ -59,9 +59,9 @@ describe('Sessions', function() { const options = Object.freeze({}); test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); - } else if (doc.count || doc.endSessions) { + } else if (doc.count || doc.aggregate || doc.endSessions) { request.reply({ ok: 1 }); } }); @@ -70,7 +70,7 @@ describe('Sessions', function() { return client.connect().then(client => { const coll = client.db('foo').collection('bar'); - return coll.count({}, options).then(() => { + return coll.countDocuments({}, options).then(() => { expect(options).to.deep.equal({}); return client.close(); }); From 7e5b14ade79975702cc9d170ffa0cdf715e728bd Mon Sep 17 00:00:00 2001 From: emadum Date: Thu, 17 Jun 2021 16:26:29 -0400 Subject: [PATCH 2/7] code review --- .evergreen/config.yml | 44 +------------- .evergreen/config.yml.in | 2 +- .evergreen/generate_evergreen_tasks.js | 21 ++++--- lib/core/wireprotocol/kill_cursors.js | 8 ++- lib/core/wireprotocol/query.js | 4 +- test/functional/apm.test.js | 2 +- test/functional/authentication.test.js | 9 +-- .../client_side_encryption/spec.test.js | 6 +- test/functional/collations.test.js | 2 +- test/functional/connection.test.js | 4 +- test/functional/cursor.test.js | 59 ------------------- test/functional/max_staleness.test.js | 8 +-- test/functional/multiple_db.test.js | 8 +-- test/functional/retryable_reads.test.js | 2 +- test/functional/transactions.test.js | 2 +- .../unified-spec-runner/entities.ts | 9 ++- test/functional/versioned-api.test.js | 8 --- test/unit/core/single/sessions.test.js | 7 ++- test/unit/sessions/collection.test.js | 6 +- 19 files changed, 55 insertions(+), 156 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 63d337408c4..6106b8e8aa8 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1,6 +1,6 @@ stepback: true command_type: system -exec_timeout_secs: 1200 +exec_timeout_secs: 1500 timeout: - command: shell.exec params: @@ -499,39 +499,6 @@ post: ignore: - '*.md' tasks: - - name: test-latest-server - tags: - - latest - - server - commands: - - func: install dependencies - - func: bootstrap mongo-orchestration - vars: - VERSION: latest - TOPOLOGY: server - - func: run tests - - name: test-latest-replica_set - tags: - - latest - - replica_set - commands: - - func: install dependencies - - func: bootstrap mongo-orchestration - vars: - VERSION: latest - TOPOLOGY: replica_set - - func: run tests - - name: test-latest-sharded_cluster - tags: - - latest - - sharded_cluster - commands: - - func: install dependencies - - func: bootstrap mongo-orchestration - vars: - VERSION: latest - TOPOLOGY: sharded_cluster - - func: run tests - name: test-latest-server-unified tags: - latest @@ -1582,9 +1549,6 @@ buildvariants: expansions: NODE_LTS_NAME: fermium tasks: &ref_0 - - test-latest-server - - test-latest-replica_set - - test-latest-sharded_cluster - test-latest-server-unified - test-latest-replica_set-unified - test-latest-sharded_cluster-unified @@ -1693,9 +1657,6 @@ buildvariants: expansions: NODE_LTS_NAME: fermium tasks: &ref_1 - - test-latest-server - - test-latest-replica_set - - test-latest-sharded_cluster - test-latest-server-unified - test-latest-replica_set-unified - test-latest-sharded_cluster-unified @@ -1881,9 +1842,6 @@ buildvariants: NODE_LTS_NAME: fermium CLIENT_ENCRYPTION: true tasks: &ref_3 - - test-latest-server - - test-latest-replica_set - - test-latest-sharded_cluster - test-latest-server-unified - test-latest-replica_set-unified - test-latest-sharded_cluster-unified diff --git a/.evergreen/config.yml.in b/.evergreen/config.yml.in index 7c5787066ab..29bd876c328 100644 --- a/.evergreen/config.yml.in +++ b/.evergreen/config.yml.in @@ -11,7 +11,7 @@ command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever # Good rule of thumb: the averageish length a task takes, times 5 # That roughly accounts for variable system performance for various buildvariants -exec_timeout_secs: 1200 +exec_timeout_secs: 1500 # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 597c62d934f..d6b94b06bda 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -5,16 +5,15 @@ const fs = require('fs'); const yaml = require('js-yaml'); const LATEST_EFFECTIVE_VERSION = '5.0'; -const MONGODB_VERSIONS = ['latest', '4.4', '4.2', '4.0', '3.6', '3.4', '3.2', '3.0', '2.6']; +const LEGACY_MONGODB_VERSIONS = new Set(['4.4', '4.2', '4.0', '3.6', '3.4', '3.2', '3.0', '2.6']); +const MONGODB_VERSIONS = ['latest'].concat(Array.from(LEGACY_MONGODB_VERSIONS)); const AWS_AUTH_VERSIONS = ['latest', '4.4']; const OCSP_VERSIONS = ['latest', '4.4']; const TLS_VERSIONS = ['latest', '4.2']; // also test on 4.2 because 4.4+ currently skipped on windows const NODE_VERSIONS = ['fermium', 'erbium', 'dubnium', 'carbon', 'boron', 'argon']; -const TOPOLOGIES = ['server', 'replica_set', 'sharded_cluster'].concat([ - 'server-unified', - 'replica_set-unified', - 'sharded_cluster-unified' -]); +const LEGACY_TOPOLOGIES = new Set(['server', 'replica_set', 'sharded_cluster']); +const UNIFIED_TOPOLOGIES = Array.from(LEGACY_TOPOLOGIES).map(topology => `${topology}-unified`); + const OPERATING_SYSTEMS = [ { @@ -110,10 +109,16 @@ function makeTask({ mongoVersion, topology }) { } MONGODB_VERSIONS.forEach(mongoVersion => { - TOPOLOGIES.forEach(topology => + Array.from(LEGACY_TOPOLOGIES).concat(UNIFIED_TOPOLOGIES).forEach(topology => { + if (LEGACY_TOPOLOGIES.has(topology) && !LEGACY_MONGODB_VERSIONS.has(mongoVersion)) { + // MongoDB 5.0+ is only supported by the Unified Topology in driver 3.7+ + // therefore testing the legacy toplogy can be skipped + return; + } BASE_TASKS.push(makeTask({ mongoVersion, topology })) - ); + }); }); + BASE_TASKS.push({ name: `test-latest-server-v1-api`, tags: ['latest', 'server', 'v1-api'], diff --git a/lib/core/wireprotocol/kill_cursors.js b/lib/core/wireprotocol/kill_cursors.js index cefebdf6817..e54001ad449 100644 --- a/lib/core/wireprotocol/kill_cursors.js +++ b/lib/core/wireprotocol/kill_cursors.js @@ -8,9 +8,12 @@ const maxWireVersion = require('../utils').maxWireVersion; const emitWarning = require('../utils').emitWarning; const command = require('./command'); -function killCursors(server, ns, cursorState, options, callback) { +function killCursors(server, ns, cursorState, defaultOptions, callback) { + if (typeof defaultOptions === 'function') { + callback = defaultOptions; + defaultOptions = {}; + } callback = typeof callback === 'function' ? callback : () => {}; - options = options || {}; const cursorId = cursorState.cursorId; @@ -47,6 +50,7 @@ function killCursors(server, ns, cursorState, options, callback) { cursors: [cursorId] }; + const options = defaultOptions || {}; if (typeof cursorState.session === 'object') options.session = cursorState.session; command(server, ns, killCursorCmd, options, (err, result) => { diff --git a/lib/core/wireprotocol/query.js b/lib/core/wireprotocol/query.js index 66ba531ce6c..33f858ec5bb 100644 --- a/lib/core/wireprotocol/query.js +++ b/lib/core/wireprotocol/query.js @@ -140,8 +140,8 @@ function prepareFindCommand(server, ns, cmd, cursorState) { if (cmd.maxTimeMS) findCmd.maxTimeMS = cmd.maxTimeMS; if (cmd.min) findCmd.min = cmd.min; if (cmd.max) findCmd.max = cmd.max; - findCmd.returnKey = cmd.returnKey ? cmd.returnKey : false; - findCmd.showRecordId = cmd.showDiskLoc ? cmd.showDiskLoc : false; + if (typeof cmd.returnKey === 'boolean') findCmd.returnKey = cmd.returnKey; + if (typeof cmd.showDiskLoc === 'boolean') findCmd.showRecordId = cmd.showDiskLoc; if (cmd.snapshot) findCmd.snapshot = cmd.snapshot; if (cmd.tailable) findCmd.tailable = cmd.tailable; if (cmd.oplogReplay) findCmd.oplogReplay = cmd.oplogReplay; diff --git a/test/functional/apm.test.js b/test/functional/apm.test.js index 465ac6edc3d..9b0a2e73917 100644 --- a/test/functional/apm.test.js +++ b/test/functional/apm.test.js @@ -1089,7 +1089,7 @@ describe('APM', function() { } loadSpecTests('command-monitoring/legacy').forEach(scenario => { - if (scenario.name === 'command') return; // FIXME(NODE-3074): remove when `count` spec tests have been fixed + if (scenario.name === 'command') return; // FIXME(NODE-3369): remove when `count` spec tests have been fixed describe(scenario.name, function() { scenario.tests.forEach(test => { const requirements = { topology: ['single', 'replicaset', 'sharded'] }; diff --git a/test/functional/authentication.test.js b/test/functional/authentication.test.js index 26c6cf7d313..75f01c79c50 100644 --- a/test/functional/authentication.test.js +++ b/test/functional/authentication.test.js @@ -10,7 +10,7 @@ describe('Authentication', function() { }); it('should still work for auth when using new url parser and no database is in url', { - metadata: { requires: { topology: ['single'], apiVersion: false } }, // FIXME(NODE-3191) + metadata: { requires: { topology: ['single'] } }, test: function(done) { const configuration = this.configuration; const username = 'testUser'; @@ -146,9 +146,8 @@ describe('Authentication', function() { * * @ignore */ - // FIXME(NODE-3191) it('should correctly call validateCollection using authenticatedMode', { - metadata: { requires: { topology: ['single', 'heap', 'wiredtiger'], apiVersion: false } }, + metadata: { requires: { topology: ['single'] } }, // The actual test we wish to run test: function(done) { @@ -163,9 +162,7 @@ describe('Authentication', function() { collection.insert({ a: 1 }, { w: 1 }, function(err) { test.equal(null, err); var adminDb = db.admin(); - adminDb.addUser('admin', 'admin', configuration.writeConcernMax(), function(err) { - test.equal(null, err); - + adminDb.addUser('admin', 'admin', configuration.writeConcernMax(), function() { const validationClient = configuration.newClient( 'mongodb://admin:admin@localhost:27017/admin' ); diff --git a/test/functional/client_side_encryption/spec.test.js b/test/functional/client_side_encryption/spec.test.js index 507149ff5ed..aed364fd841 100644 --- a/test/functional/client_side_encryption/spec.test.js +++ b/test/functional/client_side_encryption/spec.test.js @@ -35,6 +35,10 @@ describe('Client Side Encryption', function() { generateTopologyTests(testSuites, testContext, spec => { // Note: we are skipping regex tests b/c we currently deserialize straight to native // regex representation instead of to BSONRegExp. - return !spec.description.match(/type=regex/) && !spec.description.match(/maxWireVersion < 8/); + return ( + !spec.description.match(/type=regex/) && + !spec.description.match(/maxWireVersion < 8/) && + !spec.description.match(/Count with deterministic encryption/) // TODO(NODE-3369): Unskip + ); }); }); diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index 743dc8646b3..dc9fea44b1e 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -678,7 +678,7 @@ describe('Collation', function() { .collection('test') .createIndex({ a: 1 }, { collation: { caseLevel: true } }) .then(() => { - delete commandResult.apiVersion; + delete commandResult.apiVersion; // might or might not exist, this test isn't concerned with checking this expect(commandResult).to.eql({ createIndexes: 'test', indexes: [{ name: 'a_1', key: { a: 1 }, collation: { caseLevel: true } }] diff --git a/test/functional/connection.test.js b/test/functional/connection.test.js index 8a3ed00ff88..2f56b47ce66 100644 --- a/test/functional/connection.test.js +++ b/test/functional/connection.test.js @@ -325,7 +325,7 @@ describe('Connection', function() { * @ignore */ it('test connect good auth', { - metadata: { requires: { topology: 'single', apiVersion: false } }, // FIXME(NODE-3191) + metadata: { requires: { topology: 'single' } }, // The actual test we wish to run test: function(done) { @@ -361,7 +361,7 @@ describe('Connection', function() { * @ignore */ it('test connect good auth as option', { - metadata: { requires: { topology: 'single', apiVersion: false } }, // FIXME(NODE-3191) + metadata: { requires: { topology: 'single' } }, // The actual test we wish to run test: function(done) { diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index 4516ae9501b..30052528726 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -4119,65 +4119,6 @@ describe('Cursor', function() { } }); - // FIXME(NODE-3074): should this be deleted? - it.skip( - 'Correctly decorate the collection cursor count command with skip, limit, hint, readConcern', - { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { - requires: { - topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] - } - }, - - // The actual test we wish to run - test: function(done) { - var started = []; - - var listener = require('../..').instrument(function(err) { - test.equal(null, err); - }); - - listener.on('started', function(event) { - if (event.commandName === 'count') started.push(event); - }); - - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); - client.connect(function(err, client) { - var db = client.db(configuration.db); - test.equal(null, err); - - db.collection('cursor_count_test1', { readConcern: { level: 'local' } }).count( - { - project: '123' - }, - { - readConcern: { level: 'local' }, - limit: 5, - skip: 5, - hint: { project: 1 } - }, - function(err) { - test.equal(null, err); - test.equal(1, started.length); - if (started[0].command.readConcern) - test.deepEqual({ level: 'local' }, started[0].command.readConcern); - test.deepEqual({ project: 1 }, started[0].command.hint); - test.equal(5, started[0].command.skip); - test.equal(5, started[0].command.limit); - - listener.uninstrument(); - - client.close(done); - } - ); - }); - } - } - ); - it('Should properly kill a cursor', { metadata: { requires: { diff --git a/test/functional/max_staleness.test.js b/test/functional/max_staleness.test.js index 465e059741d..8a8868467c3 100644 --- a/test/functional/max_staleness.test.js +++ b/test/functional/max_staleness.test.js @@ -66,7 +66,7 @@ describe('Max Staleness', function() { expect(err).to.not.exist; delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, + $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -105,7 +105,7 @@ describe('Max Staleness', function() { expect(err).to.not.exist; delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, + $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -144,7 +144,7 @@ describe('Max Staleness', function() { expect(err).to.not.exist; delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, + $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); @@ -182,7 +182,7 @@ describe('Max Staleness', function() { expect(err).to.not.exist; delete test.checkCommand.$query.apiVersion; expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {}, returnKey: false, showRecordId: false }, + $query: { find: 'test', filter: {} }, $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } }); diff --git a/test/functional/multiple_db.test.js b/test/functional/multiple_db.test.js index 0e19fbf077e..bef99b6caad 100644 --- a/test/functional/multiple_db.test.js +++ b/test/functional/multiple_db.test.js @@ -11,13 +11,8 @@ describe('Multiple Databases', function() { /** * @ignore */ - // FIXME(NODE-3191) it('shouldCorrectlyEmitErrorOnAllDbsOnPoolClose', { - // Add a tag that our runner can trigger on - // in this case we are setting that node needs to be higher than 0.10.X to run - metadata: { requires: { topology: 'single', apiVersion: false } }, - - // The actual test we wish to run + metadata: { requires: { topology: 'single', unifiedTopology: false } }, test: function(done) { if (process.platform !== 'linux') { var configuration = this.configuration; @@ -31,7 +26,6 @@ describe('Multiple Databases', function() { test.ok(err !== null); numberOfCloses = numberOfCloses + 1; }); - client.connect(function(err, client) { var db = client.db(configuration.db); diff --git a/test/functional/retryable_reads.test.js b/test/functional/retryable_reads.test.js index d533d603f52..93662e174c2 100644 --- a/test/functional/retryable_reads.test.js +++ b/test/functional/retryable_reads.test.js @@ -24,7 +24,7 @@ describe('Retryable Reads', function() { spec.description.match(/listCollections/i) || spec.description.match(/listCollectionNames/i) || spec.description.match(/estimatedDocumentCount/i) || - // FIXME(NODE-3074): uncomment when `count` spec tests have been fixed + // FIXME(NODE-3369): uncomment when `count` spec tests have been fixed // spec.description.match(/count/i) || spec.description.match(/find/i) ); diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index 455310da16a..f6039932835 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -110,7 +110,7 @@ describe('Transactions', function() { 'commitTransaction retry succeeds on new mongos', 'commitTransaction retry fails on new mongos', 'unpin after transient error within a transaction and commit', - // FIXME(NODE-3074): unskip count tests when spec tests have been updated + // FIXME(NODE-3369): unskip count tests when spec tests have been updated 'count', // This test needs there to be multiple mongoses 'increment txnNumber', diff --git a/test/functional/unified-spec-runner/entities.ts b/test/functional/unified-spec-runner/entities.ts index 9338971ab2f..6cb94830940 100644 --- a/test/functional/unified-spec-runner/entities.ts +++ b/test/functional/unified-spec-runner/entities.ts @@ -45,11 +45,10 @@ function serverApiConfig() { } function getClient(address) { - const options: any = { useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY) }; - const serverApi = serverApiConfig(); - if (serverApi) { - options.serverApi = serverApi; - } + const options: any = { + useUnifiedTopology: Boolean(process.env.MONGODB_UNIFIED_TOPOLOGY), + serverApi: serverApiConfig() + }; return new MongoClient(`mongodb://${address}`, options); } diff --git a/test/functional/versioned-api.test.js b/test/functional/versioned-api.test.js index 1d6f12308e6..8e73a51bb09 100644 --- a/test/functional/versioned-api.test.js +++ b/test/functional/versioned-api.test.js @@ -23,14 +23,6 @@ describe('Versioned API', function() { it(String(test.description), { metadata: { sessions: { skipLeakTests: true } }, test() { - // FIXME(NODE-3191) - // MongoError: BSON field 'FindCommandRequest.returnKey' is not allowed with apiStrict:true. - if ( - versionedApiTest.description.match(/strict/) && - test.description.match(/find and getMore append API version/) - ) { - return this.skip(); - } return runUnifiedTest(this, versionedApiTest, test); } }); diff --git a/test/unit/core/single/sessions.test.js b/test/unit/core/single/sessions.test.js index 9d6222de047..5fa24b9b620 100644 --- a/test/unit/core/single/sessions.test.js +++ b/test/unit/core/single/sessions.test.js @@ -424,8 +424,7 @@ describe('Sessions (Single)', function() { } }); - // FIXME(NODE-3191) - it.skip('should use the same session for any killCursor issued by a cursor', { + it('should use the same session for any killCursor issued by a cursor', { metadata: { requires: { topology: 'single' } }, test: function(_done) { const client = new Server(test.server.address()); @@ -436,7 +435,7 @@ describe('Sessions (Single)', function() { let commands = []; test.server.setMessageHandler(request => { const doc = request.document; - if (doc.ismaster) { + if (doc.ismaster || doc.hello) { request.reply( Object.assign({}, mock.DEFAULT_ISMASTER, { maxWireVersion: 6 @@ -485,9 +484,11 @@ describe('Sessions (Single)', function() { // Execute next cursor._next(function(err) { expect(err).to.not.exist; + expect(session.id).to.exist; cursor.kill(err => { expect(err).to.not.exist; + commands.forEach(command => expect(command.lsid).to.eql(session.id)); client.destroy(); diff --git a/test/unit/sessions/collection.test.js b/test/unit/sessions/collection.test.js index 37b22895a93..7ebfe3e893a 100644 --- a/test/unit/sessions/collection.test.js +++ b/test/unit/sessions/collection.test.js @@ -61,7 +61,11 @@ describe('Sessions', function() { const doc = request.document; if (doc.ismaster || doc.hello) { request.reply(mock.DEFAULT_ISMASTER_36); - } else if (doc.count || doc.aggregate || doc.endSessions) { + } else if ( + doc.count || + doc.aggregate || // count changed behavior to use agg 3.6.x+ + doc.endSessions + ) { request.reply({ ok: 1 }); } }); From 68b466573b686a9ed4a1ca2e9cc639cbf5bbdcfc Mon Sep 17 00:00:00 2001 From: Warren James <28974128+W-A-James@users.noreply.github.com> Date: Tue, 22 Jun 2021 11:29:18 -0400 Subject: [PATCH 3/7] test(NODE-2856): ensure defaultTransactionOptions get used from session (#2845) --- test/functional/readpreference.test.js | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/functional/readpreference.test.js b/test/functional/readpreference.test.js index c51e4c8efc9..2d5921e697f 100644 --- a/test/functional/readpreference.test.js +++ b/test/functional/readpreference.test.js @@ -759,4 +759,44 @@ describe('ReadPreference', function() { }); }) }); + + describe('Session readPreference', function() { + let client; + let session; + + beforeEach(function(done) { + let configuration = this.configuration; + client = configuration.newClient(configuration.writeConcernMax(), { + readPreference: 'primaryPreferred' + }); + client.connect(err => { + done(err); + }); + }); + + afterEach(function(done) { + if (session) { + session.abortTransaction(() => { + session.endSession(() => { + client.close(() => done()); + }); + }); + } else { + client.close(() => done()); + } + }); + + it('should use session readPreference instead of client readPreference', { + metadata: { requires: { unifiedTopology: true, topology: ['single', 'replicaset'] } }, + test: function() { + session = client.startSession({ + defaultTransactionOptions: { readPreference: 'secondary' }, + causalConsistency: true + }); + session.startTransaction(); + const result = ReadPreference.resolve(client, { session: session }); + expect(result).to.have.property('mode', 'secondary'); + } + }); + }); }); From b98f2061de9e8b0a814e3e7d39a0e914245953d0 Mon Sep 17 00:00:00 2001 From: Daria Pardue <81593090+dariakp@users.noreply.github.com> Date: Thu, 24 Jun 2021 08:51:52 -0400 Subject: [PATCH 4/7] refactor(NODE-3356): Update command monitoring logging (#2853) --- lib/command_utils.js | 122 +++++++++++++++++++++++++++ lib/core/connection/apm.js | 133 +++--------------------------- lib/core/connection/connection.js | 4 +- lib/core/connection/pool.js | 4 +- lib/core/sdam/server.js | 4 +- lib/core/topologies/server.js | 7 +- lib/operations/command.js | 4 +- lib/operations/command_v2.js | 8 +- lib/operations/db_ops.js | 7 +- 9 files changed, 159 insertions(+), 134 deletions(-) create mode 100644 lib/command_utils.js diff --git a/lib/command_utils.js b/lib/command_utils.js new file mode 100644 index 00000000000..ba8289c326b --- /dev/null +++ b/lib/command_utils.js @@ -0,0 +1,122 @@ +'use strict'; +const Msg = require('./core/connection/msg').Msg; +const KillCursor = require('./core/connection/commands').KillCursor; +const GetMore = require('./core/connection/commands').GetMore; + +/** Commands that we want to redact because of the sensitive nature of their contents */ +const SENSITIVE_COMMANDS = new Set([ + 'authenticate', + 'saslStart', + 'saslContinue', + 'getnonce', + 'createUser', + 'updateUser', + 'copydbgetnonce', + 'copydbsaslstart', + 'copydb' +]); + +const HELLO_COMMANDS = new Set(['hello', 'ismaster', 'isMaster']); + +const LEGACY_FIND_QUERY_MAP = { + $query: 'filter', + $orderby: 'sort', + $hint: 'hint', + $comment: 'comment', + $maxScan: 'maxScan', + $max: 'max', + $min: 'min', + $returnKey: 'returnKey', + $showDiskLoc: 'showRecordId', + $maxTimeMS: 'maxTimeMS', + $snapshot: 'snapshot' +}; + +const LEGACY_FIND_OPTIONS_MAP = { + numberToSkip: 'skip', + numberToReturn: 'batchSize', + returnFieldsSelector: 'projection' +}; + +const OP_QUERY_KEYS = [ + 'tailable', + 'oplogReplay', + 'noCursorTimeout', + 'awaitData', + 'partial', + 'exhaust' +]; + +const collectionName = command => command.ns.split('.')[1]; + +const shouldRedactCommand = (commandName, cmd) => + SENSITIVE_COMMANDS.has(commandName) || + (HELLO_COMMANDS.has(commandName) && !!cmd.speculativeAuthenticate); + +/** + * Extract the actual command from the query, possibly upconverting if it's a legacy + * format + * + * @param {Object} command the command + */ +const extractCommand = command => { + let extractedCommand; + if (command instanceof GetMore) { + extractedCommand = { + getMore: command.cursorId, + collection: collectionName(command), + batchSize: command.numberToReturn + }; + } else if (command instanceof KillCursor) { + extractedCommand = { + killCursors: collectionName(command), + cursors: command.cursorIds + }; + } else if (command instanceof Msg) { + extractedCommand = command.command; + } else if (command.query && command.query.$query) { + let result; + if (command.ns === 'admin.$cmd') { + // upconvert legacy command + result = Object.assign({}, command.query.$query); + } else { + // upconvert legacy find command + result = { find: collectionName(command) }; + Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => { + if (typeof command.query[key] !== 'undefined') + result[LEGACY_FIND_QUERY_MAP[key]] = command.query[key]; + }); + } + + Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => { + if (typeof command[key] !== 'undefined') result[LEGACY_FIND_OPTIONS_MAP[key]] = command[key]; + }); + + OP_QUERY_KEYS.forEach(key => { + if (command[key]) result[key] = command[key]; + }); + + if (typeof command.pre32Limit !== 'undefined') { + result.limit = command.pre32Limit; + } + + if (command.query.$explain) { + extractedCommand = { explain: result }; + } else { + extractedCommand = result; + } + } else { + extractedCommand = command.query || command; + } + + const commandName = Object.keys(extractedCommand)[0]; + return { + cmd: extractedCommand, + name: commandName, + shouldRedact: shouldRedactCommand(commandName, extractedCommand) + }; +}; + +module.exports = { + extractCommand +}; diff --git a/lib/core/connection/apm.js b/lib/core/connection/apm.js index f6e293b4293..875e418262b 100644 --- a/lib/core/connection/apm.js +++ b/lib/core/connection/apm.js @@ -1,129 +1,16 @@ 'use strict'; -const Msg = require('../connection/msg').Msg; const KillCursor = require('../connection/commands').KillCursor; const GetMore = require('../connection/commands').GetMore; const calculateDurationInMs = require('../../utils').calculateDurationInMs; - -/** Commands that we want to redact because of the sensitive nature of their contents */ -const SENSITIVE_COMMANDS = new Set([ - 'authenticate', - 'saslStart', - 'saslContinue', - 'getnonce', - 'createUser', - 'updateUser', - 'copydbgetnonce', - 'copydbsaslstart', - 'copydb' -]); - -const HELLO_COMMANDS = new Set(['hello', 'ismaster', 'isMaster']); +const extractCommand = require('../../command_utils').extractCommand; // helper methods -const extractCommandName = commandDoc => Object.keys(commandDoc)[0]; const namespace = command => command.ns; const databaseName = command => command.ns.split('.')[0]; -const collectionName = command => command.ns.split('.')[1]; const generateConnectionId = pool => pool.options ? `${pool.options.host}:${pool.options.port}` : pool.address; -const maybeRedact = (commandName, cmd, result) => - SENSITIVE_COMMANDS.has(commandName) || - (HELLO_COMMANDS.has(commandName) && cmd.speculativeAuthenticate) - ? {} - : result; const isLegacyPool = pool => pool.s && pool.queue; -const LEGACY_FIND_QUERY_MAP = { - $query: 'filter', - $orderby: 'sort', - $hint: 'hint', - $comment: 'comment', - $maxScan: 'maxScan', - $max: 'max', - $min: 'min', - $returnKey: 'returnKey', - $showDiskLoc: 'showRecordId', - $maxTimeMS: 'maxTimeMS', - $snapshot: 'snapshot' -}; - -const LEGACY_FIND_OPTIONS_MAP = { - numberToSkip: 'skip', - numberToReturn: 'batchSize', - returnFieldsSelector: 'projection' -}; - -const OP_QUERY_KEYS = [ - 'tailable', - 'oplogReplay', - 'noCursorTimeout', - 'awaitData', - 'partial', - 'exhaust' -]; - -/** - * Extract the actual command from the query, possibly upconverting if it's a legacy - * format - * - * @param {Object} command the command - */ -const extractCommand = command => { - if (command instanceof GetMore) { - return { - getMore: command.cursorId, - collection: collectionName(command), - batchSize: command.numberToReturn - }; - } - - if (command instanceof KillCursor) { - return { - killCursors: collectionName(command), - cursors: command.cursorIds - }; - } - - if (command instanceof Msg) { - return command.command; - } - - if (command.query && command.query.$query) { - let result; - if (command.ns === 'admin.$cmd') { - // upconvert legacy command - result = Object.assign({}, command.query.$query); - } else { - // upconvert legacy find command - result = { find: collectionName(command) }; - Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => { - if (typeof command.query[key] !== 'undefined') - result[LEGACY_FIND_QUERY_MAP[key]] = command.query[key]; - }); - } - - Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => { - if (typeof command[key] !== 'undefined') result[LEGACY_FIND_OPTIONS_MAP[key]] = command[key]; - }); - - OP_QUERY_KEYS.forEach(key => { - if (command[key]) result[key] = command[key]; - }); - - if (typeof command.pre32Limit !== 'undefined') { - result.limit = command.pre32Limit; - } - - if (command.query.$explain) { - return { explain: result }; - } - - return result; - } - - return command.query ? command.query : command; -}; - const extractReply = (command, reply) => { if (command instanceof GetMore) { return { @@ -183,15 +70,15 @@ class CommandStartedEvent { * @param {Object} command the command */ constructor(pool, command) { - const cmd = extractCommand(command); - const commandName = extractCommandName(cmd); + const extractedCommand = extractCommand(command); + const commandName = extractedCommand.name; const connectionDetails = extractConnectionDetails(pool); Object.assign(this, connectionDetails, { requestId: command.requestId, databaseName: databaseName(command), commandName, - command: maybeRedact(commandName, cmd, cmd) + command: extractedCommand.shouldRedact ? {} : extractedCommand.cmd }); } } @@ -207,15 +94,15 @@ class CommandSucceededEvent { * @param {Array} started a high resolution tuple timestamp of when the command was first sent, to calculate duration */ constructor(pool, command, reply, started) { - const cmd = extractCommand(command); - const commandName = extractCommandName(cmd); + const extractedCommand = extractCommand(command); + const commandName = extractedCommand.name; const connectionDetails = extractConnectionDetails(pool); Object.assign(this, connectionDetails, { requestId: command.requestId, commandName, duration: calculateDurationInMs(started), - reply: maybeRedact(commandName, cmd, extractReply(command, reply)) + reply: extractedCommand.shouldRedact ? {} : extractReply(command, reply) }); } } @@ -231,15 +118,15 @@ class CommandFailedEvent { * @param {Array} started a high resolution tuple timestamp of when the command was first sent, to calculate duration */ constructor(pool, command, error, started) { - const cmd = extractCommand(command); - const commandName = extractCommandName(cmd); + const extractedCommand = extractCommand(command); + const commandName = extractedCommand.name; const connectionDetails = extractConnectionDetails(pool); Object.assign(this, connectionDetails, { requestId: command.requestId, commandName, duration: calculateDurationInMs(started), - failure: maybeRedact(commandName, cmd, error) + failure: extractedCommand.shouldRedact ? {} : error }); } } diff --git a/lib/core/connection/connection.js b/lib/core/connection/connection.js index 5b22513b2a6..c9d34960c8b 100644 --- a/lib/core/connection/connection.js +++ b/lib/core/connection/connection.js @@ -254,10 +254,10 @@ class Connection extends EventEmitter { // Debug Log if (this.logger.isDebug()) { if (!Array.isArray(buffer)) { - this.logger.debug(`writing buffer [${buffer.toString('hex')}] to ${this.address}`); + this.logger.debug(`writing buffer [ ${buffer.length} ] to ${this.address}`); } else { for (let i = 0; i < buffer.length; i++) - this.logger.debug(`writing buffer [${buffer[i].toString('hex')}] to ${this.address}`); + this.logger.debug(`writing buffer [ ${buffer[i].length} ] to ${this.address}`); } } diff --git a/lib/core/connection/pool.js b/lib/core/connection/pool.js index f0061ee914e..491df52ceb6 100644 --- a/lib/core/connection/pool.js +++ b/lib/core/connection/pool.js @@ -390,8 +390,8 @@ function messageHandler(self) { if (self.logger.isDebug()) { self.logger.debug( f( - 'message [%s] received from %s:%s', - message.raw.toString('hex'), + 'message [ %s ] received from %s:%s', + message.raw.length, self.options.host, self.options.port ) diff --git a/lib/core/sdam/server.js b/lib/core/sdam/server.js index 26aeb5ed234..7adb0dfb952 100644 --- a/lib/core/sdam/server.js +++ b/lib/core/sdam/server.js @@ -20,6 +20,7 @@ const isNodeShuttingDownError = require('../error').isNodeShuttingDownError; const isNetworkErrorBeforeHandshake = require('../error').isNetworkErrorBeforeHandshake; const maxWireVersion = require('../utils').maxWireVersion; const makeStateMachine = require('../utils').makeStateMachine; +const extractCommand = require('../../command_utils').extractCommand; const common = require('./common'); const ServerType = common.ServerType; const isTransactionCommand = require('../transactions').isTransactionCommand; @@ -261,10 +262,11 @@ class Server extends EventEmitter { // Debug log if (this.s.logger.isDebug()) { + const extractedCommand = extractCommand(cmd); this.s.logger.debug( `executing command [${JSON.stringify({ ns, - cmd, + cmd: extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd, options: debugOptions(DEBUG_FIELDS, options) })}] against ${this.name}` ); diff --git a/lib/core/topologies/server.js b/lib/core/topologies/server.js index c6a0bfa5db9..43bf7582059 100644 --- a/lib/core/topologies/server.js +++ b/lib/core/topologies/server.js @@ -16,6 +16,7 @@ var inherits = require('util').inherits, createCompressionInfo = require('./shared').createCompressionInfo, resolveClusterTime = require('./shared').resolveClusterTime, SessionMixins = require('./shared').SessionMixins, + extractCommand = require('../../command_utils').extractCommand, relayEvents = require('../utils').relayEvents; const collationNotSupported = require('../utils').collationNotSupported; @@ -608,18 +609,20 @@ Server.prototype.command = function(ns, cmd, options, callback) { options = Object.assign({}, options, { wireProtocolCommand: false }); // Debug log - if (self.s.logger.isDebug()) + if (self.s.logger.isDebug()) { + const extractedCommand = extractCommand(cmd); self.s.logger.debug( f( 'executing command [%s] against %s', JSON.stringify({ ns: ns, - cmd: cmd, + cmd: extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd, options: debugOptions(debugFields, options) }), self.name ) ); + } // If we are not connected or have a disconnectHandler specified if (disconnectHandler(self, 'command', ns, cmd, options, callback)) return; diff --git a/lib/operations/command.js b/lib/operations/command.js index fd18a543c51..e65d3bbb493 100644 --- a/lib/operations/command.js +++ b/lib/operations/command.js @@ -8,6 +8,7 @@ const handleCallback = require('../utils').handleCallback; const MongoError = require('../core').MongoError; const ReadPreference = require('../core').ReadPreference; const MongoDBNamespace = require('../utils').MongoDBNamespace; +const extractCommand = require('../command_utils').extractCommand; const debugFields = [ 'authSource', @@ -95,9 +96,10 @@ class CommandOperation extends OperationBase { // Debug information if (db.s.logger.isDebug()) { + const extractedCommand = extractCommand(command); db.s.logger.debug( `executing command ${JSON.stringify( - command + extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : command )} against ${dbName}.$cmd with options [${JSON.stringify( debugOptions(debugFields, options) )}]` diff --git a/lib/operations/command_v2.js b/lib/operations/command_v2.js index 52f982b885f..475a1c13426 100644 --- a/lib/operations/command_v2.js +++ b/lib/operations/command_v2.js @@ -9,6 +9,7 @@ const maxWireVersion = require('../core/utils').maxWireVersion; const decorateWithExplain = require('../utils').decorateWithExplain; const commandSupportsReadConcern = require('../core/sessions').commandSupportsReadConcern; const MongoError = require('../core/error').MongoError; +const extractCommand = require('../command_utils').extractCommand; const SUPPORTS_WRITE_CONCERN_AND_COLLATION = 5; @@ -89,7 +90,12 @@ class CommandOperationV2 extends OperationBase { } if (this.logger && this.logger.isDebug()) { - this.logger.debug(`executing command ${JSON.stringify(cmd)} against ${this.ns}`); + const extractedCommand = extractCommand(cmd); + this.logger.debug( + `executing command ${JSON.stringify( + extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : cmd + )} against ${this.ns}` + ); } server.command(this.ns.toString(), cmd, this.options, (err, result) => { diff --git a/lib/operations/db_ops.js b/lib/operations/db_ops.js index 8f9b89046b2..fb9c492da8d 100644 --- a/lib/operations/db_ops.js +++ b/lib/operations/db_ops.js @@ -8,6 +8,7 @@ const MongoError = require('../core').MongoError; const parseIndexOptions = require('../utils').parseIndexOptions; const ReadPreference = require('../core').ReadPreference; const toError = require('../utils').toError; +const extractCommand = require('../command_utils').extractCommand; const CONSTANTS = require('../constants'); const MongoDBNamespace = require('../utils').MongoDBNamespace; @@ -227,14 +228,16 @@ function executeCommand(db, command, options, callback) { options.readPreference = ReadPreference.resolve(db, options); // Debug information - if (db.s.logger.isDebug()) + if (db.s.logger.isDebug()) { + const extractedCommand = extractCommand(command); db.s.logger.debug( `executing command ${JSON.stringify( - command + extractedCommand.shouldRedact ? `${extractedCommand.name} details REDACTED` : command )} against ${dbName}.$cmd with options [${JSON.stringify( debugOptions(debugFields, options) )}]` ); + } // Execute command db.s.topology.command(db.s.namespace.withCollection('$cmd'), command, options, (err, result) => { From a917dfada67859412344ed238796cf3bee243f5f Mon Sep 17 00:00:00 2001 From: Warren James <28974128+W-A-James@users.noreply.github.com> Date: Thu, 24 Jun 2021 09:07:10 -0400 Subject: [PATCH 5/7] fix(NODE-2035): Exceptions thrown from awaited cursor forEach do not propagate (#2852) --- lib/cursor.js | 14 ++++++-- test/functional/cursor.test.js | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/lib/cursor.js b/lib/cursor.js index 5c17f43d788..d23d9f00545 100644 --- a/lib/cursor.js +++ b/lib/cursor.js @@ -742,7 +742,12 @@ class Cursor extends CoreCursor { return false; } if (doc != null) { - iterator(doc); + try { + iterator(doc); + } catch (error) { + callback(error); + return false; + } return true; } if (doc == null && callback) { @@ -762,7 +767,12 @@ class Cursor extends CoreCursor { fulfill(null); return false; } else { - iterator(doc); + try { + iterator(doc); + } catch (error) { + reject(error); + return false; + } return true; } }); diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index ba79b603314..95c1505632c 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -4372,6 +4372,70 @@ describe('Cursor', function() { } ); + describe('Cursor forEach Error propagation', function() { + let configuration; + let client; + let cursor; + let collection; + + beforeEach(function(done) { + configuration = this.configuration; + client = configuration.newClient({ w: 1 }, { maxPoolSize: 1 }); + client + .connect() + .then(() => { + collection = client.db(configuration.db).collection('cursor_session_tests2'); + done(); + }) + .catch(error => { + done(error); + }); + }); + + afterEach(function(done) { + if (cursor) { + cursor + .close() + .then(() => client.close()) + .then(() => done()); + } else { + client.close().then(() => done()); + } + }); + + // NODE-2035 + it('should propagate error when exceptions are thrown from an awaited forEach call', function(done) { + const docs = [{ unique_key_2035: 1 }, { unique_key_2035: 2 }, { unique_key_2035: 3 }]; + collection + .insertMany(docs) + .then(() => { + cursor = collection.find({ + unique_key_2035: { + $exists: true + } + }); + cursor + .forEach(() => { + throw new Error('FAILURE IN FOREACH CALL'); + }) + .then( + () => { + done(new Error('Error in forEach call not caught')); + }, + err => { + try { + expect(err.message).to.deep.equal('FAILURE IN FOREACH CALL'); + done(); + } catch (error) { + done(error); + } + } + ); + }) + .catch(error => done(error)); + }); + }); + it('should return a promise when no callback supplied to forEach method', function(done) { const configuration = this.configuration; const client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: false }); From 344672469881c129454a6ddb4a3071804d489178 Mon Sep 17 00:00:00 2001 From: emadum Date: Thu, 24 Jun 2021 17:40:59 -0400 Subject: [PATCH 6/7] restore deleted test, fix count bug --- lib/collection.js | 2 +- lib/operations/estimated_document_count.js | 24 ++++++++++- test/functional/cursor.test.js | 48 ++++++++++++++++++++++ test/functional/transactions.test.js | 7 +--- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/lib/collection.js b/lib/collection.js index 157bcdeb2c4..b0362d59456 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1524,7 +1524,7 @@ Collection.prototype.count = deprecate(function(query, options, callback) { return executeOperation( this.s.topology, - new CountDocumentsOperation(this, query, options), + new EstimatedDocumentCountOperation(this, Object.assign({ query }, options)), callback ); }, 'collection.count is deprecated, and will be removed in a future version.' + diff --git a/lib/operations/estimated_document_count.js b/lib/operations/estimated_document_count.js index 55f71aad330..ca102275b74 100644 --- a/lib/operations/estimated_document_count.js +++ b/lib/operations/estimated_document_count.js @@ -4,10 +4,12 @@ const Aspect = require('./operation').Aspect; const defineAspects = require('./operation').defineAspects; const CommandOperationV2 = require('./command_v2'); const maxWireVersion = require('../core/utils').maxWireVersion; +const CountDocumentsOperation = require('./count_documents'); class EstimatedDocumentCountOperation extends CommandOperationV2 { constructor(collection, options) { super(collection, options); + this.collection = collection; this.collectionName = collection.s.namespace.collection; } @@ -15,6 +17,11 @@ class EstimatedDocumentCountOperation extends CommandOperationV2 { if (maxWireVersion(server) < 12) { return this.executeLegacy(server, callback); } + // if the user specifies a filter, use a CountDocumentsOperation instead + if (this.options.query) { + const op = new CountDocumentsOperation(this.collection, this.options.query, this.options); + return op.execute(server, callback); + } const pipeline = [{ $collStats: { count: {} } }, { $group: { _id: 1, n: { $sum: '$count' } } }]; const cmd = { aggregate: this.collectionName, pipeline, cursor: {} }; @@ -42,8 +49,21 @@ class EstimatedDocumentCountOperation extends CommandOperationV2 { executeLegacy(server, callback) { const cmd = { count: this.collectionName }; - if (typeof this.options.maxTimeMS === 'number') { - cmd.maxTimeMS = this.options.maxTimeMS; + const options = this.options; + if (options.query) { + cmd.query = options.query; + } + if (options.hint) { + cmd.hint = options.hint; + } + if (typeof options.maxTimeMS === 'number') { + cmd.maxTimeMS = options.maxTimeMS; + } + if (typeof options.skip === 'number') { + cmd.skip = options.skip; + } + if (typeof options.limit === 'number') { + cmd.limit = options.limit; } super.executeCommand(server, cmd, (err, response) => { diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index 30052528726..ddfaacdc14c 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -4119,6 +4119,54 @@ describe('Cursor', function() { } }); + it('Correctly decorate the collection count command with skip, limit, hint, readConcern', { + metadata: { + requires: { topology: ['single', 'replicaset', 'sharded'], mongodb: '<4.9.0' } + }, + + test: function(done) { + var started = []; + + const configuration = this.configuration; + const client = configuration.newClient(configuration.writeConcernMax(), { + maxPoolSize: 1, + monitorCommands: true + }); + client.on('commandStarted', function(event) { + if (event.commandName === 'count') started.push(event); + }); + + client.connect((err, client) => { + expect(err).to.not.exist; + this.defer(() => client.close()); + + const db = client.db(configuration.db); + db.collection('cursor_count_test1', { readConcern: { level: 'local' } }).count( + { + project: '123' + }, + { + readConcern: { level: 'local' }, + limit: 5, + skip: 5, + hint: { project: 1 } + }, + err => { + expect(err).to.not.exist; + test.equal(1, started.length); + if (started[0].command.readConcern) + test.deepEqual({ level: 'local' }, started[0].command.readConcern); + test.deepEqual({ project: 1 }, started[0].command.hint); + test.equal(5, started[0].command.skip); + test.equal(5, started[0].command.limit); + + done(); + } + ); + }); + } + }); + it('Should properly kill a cursor', { metadata: { requires: { diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index f6039932835..8a779362c54 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -99,12 +99,7 @@ describe('Transactions', function() { return testContext.setup(this.configuration); }); - function testFilter(spec, config) { - // NODE-2574: remove this when HELP-15010 is resolved - if (config.topologyType === 'Sharded' && semver.satisfies(config.version, '>=4.4')) { - return false; - } - + function testFilter(spec) { const SKIP_TESTS = [ // commitTransaction retry seems to be swallowed by mongos in these three cases 'commitTransaction retry succeeds on new mongos', From caab8f155ed439d81ece64809fd566afa2cc9b55 Mon Sep 17 00:00:00 2001 From: emadum Date: Thu, 24 Jun 2021 19:26:21 -0400 Subject: [PATCH 7/7] review feedback --- test/functional/collations.test.js | 9 ++++--- test/functional/max_staleness.test.js | 34 ++++++++++----------------- test/functional/transactions.test.js | 1 - test/functional/view.test.js | 9 ++++--- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index dc9fea44b1e..a3f5c22bcea 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -678,11 +678,14 @@ describe('Collation', function() { .collection('test') .createIndex({ a: 1 }, { collation: { caseLevel: true } }) .then(() => { - delete commandResult.apiVersion; // might or might not exist, this test isn't concerned with checking this - expect(commandResult).to.eql({ + const expectation = { createIndexes: 'test', indexes: [{ name: 'a_1', key: { a: 1 }, collation: { caseLevel: true } }] - }); + }; + if (configuration.serverApi) { + expectation.apiVersion = configuration.serverApi; + } + expect(commandResult).to.eql(expectation); return client.close(); }); diff --git a/test/functional/max_staleness.test.js b/test/functional/max_staleness.test.js index 8a8868467c3..1d1a6353068 100644 --- a/test/functional/max_staleness.test.js +++ b/test/functional/max_staleness.test.js @@ -6,9 +6,16 @@ const mock = require('mongodb-mock-server'); const test = {}; describe('Max Staleness', function() { afterEach(() => mock.cleanup()); - beforeEach(() => { + beforeEach(function() { return mock.createServer().then(server => { test.server = server; + test.expectation = { + $query: { find: 'test', filter: {} }, + $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } + }; + if (this.configuration.serverApi) { + test.expectation.$query.apiVersion = this.configuration.serverApi; + } const defaultFields = Object.assign({}, mock.DEFAULT_ISMASTER, { msg: 'isdbgrid' @@ -64,11 +71,7 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; - delete test.checkCommand.$query.apiVersion; - expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {} }, - $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } - }); + expect(test.checkCommand).to.eql(test.expectation); client.close(done); }); @@ -103,11 +106,7 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; - delete test.checkCommand.$query.apiVersion; - expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {} }, - $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } - }); + expect(test.checkCommand).to.eql(test.expectation); client.close(done); }); @@ -142,11 +141,8 @@ describe('Max Staleness', function() { .find({}) .toArray(function(err) { expect(err).to.not.exist; - delete test.checkCommand.$query.apiVersion; - expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {} }, - $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } - }); + + expect(test.checkCommand).to.eql(test.expectation); client.close(done); }); @@ -180,11 +176,7 @@ describe('Max Staleness', function() { .setReadPreference(readPreference) .toArray(function(err) { expect(err).to.not.exist; - delete test.checkCommand.$query.apiVersion; - expect(test.checkCommand).to.eql({ - $query: { find: 'test', filter: {} }, - $readPreference: { mode: 'secondary', maxStalenessSeconds: 250 } - }); + expect(test.checkCommand).to.eql(test.expectation); client.close(done); }); diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index 8a779362c54..e8000dcf7b6 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -8,7 +8,6 @@ const TestRunnerContext = require('./spec-runner').TestRunnerContext; const loadSpecTests = require('../spec').loadSpecTests; const generateTopologyTests = require('./spec-runner').generateTopologyTests; const MongoNetworkError = require('../../lib/core').MongoNetworkError; -const semver = require('semver'); function ignoreNsNotFoundForListIndexes(err) { if (err.code !== 26) { diff --git a/test/functional/view.test.js b/test/functional/view.test.js index 5e38ea30041..a2aeec38529 100644 --- a/test/functional/view.test.js +++ b/test/functional/view.test.js @@ -58,12 +58,15 @@ describe('Views', function() { ) { expect(r).to.exist; expect(err).to.not.exist; - delete commandResult.apiVersion; - expect(commandResult).to.eql({ + const expectation = { create: 'test', viewOn: 'users', pipeline: [{ $match: {} }] - }); + }; + if (self.configuration.serverApi) { + expectation.apiVersion = self.configuration.serverApi; + } + expect(commandResult).to.eql(expectation); client.close(done); });