From 5dbdf33cf22f1ff62b6c61fef1ca164270cc092c Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 4 Mar 2025 18:21:33 -0500 Subject: [PATCH 01/17] test(NODE-6804): convert unhandled rejections into uncaught exceptions --- .mocharc.js | 1 + package-lock.json | 8 +++ package.json | 1 + src/sdam/server.ts | 7 +- .../node-specific/abort_signal.test.ts | 5 +- test/manual/mocharc.js | 6 +- test/mocha_lambda.js | 5 +- test/mocha_mongodb.js | 1 + test/tools/runner/throw_rejections.cjs | 6 ++ test/unit/assorted/optional_require.test.js | 69 ------------------ test/unit/assorted/optional_require.test.ts | 72 +++++++++++++++++++ 11 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 test/tools/runner/throw_rejections.cjs delete mode 100644 test/unit/assorted/optional_require.test.js create mode 100644 test/unit/assorted/optional_require.test.ts diff --git a/.mocharc.js b/.mocharc.js index a289ec1a9de..13d28e27c2f 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -7,6 +7,7 @@ module.exports = { require: [ 'source-map-support/register', 'ts-node/register', + 'test/tools/runner/throw_rejections.cjs', 'test/tools/runner/chai_addons.ts', 'test/tools/runner/ee_checker.ts' ], diff --git a/package-lock.json b/package-lock.json index 8fdb4ad46da..425d17ab774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@types/whatwg-url": "^11.0.5", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", + "aws4": "^1.13.2", "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", @@ -3422,6 +3423,13 @@ "node": "*" } }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index 733c8575ef1..e4fcc8521e7 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@types/whatwg-url": "^11.0.5", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", + "aws4": "^1.13.2", "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", diff --git a/src/sdam/server.ts b/src/sdam/server.ts index 55a1765b24b..007837ef365 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -48,6 +48,7 @@ import { maxWireVersion, type MongoDBNamespace, noop, + squashError, supportsRetryableWrites } from '../utils'; import { throwIfWriteConcernError } from '../write_concern'; @@ -345,10 +346,8 @@ export class Server extends TypedEventEmitter { operationError instanceof MongoError && operationError.code === MONGODB_ERROR_CODES.Reauthenticate ) { - reauthPromise = this.pool.reauthenticate(conn).catch(error => { - reauthPromise = null; - throw error; - }); + // TODO: what do we do with this error? the signal has aborted so where should it be delivered? + reauthPromise = this.pool.reauthenticate(conn).then(undefined, squashError); await abortable(reauthPromise, options); reauthPromise = null; // only reachable if reauth succeeds diff --git a/test/integration/node-specific/abort_signal.test.ts b/test/integration/node-specific/abort_signal.test.ts index a7527479382..7dfcd1f505a 100644 --- a/test/integration/node-specific/abort_signal.test.ts +++ b/test/integration/node-specific/abort_signal.test.ts @@ -751,9 +751,10 @@ describe('AbortSignal support', () => { ...args ) { if (args[1].find != null) { + await sleep(1); commandStub.restore(); controller.abort(); - throw new ReAuthenticationError({}); + throw new ReAuthenticationError({ message: 'This is a fake reauthentication error' }); } return commandStub.wrappedMethod.apply(this, args); }); @@ -793,7 +794,7 @@ describe('AbortSignal support', () => { beforeEach(() => { sinon.stub(ConnectionPool.prototype, 'reauthenticate').callsFake(async function () { await sleep(1000); - throw new Error(); + throw new Error('Rejecting reauthenticate for testing'); }); }); diff --git a/test/manual/mocharc.js b/test/manual/mocharc.js index 04c59052424..fcb5e647971 100644 --- a/test/manual/mocharc.js +++ b/test/manual/mocharc.js @@ -4,7 +4,11 @@ const [major] = process.versions.node.split('.'); /** @type {import("mocha").MochaOptions} */ module.exports = { - require: ['ts-node/register', 'test/tools/runner/chai_addons.ts'], + require: [ + 'ts-node/register', + 'test/tools/runner/throw_rejections.cjs', + 'test/tools/runner/chai_addons.ts' + ], reporter: 'test/tools/reporter/mongodb_reporter.js', failZero: true, color: true, diff --git a/test/mocha_lambda.js b/test/mocha_lambda.js index cb1079aed33..b5d117b179a 100644 --- a/test/mocha_lambda.js +++ b/test/mocha_lambda.js @@ -4,7 +4,10 @@ const [major] = process.versions.node.split('.'); /** @type {import("mocha").MochaOptions} */ module.exports = { - require: ['test/integration/node-specific/examples/setup.js'], + require: [ + 'test/tools/runner/throw_rejections.cjs', + 'test/integration/node-specific/examples/setup.js' + ], extension: ['js'], ui: 'test/tools/runner/metadata_ui.js', recursive: true, diff --git a/test/mocha_mongodb.js b/test/mocha_mongodb.js index eeb18ae4f8d..b33ea65622f 100644 --- a/test/mocha_mongodb.js +++ b/test/mocha_mongodb.js @@ -7,6 +7,7 @@ module.exports = { require: [ 'source-map-support/register', 'ts-node/register', + 'test/tools/runner/throw_rejections.cjs', 'test/tools/runner/chai_addons.ts', 'test/tools/runner/ee_checker.ts', 'test/tools/runner/hooks/configuration.ts', diff --git a/test/tools/runner/throw_rejections.cjs b/test/tools/runner/throw_rejections.cjs new file mode 100644 index 00000000000..7dcf448c5d6 --- /dev/null +++ b/test/tools/runner/throw_rejections.cjs @@ -0,0 +1,6 @@ +// eslint-disable-next-line @typescript-eslint/no-require-imports +const process = require('process'); + +process.on('unhandledRejection', error => { + throw error; +}); diff --git a/test/unit/assorted/optional_require.test.js b/test/unit/assorted/optional_require.test.js deleted file mode 100644 index 754ab4e5035..00000000000 --- a/test/unit/assorted/optional_require.test.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -const { expect } = require('chai'); -const { existsSync } = require('fs'); -const { resolve } = require('path'); - -const { compress } = require('../../mongodb'); -const { GSSAPI } = require('../../mongodb'); -const { AuthContext } = require('../../mongodb'); -const { MongoDBAWS } = require('../../mongodb'); -const { HostAddress } = require('../../mongodb'); - -function moduleExistsSync(moduleName) { - return existsSync(resolve(__dirname, `../../../node_modules/${moduleName}`)); -} - -describe('optionalRequire', function () { - context('Snappy', function () { - it('should error if not installed', function () { - const moduleName = 'snappy'; - if (moduleExistsSync(moduleName)) { - return this.skip(); - } - compress( - { - options: { - agreedCompressor: 'snappy' - } - }, - Buffer.alloc(1), - error => { - expect(error).to.exist; - expect(error.message).includes('not found'); - } - ); - }); - }); - - context('Kerberos', function () { - it('should error if not installed', function () { - const moduleName = 'kerberos'; - if (moduleExistsSync(moduleName)) { - return this.skip(); - } - const gssapi = new GSSAPI(); - gssapi.auth( - new AuthContext(null, true, { hostAddress: new HostAddress('a'), credentials: true }), - error => { - expect(error).to.exist; - expect(error.message).includes('not found'); - } - ); - }); - }); - - context('aws4', function () { - it('should error if not installed', function () { - const moduleName = 'aws4'; - if (moduleExistsSync(moduleName)) { - return this.skip(); - } - const mdbAWS = new MongoDBAWS(); - mdbAWS.auth(new AuthContext({ hello: { maxWireVersion: 9 } }, true, null), error => { - expect(error).to.exist; - expect(error.message).includes('not found'); - }); - }); - }); -}); diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts new file mode 100644 index 00000000000..dfc4715cf67 --- /dev/null +++ b/test/unit/assorted/optional_require.test.ts @@ -0,0 +1,72 @@ +import { expect } from 'chai'; +import { existsSync } from 'fs'; +import { resolve } from 'path'; + +import { + AuthContext, + compress, + GSSAPI, + HostAddress, + MongoDBAWS, + MongoMissingDependencyError +} from '../../mongodb'; + +function moduleExistsSync(moduleName) { + return existsSync(resolve(__dirname, `../../../node_modules/${moduleName}`)); +} + +describe('optionalRequire', function () { + describe('Snappy', function () { + it('should error if not installed', async function () { + const moduleName = 'snappy'; + if (moduleExistsSync(moduleName)) { + return this.skip(); + } + + const error = await compress( + { zlibCompressionLevel: 0, agreedCompressor: 'snappy' }, + Buffer.alloc(1) + ).then( + () => null, + e => e + ); + + expect(error).to.be.instanceOf(MongoMissingDependencyError); + }); + }); + + describe('Kerberos', function () { + it('should error if not installed', async function () { + const moduleName = 'kerberos'; + if (moduleExistsSync(moduleName)) { + return this.skip(); + } + const gssapi = new GSSAPI(); + + const error = await gssapi + .auth(new AuthContext(null, true, { hostAddress: new HostAddress('a'), credentials: true })) + .then( + () => null, + e => e + ); + + expect(error).to.be.instanceOf(MongoMissingDependencyError); + }); + }); + + describe('aws4', function () { + it('should error if not installed', async function () { + const moduleName = 'aws4'; + if (moduleExistsSync(moduleName)) { + return this.skip(); + } + const mdbAWS = new MongoDBAWS(); + + const error = await mdbAWS.auth( + new AuthContext({ hello: { maxWireVersion: 9 } }, true, null) + ); + + expect(error).to.be.instanceOf(MongoMissingDependencyError); + }); + }); +}); From ef1574024f3687e29caf27b060c5f832248fe51e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 11:39:39 -0500 Subject: [PATCH 02/17] chore: fix more unhandling --- src/sdam/server.ts | 7 +++++-- src/sdam/topology.ts | 13 +++++++------ src/timeout.ts | 10 ++++++++-- .../node_csot.test.ts | 4 +++- .../node-specific/examples/change_streams.test.js | 7 +------ test/tools/runner/throw_rejections.cjs | 4 +++- test/unit/cmap/auth/auth_provider.test.ts | 14 ++++++++------ 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/sdam/server.ts b/src/sdam/server.ts index 007837ef365..d8a9d129725 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -346,8 +346,11 @@ export class Server extends TypedEventEmitter { operationError instanceof MongoError && operationError.code === MONGODB_ERROR_CODES.Reauthenticate ) { - // TODO: what do we do with this error? the signal has aborted so where should it be delivered? - reauthPromise = this.pool.reauthenticate(conn).then(undefined, squashError); + reauthPromise = this.pool.reauthenticate(conn); + reauthPromise.then(undefined, error => { + reauthPromise = null; + squashError(error); + }); await abortable(reauthPromise, options); reauthPromise = null; // only reachable if reauth succeeds diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index a67f17dd9bb..82db71d1242 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -559,11 +559,6 @@ export class Topology extends TypedEventEmitter { new ServerSelectionStartedEvent(selector, this.description, options.operationName) ); } - let timeout; - if (options.timeoutContext) timeout = options.timeoutContext.serverSelectionTimeout; - else { - timeout = Timeout.expires(options.serverSelectionTimeoutMS ?? 0); - } const isSharded = this.description.type === TopologyType.Sharded; const session = options.session; @@ -586,10 +581,16 @@ export class Topology extends TypedEventEmitter { ) ); } - if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear(); + return transaction.server; } + let timeout; + if (options.timeoutContext) timeout = options.timeoutContext.serverSelectionTimeout; + else { + timeout = Timeout.expires(options.serverSelectionTimeoutMS ?? 0); + } + const { promise: serverPromise, resolve, reject } = promiseWithResolvers(); const waitQueueMember: ServerSelectionRequest = { diff --git a/src/timeout.ts b/src/timeout.ts index 3b1dbcb2346..26e04943f6a 100644 --- a/src/timeout.ts +++ b/src/timeout.ts @@ -3,7 +3,7 @@ import { clearTimeout, setTimeout } from 'timers'; import { type Document } from './bson'; import { MongoInvalidArgumentError, MongoOperationTimeoutError, MongoRuntimeError } from './error'; import { type ClientSession } from './sessions'; -import { csotMin, noop } from './utils'; +import { csotMin, noop, squashError } from './utils'; /** @internal */ export class TimeoutError extends Error { @@ -102,7 +102,13 @@ export class Timeout extends Promise { } throwIfExpired(): void { - if (this.timedOut) throw new TimeoutError('Timed out', { duration: this.duration }); + if (this.timedOut) { + // This method is invoked when someone wants to throw immediately instead of await the result of this promise + // Since they won't be handling the rejection from the promise (because we're about to throw here) + // attach handling to prevent this from bubbling up to Node.js + this.then(undefined, squashError); + throw new TimeoutError('Timed out', { duration: this.duration }); + } } public static expires(duration: number, unref?: true): Timeout { diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index ec69dcc1b7b..e89584e85a1 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -1023,7 +1023,9 @@ describe('CSOT driver tests', metadata, () => { beforeEach(async function () { cs = client.db('db').collection('coll').watch([], { timeoutMS: 120 }); - const _changePromise = once(cs, 'change'); + const changePromise = once(cs, 'change'); + void changePromise.then(undefined, () => null); // need to handled when this rejects with the timeout error + await once(cs.cursor, 'init'); await internalClient.db().admin().command(failpoint); diff --git a/test/integration/node-specific/examples/change_streams.test.js b/test/integration/node-specific/examples/change_streams.test.js index 5285da5cf14..01ee3769828 100644 --- a/test/integration/node-specific/examples/change_streams.test.js +++ b/test/integration/node-specific/examples/change_streams.test.js @@ -2,7 +2,6 @@ 'use strict'; const { setTimeout } = require('timers'); -const setupDatabase = require('../../shared').setupDatabase; const expect = require('chai').expect; // TODO: NODE-3819: Unskip flaky MacOS/Windows tests. @@ -11,13 +10,9 @@ maybeDescribe('examples(change-stream):', function () { let client; let db; - before(async function () { - await setupDatabase(this.configuration); - }); - beforeEach(async function () { client = await this.configuration.newClient().connect(); - db = client.db(this.configuration.db); + db = client.db('change_stream_examples'); // ensure database exists, we need this for 3.6 await db.collection('inventory').insertOne({}); diff --git a/test/tools/runner/throw_rejections.cjs b/test/tools/runner/throw_rejections.cjs index 7dcf448c5d6..e7f26bee1d3 100644 --- a/test/tools/runner/throw_rejections.cjs +++ b/test/tools/runner/throw_rejections.cjs @@ -1,6 +1,8 @@ +/* eslint-disable no-console */ // eslint-disable-next-line @typescript-eslint/no-require-imports const process = require('process'); -process.on('unhandledRejection', error => { +process.on('unhandledRejection', (error, promise) => { + console.log('promise:', promise, 'unhandledRejection:', error); throw error; }); diff --git a/test/unit/cmap/auth/auth_provider.test.ts b/test/unit/cmap/auth/auth_provider.test.ts index 96d49c650c3..bd1d304a68c 100644 --- a/test/unit/cmap/auth/auth_provider.test.ts +++ b/test/unit/cmap/auth/auth_provider.test.ts @@ -8,12 +8,14 @@ describe('AuthProvider', function () { const provider = new AuthProvider(); const context = { reauthenticating: true }; - it('returns an error', function () { - provider.reauth(context, error => { - expect(error).to.exist; - expect(error).to.be.instanceOf(MongoRuntimeError); - expect(error?.message).to.equal('Reauthentication already in progress.'); - }); + it('returns an error', async function () { + const error = await provider.reauth(context).then( + () => null, + error => error + ); + expect(error).to.exist; + expect(error).to.be.instanceOf(MongoRuntimeError); + expect(error?.message).to.equal('Reauthentication already in progress.'); }); }); }); From 98a07264299456606033a2acd0e76fc55d64cb32 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 12:38:10 -0500 Subject: [PATCH 03/17] chore: revert timeout move --- src/sdam/topology.ts | 13 ++++++------- test/integration/node-specific/abort_signal.test.ts | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 82db71d1242..a67f17dd9bb 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -559,6 +559,11 @@ export class Topology extends TypedEventEmitter { new ServerSelectionStartedEvent(selector, this.description, options.operationName) ); } + let timeout; + if (options.timeoutContext) timeout = options.timeoutContext.serverSelectionTimeout; + else { + timeout = Timeout.expires(options.serverSelectionTimeoutMS ?? 0); + } const isSharded = this.description.type === TopologyType.Sharded; const session = options.session; @@ -581,16 +586,10 @@ export class Topology extends TypedEventEmitter { ) ); } - + if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear(); return transaction.server; } - let timeout; - if (options.timeoutContext) timeout = options.timeoutContext.serverSelectionTimeout; - else { - timeout = Timeout.expires(options.serverSelectionTimeoutMS ?? 0); - } - const { promise: serverPromise, resolve, reject } = promiseWithResolvers(); const waitQueueMember: ServerSelectionRequest = { diff --git a/test/integration/node-specific/abort_signal.test.ts b/test/integration/node-specific/abort_signal.test.ts index 7dfcd1f505a..c2bbbc37c8a 100644 --- a/test/integration/node-specific/abort_signal.test.ts +++ b/test/integration/node-specific/abort_signal.test.ts @@ -793,8 +793,9 @@ describe('AbortSignal support', () => { describe('if reauth throws', () => { beforeEach(() => { sinon.stub(ConnectionPool.prototype, 'reauthenticate').callsFake(async function () { + const error = new Error('Rejecting reauthenticate for testing'); await sleep(1000); - throw new Error('Rejecting reauthenticate for testing'); + throw error; }); }); From c7ddff46c5b6bc1552f119380f3a25c2d8a3671f Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 13:11:43 -0500 Subject: [PATCH 04/17] chore: fix missing handling --- src/sdam/server.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sdam/server.ts b/src/sdam/server.ts index d8a9d129725..c6798316974 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -370,9 +370,10 @@ export class Server extends TypedEventEmitter { if (session?.pinnedConnection !== conn) { if (reauthPromise != null) { // The reauth promise only exists if it hasn't thrown. - void reauthPromise.finally(() => { + const checkBackIn = () => { this.pool.checkIn(conn); - }); + }; + void reauthPromise.then(checkBackIn, checkBackIn); } else { this.pool.checkIn(conn); } From 694ddc87cceafbe7985d59f68a38f86c79327493 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 14:11:10 -0500 Subject: [PATCH 05/17] chore: fix more --- src/cmap/auth/mongodb_aws.ts | 10 +++- src/cursor/abstract_cursor.ts | 89 +++++++++++++++++++---------------- src/deps.ts | 7 ++- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index d9071496b54..0f1a7be6a44 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -1,9 +1,10 @@ import type { Binary, BSONSerializeOptions } from '../../bson'; import * as BSON from '../../bson'; -import { aws4 } from '../../deps'; +import { type AWS4, loadAws4 } from '../../deps'; import { MongoCompatibilityError, MongoMissingCredentialsError, + type MongoMissingDependencyError, MongoRuntimeError } from '../../error'; import { ByteUtils, maxWireVersion, ns, randomBytes } from '../../utils'; @@ -32,6 +33,12 @@ interface AWSSaslContinuePayload { t?: string; } +let aws4: + | AWS4 + | { + kModuleError: MongoMissingDependencyError; + }; + export class MongoDBAWS extends AuthProvider { private credentialFetcher: AWSTemporaryCredentialProvider; constructor() { @@ -48,6 +55,7 @@ export class MongoDBAWS extends AuthProvider { throw new MongoMissingCredentialsError('AuthContext must provide credentials.'); } + aws4 ??= loadAws4(); if ('kModuleError' in aws4) { throw aws4['kModuleError']; } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index cf9ea8293dd..791fab6f584 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -1146,50 +1146,59 @@ class ReadableCursorStream extends Readable { return; } - this._cursor.next().then( - result => { - if (result == null) { - this.push(null); - } else if (this.destroyed) { - this._cursor.close().then(undefined, squashError); - } else { - if (this.push(result)) { - return this._readNext(); + this._cursor + .next() + .then( + // result from next() + result => { + if (result == null) { + this.push(null); + } else if (this.destroyed) { + this._cursor.close().then(undefined, squashError); + } else { + if (this.push(result)) { + return this._readNext(); + } + + this._readInProgress = false; + } + }, + // error from next() + err => { + // NOTE: This is questionable, but we have a test backing the behavior. It seems the + // desired behavior is that a stream ends cleanly when a user explicitly closes + // a client during iteration. Alternatively, we could do the "right" thing and + // propagate the error message by removing this special case. + if (err.message.match(/server is closed/)) { + this._cursor.close().then(undefined, squashError); + return this.push(null); } - this._readInProgress = false; - } - }, - err => { - // NOTE: This is questionable, but we have a test backing the behavior. It seems the - // desired behavior is that a stream ends cleanly when a user explicitly closes - // a client during iteration. Alternatively, we could do the "right" thing and - // propagate the error message by removing this special case. - if (err.message.match(/server is closed/)) { - this._cursor.close().then(undefined, squashError); - return this.push(null); - } + // NOTE: This is also perhaps questionable. The rationale here is that these errors tend + // to be "operation was interrupted", where a cursor has been closed but there is an + // active getMore in-flight. This used to check if the cursor was killed but once + // that changed to happen in cleanup legitimate errors would not destroy the + // stream. There are change streams test specifically test these cases. + if (err.message.match(/operation was interrupted/)) { + return this.push(null); + } - // NOTE: This is also perhaps questionable. The rationale here is that these errors tend - // to be "operation was interrupted", where a cursor has been closed but there is an - // active getMore in-flight. This used to check if the cursor was killed but once - // that changed to happen in cleanup legitimate errors would not destroy the - // stream. There are change streams test specifically test these cases. - if (err.message.match(/operation was interrupted/)) { - return this.push(null); + // NOTE: The two above checks on the message of the error will cause a null to be pushed + // to the stream, thus closing the stream before the destroy call happens. This means + // that either of those error messages on a change stream will not get a proper + // 'error' event to be emitted (the error passed to destroy). Change stream resumability + // relies on that error event to be emitted to create its new cursor and thus was not + // working on 4.4 servers because the error emitted on failover was "interrupted at + // shutdown" while on 5.0+ it is "The server is in quiesce mode and will shut down". + // See NODE-4475. + return this.destroy(err); } - - // NOTE: The two above checks on the message of the error will cause a null to be pushed - // to the stream, thus closing the stream before the destroy call happens. This means - // that either of those error messages on a change stream will not get a proper - // 'error' event to be emitted (the error passed to destroy). Change stream resumability - // relies on that error event to be emitted to create its new cursor and thus was not - // working on 4.4 servers because the error emitted on failover was "interrupted at - // shutdown" while on 5.0+ it is "The server is in quiesce mode and will shut down". - // See NODE-4475. - return this.destroy(err); - } - ); + ) + // if either of the above handlers throw + .catch(error => { + this._readInProgress = false; + this.destroy(error); + }); } } diff --git a/src/deps.ts b/src/deps.ts index 0947ca6efc5..78c57123d9e 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -203,7 +203,8 @@ export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyErr } } -interface AWS4 { +/** @internal */ +export interface AWS4 { /** * Created these inline types to better assert future usage of this API * @param options - options for request @@ -244,9 +245,7 @@ interface AWS4 { }; } -export const aws4: AWS4 | { kModuleError: MongoMissingDependencyError } = loadAws4(); - -function loadAws4() { +export function loadAws4() { let aws4: AWS4 | { kModuleError: MongoMissingDependencyError }; try { // eslint-disable-next-line @typescript-eslint/no-require-imports From 043cd7620ed4e31e0fe4abab13cfc3a73e90280e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 14:33:53 -0500 Subject: [PATCH 06/17] fix setupDatabase --- .evergreen/run-mongodb-aws-ecs-test.sh | 3 -- .evergreen/setup-mongodb-aws-auth-tests.sh | 2 -- .../examples/change_streams.test.js | 7 +++- test/integration/shared.js | 33 +++++++------------ 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/.evergreen/run-mongodb-aws-ecs-test.sh b/.evergreen/run-mongodb-aws-ecs-test.sh index bcbfff4a08f..2f0fac365ad 100755 --- a/.evergreen/run-mongodb-aws-ecs-test.sh +++ b/.evergreen/run-mongodb-aws-ecs-test.sh @@ -13,6 +13,3 @@ source ./.evergreen/prepare-shell.sh # should not run git clone # load node.js source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh - -# run the tests -npm install aws4 diff --git a/.evergreen/setup-mongodb-aws-auth-tests.sh b/.evergreen/setup-mongodb-aws-auth-tests.sh index 79ab66e55bc..92b752cbbcc 100644 --- a/.evergreen/setup-mongodb-aws-auth-tests.sh +++ b/.evergreen/setup-mongodb-aws-auth-tests.sh @@ -23,8 +23,6 @@ cd $DRIVERS_TOOLS/.evergreen/auth_aws cd $BEFORE -npm install --no-save aws4 - if [ $MONGODB_AWS_SDK = 'false' ]; then rm -rf ./node_modules/@aws-sdk/credential-providers; fi # revert to show test output diff --git a/test/integration/node-specific/examples/change_streams.test.js b/test/integration/node-specific/examples/change_streams.test.js index 01ee3769828..5285da5cf14 100644 --- a/test/integration/node-specific/examples/change_streams.test.js +++ b/test/integration/node-specific/examples/change_streams.test.js @@ -2,6 +2,7 @@ 'use strict'; const { setTimeout } = require('timers'); +const setupDatabase = require('../../shared').setupDatabase; const expect = require('chai').expect; // TODO: NODE-3819: Unskip flaky MacOS/Windows tests. @@ -10,9 +11,13 @@ maybeDescribe('examples(change-stream):', function () { let client; let db; + before(async function () { + await setupDatabase(this.configuration); + }); + beforeEach(async function () { client = await this.configuration.newClient().connect(); - db = client.db('change_stream_examples'); + db = client.db(this.configuration.db); // ensure database exists, we need this for 3.6 await db.collection('inventory').insertOne({}); diff --git a/test/integration/shared.js b/test/integration/shared.js index 6bbbff6beb0..84c15a82b24 100644 --- a/test/integration/shared.js +++ b/test/integration/shared.js @@ -90,32 +90,21 @@ function ignoreNsNotFound(err) { if (!err.message.match(/ns not found/)) throw err; } -function setupDatabase(configuration, dbsToClean) { +async function setupDatabase(configuration, dbsToClean) { dbsToClean = Array.isArray(dbsToClean) ? dbsToClean : []; - var configDbName = configuration.db; - var client = configuration.newClient(configuration.writeConcernMax(), { - maxPoolSize: 1 - }); + const configDbName = configuration.db; dbsToClean.push(configDbName); - return client - .connect() - .then(() => - dbsToClean.reduce( - (result, dbName) => - result - .then(() => - client.db(dbName).command({ dropAllUsersFromDatabase: 1, writeConcern: { w: 1 } }) - ) - .then(() => client.db(dbName).dropDatabase({ writeConcern: { w: 1 } })), - Promise.resolve() - ) - ) - .then( - () => client.close(), - err => client.close(() => Promise.reject(err)) - ); + const client = configuration.newClient(configuration.writeConcernMax()); + try { + for (const dbName of dbsToClean) { + await client.db(dbName).command({ dropAllUsersFromDatabase: 1, writeConcern: { w: 1 } }); + await client.db(dbName).dropDatabase({ writeConcern: { w: 1 } }); + } + } finally { + await client.close(); + } } /** From c4d0ed6b69c8024f0dba853aa1303d5006975cd3 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 5 Mar 2025 15:06:27 -0500 Subject: [PATCH 07/17] ignore dropping error --- .../node-specific/examples/change_streams.test.js | 4 +++- test/integration/shared.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test/integration/node-specific/examples/change_streams.test.js b/test/integration/node-specific/examples/change_streams.test.js index 5285da5cf14..90737e2ddfc 100644 --- a/test/integration/node-specific/examples/change_streams.test.js +++ b/test/integration/node-specific/examples/change_streams.test.js @@ -60,7 +60,9 @@ maybeDescribe('examples(change-stream):', function () { it('Open A Change Stream', { metadata: { requires: { topology: ['replicaset'], mongodb: '>=3.6.0' } }, test: async function () { - const looper = new Looper(() => db.collection('inventory').insertOne({ a: 1 })); + const looper = new Looper(async () => { + await db.collection('inventory').insertOne({ a: 1 }); + }); looper.run(); // Start Changestream Example 1 diff --git a/test/integration/shared.js b/test/integration/shared.js index 84c15a82b24..9c5c6c2ef87 100644 --- a/test/integration/shared.js +++ b/test/integration/shared.js @@ -99,8 +99,14 @@ async function setupDatabase(configuration, dbsToClean) { const client = configuration.newClient(configuration.writeConcernMax()); try { for (const dbName of dbsToClean) { - await client.db(dbName).command({ dropAllUsersFromDatabase: 1, writeConcern: { w: 1 } }); - await client.db(dbName).dropDatabase({ writeConcern: { w: 1 } }); + await client + .db(dbName) + .command({ dropAllUsersFromDatabase: 1, writeConcern: { w: 'majority' } }); + try { + await client.db(dbName).dropDatabase({ writeConcern: { w: 'majority' } }); + } catch (error) { + if (!error.message.match(/database is currently being dropped/)) throw error; + } } } finally { await client.close(); From 4d18128c87a0a980f5c238ee9ea2857f36785c35 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 7 Mar 2025 12:18:19 -0500 Subject: [PATCH 08/17] chore: change shared to delete instead of drop --- .gitignore | 1 + src/cmap/auth/mongodb_aws.ts | 6 +----- test/integration/shared.js | 13 +++++-------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index d0fdc406efd..36a3aac2c16 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ secrets-export.fish mo-expansion.sh mo-expansion.yml expansions.sh +uri.txt .drivers-tools/ diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 0f1a7be6a44..133ff425332 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -33,11 +33,7 @@ interface AWSSaslContinuePayload { t?: string; } -let aws4: - | AWS4 - | { - kModuleError: MongoMissingDependencyError; - }; +let aws4: AWS4 | { kModuleError: MongoMissingDependencyError }; export class MongoDBAWS extends AuthProvider { private credentialFetcher: AWSTemporaryCredentialProvider; diff --git a/test/integration/shared.js b/test/integration/shared.js index 9c5c6c2ef87..206dce205e7 100644 --- a/test/integration/shared.js +++ b/test/integration/shared.js @@ -96,16 +96,13 @@ async function setupDatabase(configuration, dbsToClean) { dbsToClean.push(configDbName); - const client = configuration.newClient(configuration.writeConcernMax()); + const client = configuration.newClient(); try { for (const dbName of dbsToClean) { - await client - .db(dbName) - .command({ dropAllUsersFromDatabase: 1, writeConcern: { w: 'majority' } }); - try { - await client.db(dbName).dropDatabase({ writeConcern: { w: 'majority' } }); - } catch (error) { - if (!error.message.match(/database is currently being dropped/)) throw error; + const db = await client.db(dbName); + for await (const { name } of db.listCollections({}, { nameOnly: true })) { + const collection = db.collection(name); + await collection.deleteMany({}); } } } finally { From a39283ad308616c23f3040b8364d6b3cc78d2a6e Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 7 Mar 2025 12:38:14 -0500 Subject: [PATCH 09/17] chore: drop collections --- test/integration/collection-management/view.test.ts | 3 +-- test/integration/shared.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/collection-management/view.test.ts b/test/integration/collection-management/view.test.ts index 88b30d03f3e..00b717bdf6e 100644 --- a/test/integration/collection-management/view.test.ts +++ b/test/integration/collection-management/view.test.ts @@ -7,9 +7,8 @@ describe('Views', function () { let db: Db; beforeEach(async function () { - const configuration = this.configuration; client = this.configuration.newClient(); - db = client.db(configuration.db); + db = client.db('views'); }); afterEach(async function () { diff --git a/test/integration/shared.js b/test/integration/shared.js index 206dce205e7..5019e4fad9c 100644 --- a/test/integration/shared.js +++ b/test/integration/shared.js @@ -102,7 +102,8 @@ async function setupDatabase(configuration, dbsToClean) { const db = await client.db(dbName); for await (const { name } of db.listCollections({}, { nameOnly: true })) { const collection = db.collection(name); - await collection.deleteMany({}); + await collection.deleteMany({}).catch(() => null); + await collection.drop().catch(() => null); } } } finally { From 374a5cd29bc67f1d35963ec18b2f2daad7a39ce0 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 14:57:14 -0400 Subject: [PATCH 10/17] chore: remove promise creation --- .../client-side-operations-timeout/node_csot.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/client-side-operations-timeout/node_csot.test.ts b/test/integration/client-side-operations-timeout/node_csot.test.ts index e89584e85a1..b544917229e 100644 --- a/test/integration/client-side-operations-timeout/node_csot.test.ts +++ b/test/integration/client-side-operations-timeout/node_csot.test.ts @@ -1023,8 +1023,7 @@ describe('CSOT driver tests', metadata, () => { beforeEach(async function () { cs = client.db('db').collection('coll').watch([], { timeoutMS: 120 }); - const changePromise = once(cs, 'change'); - void changePromise.then(undefined, () => null); // need to handled when this rejects with the timeout error + cs.once('change', () => null); await once(cs.cursor, 'init'); From 6c42bfb4b43771817c665ad3dec184c125589b6d Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 15:29:51 -0400 Subject: [PATCH 11/17] Revert views --- test/integration/collection-management/view.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/collection-management/view.test.ts b/test/integration/collection-management/view.test.ts index 00b717bdf6e..88b30d03f3e 100644 --- a/test/integration/collection-management/view.test.ts +++ b/test/integration/collection-management/view.test.ts @@ -7,8 +7,9 @@ describe('Views', function () { let db: Db; beforeEach(async function () { + const configuration = this.configuration; client = this.configuration.newClient(); - db = client.db('views'); + db = client.db(configuration.db); }); afterEach(async function () { From 19e1adbd6db48f28db4d3aff2907c94b43d32c94 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 15:30:51 -0400 Subject: [PATCH 12/17] chore: revert sleep --- test/integration/node-specific/abort_signal.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/node-specific/abort_signal.test.ts b/test/integration/node-specific/abort_signal.test.ts index c2bbbc37c8a..7aa59a88ff6 100644 --- a/test/integration/node-specific/abort_signal.test.ts +++ b/test/integration/node-specific/abort_signal.test.ts @@ -751,7 +751,6 @@ describe('AbortSignal support', () => { ...args ) { if (args[1].find != null) { - await sleep(1); commandStub.restore(); controller.abort(); throw new ReAuthenticationError({ message: 'This is a fake reauthentication error' }); From 7ab7194b5b8c0f8922318a48b20a0926dc470496 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 15:44:53 -0400 Subject: [PATCH 13/17] undo aws4 change --- .evergreen/run-mongodb-aws-ecs-test.sh | 3 +++ .evergreen/setup-mongodb-aws-auth-tests.sh | 2 ++ package-lock.json | 8 -------- package.json | 1 - src/cmap/auth/mongodb_aws.ts | 6 +----- src/deps.ts | 7 ++++--- test/unit/assorted/optional_require.test.ts | 6 +++--- 7 files changed, 13 insertions(+), 20 deletions(-) diff --git a/.evergreen/run-mongodb-aws-ecs-test.sh b/.evergreen/run-mongodb-aws-ecs-test.sh index 2f0fac365ad..bcbfff4a08f 100755 --- a/.evergreen/run-mongodb-aws-ecs-test.sh +++ b/.evergreen/run-mongodb-aws-ecs-test.sh @@ -13,3 +13,6 @@ source ./.evergreen/prepare-shell.sh # should not run git clone # load node.js source $DRIVERS_TOOLS/.evergreen/init-node-and-npm-env.sh + +# run the tests +npm install aws4 diff --git a/.evergreen/setup-mongodb-aws-auth-tests.sh b/.evergreen/setup-mongodb-aws-auth-tests.sh index 92b752cbbcc..79ab66e55bc 100644 --- a/.evergreen/setup-mongodb-aws-auth-tests.sh +++ b/.evergreen/setup-mongodb-aws-auth-tests.sh @@ -23,6 +23,8 @@ cd $DRIVERS_TOOLS/.evergreen/auth_aws cd $BEFORE +npm install --no-save aws4 + if [ $MONGODB_AWS_SDK = 'false' ]; then rm -rf ./node_modules/@aws-sdk/credential-providers; fi # revert to show test output diff --git a/package-lock.json b/package-lock.json index 425d17ab774..8fdb4ad46da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,6 @@ "@types/whatwg-url": "^11.0.5", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", - "aws4": "^1.13.2", "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", @@ -3423,13 +3422,6 @@ "node": "*" } }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index e4fcc8521e7..733c8575ef1 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "@types/whatwg-url": "^11.0.5", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", - "aws4": "^1.13.2", "chai": "^4.4.1", "chai-subset": "^1.6.0", "chalk": "^4.1.2", diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index 133ff425332..d9071496b54 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -1,10 +1,9 @@ import type { Binary, BSONSerializeOptions } from '../../bson'; import * as BSON from '../../bson'; -import { type AWS4, loadAws4 } from '../../deps'; +import { aws4 } from '../../deps'; import { MongoCompatibilityError, MongoMissingCredentialsError, - type MongoMissingDependencyError, MongoRuntimeError } from '../../error'; import { ByteUtils, maxWireVersion, ns, randomBytes } from '../../utils'; @@ -33,8 +32,6 @@ interface AWSSaslContinuePayload { t?: string; } -let aws4: AWS4 | { kModuleError: MongoMissingDependencyError }; - export class MongoDBAWS extends AuthProvider { private credentialFetcher: AWSTemporaryCredentialProvider; constructor() { @@ -51,7 +48,6 @@ export class MongoDBAWS extends AuthProvider { throw new MongoMissingCredentialsError('AuthContext must provide credentials.'); } - aws4 ??= loadAws4(); if ('kModuleError' in aws4) { throw aws4['kModuleError']; } diff --git a/src/deps.ts b/src/deps.ts index 78c57123d9e..0947ca6efc5 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -203,8 +203,7 @@ export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyErr } } -/** @internal */ -export interface AWS4 { +interface AWS4 { /** * Created these inline types to better assert future usage of this API * @param options - options for request @@ -245,7 +244,9 @@ export interface AWS4 { }; } -export function loadAws4() { +export const aws4: AWS4 | { kModuleError: MongoMissingDependencyError } = loadAws4(); + +function loadAws4() { let aws4: AWS4 | { kModuleError: MongoMissingDependencyError }; try { // eslint-disable-next-line @typescript-eslint/no-require-imports diff --git a/test/unit/assorted/optional_require.test.ts b/test/unit/assorted/optional_require.test.ts index dfc4715cf67..83eb32cfc24 100644 --- a/test/unit/assorted/optional_require.test.ts +++ b/test/unit/assorted/optional_require.test.ts @@ -62,9 +62,9 @@ describe('optionalRequire', function () { } const mdbAWS = new MongoDBAWS(); - const error = await mdbAWS.auth( - new AuthContext({ hello: { maxWireVersion: 9 } }, true, null) - ); + const error = await mdbAWS + .auth(new AuthContext({ hello: { maxWireVersion: 9 } }, true, null)) + .catch(error => error); expect(error).to.be.instanceOf(MongoMissingDependencyError); }); From bcd44f207d620afc04319beb6475be36b78a8456 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 15:45:44 -0400 Subject: [PATCH 14/17] chore: disable ts warning --- test/unit/cmap/auth/auth_provider.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/cmap/auth/auth_provider.test.ts b/test/unit/cmap/auth/auth_provider.test.ts index bd1d304a68c..40bb40b87a5 100644 --- a/test/unit/cmap/auth/auth_provider.test.ts +++ b/test/unit/cmap/auth/auth_provider.test.ts @@ -5,6 +5,7 @@ import { AuthProvider, MongoRuntimeError } from '../../../mongodb'; describe('AuthProvider', function () { describe('#reauth', function () { context('when the provider is already reauthenticating', function () { + //@ts-expect-error: cannot make an instance of an abstract class const provider = new AuthProvider(); const context = { reauthenticating: true }; From caf2a894a7651bbfe483a111ca2984e7052287a7 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 10 Mar 2025 15:48:38 -0400 Subject: [PATCH 15/17] rm print stmt --- test/tools/runner/throw_rejections.cjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/tools/runner/throw_rejections.cjs b/test/tools/runner/throw_rejections.cjs index e7f26bee1d3..7dcf448c5d6 100644 --- a/test/tools/runner/throw_rejections.cjs +++ b/test/tools/runner/throw_rejections.cjs @@ -1,8 +1,6 @@ -/* eslint-disable no-console */ // eslint-disable-next-line @typescript-eslint/no-require-imports const process = require('process'); -process.on('unhandledRejection', (error, promise) => { - console.log('promise:', promise, 'unhandledRejection:', error); +process.on('unhandledRejection', error => { throw error; }); From 489ebf60fb7299d629846cceeacb8c73a01782f4 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 11 Mar 2025 14:47:54 -0400 Subject: [PATCH 16/17] rm ts-expect-error --- test/unit/cmap/auth/auth_provider.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/unit/cmap/auth/auth_provider.test.ts b/test/unit/cmap/auth/auth_provider.test.ts index 40bb40b87a5..7c9b530e69f 100644 --- a/test/unit/cmap/auth/auth_provider.test.ts +++ b/test/unit/cmap/auth/auth_provider.test.ts @@ -1,12 +1,16 @@ import { expect } from 'chai'; -import { AuthProvider, MongoRuntimeError } from '../../../mongodb'; +import { type AuthContext, AuthProvider, MongoRuntimeError } from '../../../mongodb'; describe('AuthProvider', function () { describe('#reauth', function () { context('when the provider is already reauthenticating', function () { - //@ts-expect-error: cannot make an instance of an abstract class - const provider = new AuthProvider(); + const provider = new (class extends AuthProvider { + override auth(_context: AuthContext): Promise { + throw new Error('Method not implemented.'); + } + })(); + const context = { reauthenticating: true }; it('returns an error', async function () { From 31717b9746a87875bf360a2aebd88d2aa5e7f0d1 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Wed, 12 Mar 2025 16:00:57 -0400 Subject: [PATCH 17/17] mark nextP as handled --- test/integration/change-streams/change_stream.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/change-streams/change_stream.test.ts b/test/integration/change-streams/change_stream.test.ts index a115caf29e6..f8aabb83215 100644 --- a/test/integration/change-streams/change_stream.test.ts +++ b/test/integration/change-streams/change_stream.test.ts @@ -819,6 +819,7 @@ describe('Change Streams', function () { const write = lastWrite(); const nextP = changeStream.next(); + nextP.catch(() => null); await changeStream.close();