Skip to content

Commit

Permalink
Merge pull request #12824 from Automattic/vkarpov15/gh-12621
Browse files Browse the repository at this point in the history
fix(model): respect discriminators with `Model.validate()`
  • Loading branch information
vkarpov15 committed Dec 26, 2022
2 parents 2bf15d5 + f140bf2 commit dc1d82f
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 114 deletions.
7 changes: 6 additions & 1 deletion lib/model.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand Down
113 changes: 0 additions & 113 deletions test/model.test.js
Expand Up @@ -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 = [];
Expand Down
147 changes: 147 additions & 0 deletions 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);
});
});

0 comments on commit dc1d82f

Please sign in to comment.