From f4bf9125e9a9e64d03e625580bd7c84d03fa8d09 Mon Sep 17 00:00:00 2001 From: Eric Adum Date: Fri, 10 Apr 2020 16:05:43 -0400 Subject: [PATCH] fix(ChangeStream): whitelist change stream resumable errors - Changes which errors are considered resumable on change streams, adding support for the new ResumableChangeStreamError label. - Removes ElectionInProgress (216) from ResumableChangeStreamError. - Updates ChangeStream prose tests which described startAfter behavior for unsupported server versions. - Fixes use of startAfter/resumeAfter when resuming from an invalidate event. Implement prose tests #17 and #18. NODE-2478 NODE-2522 --- lib/change_stream.js | 60 +- lib/error.js | 33 +- lib/utils.js | 24 +- test/functional/change_stream.test.js | 656 +++---- test/functional/change_stream_spec.test.js | 25 +- test/spec/change-stream/README.rst | 130 +- .../change-stream/change-streams-errors.json | 51 +- .../change-stream/change-streams-errors.yml | 32 +- .../change-streams-resume-errorLabels.json | 1634 ++++++++++++++++ .../change-streams-resume-errorLabels.yml | 1105 +++++++++++ .../change-streams-resume-whitelist.json | 1653 +++++++++++++++++ .../change-streams-resume-whitelist.yml | 1107 +++++++++++ test/spec/change-stream/change-streams.json | 33 +- test/spec/change-stream/change-streams.yml | 25 +- test/unit/change_stream_resume.test.js | 223 --- 15 files changed, 6092 insertions(+), 699 deletions(-) create mode 100644 test/spec/change-stream/change-streams-resume-errorLabels.json create mode 100644 test/spec/change-stream/change-streams-resume-errorLabels.yml create mode 100644 test/spec/change-stream/change-streams-resume-whitelist.json create mode 100644 test/spec/change-stream/change-streams-resume-whitelist.yml delete mode 100644 test/unit/change_stream_resume.test.js diff --git a/lib/change_stream.js b/lib/change_stream.js index 5773ac5e43..35a9f9487f 100644 --- a/lib/change_stream.js +++ b/lib/change_stream.js @@ -296,7 +296,9 @@ class ChangeStreamCursor extends Cursor { ['resumeAfter', 'startAfter', 'startAtOperationTime'].forEach(key => delete result[key]); if (this.resumeToken) { - result.resumeAfter = this.resumeToken; + const resumeKey = + this.options.startAfter && !this.hasReceived ? 'startAfter' : 'resumeAfter'; + result[resumeKey] = this.resumeToken; } else if (this.startAtOperationTime && maxWireVersion(this.server) >= 7) { result.startAtOperationTime = this.startAtOperationTime; } @@ -305,6 +307,26 @@ class ChangeStreamCursor extends Cursor { return result; } + cacheResumeToken(resumeToken) { + if (this.bufferedCount() === 0 && this.cursorState.postBatchResumeToken) { + this.resumeToken = this.cursorState.postBatchResumeToken; + } else { + this.resumeToken = resumeToken; + } + this.hasReceived = true; + } + + _processBatch(batchName, response) { + const cursor = response.cursor; + if (cursor.postBatchResumeToken) { + this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken; + + if (cursor[batchName].length === 0) { + this.resumeToken = cursor.postBatchResumeToken; + } + } + } + _initializeCursor(callback) { super._initializeCursor((err, result) => { if (err) { @@ -323,15 +345,9 @@ class ChangeStreamCursor extends Cursor { this.startAtOperationTime = response.operationTime; } - const cursor = response.cursor; - if (cursor.postBatchResumeToken) { - this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken; - - if (cursor.firstBatch.length === 0) { - this.resumeToken = cursor.postBatchResumeToken; - } - } + this._processBatch('firstBatch', response); + this.emit('init', result); this.emit('response'); callback(err, result); }); @@ -344,15 +360,9 @@ class ChangeStreamCursor extends Cursor { return; } - const cursor = response.cursor; - if (cursor.postBatchResumeToken) { - this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken; - - if (cursor.nextBatch.length === 0) { - this.resumeToken = cursor.postBatchResumeToken; - } - } + this._processBatch('nextBatch', response); + this.emit('more', response); this.emit('response'); callback(err, response); }); @@ -374,6 +384,7 @@ function createChangeStreamCursor(self, options) { const pipeline = [{ $changeStream: changeStreamStageOptions }].concat(self.pipeline); const cursorOptions = applyKnownOptions({}, options, CURSOR_OPTIONS); + const changeStreamCursor = new ChangeStreamCursor( self.topology, new AggregateOperation(self.parent, pipeline, options), @@ -472,9 +483,10 @@ function processNewChange(args) { const change = args.change; const callback = args.callback; const eventEmitter = args.eventEmitter || false; + const cursor = changeStream.cursor; - // If the changeStream is closed, then it should not process a change. - if (changeStream.isClosed()) { + // If the cursor is null, then it should not process a change. + if (cursor == null) { // We do not error in the eventEmitter case. if (eventEmitter) { return; @@ -486,12 +498,12 @@ function processNewChange(args) { : changeStream.promiseLibrary.reject(error); } - const cursor = changeStream.cursor; const topology = changeStream.topology; const options = changeStream.cursor.options; + const wireVersion = maxWireVersion(cursor.server); if (error) { - if (isResumableError(error) && !changeStream.attemptingResume) { + if (isResumableError(error, wireVersion) && !changeStream.attemptingResume) { changeStream.attemptingResume = true; // stop listening to all events from old cursor @@ -557,11 +569,7 @@ function processNewChange(args) { } // cache the resume token - if (cursor.bufferedCount() === 0 && cursor.cursorState.postBatchResumeToken) { - cursor.resumeToken = cursor.cursorState.postBatchResumeToken; - } else { - cursor.resumeToken = change._id; - } + cursor.cacheResumeToken(change._id); // wipe the startAtOperationTime if there was one so that there won't be a conflict // between resumeToken and startAtOperationTime if we need to reconnect the cursor diff --git a/lib/error.js b/lib/error.js index 995a9b8196..8fda1306ba 100644 --- a/lib/error.js +++ b/lib/error.js @@ -7,6 +7,26 @@ const GET_MORE_NON_RESUMABLE_CODES = new Set([ 237, // CursorKilled 11601 // Interrupted ]); +// From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error +const GET_MORE_RESUMABLE_CODES = new Set([ + 6, // HostUnreachable + 7, // HostNotFound + 89, // NetworkTimeout + 91, // ShutdownInProgress + 189, // PrimarySteppedDown + 262, // ExceededTimeLimit + 9001, // SocketException + 10107, // NotMaster + 11600, // InterruptedAtShutdown + 11602, // InterruptedDueToReplStateChange + 13435, // NotMasterNoSlaveOk + 13436, // NotMasterOrSecondary + 63, // StaleShardVersion + 150, // StaleEpoch + 13388, // StaleConfig + 234, // RetryChangeStream + 133 // FailedToSatisfyReadPreference +]); /** * Creates a new MongoError @@ -329,7 +349,7 @@ function isGetMoreError(error) { } } -function isResumableError(error) { +function isResumableError(error, wireVersion) { if (!isGetMoreError(error)) { return false; } @@ -338,14 +358,19 @@ function isResumableError(error) { return true; } - return !( - GET_MORE_NON_RESUMABLE_CODES.has(error.code) || - error.hasErrorLabel('NonRetryableChangeStreamError') + if (wireVersion >= 9) { + return error.hasErrorLabel('ResumableChangeStreamError'); + } + + return ( + GET_MORE_RESUMABLE_CODES.has(error.code) && + !error.hasErrorLabel('NonResumableChangeStreamError') ); } module.exports = { GET_MORE_NON_RESUMABLE_CODES, + GET_MORE_RESUMABLE_CODES, MongoError, MongoNetworkError, MongoParseError, diff --git a/lib/utils.js b/lib/utils.js index a385d60507..2c7df6cc3f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -772,22 +772,24 @@ function relayEvents(listener, emitter, events) { * @param {(Topology|Server)} topologyOrServer */ function maxWireVersion(topologyOrServer) { - if (topologyOrServer.ismaster) { - return topologyOrServer.ismaster.maxWireVersion; - } + if (topologyOrServer) { + if (topologyOrServer.ismaster) { + return topologyOrServer.ismaster.maxWireVersion; + } - if (typeof topologyOrServer.lastIsMaster === 'function') { - const lastIsMaster = topologyOrServer.lastIsMaster(); - if (lastIsMaster) { - return lastIsMaster.maxWireVersion; + if (typeof topologyOrServer.lastIsMaster === 'function') { + const lastIsMaster = topologyOrServer.lastIsMaster(); + if (lastIsMaster) { + return lastIsMaster.maxWireVersion; + } } - } - if (topologyOrServer.description) { - return topologyOrServer.description.maxWireVersion; + if (topologyOrServer.description) { + return topologyOrServer.description.maxWireVersion; + } } - return null; + return 0; } /* diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index 174e5a6170..9849500b8c 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -1,7 +1,12 @@ 'use strict'; const assert = require('assert'); const { Transform } = require('stream'); -const { MongoError, MongoNetworkError } = require('../../lib/error'); +const { + MongoError, + MongoNetworkError, + mongoErrorContextSymbol, + isResumableError +} = require('../../lib/error'); const { setupDatabase, delay } = require('./shared'); const co = require('co'); const mock = require('mongodb-mock-server'); @@ -12,6 +17,96 @@ const { ObjectId, Timestamp, Long, ReadPreference } = require('../..'); chai.use(require('chai-subset')); +/** + * Triggers a fake resumable error on a change stream + * + * @param {ChangeStream} changeStream + * @param {Function} onCursorClosed callback when cursor closed due this error + */ +function triggerResumableError(changeStream, onCursorClosed) { + const closeCursor = changeStream.cursor.close; + changeStream.cursor.close = callback => { + onCursorClosed(); + changeStream.cursor.close = closeCursor; + changeStream.cursor.close(callback); + }; + const fakeResumableError = new MongoNetworkError('fake error'); + fakeResumableError[mongoErrorContextSymbol] = { isGetMore: true }; + changeStream.cursor.emit('error', fakeResumableError); +} + +/** + * Waits for a change stream to start + * + * @param {ChangeStream} changeStream + * @param {Function} callback + */ +function waitForStarted(changeStream, callback) { + changeStream.cursor.once('init', () => { + callback(); + }); +} + +/** + * Iterates the next discrete batch of a change stream non-eagerly. This + * will return `null` if the next bach is empty, rather than waiting forever + * for a non-empty batch. + * + * @param {ChangeStream} changeStream + * @param {Function} callback + */ +function tryNext(changeStream, callback) { + let complete = false; + function done(err, result) { + if (complete) return; + + // if the arity is 1 then this a callback for `more` + if (arguments.length === 1) { + result = err; + const batch = result.cursor.firstBatch || result.cursor.nextBatch; + if (batch.length === 0) { + complete = true; + callback(null, null); + } + + return; + } + + // otherwise, this a normal response to `next` + complete = true; + changeStream.removeListener('more', done); + if (err) return callback(err); + callback(err, result); + } + + // race the two requests + changeStream.next(done); + changeStream.cursor.once('more', done); +} + +/** + * Exhausts a change stream aggregating all responses until the first + * empty batch into a returned array of events. + * + * @param {ChangeStream} changeStream + * @param {Function|Array} bag + * @param {Function} [callback] + */ +function exhaust(changeStream, bag, callback) { + if (typeof bag === 'function') { + callback = bag; + bag = []; + } + + tryNext(changeStream, (err, doc) => { + if (err) return callback(err); + if (doc === null) return callback(undefined, bag); + + bag.push(doc); + exhaust(changeStream, bag, callback); + }); +} + // Define the pipeline processing changes var pipeline = [ { $addFields: { addedField: 'This is a field added using $addFields' } }, @@ -42,7 +137,7 @@ describe('Change Streams', function() { afterEach(() => mock.cleanup()); it('Should close the listeners after the cursor is closed', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { let closed = false; @@ -75,7 +170,7 @@ describe('Change Streams', function() { }); it('Should create a Change Stream on a collection and emit `change` events', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { const configuration = this.configuration; @@ -140,7 +235,7 @@ describe('Change Streams', function() { it( 'Should create a Change Stream on a collection and get change events through imperative callback form', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -198,7 +293,7 @@ describe('Change Streams', function() { ); it('Should support creating multiple simultaneous Change Streams', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -283,7 +378,7 @@ describe('Change Streams', function() { }); it('Should properly close Change Stream cursor', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -313,7 +408,7 @@ describe('Change Streams', function() { it( 'Should error when attempting to create a Change Stream with a forbidden aggregation pipeline stage', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -338,8 +433,8 @@ describe('Change Streams', function() { } ); - it.skip('Should cache the change stream resume token using imperative callback form', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + it('Should cache the change stream resume token using imperative callback form', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -376,8 +471,8 @@ describe('Change Streams', function() { } }); - it.skip('Should cache the change stream resume token using promises', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + it('Should cache the change stream resume token using promises', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { var configuration = this.configuration; @@ -412,8 +507,8 @@ describe('Change Streams', function() { } }); - it.skip('Should cache the change stream resume token using event listeners', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + it('Should cache the change stream resume token using event listeners', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -448,7 +543,7 @@ describe('Change Streams', function() { it( 'Should error if resume token projected out of change stream document using imperative callback form', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -485,7 +580,7 @@ describe('Change Streams', function() { ); it('Should error if resume token projected out of change stream document using event listeners', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -523,7 +618,7 @@ describe('Change Streams', function() { }); it('Should invalidate change stream on collection rename using event listeners', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -578,7 +673,7 @@ describe('Change Streams', function() { }); it('Should invalidate change stream on database drop using imperative callback form', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -632,7 +727,7 @@ describe('Change Streams', function() { }); it('Should invalidate change stream on collection drop using promises', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -688,7 +783,7 @@ describe('Change Streams', function() { requires: { generators: true, topology: 'single', - mongodb: '>=3.5.10' + mongodb: '>=3.6' } }, @@ -785,7 +880,7 @@ describe('Change Streams', function() { requires: { generators: true, topology: 'single', - mongodb: '>=3.5.10' + mongodb: '>=3.6' } }, test: function(done) { @@ -879,7 +974,7 @@ describe('Change Streams', function() { requires: { generators: true, topology: 'single', - mongodb: '>=3.5.10' + mongodb: '>=3.6' } }, test: function(done) { @@ -1011,7 +1106,7 @@ describe('Change Streams', function() { }); it('Should resume from point in time using user-provided resumeAfter', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { var configuration = this.configuration; @@ -1100,7 +1195,7 @@ describe('Change Streams', function() { }); it('Should support full document lookup', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { var configuration = this.configuration; @@ -1154,7 +1249,7 @@ describe('Change Streams', function() { }); it('Should support full document lookup with deleted documents', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { var configuration = this.configuration; @@ -1221,7 +1316,7 @@ describe('Change Streams', function() { }); it('Should create Change Streams with correct read preferences', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { var configuration = this.configuration; @@ -1267,7 +1362,7 @@ describe('Change Streams', function() { }); it('Should support piping of Change Streams', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { const configuration = this.configuration; @@ -1316,7 +1411,7 @@ describe('Change Streams', function() { requires: { generators: true, topology: 'single', - mongodb: '>=3.5.10' + mongodb: '>=3.6' } }, test: function(done) { @@ -1462,7 +1557,7 @@ describe('Change Streams', function() { }); it('Should support piping of Change Streams through multiple pipes', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { var configuration = this.configuration; @@ -1522,50 +1617,8 @@ describe('Change Streams', function() { } }); - it('Should resume after a killCursors command is issued for its child cursor', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, - test: function(done) { - const configuration = this.configuration; - const client = configuration.newClient(); - - const collectionName = 'resumeAfterKillCursor'; - - let db; - let coll; - let changeStream; - - function close(e) { - changeStream.close(() => client.close(() => done(e))); - } - - client - .connect() - .then(() => (db = client.db('integration_tests'))) - .then(() => (coll = db.collection(collectionName))) - .then(() => (changeStream = coll.watch())) - .then(() => ({ p: changeStream.next() })) - .then(x => coll.insertOne({ darmok: 'jalad' }).then(() => x.p)) - .then(() => - db.command({ - killCursors: collectionName, - cursors: [changeStream.cursor.cursorState.cursorId] - }) - ) - .then(() => coll.insertOne({ shaka: 'walls fell' })) - .then(() => changeStream.next()) - .then(change => { - expect(change).to.have.property('operationType', 'insert'); - expect(change).to.have.nested.property('fullDocument.shaka', 'walls fell'); - }) - .then( - () => close(), - e => close(e) - ); - } - }); - it('should maintain change stream options on resume', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { const configuration = this.configuration; const client = configuration.newClient(); @@ -1602,8 +1655,10 @@ describe('Change Streams', function() { } }); + // 9. $changeStream stage for ChangeStream against a server >=4.0 and <4.0.7 that has not received + // any results yet MUST include a startAtOperationTime option when resuming a change stream. it('Should include a startAtOperationTime field when resuming if no changes have been received', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.7.3' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.0 <4.0.7' } }, test: function(done) { const configuration = this.configuration; @@ -1859,7 +1914,7 @@ describe('Change Streams', function() { }); it('should emit close event after error event', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { const configuration = this.configuration; const client = configuration.newClient(); @@ -1928,7 +1983,7 @@ describe('Change Streams', function() { }); it('when invoked with promises', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function() { function read() { const changeStream = coll.watch(); @@ -1950,7 +2005,7 @@ describe('Change Streams', function() { }); it('when invoked with callbacks', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { const changeStream = coll.watch(); @@ -1975,7 +2030,7 @@ describe('Change Streams', function() { }); it('when invoked using eventEmitter API', { - metadata: { requires: { topology: 'replicaset', mongodb: '>=3.5.10' } }, + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, test: function(done) { let closed = false; const close = _err => { @@ -2195,7 +2250,7 @@ describe('Change Streams', function() { } } - // For a ChangeStream under these conditions: + // 11. For a ChangeStream under these conditions: // Running against a server >=4.0.7. // The batch is empty or has been iterated to the last document. // Expected result: @@ -2244,14 +2299,13 @@ describe('Change Streams', function() { }); }); - // For a ChangeStream under these conditions: + // 12. For a ChangeStream under these conditions: // Running against a server <4.0.7. // The batch is empty or has been iterated to the last document. // Expected result: // getResumeToken must return the _id of the last document returned if one exists. - // getResumeToken must return startAfter from the initial aggregate if the option was specified. // getResumeToken must return resumeAfter from the initial aggregate if the option was specified. - // If neither the startAfter nor resumeAfter options were specified, the getResumeToken result must be empty. + // If ``resumeAfter`` was not specified, the ``getResumeToken`` result must be empty. describe('for emptied batch on server <= 4.0.7', function() { it('must return the _id of the last document returned if one exists', function() { const manager = new MockServerManager(this.configuration, { @@ -2287,47 +2341,6 @@ describe('Change Streams', function() { expect(tokens[0]).to.deep.equal(successes[1].nextBatch[0]._id); }); }); - it('must return startAfter from the initial aggregate if the option was specified', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 0, postBatchResumeToken: false }; - })(), - getMore: (function*() { - yield { numDocuments: 0, postBatchResumeToken: false }; - })() - }); - let token; - const startAfter = manager.resumeToken(); - const resumeAfter = manager.resumeToken(); - - return manager - .ready() - .then(() => { - return new Promise(resolve => { - const changeStream = manager.makeChangeStream({ startAfter, resumeAfter }); - let counter = 0; - changeStream.cursor.on('response', () => { - if (counter === 1) { - token = changeStream.resumeToken; - resolve(); - } - counter += 1; - }); - - // Note: this is expected to fail - changeStream.next().catch(() => {}); - }); - }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - expect(token) - .to.deep.equal(startAfter) - .and.to.not.deep.equal(resumeAfter); - }); - }); it('must return resumeAfter from the initial aggregate if the option was specified', function() { const manager = new MockServerManager(this.configuration, { aggregate: (function*() { @@ -2366,7 +2379,7 @@ describe('Change Streams', function() { expect(token).to.deep.equal(resumeAfter); }); }); - it('must be empty if neither the startAfter nor resumeAfter options were specified', function() { + it('must be empty if resumeAfter options was not specified', function() { const manager = new MockServerManager(this.configuration, { aggregate: (function*() { yield { numDocuments: 0, postBatchResumeToken: false }; @@ -2405,7 +2418,7 @@ describe('Change Streams', function() { }); }); - // For a ChangeStream under these conditions: + // 13. For a ChangeStream under these conditions: // The batch is not empty. // The batch has been iterated up to but not including the last element. // Expected result: @@ -2450,7 +2463,7 @@ describe('Change Streams', function() { }); }); - // For a ChangeStream under these conditions: + // 14. For a ChangeStream under these conditions: // The batch is not empty. // The batch hasn’t been iterated at all. // Only the initial aggregate command has been executed. @@ -2564,233 +2577,230 @@ describe('Change Streams', function() { }); }); }); + }); - // For a ChangeStream under these conditions: - // Running against a server >=4.0.7. - // The batch is not empty. - // The batch hasn’t been iterated at all. - // The stream has iterated beyond a previous batch and a getMore command has just been executed. - // Expected result: - // getResumeToken must return the postBatchResumeToken from the previous command response. - describe('for non-empty non-iterated batch where getMore has just been executed against server >=4.0.7', function() { - it('must return the postBatchResumeToken from the previous command response', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 1, postBatchResumeToken: true }; - })(), - getMore: (function*() { - yield { numDocuments: 1, postBatchResumeToken: true }; - })() + describe('tryNext', function() { + it('should return null on single iteration of empty cursor', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, + test: function(done) { + const client = this.configuration.newClient(); + client.connect(err => { + expect(err).to.not.exist; + + const changeStream = client + .db() + .collection('test') + .watch(); + + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; + + changeStream.close(() => client.close(done)); + }); }); - let token; - const startAfter = manager.resumeToken(); - const resumeAfter = manager.resumeToken(); + } + }); - return manager - .ready() - .then(() => { - return manager.makeChangeStream({ startAfter, resumeAfter }).next(); - }) - .then(() => { - manager.changeStream.cursor.once('response', () => { - token = manager.changeStream.resumeToken; - }); + it('should iterate a change stream until first empty batch', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, + test: function(done) { + const client = this.configuration.newClient(); + client.connect(err => { + expect(err).to.not.exist; - // Note: this is expected to fail - return manager.changeStream.next(); - }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - const successes = manager.apm.succeeded.map(e => { - try { - return e.reply.cursor; - } catch (e) { - return {}; - } + const collection = client.db().collection('test'); + const changeStream = collection.watch(); + waitForStarted(changeStream, () => { + collection.insertOne({ a: 42 }, err => { + expect(err).to.not.exist; + + collection.insertOne({ b: 24 }, err => { + expect(err).to.not.exist; + }); }); + }); - expect(successes).to.have.a.lengthOf(2); - expect(successes[0]).to.have.a.property('postBatchResumeToken'); - expect(successes[0]).to.have.a.nested.property('firstBatch[0]._id'); + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; - expect(token) - .to.deep.equal(successes[0].postBatchResumeToken) - .and.to.not.deep.equal(successes[0].firstBatch[0]._id) - .and.to.not.deep.equal(startAfter) - .and.to.not.deep.equal(resumeAfter); + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; + + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; + + changeStream.close(() => client.close(done)); + }); + }); }); - }); + }); + } }); + }); - // For a ChangeStream under these conditions: - // Running against a server <4.0.7. - // The batch is not empty. - // The batch hasn’t been iterated at all. - // The stream has iterated beyond a previous batch and a getMore command has just been executed. - // Expected result: - // getResumeToken must return the _id of the previous document returned if one exists. - // getResumeToken must return startAfter from the initial aggregate if the option was specified. - // getResumeToken must return resumeAfter from the initial aggregate if the option was specified. - // If neither the startAfter nor resumeAfter options were specified, the getResumeToken result must be empty. - describe('for non-empty non-iterated batch where getMore has just been executed against server < 4.0.7', function() { - it('must return the _id of the previous document returned if one exists', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 1, postBatchResumeToken: false }; - })(), - getMore: (function*() { - yield { numDocuments: 1, postBatchResumeToken: false }; - })() - }); - let token; - const startAfter = manager.resumeToken(); - const resumeAfter = manager.resumeToken(); + describe('startAfter', function() { + let client; + let coll; + let startAfter; - return manager - .ready() - .then(() => { - return manager.makeChangeStream({ startAfter, resumeAfter }).next(); - }) - .then(() => { - manager.changeStream.cursor.once('response', () => { - token = manager.changeStream.resumeToken; - }); + function recordEvent(events, e) { + if (e.commandName === 'aggregate') { + events.push({ $changeStream: e.command.pipeline[0].$changeStream }); + } + } - // Note: this is expected to fail - return manager.changeStream.next(); - }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - const successes = manager.apm.succeeded.map(e => { - try { - return e.reply.cursor; - } catch (e) { - return {}; - } + beforeEach(function(done) { + const configuration = this.configuration; + client = configuration.newClient({ monitorCommands: true }); + client.connect(err => { + expect(err).to.not.exist; + coll = client.db('integration_tests').collection('setupAfterTest'); + const changeStream = coll.watch(); + waitForStarted(changeStream, () => { + coll.insertOne({ x: 1 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; + + coll.drop(err => { + expect(err).to.not.exist; }); + }); + }); - expect(successes).to.have.a.lengthOf(2); - expect(successes[0]).to.have.a.nested.property('firstBatch[0]._id'); + changeStream.on('change', change => { + if (change.operationType === 'invalidate') { + startAfter = change._id; + changeStream.close(done); + } + }); + }); + }); - expect(token) - .to.deep.equal(successes[0].firstBatch[0]._id) - .and.to.not.deep.equal(startAfter) - .and.to.not.deep.equal(resumeAfter); + afterEach(function(done) { + client.close(done); + }); + + it('should work with events', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.1.1' } }, + test: function(done) { + const changeStream = coll.watch([], { startAfter }); + coll.insertOne({ x: 2 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; + changeStream.once('change', change => { + expect(change).to.containSubset({ + operationType: 'insert', + fullDocument: { x: 2 } + }); + changeStream.close(done); }); - }); - it('must return startAfter from the initial aggregate if the option was specified', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 0, postBatchResumeToken: false }; - })(), - getMore: (function*() { - yield { numDocuments: 1, postBatchResumeToken: false }; - })() }); - let token; - const startAfter = manager.resumeToken(); - const resumeAfter = manager.resumeToken(); + } + }); - return manager - .ready() - .then(() => { - const changeStream = manager.makeChangeStream({ startAfter, resumeAfter }); - let counter = 0; - changeStream.cursor.on('response', () => { - if (counter === 1) { - token = changeStream.resumeToken; - } - counter += 1; + it('should work with callbacks', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.1.1' } }, + test: function(done) { + const changeStream = coll.watch([], { startAfter }); + coll.insertOne({ x: 2 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; + exhaust(changeStream, (err, bag) => { + expect(err).to.not.exist; + const finalOperation = bag.pop(); + expect(finalOperation).to.containSubset({ + operationType: 'insert', + fullDocument: { x: 2 } }); - - // Note: this is expected to fail - return changeStream.next(); - }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - expect(token) - .to.deep.equal(startAfter) - .and.to.not.deep.equal(resumeAfter); + changeStream.close(done); }); - }); - it('must return resumeAfter from the initial aggregate if the option was specified', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 0, postBatchResumeToken: false }; - })(), - getMore: (function*() { - yield { numDocuments: 1, postBatchResumeToken: false }; - })() }); - let token; - const resumeAfter = manager.resumeToken(); + } + }); - return manager - .ready() - .then(() => { - const changeStream = manager.makeChangeStream({ resumeAfter }); - let counter = 0; - changeStream.cursor.on('response', () => { - if (counter === 1) { - token = changeStream.resumeToken; - } - counter += 1; + // 17. $changeStream stage for ChangeStream started with startAfter against a server >=4.1.1 + // that has not received any results yet + // - MUST include a startAfter option + // - MUST NOT include a resumeAfter option + // when resuming a change stream. + it('$changeStream that has not received results must include startAfter and not resumeAfter', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.1.1' } }, + test: function(done) { + const events = []; + client.on('commandStarted', e => recordEvent(events, e)); + const changeStream = coll.watch([], { startAfter }); + changeStream.once('change', change => { + expect(change).to.containSubset({ + operationType: 'insert', + fullDocument: { x: 2 } + }); + expect(events) + .to.be.an('array') + .with.lengthOf(3); + expect(events[0]).nested.property('$changeStream.startAfter').to.exist; + expect(events[1]).to.equal('error'); + expect(events[2]).nested.property('$changeStream.startAfter').to.exist; + changeStream.close(done); + }); + + waitForStarted(changeStream, () => { + triggerResumableError(changeStream, () => { + events.push('error'); + coll.insertOne({ x: 2 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; }); - - // Note: this is expected to fail - return changeStream.next(); - }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - expect(token).to.deep.equal(resumeAfter); }); - }); - it('must be empty if neither the startAfter nor resumeAfter options were specified', function() { - const manager = new MockServerManager(this.configuration, { - aggregate: (function*() { - yield { numDocuments: 0, postBatchResumeToken: false }; - })(), - getMore: (function*() { - yield { numDocuments: 1, postBatchResumeToken: false }; - })() }); - let token; + } + }); - return manager - .ready() - .then(() => { - const changeStream = manager.makeChangeStream(); - let counter = 0; - changeStream.cursor.on('response', () => { - if (counter === 1) { - token = changeStream.resumeToken; - } - counter += 1; + // 18. $changeStream stage for ChangeStream started with startAfter against a server >=4.1.1 + // that has received at least one result + // - MUST include a resumeAfter option + // - MUST NOT include a startAfter option + // when resuming a change stream. + it('$changeStream that has received results must include resumeAfter and not startAfter', { + metadata: { requires: { topology: 'replicaset', mongodb: '>=4.1.1' } }, + test: function(done) { + let events = []; + client.on('commandStarted', e => recordEvent(events, e)); + const changeStream = coll.watch([], { startAfter }); + + changeStream.on('change', change => { + events.push({ change: { insert: { x: change.fullDocument.x } } }); + switch (change.fullDocument.x) { + case 2: + // only events after this point are relevant to this test + events = []; + triggerResumableError(changeStream, () => events.push('error')); + break; + case 3: + expect(events) + .to.be.an('array') + .with.lengthOf(3); + expect(events[0]).to.equal('error'); + expect(events[1]).nested.property('$changeStream.resumeAfter').to.exist; + expect(events[2]).to.eql({ change: { insert: { x: 3 } } }); + changeStream.close(done); + break; + } + }); + waitForStarted(changeStream, () => + coll.insertOne({ x: 2 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; + coll.insertOne({ x: 3 }, { w: 'majority', j: true }, err => { + expect(err).to.not.exist; }); - - // Note: this is expected to fail - return changeStream.next(); }) - .then( - () => manager.teardown(), - err => manager.teardown(err) - ) - .then(() => { - expect(token).to.not.exist; - }); - }); + ); + } }); }); }); + +describe('Change Stream Resume Error Tests', function() { + it('should properly process errors that lack the `mongoErrorContextSymbol`', function() { + expect(() => isResumableError(new Error())).to.not.throw(); + }); +}); diff --git a/test/functional/change_stream_spec.test.js b/test/functional/change_stream_spec.test.js index 251b84fad4..7fe16f14f9 100644 --- a/test/functional/change_stream_spec.test.js +++ b/test/functional/change_stream_spec.test.js @@ -39,7 +39,11 @@ describe('Change Stream Spec', function() { const configuration = this.configuration; return Promise.all(ALL_DBS.map(db => gc.db(db).dropDatabase({ w: 'majority' }))) .then(() => gc.db(sDB).createCollection(sColl)) - .then(() => gc.db(suite.database2_name).createCollection(suite.collection2_name)) + .then(() => { + if (suite.database2_name && suite.collection2_name) { + return gc.db(suite.database2_name).createCollection(suite.collection2_name); + } + }) .then(() => configuration.newClient({}, { monitorCommands: true }).connect()) .then(client => { ctx = { gc, client }; @@ -78,12 +82,19 @@ describe('Change Stream Spec', function() { // Fn Generator methods function generateMetadata(test) { - const mongodb = test.minServerVersion; const topology = test.topology; const requires = {}; - if (mongodb) { - requires.mongodb = `>=${mongodb}`; + const versionLimits = []; + if (test.minServerVersion) { + versionLimits.push(`>=${test.minServerVersion}`); } + if (test.maxServerVersion) { + versionLimits.push(`<=${test.maxServerVersion}`); + } + if (versionLimits.length) { + requires.mongodb = versionLimits.join(' '); + } + if (topology) { requires.topology = topology; } @@ -156,7 +167,11 @@ describe('Change Stream Spec', function() { `Expected there to be an APM event at index ${idx}, but there was none` ); } - + // killCursors events should be skipped + // (see https://github.com/mongodb/specifications/blob/master/source/change-streams/tests/README.rst#spec-test-runner) + if (events[idx].commandName === 'killCursors') { + return; + } expect(events[idx]).to.matchMongoSpec(expected); }); }; diff --git a/test/spec/change-stream/README.rst b/test/spec/change-stream/README.rst index 8472d1f6ce..7cb49eeccc 100644 --- a/test/spec/change-stream/README.rst +++ b/test/spec/change-stream/README.rst @@ -63,7 +63,7 @@ The definition of MATCH or MATCHES in the Spec Test Runner is as follows: - MATCH takes two values, ``expected`` and ``actual`` - Notation is "Assert [actual] MATCHES [expected] -- Assertion passes if ``expected`` is a subset of ``actual``, with the values ``42`` and ``"42"`` acting as placeholders for "any value" +- Assertion passes if ``expected`` is a subset of ``actual``, with the value ``42`` acting as placeholders for "any value" Pseudocode implementation of ``actual`` MATCHES ``expected``: @@ -93,7 +93,10 @@ Spec Test Runner Before running the tests -- Create a MongoClient ``globalClient``, and connect to the server +- Create a MongoClient ``globalClient``, and connect to the server. +When executing tests against a sharded cluster, ``globalClient`` must only connect to one mongos. This is because tests +that set failpoints will only work consistently if both the ``configureFailPoint`` and failing commands are sent to the +same mongos. For each YAML file, for each element in ``tests``: @@ -110,13 +113,10 @@ For each YAML file, for each element in ``tests``: - Create a new MongoClient ``client`` - Begin monitoring all APM events for ``client``. (If the driver uses global listeners, filter out all events that do not originate with ``client``). Filter out any "internal" commands (e.g. ``isMaster``) -- Using ``client``, create a changeStream ``changeStream`` against the specified ``target``. Use ``changeStreamPipeline`` and ``changeStreamOptions`` if they are non-empty -- Using ``globalClient``, run every operation in ``operations`` in serial against the server -- Wait until either: - - - An error occurs - - All operations have been successful AND the changeStream has received as many changes as there are in ``result.success`` - +- Using ``client``, create a changeStream ``changeStream`` against the specified ``target``. Use ``changeStreamPipeline`` and ``changeStreamOptions`` if they are non-empty. Capture any error. +- If there was no error, use ``globalClient`` and run every operation in ``operations`` in serial against the server until all operations have been executed or an error is thrown. Capture any error. +- If there was no error and ``result.error`` is set, iterate ``changeStream`` once and capture any error. +- If there was no error and ``result.success`` is non-empty, iterate ``changeStream`` until it returns as many changes as there are elements in the ``result.success`` array or an error is thrown. Capture any error. - Close ``changeStream`` - If there was an error: @@ -131,8 +131,8 @@ For each YAML file, for each element in ``tests``: - If there are any ``expectations`` - For each (``expected``, ``idx``) in ``expectations`` - - - Assert that ``actual[idx]`` MATCHES ``expected`` + - If ``actual[idx]`` is a ``killCursors`` event, skip it and move to ``actual[idx+1]``. + - Else assert that ``actual[idx]`` MATCHES ``expected`` - Close the MongoClient ``client`` @@ -142,62 +142,72 @@ After running all tests - Drop database ``database_name`` - Drop database ``database2_name`` +Iterating the Change Stream +--------------------------- + +Although synchronous drivers must provide a `non-blocking mode of iteration <../change-streams.rst#not-blocking-on-iteration>`_, asynchronous drivers may not have such a mechanism. Those drivers with only a blocking mode of iteration should be careful not to iterate the change stream unnecessarily, as doing so could cause the test runner to block indefinitely. For this reason, the test runner procedure above advises drivers to take a conservative approach to iteration. + +If the test expects an error and one was not thrown by either creating the change stream or executing the test's operations, iterating the change stream once allows for an error to be thrown by a ``getMore`` command. If the test does not expect any error, the change stream should be iterated only until it returns as many result documents as are expected by the test. Prose Tests =========== -The following tests have not yet been automated, but MUST still be tested +The following tests have not yet been automated, but MUST still be tested. All tests SHOULD be run on both replica sets and sharded clusters unless otherwise specified: #. ``ChangeStream`` must continuously track the last seen ``resumeToken`` #. ``ChangeStream`` will throw an exception if the server response is missing the resume token (if wire version is < 8, this is a driver-side error; for 8+, this is a server-side error) -#. ``ChangeStream`` will automatically resume one time on a resumable error (including `not master`) with the initial pipeline and options, except for the addition/update of a ``resumeToken``. -#. ``ChangeStream`` will not attempt to resume on any error encountered while executing an ``aggregate`` command. -#. ``ChangeStream`` will not attempt to resume after encountering error code 11601 (Interrupted), 136 (CappedPositionLost), or 237 (CursorKilled) while executing a ``getMore`` command. +#. After receiving a ``resumeToken``, ``ChangeStream`` will automatically resume one time on a resumable error with the initial pipeline and options, except for the addition/update of a ``resumeToken``. +#. ``ChangeStream`` will not attempt to resume on any error encountered while executing an ``aggregate`` command. Note that retryable reads may retry ``aggregate`` commands. Drivers should be careful to distinguish retries from resume attempts. Alternatively, drivers may specify `retryReads=false` or avoid using a [retryable error](../../retryable-reads/retryable-reads.rst#retryable-error) for this test. +#. **Removed** #. ``ChangeStream`` will perform server selection before attempting to resume, using initial ``readPreference`` #. Ensure that a cursor returned from an aggregate command with a cursor id and an initial empty batch is not closed on the driver side. #. The ``killCursors`` command sent during the "Resume Process" must not be allowed to throw an exception. -#. ``$changeStream`` stage for ``ChangeStream`` against a server ``>=4.0`` and ``<4.0.7`` that has not received any results yet MUST include a ``startAtOperationTime`` option when resuming a changestream. -#. ``ChangeStream`` will resume after a ``killCursors`` command is issued for its child cursor. -#. - For a ``ChangeStream`` under these conditions: - - Running against a server ``>=4.0.7``. - - The batch is empty or has been iterated to the last document. - - Expected result: - - ``getResumeToken`` must return the ``postBatchResumeToken`` from the current command response. -#. - For a ``ChangeStream`` under these conditions: - - Running against a server ``<4.0.7``. - - The batch is empty or has been iterated to the last document. - - Expected result: - - ``getResumeToken`` must return the ``_id`` of the last document returned if one exists. - - ``getResumeToken`` must return ``startAfter`` from the initial aggregate if the option was specified. - - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. - - If neither the ``startAfter`` nor ``resumeAfter`` options were specified, the ``getResumeToken`` result must be empty. -#. - For a ``ChangeStream`` under these conditions: - - The batch is not empty. - - The batch has been iterated up to but not including the last element. - - Expected result: - - ``getResumeToken`` must return the ``_id`` of the previous document returned. -#. - For a ``ChangeStream`` under these conditions: - - The batch is not empty. - - The batch hasn’t been iterated at all. - - Only the initial ``aggregate`` command has been executed. - - Expected result: - - ``getResumeToken`` must return ``startAfter`` from the initial aggregate if the option was specified. - - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. - - If neither the ``startAfter`` nor ``resumeAfter`` options were specified, the ``getResumeToken`` result must be empty. -#. - For a ``ChangeStream`` under these conditions: - - Running against a server ``>=4.0.7``. - - The batch is not empty. - - The batch hasn’t been iterated at all. - - The stream has iterated beyond a previous batch and a ``getMore`` command has just been executed. - - Expected result: - - ``getResumeToken`` must return the ``postBatchResumeToken`` from the previous command response. -#. - For a ``ChangeStream`` under these conditions: - - Running against a server ``<4.0.7``. - - The batch is not empty. - - The batch hasn’t been iterated at all. - - The stream has iterated beyond a previous batch and a ``getMore`` command has just been executed. - - Expected result: - - ``getResumeToken`` must return the ``_id`` of the previous document returned if one exists. - - ``getResumeToken`` must return ``startAfter`` from the initial aggregate if the option was specified. - - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. - - If neither the ``startAfter`` nor ``resumeAfter`` options were specified, the ``getResumeToken`` result must be empty. \ No newline at end of file +#. ``$changeStream`` stage for ``ChangeStream`` against a server ``>=4.0`` and ``<4.0.7`` that has not received any results yet MUST include a ``startAtOperationTime`` option when resuming a change stream. +#. **Removed** +#. For a ``ChangeStream`` under these conditions: + + - Running against a server ``>=4.0.7``. + - The batch is empty or has been iterated to the last document. + + Expected result: + + - ``getResumeToken`` must return the ``postBatchResumeToken`` from the current command response. + +#. For a ``ChangeStream`` under these conditions: + + - Running against a server ``<4.0.7``. + - The batch is empty or has been iterated to the last document. + + Expected result: + + - ``getResumeToken`` must return the ``_id`` of the last document returned if one exists. + - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. + - If ``resumeAfter`` was not specified, the ``getResumeToken`` result must be empty. + +#. For a ``ChangeStream`` under these conditions: + + - The batch is not empty. + - The batch has been iterated up to but not including the last element. + + Expected result: + + - ``getResumeToken`` must return the ``_id`` of the previous document returned. + +#. For a ``ChangeStream`` under these conditions: + + - The batch is not empty. + - The batch hasn’t been iterated at all. + - Only the initial ``aggregate`` command has been executed. + + Expected result: + + - ``getResumeToken`` must return ``startAfter`` from the initial aggregate if the option was specified. + - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. + - If neither the ``startAfter`` nor ``resumeAfter`` options were specified, the ``getResumeToken`` result must be empty. + + Note that this test cannot be run against sharded topologies because in that case the initial ``aggregate`` command only establishes cursors on the shards and always returns an empty ``firstBatch``. + +#. **Removed** +#. **Removed** +#. ``$changeStream`` stage for ``ChangeStream`` started with ``startAfter`` against a server ``>=4.1.1`` that has not received any results yet MUST include a ``startAfter`` option and MUST NOT include a ``resumeAfter`` option when resuming a change stream. +#. ``$changeStream`` stage for ``ChangeStream`` started with ``startAfter`` against a server ``>=4.1.1`` that has received at least one result MUST include a ``resumeAfter`` option and MUST NOT include a ``startAfter`` option when resuming a change stream. diff --git a/test/spec/change-stream/change-streams-errors.json b/test/spec/change-stream/change-streams-errors.json index 7eed273feb..05b3665f9d 100644 --- a/test/spec/change-stream/change-streams-errors.json +++ b/test/spec/change-stream/change-streams-errors.json @@ -54,9 +54,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} }, { "$unsupported": "foo" @@ -110,6 +108,53 @@ ] } } + }, + { + "description": "change stream errors on MaxTimeMSExpired", + "minServerVersion": "4.2", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 50, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [ + { + "$project": { + "_id": 0 + } + } + ], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "z": 3 + } + } + } + ], + "result": { + "error": { + "code": 50 + } + } } ] } diff --git a/test/spec/change-stream/change-streams-errors.yml b/test/spec/change-stream/change-streams-errors.yml index f50c80cd06..d51090cb02 100644 --- a/test/spec/change-stream/change-streams-errors.yml +++ b/test/spec/change-stream/change-streams-errors.yml @@ -42,8 +42,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} - $unsupported: foo command_name: aggregate @@ -74,3 +73,32 @@ tests: error: code: 280 errorLabels: [ "NonResumableChangeStreamError" ] + - + description: change stream errors on MaxTimeMSExpired + minServerVersion: "4.2" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 50 # An error code that's not on the old blacklist or whitelist + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: + - + $project: { _id: 0 } + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + z: 3 + result: + error: + code: 50 diff --git a/test/spec/change-stream/change-streams-resume-errorLabels.json b/test/spec/change-stream/change-streams-resume-errorLabels.json new file mode 100644 index 0000000000..cf8957b21f --- /dev/null +++ b/test/spec/change-stream/change-streams-resume-errorLabels.json @@ -0,0 +1,1634 @@ +{ + "collection_name": "test", + "database_name": "change-stream-tests", + "tests": [ + { + "description": "change stream resumes after HostUnreachable", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 6, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after HostNotFound", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 7, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NetworkTimeout", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 89, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after ShutdownInProgress", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 91, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after PrimarySteppedDown", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 189, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after ExceededTimeLimit", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 262, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after SocketException", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 9001, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMaster", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 10107, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after InterruptedAtShutdown", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 11600, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after InterruptedDueToReplStateChange", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 11602, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMasterNoSlaveOk", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 13435, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMasterOrSecondary", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 13436, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after StaleShardVersion", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 63, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after StaleEpoch", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 150, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after RetryChangeStream", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 234, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after FailedToSatisfyReadPreference", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failGetMoreAfterCursorCheckout", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 133, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes if error contains ResumableChangeStreamError", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 50, + "closeConnection": false, + "errorLabels": [ + "ResumableChangeStreamError" + ] + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream does not resume if error does not contain ResumableChangeStreamError", + "minServerVersion": "4.3.1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 6, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "result": { + "error": { + "code": 6 + } + } + } + ] +} diff --git a/test/spec/change-stream/change-streams-resume-errorLabels.yml b/test/spec/change-stream/change-streams-resume-errorLabels.yml new file mode 100644 index 0000000000..94570a1457 --- /dev/null +++ b/test/spec/change-stream/change-streams-resume-errorLabels.yml @@ -0,0 +1,1105 @@ +# Tests for resume behavior on server versions that support the ResumableChangeStreamError label +collection_name: &collection_name "test" +database_name: &database_name "change-stream-tests" +tests: + - + description: "change stream resumes after HostUnreachable" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout # SERVER-46091 explains why a new failpoint was needed + mode: { times: 1 } + data: + errorCode: 6 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after HostNotFound" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 7 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NetworkTimeout" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 89 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after ShutdownInProgress" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 91 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after PrimarySteppedDown" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 189 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after ExceededTimeLimit" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 262 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after SocketException" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 9001 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMaster" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 10107 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after InterruptedAtShutdown" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 11600 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after InterruptedDueToReplStateChange" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 11602 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMasterNoSlaveOk" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 13435 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMasterOrSecondary" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 13436 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after StaleShardVersion" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 63 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after StaleEpoch" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 150 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after RetryChangeStream" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 234 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after FailedToSatisfyReadPreference" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failGetMoreAfterCursorCheckout + mode: { times: 1 } + data: + errorCode: 133 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + # The next two tests ensure that the driver only uses the error label, not the whitelist. + - + description: "change stream resumes if error contains ResumableChangeStreamError" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 50 # Use an error code that does not have the whitelist label by default + closeConnection: false + errorLabels: ["ResumableChangeStreamError"] + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream does not resume if error does not contain ResumableChangeStreamError" + minServerVersion: "4.3.1" + failPoint: + configureFailPoint: failCommand # failCommand will not add the whitelist error label + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 6 # Use an error code that is on the whitelist + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + result: + error: + code: 6 diff --git a/test/spec/change-stream/change-streams-resume-whitelist.json b/test/spec/change-stream/change-streams-resume-whitelist.json new file mode 100644 index 0000000000..80e4df6a0d --- /dev/null +++ b/test/spec/change-stream/change-streams-resume-whitelist.json @@ -0,0 +1,1653 @@ +{ + "collection_name": "test", + "database_name": "change-stream-tests", + "tests": [ + { + "description": "change stream resumes after a network error", + "minServerVersion": "4.2", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "closeConnection": true + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after HostUnreachable", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 6, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after HostNotFound", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 7, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NetworkTimeout", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 89, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after ShutdownInProgress", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 91, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after PrimarySteppedDown", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 189, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after ExceededTimeLimit", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 262, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after SocketException", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 9001, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMaster", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 10107, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after InterruptedAtShutdown", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 11600, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after InterruptedDueToReplStateChange", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 11602, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMasterNoSlaveOk", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 13435, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after NotMasterOrSecondary", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 13436, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after StaleShardVersion", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 63, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after StaleEpoch", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 150, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after RetryChangeStream", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 234, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + }, + { + "description": "change stream resumes after FailedToSatisfyReadPreference", + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 133, + "closeConnection": false + } + }, + "target": "collection", + "topology": [ + "replicaset", + "sharded" + ], + "changeStreamPipeline": [], + "changeStreamOptions": {}, + "operations": [ + { + "database": "change-stream-tests", + "collection": "test", + "name": "insertOne", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "getMore": 42, + "collection": "test" + }, + "command_name": "getMore", + "database_name": "change-stream-tests" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "test", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "command_name": "aggregate", + "database_name": "change-stream-tests" + } + } + ], + "result": { + "success": [ + { + "_id": "42", + "documentKey": "42", + "operationType": "insert", + "ns": { + "db": "change-stream-tests", + "coll": "test" + }, + "fullDocument": { + "x": { + "$numberInt": "1" + } + } + } + ] + } + } + ] +} diff --git a/test/spec/change-stream/change-streams-resume-whitelist.yml b/test/spec/change-stream/change-streams-resume-whitelist.yml new file mode 100644 index 0000000000..40d0ac4386 --- /dev/null +++ b/test/spec/change-stream/change-streams-resume-whitelist.yml @@ -0,0 +1,1107 @@ +# Tests for resume behavior on server versions that do not support the ResumableChangeStreamError label +collection_name: &collection_name "test" +database_name: &database_name "change-stream-tests" +tests: + - + description: "change stream resumes after a network error" + minServerVersion: "4.2" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + closeConnection: true + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after HostUnreachable" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 6 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after HostNotFound" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 7 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NetworkTimeout" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 89 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after ShutdownInProgress" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 91 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after PrimarySteppedDown" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 189 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after ExceededTimeLimit" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 262 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after SocketException" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 9001 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMaster" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 10107 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after InterruptedAtShutdown" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 11600 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after InterruptedDueToReplStateChange" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 11602 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMasterNoSlaveOk" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 13435 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after NotMasterOrSecondary" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 13436 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after StaleShardVersion" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 63 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after StaleEpoch" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 150 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after RetryChangeStream" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 234 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" + - + description: "change stream resumes after FailedToSatisfyReadPreference" + minServerVersion: "4.2" + maxServerVersion: "4.2.99" + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["getMore"] + errorCode: 133 + closeConnection: false + target: collection + topology: + - replicaset + - sharded + changeStreamPipeline: [] + changeStreamOptions: {} + operations: + - + database: *database_name + collection: *collection_name + name: insertOne + arguments: + document: + x: 1 + expectations: + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + - + command_started_event: + command: + getMore: 42 + collection: *collection_name + command_name: getMore + database_name: *database_name + - + command_started_event: + command: + aggregate: *collection_name + cursor: {} + pipeline: + - + $changeStream: {} + command_name: aggregate + database_name: *database_name + result: + success: + - + _id: "42" + documentKey: "42" + operationType: insert + ns: + db: *database_name + coll: *collection_name + fullDocument: + x: + $numberInt: "1" diff --git a/test/spec/change-stream/change-streams.json b/test/spec/change-stream/change-streams.json index 34371ced09..4aeb2c7f70 100644 --- a/test/spec/change-stream/change-streams.json +++ b/test/spec/change-stream/change-streams.json @@ -33,9 +33,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -153,9 +151,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -226,9 +222,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} }, { "$match": { @@ -312,9 +306,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -404,7 +396,6 @@ "pipeline": [ { "$changeStream": { - "fullDocument": "default", "allChangesForCluster": true } } @@ -523,9 +514,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -611,9 +600,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -665,9 +652,7 @@ "cursor": {}, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, @@ -756,9 +741,7 @@ }, "pipeline": [ { - "$changeStream": { - "fullDocument": "default" - } + "$changeStream": {} } ] }, diff --git a/test/spec/change-stream/change-streams.yml b/test/spec/change-stream/change-streams.yml index 881c59d1b8..8037e9913e 100644 --- a/test/spec/change-stream/change-streams.yml +++ b/test/spec/change-stream/change-streams.yml @@ -27,8 +27,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -110,8 +109,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -158,8 +156,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} - $match: "fullDocument.z": @@ -215,8 +212,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -277,7 +273,6 @@ tests: pipeline: - $changeStream: - fullDocument: default allChangesForCluster: true command_name: aggregate database_name: admin @@ -357,8 +352,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -416,8 +410,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -453,8 +446,7 @@ tests: cursor: {} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: @@ -512,8 +504,7 @@ tests: cursor: {batchSize: 1} pipeline: - - $changeStream: - fullDocument: default + $changeStream: {} command_name: aggregate database_name: *database_name result: diff --git a/test/unit/change_stream_resume.test.js b/test/unit/change_stream_resume.test.js deleted file mode 100644 index 6b86cd98b2..0000000000 --- a/test/unit/change_stream_resume.test.js +++ /dev/null @@ -1,223 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const mock = require('mongodb-mock-server'); -const ObjectId = require('../../index').ObjectId; -const Timestamp = require('../../index').Timestamp; -const Long = require('../../index').Long; -const GET_MORE_NON_RESUMABLE_CODES = require('../../lib/error').GET_MORE_NON_RESUMABLE_CODES; -const isResumableError = require('../../lib/error').isResumableError; - -describe('Change Stream Resume Tests', function() { - const test = {}; - const DEFAULT_IS_MASTER = Object.assign({}, mock.DEFAULT_ISMASTER, { - setName: 'rs', - setVersion: 1, - maxWireVersion: 7, - secondary: false - }); - - const AGGREGATE_RESPONSE = { - ok: 1, - cursor: { - firstBatch: [], - id: new Long('9064341847921713401'), - ns: 'test.test' - }, - operationTime: new Timestamp(1527200325, 1), - $clusterTime: { - clusterTime: new Timestamp(1527200325, 1), - signature: { - keyId: new Long(0) - } - } - }; - - const CHANGE_DOC = { - _id: { - ts: new Timestamp(4, 1501511802), - ns: 'integration_tests.docsDataEvent', - _id: new ObjectId('597f407a8fd4abb616feca93') - }, - operationType: 'insert', - ns: { - db: 'integration_tests', - coll: 'docsDataEvent' - }, - fullDocument: { - _id: new ObjectId('597f407a8fd4abb616feca93'), - a: 1, - counter: 0 - } - }; - - const GET_MORE_RESPONSE = { - ok: 1, - cursor: { - nextBatch: [CHANGE_DOC], - id: new Long('9064341847921713401'), - ns: 'test.test' - }, - operationTime: new Timestamp(1527200325, 1), - $clusterTime: { - clusterTime: new Timestamp(1527200325, 1), - signature: { - keyId: new Long(0) - } - } - }; - - function makeIsMaster(server) { - const uri = server.uri(); - - return Object.assign({}, DEFAULT_IS_MASTER, { - hosts: [uri], - me: uri, - primary: uri - }); - } - - function makeServerHandler(config) { - let firstGetMore = true; - let firstAggregate = true; - return request => { - const doc = request.document; - - if (doc.ismaster) { - return request.reply(makeIsMaster(test.server)); - } - if (doc.endSessions) { - return request.reply({ ok: 1 }); - } - if (doc.aggregate) { - if (firstAggregate) { - firstAggregate = false; - return config.firstAggregate(request); - } - return config.secondAggregate(request); - } - if (doc.getMore) { - if (firstGetMore) { - firstGetMore = false; - return config.firstGetMore(request); - } - return config.secondGetMore(request); - } - }; - } - - const RESUMABLE_ERROR_CODES = [1, 40, 20000]; - - const configs = RESUMABLE_ERROR_CODES.map(code => ({ - description: `should resume on error code ${code}`, - passing: true, - firstAggregate: req => req.reply(AGGREGATE_RESPONSE), - secondAggregate: req => req.reply(AGGREGATE_RESPONSE), - firstGetMore: req => req.reply({ ok: 0, errmsg: 'firstGetMoreError', code }), - secondGetMore: req => req.reply(GET_MORE_RESPONSE) - })) - .concat([ - { - description: `should resume on a network error`, - passing: true, - firstAggregate: req => req.reply(AGGREGATE_RESPONSE), - secondAggregate: req => req.reply(AGGREGATE_RESPONSE), - firstGetMore: () => {}, // Simulates a timeout - secondGetMore: req => req.reply(GET_MORE_RESPONSE) - }, - { - description: `should resume on an error that says 'not master'`, - passing: true, - firstAggregate: req => req.reply(AGGREGATE_RESPONSE), - secondAggregate: req => req.reply(AGGREGATE_RESPONSE), - firstGetMore: req => req.reply({ ok: 0, errmsg: 'not master' }), - secondGetMore: req => req.reply(GET_MORE_RESPONSE) - }, - { - description: `should resume on an error that says 'node is recovering'`, - passing: true, - firstAggregate: req => req.reply(AGGREGATE_RESPONSE), - secondAggregate: req => req.reply(AGGREGATE_RESPONSE), - firstGetMore: req => req.reply({ ok: 0, errmsg: 'node is recovering' }), - secondGetMore: req => req.reply(GET_MORE_RESPONSE) - } - ]) - .concat( - Array.from(GET_MORE_NON_RESUMABLE_CODES).map(code => ({ - description: `should not resume on error code ${code}`, - passing: false, - errmsg: 'firstGetMoreError', - firstAggregate: req => req.reply(AGGREGATE_RESPONSE), - secondAggregate: req => - req.reply({ ok: 0, errmsg: 'We should not have a second aggregate' }), - firstGetMore: req => req.reply({ ok: 0, errmsg: 'firstGetMoreError', code }), - secondGetMore: req => req.reply({ ok: 0, errmsg: 'We should not have a second getMore' }) - })) - ) - .concat( - RESUMABLE_ERROR_CODES.map(code => ({ - description: `should not resume on aggregate, even for valid code ${code}`, - passing: false, - errmsg: 'fail aggregate', - firstAggregate: req => req.reply({ ok: 0, errmsg: 'fail aggregate', code }), - secondAggregate: req => - req.reply({ ok: 0, errmsg: 'We should not have a second aggregate' }), - firstGetMore: req => req.reply({ ok: 0, errmsg: 'We should not have a first getMore' }), - secondGetMore: req => req.reply({ ok: 0, errmsg: 'We should not have a second getMore' }) - })) - ); - - let client; - let changeStream; - - beforeEach(() => { - return mock.createServer().then(server => { - test.server = server; - }); - }); - - afterEach(done => changeStream.close(() => client.close(() => mock.cleanup(done)))); - - configs.forEach(config => { - it(config.description, { - metadata: { requires: { topology: 'single' } }, - test: function() { - const configuration = this.configuration; - - test.server.setMessageHandler(makeServerHandler(config)); - client = configuration.newClient(`mongodb://${test.server.uri()}`, { - socketTimeoutMS: 300 - }); - return client - .connect() - .then(client => client.db('test')) - .then(db => db.collection('test')) - .then(collection => collection.watch()) - .then(_changeStream => (changeStream = _changeStream)) - .then(() => changeStream.next()) - .then( - change => { - if (!config.passing) { - throw new Error('Expected test to not pass'); - } - - expect(change).to.deep.equal(CHANGE_DOC); - }, - err => { - if (config.passing) { - throw err; - } - - expect(err).to.have.property('errmsg', config.errmsg); - } - ); - } - }); - }); -}); - -describe('Change Stream Resume Error Tests', function() { - it('should properly process errors that lack the `mongoErrorContextSymbol`', function() { - expect(() => isResumableError(new Error())).to.not.throw(); - }); -});