Last active
October 1, 2021 19:37
-
-
Save Corky3892/2452203657e5a31423d990faddf4ebad to your computer and use it in GitHub Desktop.
Trying to pass the runValidators to the mongoose findOneAndUpdate() method does produce great results (as of 6.0.5). Here is a generic solution to the problem. PS: If any mongoose / typescript Guru's out there know how to make this truly generic (where type T can be inferred and not assigned) please do let me know.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FilterQuery, UpdateQuery, QueryOptions, Model, model } from 'mongoose'; | |
/** | |
* As of mongoose 6.0.5 findOneAndUpdate does not handle the runValidators flag correctly. | |
* If you are running upsert in combination with this flag then the update will not get validated correctly. | |
* This is will fix that problem. | |
* @param filter The query which will be used to find the document. | |
* @param update The update which shall be applied. | |
* @param options The optional Query Options to extend the behavior. | |
*/ | |
function findOneAndUpdateWithValidation<T>( | |
filter: FilterQuery<T>, | |
update: UpdateQuery<T>, | |
options?: QueryOptions | |
) { | |
const model: Model<T> = this; | |
const newDoc = new model(update); | |
if (options && options.runValidators) { | |
const error = newDoc.validateSync(); | |
if (error) throw error; | |
} | |
return model.findOneAndUpdate(filter, update, options); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @jest-environment node | |
*/ | |
import 'jest-extended'; | |
import * as mongoose from 'mongoose'; | |
import { Database } from '../../../global'; | |
import { findOneAndUpdateWithValidation } from './findOneAndUpdateWithValidation'; | |
interface Person { | |
firstName: string; | |
lastName: string; | |
age: number; | |
} | |
type PersonDoc = Person & mongoose.Document | |
interface PersonModel extends mongoose.Model<Person> { | |
findOneAndUpdateWithValidation: typeof findOneAndUpdateWithValidation | |
} | |
const PersonSchema = new mongoose.Schema({ | |
firstName: { type: String, required: true }, | |
lastName: { type: String, required: true }, | |
age: { type: Number, required: true }, | |
}) | |
PersonSchema.statics = { | |
findOneAndUpdateWithValidation, | |
} | |
const PersonModel = mongoose.model<PersonDoc, PersonModel>('person', PersonSchema); | |
describe('update validation tests', () => { | |
// References mongoose.connect() | |
new Database(); | |
// If this test passed then it demonstrates that run validators does not work when inserting. | |
// Given that the age param is missing we expect an error, if you try however you will see the test passes. | |
test('findOneAndUpdate - runValidators does not work on upsert', async () => { | |
const rob = { | |
firstName: 'Rob', | |
lastName: 'Smith', | |
} | |
await expect(PersonModel.findOneAndUpdate({firstName: 'Rob'}, rob, { | |
runValidators: true, | |
upsert: true, | |
new: true | |
})).resolves.toMatchObject({ | |
firstName: 'Rob', | |
lastName: 'Smith' | |
}) | |
}) | |
// If this test passed then it demonstrates that run validators does not work when updating. | |
// Given that the age param is missing we expect an error, if you try however you will see the test passes. | |
test('findOneAndUpdate - runValidators does not work on edit', async () => { | |
const rob = { | |
firstName: 'Rob', | |
lastName: 'Rogers', | |
} | |
await expect(PersonModel.findOneAndUpdate({firstName: 'Rob'}, rob, { | |
runValidators: true, | |
new: true | |
})).resolves.toMatchObject({ | |
firstName: 'Rob', | |
lastName: 'Rogers' | |
}) | |
}) | |
// Test the custom method to see if validation works. | |
// The request now correctly returns an error. | |
test('findOneAndUpdateWithValidation - run Validators now gets processed', async () => { | |
const jason = { | |
firstName: 'Jason', | |
lastName: 'Jones', | |
} | |
expect(async () => await PersonModel.findOneAndUpdateWithValidation<PersonDoc>({firstName: 'Jason'}, jason, { | |
runValidators: true, | |
upsert: true, | |
new: true | |
})).rejects.toThrow(); | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment