From b160d0d2eed4d35300b5f6db03c0a6964db6e9e6 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Mon, 28 Oct 2024 18:42:59 -0400 Subject: [PATCH] feat(NODE-6392): add timeoutMS support to ClientEncryption helpers part 1 (#4281) --- src/client-side-encryption/auto_encrypter.ts | 9 +- .../client_encryption.ts | 45 +++- src/cursor/abstract_cursor.ts | 4 +- src/gridfs/index.ts | 4 +- src/gridfs/upload.ts | 5 +- src/mongo_client.ts | 4 +- src/operations/client_bulk_write/executor.ts | 2 +- src/operations/create_collection.ts | 2 +- src/operations/drop.ts | 2 +- .../client-side-encryption/driver.test.ts | 248 ++++++++++++++++-- test/mongodb.ts | 10 + test/tools/utils.ts | 1 + .../auto_encrypter.test.ts | 2 + .../client_encryption.test.ts | 2 + 14 files changed, 288 insertions(+), 52 deletions(-) diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 47c7ff62901..e27c32123c9 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -310,7 +310,10 @@ export class AutoEncrypter { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: TS complains as this always returns true on versions where it is present. if (net.getDefaultAutoSelectFamily) { - Object.assign(clientOptions, autoSelectSocketOptions(this._client.options)); + // AutoEncrypter is made inside of MongoClient constructor while options are being parsed, + // we do not have access to the options that are in progress. + // TODO(NODE-NODE-6449): AutoEncrypter does not use client options for autoSelectFamily + Object.assign(clientOptions, autoSelectSocketOptions(this._client.s?.options ?? {})); } this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions); @@ -392,7 +395,7 @@ export class AutoEncrypter { promoteLongs: false, proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); return deserialize(await stateMachine.execute(this, context, options.timeoutContext), { @@ -413,7 +416,7 @@ export class AutoEncrypter { ...options, proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); return await stateMachine.execute( diff --git a/src/client-side-encryption/client_encryption.ts b/src/client-side-encryption/client_encryption.ts index ca62b5d2393..01c2cd3622d 100644 --- a/src/client-side-encryption/client_encryption.ts +++ b/src/client-side-encryption/client_encryption.ts @@ -24,7 +24,8 @@ import { type MongoClient, type MongoClientOptions } from '../mongo_client'; import { type Filter, type WithId } from '../mongo_types'; import { type CreateCollectionOptions } from '../operations/create_collection'; import { type DeleteResult } from '../operations/delete'; -import { MongoDBCollectionNamespace } from '../utils'; +import { TimeoutContext } from '../timeout'; +import { MongoDBCollectionNamespace, resolveTimeoutOptions } from '../utils'; import * as cryptoCallbacks from './crypto_callbacks'; import { MongoCryptCreateDataKeyError, @@ -74,6 +75,8 @@ export class ClientEncryption { _tlsOptions: CSFLEKMSTlsOptions; /** @internal */ _kmsProviders: KMSProviders; + /** @internal */ + _timeoutMS?: number; /** @internal */ _mongoCrypt: MongoCrypt; @@ -120,6 +123,8 @@ export class ClientEncryption { this._proxyOptions = options.proxyOptions ?? {}; this._tlsOptions = options.tlsOptions ?? {}; this._kmsProviders = options.kmsProviders || {}; + const { timeoutMS } = resolveTimeoutOptions(client, options); + this._timeoutMS = timeoutMS; if (options.keyVaultNamespace == null) { throw new MongoCryptInvalidArgumentError('Missing required option `keyVaultNamespace`'); @@ -212,7 +217,7 @@ export class ClientEncryption { const stateMachine = new StateMachine({ proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey; @@ -270,10 +275,14 @@ export class ClientEncryption { const stateMachine = new StateMachine({ proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); - const { v: dataKeys } = deserialize(await stateMachine.execute(this, context)); + const timeoutContext = TimeoutContext.create( + resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }) + ); + + const { v: dataKeys } = deserialize(await stateMachine.execute(this, context, timeoutContext)); if (dataKeys.length === 0) { return {}; } @@ -303,7 +312,8 @@ export class ClientEncryption { .db(dbName) .collection(collectionName) .bulkWrite(replacements, { - writeConcern: { w: 'majority' } + writeConcern: { w: 'majority' }, + timeoutMS: timeoutContext.csotEnabled() ? timeoutContext?.remainingTimeMS : undefined }); return { bulkWriteResult: result }; @@ -332,7 +342,7 @@ export class ClientEncryption { return await this._keyVaultClient .db(dbName) .collection(collectionName) - .deleteOne({ _id }, { writeConcern: { w: 'majority' } }); + .deleteOne({ _id }, { writeConcern: { w: 'majority' }, timeoutMS: this._timeoutMS }); } /** @@ -355,7 +365,7 @@ export class ClientEncryption { return this._keyVaultClient .db(dbName) .collection(collectionName) - .find({}, { readConcern: { level: 'majority' } }); + .find({}, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS }); } /** @@ -381,7 +391,7 @@ export class ClientEncryption { return await this._keyVaultClient .db(dbName) .collection(collectionName) - .findOne({ _id }, { readConcern: { level: 'majority' } }); + .findOne({ _id }, { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS }); } /** @@ -408,7 +418,10 @@ export class ClientEncryption { return await this._keyVaultClient .db(dbName) .collection(collectionName) - .findOne({ keyAltNames: keyAltName }, { readConcern: { level: 'majority' } }); + .findOne( + { keyAltNames: keyAltName }, + { readConcern: { level: 'majority' }, timeoutMS: this._timeoutMS } + ); } /** @@ -442,7 +455,7 @@ export class ClientEncryption { .findOneAndUpdate( { _id }, { $addToSet: { keyAltNames: keyAltName } }, - { writeConcern: { w: 'majority' }, returnDocument: 'before' } + { writeConcern: { w: 'majority' }, returnDocument: 'before', timeoutMS: this._timeoutMS } ); return value; @@ -503,7 +516,8 @@ export class ClientEncryption { .collection(collectionName) .findOneAndUpdate({ _id }, pipeline, { writeConcern: { w: 'majority' }, - returnDocument: 'before' + returnDocument: 'before', + timeoutMS: this._timeoutMS }); return value; @@ -650,7 +664,7 @@ export class ClientEncryption { const stateMachine = new StateMachine({ proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); const { v } = deserialize(await stateMachine.execute(this, context)); @@ -729,7 +743,7 @@ export class ClientEncryption { const stateMachine = new StateMachine({ proxyOptions: this._proxyOptions, tlsOptions: this._tlsOptions, - socketOptions: autoSelectSocketOptions(this._client.options) + socketOptions: autoSelectSocketOptions(this._client.s.options) }); const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions); @@ -818,6 +832,11 @@ export interface ClientEncryptionOptions { * TLS options for kms providers to use. */ tlsOptions?: CSFLEKMSTlsOptions; + + /** + * The timeout setting to be used for all the operations on ClientEncryption. + */ + timeoutMS?: number; } /** diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index dd3c40bfab6..4188c1e943e 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -835,7 +835,7 @@ export abstract class AbstractCursor< if (this.cursorOptions.timeoutMS != null) { this.timeoutContext ??= new CursorTimeoutContext( TimeoutContext.create({ - serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, + serverSelectionTimeoutMS: this.client.s.options.serverSelectionTimeoutMS, timeoutMS: this.cursorOptions.timeoutMS }), this @@ -925,7 +925,7 @@ export abstract class AbstractCursor< this.timeoutContext?.clear(); return new CursorTimeoutContext( TimeoutContext.create({ - serverSelectionTimeoutMS: this.client.options.serverSelectionTimeoutMS, + serverSelectionTimeoutMS: this.client.s.options.serverSelectionTimeoutMS, timeoutMS }), this diff --git a/src/gridfs/index.ts b/src/gridfs/index.ts index de114e5e597..67df4548cb0 100644 --- a/src/gridfs/index.ts +++ b/src/gridfs/index.ts @@ -161,7 +161,7 @@ export class GridFSBucket extends TypedEventEmitter { if (timeoutMS) { timeoutContext = new CSOTTimeoutContext({ timeoutMS, - serverSelectionTimeoutMS: this.s.db.client.options.serverSelectionTimeoutMS + serverSelectionTimeoutMS: this.s.db.client.s.options.serverSelectionTimeoutMS }); } @@ -241,7 +241,7 @@ export class GridFSBucket extends TypedEventEmitter { if (timeoutMS) { timeoutContext = new CSOTTimeoutContext({ timeoutMS, - serverSelectionTimeoutMS: this.s.db.client.options.serverSelectionTimeoutMS + serverSelectionTimeoutMS: this.s.db.client.s.options.serverSelectionTimeoutMS }); } diff --git a/src/gridfs/upload.ts b/src/gridfs/upload.ts index c7544b715d8..ef3d25f62ec 100644 --- a/src/gridfs/upload.ts +++ b/src/gridfs/upload.ts @@ -10,7 +10,7 @@ import { MongoOperationTimeoutError } from '../error'; import { CSOTTimeoutContext } from '../timeout'; -import { type Callback, squashError } from '../utils'; +import { type Callback, resolveTimeoutOptions, squashError } from '../utils'; import type { WriteConcernOptions } from '../write_concern'; import { WriteConcern } from './../write_concern'; import type { GridFSFile } from './download'; @@ -143,7 +143,8 @@ export class GridFSBucketWriteStream extends Writable { if (options.timeoutMS != null) this.timeoutContext = new CSOTTimeoutContext({ timeoutMS: options.timeoutMS, - serverSelectionTimeoutMS: this.bucket.s.db.client.options.serverSelectionTimeoutMS + serverSelectionTimeoutMS: resolveTimeoutOptions(this.bucket.s.db.client, {}) + .serverSelectionTimeoutMS }); } diff --git a/src/mongo_client.ts b/src/mongo_client.ts index e6f64bae217..9374ee388cc 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -490,7 +490,7 @@ export class MongoClient extends TypedEventEmitter implements /** @internal */ get timeoutMS(): number | undefined { - return this.options.timeoutMS; + return this.s.options.timeoutMS; } /** @@ -706,7 +706,7 @@ export class MongoClient extends TypedEventEmitter implements // Default to db from connection string if not provided if (!dbName) { - dbName = this.options.dbName; + dbName = this.s.options.dbName; } // Copy the options and add out internal override of the not shared flag diff --git a/src/operations/client_bulk_write/executor.ts b/src/operations/client_bulk_write/executor.ts index ab7c4404f66..6d12c79c253 100644 --- a/src/operations/client_bulk_write/executor.ts +++ b/src/operations/client_bulk_write/executor.ts @@ -56,7 +56,7 @@ export class ClientBulkWriteExecutor { // If no write concern was provided, we inherit one from the client. if (!this.options.writeConcern) { - this.options.writeConcern = WriteConcern.fromOptions(this.client.options); + this.options.writeConcern = WriteConcern.fromOptions(this.client.s.options); } if (this.options.writeConcern?.w === 0) { diff --git a/src/operations/create_collection.ts b/src/operations/create_collection.ts index 293ecc8be52..da278f88c11 100644 --- a/src/operations/create_collection.ts +++ b/src/operations/create_collection.ts @@ -137,7 +137,7 @@ export class CreateCollectionOperation extends CommandOperation { const encryptedFields: Document | undefined = options.encryptedFields ?? - db.client.options.autoEncryption?.encryptedFieldsMap?.[`${db.databaseName}.${name}`]; + db.client.s.options.autoEncryption?.encryptedFieldsMap?.[`${db.databaseName}.${name}`]; if (encryptedFields) { // Creating a QE collection required min server of 7.0.0 diff --git a/src/operations/drop.ts b/src/operations/drop.ts index 787bb6e7d0f..0ead5a4927a 100644 --- a/src/operations/drop.ts +++ b/src/operations/drop.ts @@ -39,7 +39,7 @@ export class DropCollectionOperation extends CommandOperation { const options = this.options; const name = this.name; - const encryptedFieldsMap = db.client.options.autoEncryption?.encryptedFieldsMap; + const encryptedFieldsMap = db.client.s.options.autoEncryption?.encryptedFieldsMap; let encryptedFields: Document | undefined = options.encryptedFields ?? encryptedFieldsMap?.[`${db.databaseName}.${name}`]; diff --git a/test/integration/client-side-encryption/driver.test.ts b/test/integration/client-side-encryption/driver.test.ts index 202501fad22..937a197defe 100644 --- a/test/integration/client-side-encryption/driver.test.ts +++ b/test/integration/client-side-encryption/driver.test.ts @@ -1,31 +1,45 @@ -import { EJSON, UUID } from 'bson'; +import { type Binary, EJSON, UUID } from 'bson'; import { expect } from 'chai'; import * as crypto from 'crypto'; import * as sinon from 'sinon'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { ClientEncryption } from '../../../src/client-side-encryption/client_encryption'; -// eslint-disable-next-line @typescript-eslint/no-restricted-imports -import { StateMachine } from '../../../src/client-side-encryption/state_machine'; import { + BSON, type Collection, type CommandStartedEvent, Connection, CSOTTimeoutContext, - type KMSProviders, type MongoClient, - MongoOperationTimeoutError + MongoOperationTimeoutError, + StateMachine } from '../../mongodb'; -import * as BSON from '../../mongodb'; -import { type FailPoint, getEncryptExtraOptions, measureDuration, sleep } from '../../tools/utils'; - -const metadata = { +import { + clearFailPoint, + configureFailPoint, + type FailPoint, + getEncryptExtraOptions, + measureDuration, + sleep +} from '../../tools/utils'; + +const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.2.0', clientSideEncryption: true } }; +const getLocalKmsProvider = (): { local: { key: Binary } } => { + const { local } = EJSON.parse(process.env.CSFLE_KMS_PROVIDERS || '{}') as { + local: { key: Binary }; + [key: string]: unknown; + }; + + return { local }; +}; + describe('Client Side Encryption Functional', function () { const dataDbName = 'db'; const dataCollName = 'coll'; @@ -141,10 +155,8 @@ describe('Client Side Encryption Functional', function () { await client.connect(); const encryption = new ClientEncryption(client, { - bson: BSON, keyVaultNamespace, - kmsProviders, - extraOptions: getEncryptExtraOptions() + kmsProviders }); const dataDb = client.db(dataDbName); @@ -330,8 +342,7 @@ describe('Client Side Encryption Functional', function () { const encryption = new ClientEncryption(client, { keyVaultNamespace, - kmsProviders, - extraOptions: getEncryptExtraOptions() + kmsProviders }); const dataDb = client.db(dataDbName); @@ -412,6 +423,201 @@ describe('Client Side Encryption Functional', function () { }); } ); + + describe('CSOT on ClientEncryption', { requires: { clientSideEncryption: true } }, function () { + const metadata: MongoDBMetadataUI = { + requires: { clientSideEncryption: true, mongodb: '>=4.4' } + }; + + function makeBlockingFailFor(command: string | string[], blockTimeMS: number) { + beforeEach(async function () { + await configureFailPoint(this.configuration, { + configureFailPoint: 'failCommand', + mode: { times: 2 }, + data: { + failCommands: Array.isArray(command) ? command : [command], + blockConnection: true, + blockTimeMS, + appName: 'clientEncryption' + } + }); + }); + + afterEach(async function () { + sinon.restore(); + await clearFailPoint(this.configuration); + }); + } + + function runAndCheckForCSOTTimeout(fn: () => Promise) { + return async () => { + const start = performance.now(); + const error = await fn().then( + () => 'API did not reject', + error => error + ); + const end = performance.now(); + if (error?.name === 'MongoBulkWriteError') { + expect(error) + .to.have.property('errorResponse') + .that.is.instanceOf(MongoOperationTimeoutError); + } else { + expect(error).to.be.instanceOf(MongoOperationTimeoutError); + } + expect(end - start).to.be.within(498, 1000); + }; + } + + let key1Id; + let keyVaultClient: MongoClient; + let clientEncryption: ClientEncryption; + let commandsStarted: CommandStartedEvent[]; + + beforeEach(async function () { + const internalClient = this.configuration.newClient(); + await internalClient + .db('keyvault') + .dropCollection('datakeys', { writeConcern: { w: 'majority' } }) + .catch(() => null); + await internalClient.db('keyvault').createCollection('datakeys'); + await internalClient.close(); + + keyVaultClient = this.configuration.newClient(undefined, { + timeoutMS: 500, + monitorCommands: true, + minPoolSize: 1, + appName: 'clientEncryption' + }); + await keyVaultClient.connect(); + + clientEncryption = new ClientEncryption(keyVaultClient, { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: getLocalKmsProvider(), + timeoutMS: 500 + }); + + key1Id = await clientEncryption.createDataKey('local'); + while ((await clientEncryption.getKey(key1Id)) == null); + + commandsStarted = []; + keyVaultClient.on('commandStarted', ev => commandsStarted.push(ev)); + }); + + afterEach(async function () { + await keyVaultClient?.close(); + }); + + describe('rewrapManyDataKey', function () { + describe('when the bulk operation takes too long', function () { + makeBlockingFailFor('update', 2000); + + it( + 'throws a timeout error', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.rewrapManyDataKey({ _id: key1Id }, { provider: 'local' }); + }) + ); + }); + + describe('when the find operation for fetchKeys takes too long', function () { + makeBlockingFailFor('find', 2000); + + it( + 'throws a timeout error', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.rewrapManyDataKey({ _id: key1Id }, { provider: 'local' }); + }) + ); + }); + + describe('when the find and bulk operation takes too long', function () { + // together they add up to 800, exceeding the timeout of 500 + makeBlockingFailFor(['update', 'find'], 400); + + it( + 'throws a timeout error', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.rewrapManyDataKey({ _id: key1Id }, { provider: 'local' }); + }) + ); + }); + }); + + describe('deleteKey', function () { + makeBlockingFailFor('delete', 2000); + + it( + 'throws a timeout error if the delete operation takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.deleteKey(new UUID()); + }) + ); + }); + + describe('getKey', function () { + makeBlockingFailFor('find', 2000); + + it( + 'throws a timeout error if the find takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.getKey(new UUID()); + }) + ); + }); + + describe('getKeys', function () { + makeBlockingFailFor('find', 2000); + + it( + 'throws a timeout error if the find operation takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.getKeys().toArray(); + }) + ); + }); + + describe('removeKeyAltName', function () { + makeBlockingFailFor('findAndModify', 2000); + + it( + 'throws a timeout error if the findAndModify operation takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.removeKeyAltName(new UUID(), 'blah'); + }) + ); + }); + + describe('addKeyAltName', function () { + makeBlockingFailFor('findAndModify', 2000); + + it( + 'throws a timeout error if the findAndModify operation takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.addKeyAltName(new UUID(), 'blah'); + }) + ); + }); + + describe('getKeyByAltName', function () { + makeBlockingFailFor('find', 2000); + + it( + 'throws a timeout error if the find operation takes too long', + metadata, + runAndCheckForCSOTTimeout(async () => { + await clientEncryption.getKeyByAltName('blah'); + }) + ); + }); + }); }); describe('Range Explicit Encryption with JS native types', function () { @@ -495,7 +701,7 @@ describe('CSOT', function () { await keyVaultClient.db('keyvault').collection('datakeys'); const clientEncryption = new ClientEncryption(keyVaultClient, { keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: getKmsProviders() + kmsProviders: getLocalKmsProvider() }); dataKey = await clientEncryption.createDataKey('local'); setupClient = this.configuration.newClient(); @@ -525,14 +731,6 @@ describe('CSOT', function () { await setupClient.close(); }); - const getKmsProviders = (): KMSProviders => { - const result = EJSON.parse(process.env.CSFLE_KMS_PROVIDERS || '{}') as unknown as { - local: unknown; - }; - - return { local: result.local }; - }; - const metadata: MongoDBMetadataUI = { requires: { mongodb: '>=4.2.0', @@ -553,7 +751,7 @@ describe('CSOT', function () { autoEncryption: { keyVaultClient, keyVaultNamespace: 'keyvault.datakeys', - kmsProviders: getKmsProviders(), + kmsProviders: getLocalKmsProvider(), schemaMap: { 'test.test': { bsonType: 'object', @@ -607,7 +805,7 @@ describe('CSOT', function () { autoEncryption: { keyVaultClient, keyVaultNamespace: 'admin.datakeys', - kmsProviders: getKmsProviders() + kmsProviders: getLocalKmsProvider() } } ); diff --git a/test/mongodb.ts b/test/mongodb.ts index 35034123048..f94a511929c 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -103,6 +103,16 @@ export * from '../src/bulk/common'; export * from '../src/bulk/ordered'; export * from '../src/bulk/unordered'; export * from '../src/change_stream'; +export * from '../src/client-side-encryption/auto_encrypter'; +export * from '../src/client-side-encryption/client_encryption'; +export * from '../src/client-side-encryption/crypto_callbacks'; +export * from '../src/client-side-encryption/errors'; +export * from '../src/client-side-encryption/mongocryptd_manager'; +export * from '../src/client-side-encryption/providers/aws'; +export * from '../src/client-side-encryption/providers/azure'; +export * from '../src/client-side-encryption/providers/gcp'; +export * from '../src/client-side-encryption/providers/index'; +export * from '../src/client-side-encryption/state_machine'; export * from '../src/cmap/auth/auth_provider'; export * from '../src/cmap/auth/aws_temporary_credentials'; export * from '../src/cmap/auth/gssapi'; diff --git a/test/tools/utils.ts b/test/tools/utils.ts index cd79bb2d4c2..23df4f1650b 100644 --- a/test/tools/utils.ts +++ b/test/tools/utils.ts @@ -269,6 +269,7 @@ export interface FailPoint { failInternalCommands?: boolean; errorLabels?: string[]; appName?: string; + namespace?: string; }; } diff --git a/test/unit/client-side-encryption/auto_encrypter.test.ts b/test/unit/client-side-encryption/auto_encrypter.test.ts index 1e13c0b07c5..79bc321b802 100644 --- a/test/unit/client-side-encryption/auto_encrypter.test.ts +++ b/test/unit/client-side-encryption/auto_encrypter.test.ts @@ -40,9 +40,11 @@ const MOCK_KMS_DECRYPT_REPLY = readHttpResponse(`${__dirname}/data/kms-decrypt-r class MockClient { options: any; + s: { options: any }; constructor(options?: any) { this.options = { options: options || {} }; + this.s = { options: this.options }; } } diff --git a/test/unit/client-side-encryption/client_encryption.test.ts b/test/unit/client-side-encryption/client_encryption.test.ts index 2ecf634771f..8489138742d 100644 --- a/test/unit/client-side-encryption/client_encryption.test.ts +++ b/test/unit/client-side-encryption/client_encryption.test.ts @@ -20,9 +20,11 @@ const { EJSON } = BSON; class MockClient { options: any; + s: { options: any }; constructor(options?: any) { this.options = { options: options || {} }; + this.s = { options: this.options }; } db(dbName) { return {