diff --git a/lib/model.js b/lib/model.js index 51d33bf5a41..a146eea1ef0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -36,6 +36,7 @@ const assignVals = require('./helpers/populate/assignVals'); const castBulkWrite = require('./helpers/model/castBulkWrite'); const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter'); const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult'); +const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue'); const discriminator = require('./helpers/model/discriminator'); const firstKey = require('./helpers/firstKey'); const each = require('./helpers/each'); @@ -4474,7 +4475,11 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) { } return this.db.base._promiseOrCallback(callback, cb => { - const schema = this.schema; + let schema = this.schema; + const discriminatorKey = schema.options.discriminatorKey; + if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) { + schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema; + } let paths = Object.keys(schema.paths); if (pathsToValidate != null) { diff --git a/test/model.test.js b/test/model.test.js index 5728e286114..069a5f64502 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -7458,119 +7458,6 @@ describe('Model', function() { }); }); - it('Model.validate() (gh-7587)', async function() { - const Model = db.model('Test', new Schema({ - name: { - first: { - type: String, - required: true - }, - last: { - type: String, - required: true - } - }, - age: { - type: Number, - required: true - }, - comments: [{ name: { type: String, required: true } }] - })); - - - let err = null; - let obj = null; - - err = await Model.validate({ age: null }, ['age']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['age']); - - err = await Model.validate({ name: {} }, ['name']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']); - - obj = { name: { first: 'foo' } }; - err = await Model.validate(obj, ['name']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['name.last']); - - obj = { comments: [{ name: 'test' }, {}] }; - err = await Model.validate(obj, ['comments']). - then(() => null, err => err); - assert.ok(err); - assert.deepEqual(Object.keys(err.errors), ['comments.name']); - - obj = { age: '42' }; - await Model.validate(obj, ['age']); - assert.strictEqual(obj.age, 42); - }); - - it('Model.validate(...) validates paths in arrays (gh-8821)', async function() { - const userSchema = new Schema({ - friends: [{ type: String, required: true, minlength: 3 }] - }); - - const User = db.model('User', userSchema); - - const err = await User.validate({ friends: [null, 'A'] }).catch(err => err); - - assert.ok(err.errors['friends.0']); - assert.ok(err.errors['friends.1']); - - }); - - it('Model.validate() works with arrays (gh-10669)', async function() { - const testSchema = new Schema({ - docs: [String] - }); - - const Test = db.model('Test', testSchema); - - const test = { docs: ['6132655f2cdb9d94eaebc09b'] }; - - const err = await Test.validate(test); - assert.ifError(err); - }); - - it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() { - const userSchema = new Schema({ - name: { - type: String, - required: function() { - return this.nameRequired; - } - }, - nameRequired: Boolean - }); - - const User = db.model('User', userSchema); - - const user = new User({ name: 'test', nameRequired: false }); - const err = await User.validate(user).catch(err => err); - - assert.ifError(err); - - }); - it('Model.validate(...) uses object as context by default (gh-10346)', async() => { - - const userSchema = new mongoose.Schema({ - name: { type: String, required: true }, - age: { type: Number, required() {return this && this.name === 'John';} } - }); - - const User = db.model('User', userSchema); - - const err1 = await User.validate({ name: 'John' }).then(() => null, err => err); - assert.ok(err1); - - const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err); - assert.ok(err2 === null); - - }); - it('sets correct `Document#op` with `save()` (gh-8439)', function() { const schema = Schema({ name: String }); const ops = []; diff --git a/test/model.validate.test.js b/test/model.validate.test.js new file mode 100644 index 00000000000..30891867ff3 --- /dev/null +++ b/test/model.validate.test.js @@ -0,0 +1,147 @@ +'use strict'; + +const start = require('./common'); + +const assert = require('assert'); + +const mongoose = start.mongoose; +const Schema = mongoose.Schema; + +describe('model: validate: ', function() { + beforeEach(() => mongoose.deleteModel(/.*/)); + after(() => mongoose.deleteModel(/.*/)); + + it('Model.validate() (gh-7587)', async function() { + const Model = mongoose.model('Test', new Schema({ + name: { + first: { + type: String, + required: true + }, + last: { + type: String, + required: true + } + }, + age: { + type: Number, + required: true + }, + comments: [{ name: { type: String, required: true } }] + })); + + + let err = null; + let obj = null; + + err = await Model.validate({ age: null }, ['age']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['age']); + + err = await Model.validate({ name: {} }, ['name']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']); + + obj = { name: { first: 'foo' } }; + err = await Model.validate(obj, ['name']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['name.last']); + + obj = { comments: [{ name: 'test' }, {}] }; + err = await Model.validate(obj, ['comments']). + then(() => null, err => err); + assert.ok(err); + assert.deepEqual(Object.keys(err.errors), ['comments.name']); + + obj = { age: '42' }; + await Model.validate(obj, ['age']); + assert.strictEqual(obj.age, 42); + }); + + it('Model.validate(...) validates paths in arrays (gh-8821)', async function() { + const userSchema = new Schema({ + friends: [{ type: String, required: true, minlength: 3 }] + }); + + const User = mongoose.model('User', userSchema); + + const err = await User.validate({ friends: [null, 'A'] }).catch(err => err); + + assert.ok(err.errors['friends.0']); + assert.ok(err.errors['friends.1']); + }); + + it('Model.validate(...) respects discriminators (gh-12621)', async function() { + const CatSchema = new Schema({ meows: { type: Boolean, required: true } }); + const DogSchema = new Schema({ barks: { type: Boolean, required: true } }); + const AnimalSchema = new Schema( + { id: String }, + { discriminatorKey: 'kind' } + ); + AnimalSchema.discriminator('cat', CatSchema); + AnimalSchema.discriminator('dog', DogSchema); + + const Animal = mongoose.model('Test', AnimalSchema); + + const invalidPet1 = new Animal({ + id: '123', + kind: 'dog', + meows: true + }); + + const err = await Animal.validate(invalidPet1).then(() => null, err => err); + assert.ok(err); + assert.ok(err.errors['barks']); + }); + + it('Model.validate() works with arrays (gh-10669)', async function() { + const testSchema = new Schema({ + docs: [String] + }); + + const Test = mongoose.model('Test', testSchema); + + const test = { docs: ['6132655f2cdb9d94eaebc09b'] }; + + const err = await Test.validate(test); + assert.ifError(err); + }); + + it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() { + const userSchema = new Schema({ + name: { + type: String, + required: function() { + return this.nameRequired; + } + }, + nameRequired: Boolean + }); + + const User = mongoose.model('User', userSchema); + + const user = new User({ name: 'test', nameRequired: false }); + const err = await User.validate(user).catch(err => err); + + assert.ifError(err); + + }); + it('Model.validate(...) uses object as context by default (gh-10346)', async() => { + + const userSchema = new mongoose.Schema({ + name: { type: String, required: true }, + age: { type: Number, required() {return this && this.name === 'John';} } + }); + + const User = mongoose.model('User', userSchema); + + const err1 = await User.validate({ name: 'John' }).then(() => null, err => err); + assert.ok(err1); + + const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err); + assert.ok(err2 === null); + }); +});