From 8a129bd02dd64ad7de9b41a7690342c65efa55a5 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Wed, 23 Apr 2025 19:13:58 +0200 Subject: [PATCH 1/9] fix(NODE-6638): throw if all atomic updates are undefined --- src/utils.ts | 20 ++++++++++++- test/unit/utils.test.ts | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 4a436c2a35d..cd55b91b2f0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -476,7 +476,10 @@ export function calculateDurationInMs(started: number | undefined): number { } /** @internal */ -export function hasAtomicOperators(doc: Document | Document[]): boolean { +export function hasAtomicOperators( + doc: Document | Document[], + options?: CommandOperationOptions +): boolean { if (Array.isArray(doc)) { for (const document of doc) { if (hasAtomicOperators(document)) { @@ -487,6 +490,21 @@ export function hasAtomicOperators(doc: Document | Document[]): boolean { } const keys = Object.keys(doc); + // In this case we need to throw if all the atomic operators are undefined. + if (options?.ignoreUndefined) { + let allUndefined = true; + for (const key of keys) { + // eslint-disable-next-line no-restricted-syntax + if (doc[key] !== undefined) { + allUndefined = false; + break; + } + } + if (allUndefined) { + throw new MongoInvalidArgumentError('All atomic operators provided have undefined values.'); + } + } + return keys.length > 0 && keys[0][0] === '$'; } diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 26c6211075f..716c6386132 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -11,6 +11,7 @@ import { compareObjectId, decorateWithExplain, Explain, + hasAtomicOperators, HostAddress, hostMatchesWildcards, isHello, @@ -19,6 +20,7 @@ import { List, MongoDBCollectionNamespace, MongoDBNamespace, + MongoInvalidArgumentError, MongoRuntimeError, ObjectId, shuffle @@ -26,6 +28,66 @@ import { import { sleep } from '../tools/utils'; describe('driver utils', function () { + describe.only('.hasAtomicOperators', function () { + context('when ignoreUndefined is true', function () { + const options = { ignoreUndefined: true }; + + context('when no operator is undefined', function () { + const document = { $set: { n: 1 }, $unset: '' }; + + it('returns true', function () { + expect(hasAtomicOperators(document, options)).to.be.true; + }); + }); + + context('when some operators are undefined', function () { + const document = { $set: { n: 1 }, $unset: undefined }; + + it('returns true', function () { + expect(hasAtomicOperators(document, options)).to.be.true; + }); + }); + + context('when all operators are undefined', function () { + const document = { $set: undefined, $unset: undefined }; + + it('throws an error', function () { + expect(() => { + hasAtomicOperators(document, options); + }).to.throw(MongoInvalidArgumentError); + }); + }); + }); + + context('when ignoreUndefined is false', function () { + const options = { ignoreUndefined: false }; + + context('when no operator is undefined', function () { + const document = { $set: { n: 1 }, $unset: '' }; + + it('returns true', function () { + expect(hasAtomicOperators(document, options)).to.be.true; + }); + }); + + context('when some operators are undefined', function () { + const document = { $set: { n: 1 }, $unset: undefined }; + + it('returns true', function () { + expect(hasAtomicOperators(document, options)).to.be.true; + }); + }); + + context('when all operators are undefined', function () { + const document = { $set: undefined, $unset: undefined }; + + it('returns true', function () { + expect(hasAtomicOperators(document, options)).to.be.true; + }); + }); + }); + }); + describe('.hostMatchesWildcards', function () { context('when using domains', function () { context('when using exact match', function () { From e8845374986e86aee6e0ee74696f3e7489fcae09 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 18:30:16 +0200 Subject: [PATCH 2/9] chore: hook it all up --- src/bulk/common.ts | 6 +- .../client_bulk_write/command_builder.ts | 37 +++++++---- src/operations/find_and_modify.ts | 2 +- src/operations/update.ts | 4 +- test/integration/crud/bulk.test.ts | 38 +++++++++++ .../crud/client_bulk_write.test.ts | 44 +++++++++++++ test/integration/crud/crud_api.test.ts | 65 +++++++++++++++++++ test/unit/utils.test.ts | 2 +- 8 files changed, 177 insertions(+), 21 deletions(-) diff --git a/src/bulk/common.ts b/src/bulk/common.ts index da972d52f19..782b4041e39 100644 --- a/src/bulk/common.ts +++ b/src/bulk/common.ts @@ -705,7 +705,7 @@ export class FindOperators { /** Add a single update operation to the bulk operation */ updateOne(updateDocument: Document | Document[]): BulkOperationBase { - if (!hasAtomicOperators(updateDocument)) { + if (!hasAtomicOperators(updateDocument, this.bulkOperation.bsonOptions)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } @@ -1115,7 +1115,7 @@ export abstract class BulkOperationBase { ...op.updateOne, multi: false }); - if (!hasAtomicOperators(updateStatement.u)) { + if (!hasAtomicOperators(updateStatement.u, this.bsonOptions)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } return this.addToOperationsList(BatchType.UPDATE, updateStatement); @@ -1129,7 +1129,7 @@ export abstract class BulkOperationBase { ...op.updateMany, multi: true }); - if (!hasAtomicOperators(updateStatement.u)) { + if (!hasAtomicOperators(updateStatement.u, this.bsonOptions)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } return this.addToOperationsList(BatchType.UPDATE, updateStatement); diff --git a/src/operations/client_bulk_write/command_builder.ts b/src/operations/client_bulk_write/command_builder.ts index 9ec3e6b029f..6e937f058c7 100644 --- a/src/operations/client_bulk_write/command_builder.ts +++ b/src/operations/client_bulk_write/command_builder.ts @@ -1,4 +1,4 @@ -import { BSON, type Document } from '../../bson'; +import { BSON, type BSONSerializeOptions, type Document } from '../../bson'; import { DocumentSequence } from '../../cmap/commands'; import { MongoAPIError, MongoInvalidArgumentError } from '../../error'; import { type PkFactory } from '../../mongo_client'; @@ -128,7 +128,7 @@ export class ClientBulkWriteCommandBuilder { if (nsIndex != null) { // Build the operation and serialize it to get the bytes buffer. - const operation = buildOperation(model, nsIndex, this.pkFactory); + const operation = buildOperation(model, nsIndex, this.pkFactory, this.options); let operationBuffer; try { operationBuffer = BSON.serialize(operation); @@ -159,7 +159,12 @@ export class ClientBulkWriteCommandBuilder { // construct our nsInfo and ops documents and buffers. namespaces.set(ns, currentNamespaceIndex); const nsInfo = { ns: ns }; - const operation = buildOperation(model, currentNamespaceIndex, this.pkFactory); + const operation = buildOperation( + model, + currentNamespaceIndex, + this.pkFactory, + this.options + ); let nsInfoBuffer; let operationBuffer; try { @@ -339,9 +344,10 @@ export interface ClientUpdateOperation { */ export const buildUpdateOneOperation = ( model: ClientUpdateOneModel, - index: number + index: number, + options: BSONSerializeOptions ): ClientUpdateOperation => { - return createUpdateOperation(model, index, false); + return createUpdateOperation(model, index, false, options); }; /** @@ -352,17 +358,18 @@ export const buildUpdateOneOperation = ( */ export const buildUpdateManyOperation = ( model: ClientUpdateManyModel, - index: number + index: number, + options: BSONSerializeOptions ): ClientUpdateOperation => { - return createUpdateOperation(model, index, true); + return createUpdateOperation(model, index, true, options); }; /** * Validate the update document. * @param update - The update document. */ -function validateUpdate(update: Document) { - if (!hasAtomicOperators(update)) { +function validateUpdate(update: Document, options: BSONSerializeOptions) { + if (!hasAtomicOperators(update, options)) { throw new MongoAPIError( 'Client bulk write update models must only contain atomic modifiers (start with $) and must not be empty.' ); @@ -375,13 +382,14 @@ function validateUpdate(update: Document) { function createUpdateOperation( model: ClientUpdateOneModel | ClientUpdateManyModel, index: number, - multi: boolean + multi: boolean, + options: BSONSerializeOptions ): ClientUpdateOperation { // Update documents provided in UpdateOne and UpdateMany write models are // required only to contain atomic modifiers (i.e. keys that start with "$"). // Drivers MUST throw an error if an update document is empty or if the // document's first key does not start with "$". - validateUpdate(model.update); + validateUpdate(model.update, options); const document: ClientUpdateOperation = { update: index, multi: multi, @@ -459,7 +467,8 @@ export const buildReplaceOneOperation = ( export function buildOperation( model: AnyClientBulkWriteModel, index: number, - pkFactory: PkFactory + pkFactory: PkFactory, + options: BSONSerializeOptions ): Document { switch (model.name) { case 'insertOne': @@ -469,9 +478,9 @@ export function buildOperation( case 'deleteMany': return buildDeleteManyOperation(model, index); case 'updateOne': - return buildUpdateOneOperation(model, index); + return buildUpdateOneOperation(model, index, options); case 'updateMany': - return buildUpdateManyOperation(model, index); + return buildUpdateManyOperation(model, index, options); case 'replaceOne': return buildReplaceOneOperation(model, index); } diff --git a/src/operations/find_and_modify.ts b/src/operations/find_and_modify.ts index 651bcccb626..759cb02e72c 100644 --- a/src/operations/find_and_modify.ts +++ b/src/operations/find_and_modify.ts @@ -273,7 +273,7 @@ export class FindOneAndUpdateOperation extends FindAndModifyOperation { throw new MongoInvalidArgumentError('Argument "update" must be an object'); } - if (!hasAtomicOperators(update)) { + if (!hasAtomicOperators(update, options)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } diff --git a/src/operations/update.ts b/src/operations/update.ts index d020e2cb85e..f731e77945d 100644 --- a/src/operations/update.ts +++ b/src/operations/update.ts @@ -144,7 +144,7 @@ export class UpdateOneOperation extends UpdateOperation { options ); - if (!hasAtomicOperators(update)) { + if (!hasAtomicOperators(update, options)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } } @@ -179,7 +179,7 @@ export class UpdateManyOperation extends UpdateOperation { options ); - if (!hasAtomicOperators(update)) { + if (!hasAtomicOperators(update, options)) { throw new MongoInvalidArgumentError('Update document requires atomic operators'); } } diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index f32b340a3e1..cee834f19c4 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -46,6 +46,44 @@ describe('Bulk', function () { client = null; }); + describe('#bulkWrite', function () { + context('when including an update with all undefined atomic operators', function () { + context('when performing an update many', function () { + it('throws an error', async function () { + const collection = client.db('test').collection('test'); + const error = await collection + .bulkWrite([ + { + updateMany: { + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + } + ]) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + + context('when performing an update one', function () { + it('throws an error', async function () { + const collection = client.db('test').collection('test'); + const error = await collection + .bulkWrite([ + { + updateOne: { + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + } + ]) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + }); + }); + describe('BulkOperationBase', () => { describe('#raw()', function () { context('when called with an undefined operation', function () { diff --git a/test/integration/crud/client_bulk_write.test.ts b/test/integration/crud/client_bulk_write.test.ts index 5acf6029fe3..6280ac6983e 100644 --- a/test/integration/crud/client_bulk_write.test.ts +++ b/test/integration/crud/client_bulk_write.test.ts @@ -34,6 +34,50 @@ describe('Client Bulk Write', function () { await clearFailPoint(this.configuration); }); + describe('#bulkWrite', function () { + context('when including an update with all undefined atomic operators', function () { + context('when performing an update many', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); + + it('throws an error', async function () { + const error = await client + .bulkWrite([ + { + name: 'updateMany', + namespace: 'foo.bar', + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + ]) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + + context('when performing an update one', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); + + it('throws an error', async function () { + const error = await client + .bulkWrite([ + { + name: 'updateOne', + namespace: 'foo.bar', + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + ]) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + }); + }); + describe('CSOT enabled', function () { describe('when timeoutMS is set on the client', function () { beforeEach(async function () { diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 2f82c6164c1..9f979a5019a 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -896,6 +896,58 @@ describe('CRUD API', function () { }); }); + describe('#updateOne', function () { + let collection; + + beforeEach(async function () { + await client.connect(); + collection = client.db().collection('updateOneTest'); + }); + + afterEach(async function () { + await collection.drop(); + }); + + context('when including an update with all undefined atomic operators', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); + + it('throws an error', async function () { + const error = await collection + .updateOne({ a: 1 }, { $set: undefined, $unset: undefined }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + }); + + describe('#updateMany', function () { + let collection; + + beforeEach(async function () { + await client.connect(); + collection = client.db().collection('updateManyTest'); + }); + + afterEach(async function () { + await collection.drop(); + }); + + context('when including an update with all undefined atomic operators', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); + + it('throws an error', async function () { + const error = await collection + .updateMany({ a: 1 }, { $set: undefined, $unset: undefined }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + }); + describe('#findOneAndUpdate', function () { let collection; @@ -908,6 +960,19 @@ describe('CRUD API', function () { await collection.drop(); }); + context('when including an update with all undefined atomic operators', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); + + it('throws an error', async function () { + const error = await collection + .findOneAndUpdate({ a: 1 }, { $set: undefined, $unset: undefined }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + }); + context('when includeResultMetadata is true', function () { beforeEach(async function () { await collection.insertMany([{ a: 1, b: 1 }], { writeConcern: { w: 1 } }); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 716c6386132..6fa7e9784af 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -28,7 +28,7 @@ import { import { sleep } from '../tools/utils'; describe('driver utils', function () { - describe.only('.hasAtomicOperators', function () { + describe('.hasAtomicOperators', function () { context('when ignoreUndefined is true', function () { const options = { ignoreUndefined: true }; From 3d73beec9ca785beb7ea22f7f772a98817c02f68 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:02:16 +0200 Subject: [PATCH 3/9] chore: pass ignoreUndefined --- test/integration/crud/bulk.test.ts | 70 +++++++++------- .../crud/client_bulk_write.test.ts | 80 +++++++++++-------- test/integration/crud/crud_api.test.ts | 77 ++++++++++-------- 3 files changed, 129 insertions(+), 98 deletions(-) diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index cee834f19c4..408397b4c49 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -48,37 +48,49 @@ describe('Bulk', function () { describe('#bulkWrite', function () { context('when including an update with all undefined atomic operators', function () { - context('when performing an update many', function () { - it('throws an error', async function () { - const collection = client.db('test').collection('test'); - const error = await collection - .bulkWrite([ - { - updateMany: { - filter: { age: { $lte: 5 } }, - update: { $set: undefined, $unset: undefined } - } - } - ]) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + context('when ignoreUndefined is true', function () { + context('when performing an update many', function () { + it('throws an error', async function () { + const collection = client.db('test').collection('test'); + const error = await collection + .bulkWrite( + [ + { + updateMany: { + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + } + ], + { ignoreUndefined: true } + ) + .catch(error => error); + expect(error.message).to.include( + 'All atomic operators provided have undefined values.' + ); + }); }); - }); - context('when performing an update one', function () { - it('throws an error', async function () { - const collection = client.db('test').collection('test'); - const error = await collection - .bulkWrite([ - { - updateOne: { - filter: { age: { $lte: 5 } }, - update: { $set: undefined, $unset: undefined } - } - } - ]) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + context('when performing an update one', function () { + it('throws an error', async function () { + const collection = client.db('test').collection('test'); + const error = await collection + .bulkWrite( + [ + { + updateOne: { + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + } + ], + { ignoreUndefined: true } + ) + .catch(error => error); + expect(error.message).to.include( + 'All atomic operators provided have undefined values.' + ); + }); }); }); }); diff --git a/test/integration/crud/client_bulk_write.test.ts b/test/integration/crud/client_bulk_write.test.ts index 6280ac6983e..686ebf9f895 100644 --- a/test/integration/crud/client_bulk_write.test.ts +++ b/test/integration/crud/client_bulk_write.test.ts @@ -35,44 +35,56 @@ describe('Client Bulk Write', function () { }); describe('#bulkWrite', function () { - context('when including an update with all undefined atomic operators', function () { - context('when performing an update many', function () { - beforeEach(async function () { - client = this.configuration.newClient(); - }); + context('when ignoreUndefined is true', function () { + context('when including an update with all undefined atomic operators', function () { + context('when performing an update many', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); - it('throws an error', async function () { - const error = await client - .bulkWrite([ - { - name: 'updateMany', - namespace: 'foo.bar', - filter: { age: { $lte: 5 } }, - update: { $set: undefined, $unset: undefined } - } - ]) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + it('throws an error', async function () { + const error = await client + .bulkWrite( + [ + { + name: 'updateMany', + namespace: 'foo.bar', + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + ], + { ignoreUndefined: true } + ) + .catch(error => error); + expect(error.message).to.include( + 'All atomic operators provided have undefined values.' + ); + }); }); - }); - context('when performing an update one', function () { - beforeEach(async function () { - client = this.configuration.newClient(); - }); + context('when performing an update one', function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); - it('throws an error', async function () { - const error = await client - .bulkWrite([ - { - name: 'updateOne', - namespace: 'foo.bar', - filter: { age: { $lte: 5 } }, - update: { $set: undefined, $unset: undefined } - } - ]) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + it('throws an error', async function () { + const error = await client + .bulkWrite( + [ + { + name: 'updateOne', + namespace: 'foo.bar', + filter: { age: { $lte: 5 } }, + update: { $set: undefined, $unset: undefined } + } + ], + { ignoreUndefined: true } + ) + .catch(error => error); + expect(error.message).to.include( + 'All atomic operators provided have undefined values.' + ); + }); }); }); }); diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 9f979a5019a..463ebb76119 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -15,8 +15,6 @@ import { ReturnDocument } from '../../mongodb'; import { type FailPoint } from '../../tools/utils'; -import { assert as test } from '../shared'; - // instanceof cannot be use reliably to detect the new models in js due to scoping and new // contexts killing class info find/distinct/count thus cannot be overloaded without breaking // backwards compatibility in a fundamental way @@ -908,18 +906,21 @@ describe('CRUD API', function () { await collection.drop(); }); - context('when including an update with all undefined atomic operators', function () { - beforeEach(async function () { - client = this.configuration.newClient(); - }); + context( + 'when including an update with all undefined atomic operators ignoring undefined', + function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); - it('throws an error', async function () { - const error = await collection - .updateOne({ a: 1 }, { $set: undefined, $unset: undefined }) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); - }); - }); + it('throws an error', async function () { + const error = await collection + .updateOne({ a: 1 }, { $set: undefined, $unset: undefined }, { ignoreUndefined: true }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + } + ); }); describe('#updateMany', function () { @@ -934,18 +935,21 @@ describe('CRUD API', function () { await collection.drop(); }); - context('when including an update with all undefined atomic operators', function () { - beforeEach(async function () { - client = this.configuration.newClient(); - }); + context( + 'when including an update with all undefined atomic operators ignoring undefined', + function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); - it('throws an error', async function () { - const error = await collection - .updateMany({ a: 1 }, { $set: undefined, $unset: undefined }) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); - }); - }); + it('throws an error', async function () { + const error = await collection + .updateMany({ a: 1 }, { $set: undefined, $unset: undefined }, { ignoreUndefined: true }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + } + ); }); describe('#findOneAndUpdate', function () { @@ -960,18 +964,21 @@ describe('CRUD API', function () { await collection.drop(); }); - context('when including an update with all undefined atomic operators', function () { - beforeEach(async function () { - client = this.configuration.newClient(); - }); + context( + 'when including an update with all undefined atomic operators ignoring undefined', + function () { + beforeEach(async function () { + client = this.configuration.newClient(); + }); - it('throws an error', async function () { - const error = await collection - .findOneAndUpdate({ a: 1 }, { $set: undefined, $unset: undefined }) - .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); - }); - }); + it('throws an error', async function () { + const error = await collection + .findOneAndUpdate({ a: 1 }, { $set: undefined, $unset: undefined }) + .catch(error => error); + expect(error.message).to.include('All atomic operators provided have undefined values.'); + }); + } + ); context('when includeResultMetadata is true', function () { beforeEach(async function () { From 8b406731fd29945ded343790c92a33c8fa0a7624 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:20:26 +0200 Subject: [PATCH 4/9] fix: import --- test/integration/crud/crud_api.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 463ebb76119..73728b9bbb0 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -15,6 +15,7 @@ import { ReturnDocument } from '../../mongodb'; import { type FailPoint } from '../../tools/utils'; +import { assert as test } from '../shared'; // instanceof cannot be use reliably to detect the new models in js due to scoping and new // contexts killing class info find/distinct/count thus cannot be overloaded without breaking // backwards compatibility in a fundamental way From 49ce914756fcdabd745342ad0acf0ec705904755 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:24:52 +0200 Subject: [PATCH 5/9] fix: dont drop nonexistant collections --- test/integration/crud/crud_api.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 73728b9bbb0..37c86c3b39d 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -903,10 +903,6 @@ describe('CRUD API', function () { collection = client.db().collection('updateOneTest'); }); - afterEach(async function () { - await collection.drop(); - }); - context( 'when including an update with all undefined atomic operators ignoring undefined', function () { @@ -932,10 +928,6 @@ describe('CRUD API', function () { collection = client.db().collection('updateManyTest'); }); - afterEach(async function () { - await collection.drop(); - }); - context( 'when including an update with all undefined atomic operators ignoring undefined', function () { @@ -961,10 +953,6 @@ describe('CRUD API', function () { collection = client.db().collection('findAndModifyTest'); }); - afterEach(async function () { - await collection.drop(); - }); - context( 'when including an update with all undefined atomic operators ignoring undefined', function () { From 425f2ff97c757bf548062b45abb78df7c6194207 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:39:15 +0200 Subject: [PATCH 6/9] fix: leaks --- test/integration/crud/crud_api.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 37c86c3b39d..f05acb2259c 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -899,7 +899,6 @@ describe('CRUD API', function () { let collection; beforeEach(async function () { - await client.connect(); collection = client.db().collection('updateOneTest'); }); @@ -924,7 +923,6 @@ describe('CRUD API', function () { let collection; beforeEach(async function () { - await client.connect(); collection = client.db().collection('updateManyTest'); }); @@ -949,7 +947,6 @@ describe('CRUD API', function () { let collection; beforeEach(async function () { - await client.connect(); collection = client.db().collection('findAndModifyTest'); }); From 562b3cec50c71d9cabd7d841dcae590e3929d3c3 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:39:58 +0200 Subject: [PATCH 7/9] chore: add option --- test/integration/crud/crud_api.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index f05acb2259c..2257b5455c0 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -959,7 +959,11 @@ describe('CRUD API', function () { it('throws an error', async function () { const error = await collection - .findOneAndUpdate({ a: 1 }, { $set: undefined, $unset: undefined }) + .findOneAndUpdate( + { a: 1 }, + { $set: undefined, $unset: undefined }, + { ignoreUndefined: true } + ) .catch(error => error); expect(error.message).to.include('All atomic operators provided have undefined values.'); }); From 8d2d8abe0d88ab6bccee2d5a85367fe19394cfa3 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 19:48:53 +0200 Subject: [PATCH 8/9] fix: fail command --- test/integration/crud/client_bulk_write.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/crud/client_bulk_write.test.ts b/test/integration/crud/client_bulk_write.test.ts index 686ebf9f895..aceababd869 100644 --- a/test/integration/crud/client_bulk_write.test.ts +++ b/test/integration/crud/client_bulk_write.test.ts @@ -31,7 +31,7 @@ describe('Client Bulk Write', function () { afterEach(async function () { await client?.close(); - await clearFailPoint(this.configuration); + await clearFailPoint(this.configuration).catch(() => null); }); describe('#bulkWrite', function () { From a13d1bab38e48c7806bcabff377e535ac311aba0 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Thu, 24 Apr 2025 21:15:13 +0200 Subject: [PATCH 9/9] fix: error message --- src/utils.ts | 4 +++- test/integration/crud/bulk.test.ts | 4 ++-- test/integration/crud/client_bulk_write.test.ts | 4 ++-- test/integration/crud/crud_api.test.ts | 12 +++++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index cd55b91b2f0..09e86b7349c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -501,7 +501,9 @@ export function hasAtomicOperators( } } if (allUndefined) { - throw new MongoInvalidArgumentError('All atomic operators provided have undefined values.'); + throw new MongoInvalidArgumentError( + 'Update operations require that all atomic operators have defined values, but none were provided.' + ); } } diff --git a/test/integration/crud/bulk.test.ts b/test/integration/crud/bulk.test.ts index 408397b4c49..dfeb45ce62e 100644 --- a/test/integration/crud/bulk.test.ts +++ b/test/integration/crud/bulk.test.ts @@ -66,7 +66,7 @@ describe('Bulk', function () { ) .catch(error => error); expect(error.message).to.include( - 'All atomic operators provided have undefined values.' + 'Update operations require that all atomic operators have defined values, but none were provided' ); }); }); @@ -88,7 +88,7 @@ describe('Bulk', function () { ) .catch(error => error); expect(error.message).to.include( - 'All atomic operators provided have undefined values.' + 'Update operations require that all atomic operators have defined values, but none were provided' ); }); }); diff --git a/test/integration/crud/client_bulk_write.test.ts b/test/integration/crud/client_bulk_write.test.ts index aceababd869..c2a626fe9d9 100644 --- a/test/integration/crud/client_bulk_write.test.ts +++ b/test/integration/crud/client_bulk_write.test.ts @@ -57,7 +57,7 @@ describe('Client Bulk Write', function () { ) .catch(error => error); expect(error.message).to.include( - 'All atomic operators provided have undefined values.' + 'Update operations require that all atomic operators have defined values, but none were provided' ); }); }); @@ -82,7 +82,7 @@ describe('Client Bulk Write', function () { ) .catch(error => error); expect(error.message).to.include( - 'All atomic operators provided have undefined values.' + 'Update operations require that all atomic operators have defined values, but none were provided' ); }); }); diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 2257b5455c0..5ad2aa41086 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -913,7 +913,9 @@ describe('CRUD API', function () { const error = await collection .updateOne({ a: 1 }, { $set: undefined, $unset: undefined }, { ignoreUndefined: true }) .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + expect(error.message).to.include( + 'Update operations require that all atomic operators have defined values, but none were provided' + ); }); } ); @@ -937,7 +939,9 @@ describe('CRUD API', function () { const error = await collection .updateMany({ a: 1 }, { $set: undefined, $unset: undefined }, { ignoreUndefined: true }) .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + expect(error.message).to.include( + 'Update operations require that all atomic operators have defined values, but none were provided' + ); }); } ); @@ -965,7 +969,9 @@ describe('CRUD API', function () { { ignoreUndefined: true } ) .catch(error => error); - expect(error.message).to.include('All atomic operators provided have undefined values.'); + expect(error.message).to.include( + 'Update operations require that all atomic operators have defined values, but none were provided' + ); }); } );