Skip to content

Commit ce4c3d5

Browse files
Fix: Add strictFilter option to findOneAndUpdate with tests (Automattic#14913)
1 parent 65b2d12 commit ce4c3d5

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

lib/query.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3311,6 +3311,7 @@ function prepareDiscriminatorCriteria(query) {
33113311
* - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
33123312
* - `runValidators`: if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
33133313
* - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
3314+
* - `strictFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
33143315
*
33153316
* #### Example:
33163317
*
@@ -3337,6 +3338,7 @@ function prepareDiscriminatorCriteria(query) {
33373338
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
33383339
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
33393340
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
3341+
* @param {Boolean} [options.strictFilter=false] If true, throws an error if the filter is empty (`{}`)
33403342
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
33413343
* @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
33423344
* @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
@@ -3353,6 +3355,11 @@ Query.prototype.findOneAndUpdate = function(filter, doc, options) {
33533355
throw new MongooseError('Query.prototype.findOneAndUpdate() no longer accepts a callback');
33543356
}
33553357

3358+
// Check for empty filter with strictFilter option
3359+
if (options && options.strictFilter && filter && Object.keys(filter).length === 0) {
3360+
return Promise.reject(new Error('Empty filter not allowed in findOneAndUpdate with strictFilter enabled'));
3361+
}
3362+
33563363
this.op = 'findOneAndUpdate';
33573364
this._validateOp();
33583365
this._validate();

test/query.test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4455,4 +4455,58 @@ describe('Query', function() {
44554455
assert.strictEqual(target, null);
44564456
});
44574457
});
4458+
4459+
describe('findOneAndUpdate with strictFilter', function() {
4460+
let Person;
4461+
let _id;
4462+
4463+
beforeEach(async function() {
4464+
try {
4465+
const schema = new Schema({ name: String });
4466+
Person = db.model('Person', schema);
4467+
await Person.deleteMany({})
4468+
const person = await Person.create({ name: 'Alice' });
4469+
_id = person._id;
4470+
} catch (err) {
4471+
throw err;
4472+
}
4473+
});
4474+
4475+
it('throws error for empty filter when strictFilter is true', async function() {
4476+
await assert.rejects(
4477+
() => Person.findOneAndUpdate({}, { name: 'Updated' }, { strictFilter: true }),
4478+
/Empty filter not allowed in findOneAndUpdate with strictFilter enabled/
4479+
);
4480+
});
4481+
4482+
4483+
it('updates first document when strictFilter is false', async function() {
4484+
const updated = await Person.findOneAndUpdate({}, { name: 'Updated' }, { new: true });
4485+
assert.strictEqual(updated.name, 'Updated');
4486+
const person = await Person.findById(_id);
4487+
assert.strictEqual(person.name, 'Updated');
4488+
});
4489+
4490+
it('updates with non-empty filter when strictFilter is true', async function() {
4491+
const updated = await Person.findOneAndUpdate(
4492+
{ _id },
4493+
{ name: 'Updated Alice' },
4494+
{ strictFilter: true, new: true }
4495+
);
4496+
assert.strictEqual(updated.name, 'Updated Alice');
4497+
const person = await Person.findById(_id);
4498+
assert.strictEqual(person.name, 'Updated Alice');
4499+
});
4500+
4501+
it('handles undefined filter with strictFilter true', async function() {
4502+
const updated = await Person.findOneAndUpdate(
4503+
undefined,
4504+
{ name: 'Updated' },
4505+
{ strictFilter: true, new: true }
4506+
);
4507+
assert.strictEqual(updated.name, 'Updated');
4508+
const person = await Person.findById(_id);
4509+
assert.strictEqual(person.name, 'Updated');
4510+
});
4511+
});
44584512
});

0 commit comments

Comments
 (0)