Skip to content

Commit dc1d82f

Browse files
authoredDec 26, 2022
Merge pull request #12824 from Automattic/vkarpov15/gh-12621
fix(model): respect discriminators with `Model.validate()`
2 parents 2bf15d5 + f140bf2 commit dc1d82f

File tree

3 files changed

+153
-114
lines changed

3 files changed

+153
-114
lines changed
 

‎lib/model.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const assignVals = require('./helpers/populate/assignVals');
3636
const castBulkWrite = require('./helpers/model/castBulkWrite');
3737
const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
3838
const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
39+
const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
3940
const discriminator = require('./helpers/model/discriminator');
4041
const firstKey = require('./helpers/firstKey');
4142
const each = require('./helpers/each');
@@ -4474,7 +4475,11 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
44744475
}
44754476

44764477
return this.db.base._promiseOrCallback(callback, cb => {
4477-
const schema = this.schema;
4478+
let schema = this.schema;
4479+
const discriminatorKey = schema.options.discriminatorKey;
4480+
if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
4481+
schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
4482+
}
44784483
let paths = Object.keys(schema.paths);
44794484

44804485
if (pathsToValidate != null) {

‎test/model.test.js

-113
Original file line numberDiff line numberDiff line change
@@ -7458,119 +7458,6 @@ describe('Model', function() {
74587458
});
74597459
});
74607460

7461-
it('Model.validate() (gh-7587)', async function() {
7462-
const Model = db.model('Test', new Schema({
7463-
name: {
7464-
first: {
7465-
type: String,
7466-
required: true
7467-
},
7468-
last: {
7469-
type: String,
7470-
required: true
7471-
}
7472-
},
7473-
age: {
7474-
type: Number,
7475-
required: true
7476-
},
7477-
comments: [{ name: { type: String, required: true } }]
7478-
}));
7479-
7480-
7481-
let err = null;
7482-
let obj = null;
7483-
7484-
err = await Model.validate({ age: null }, ['age']).
7485-
then(() => null, err => err);
7486-
assert.ok(err);
7487-
assert.deepEqual(Object.keys(err.errors), ['age']);
7488-
7489-
err = await Model.validate({ name: {} }, ['name']).
7490-
then(() => null, err => err);
7491-
assert.ok(err);
7492-
assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']);
7493-
7494-
obj = { name: { first: 'foo' } };
7495-
err = await Model.validate(obj, ['name']).
7496-
then(() => null, err => err);
7497-
assert.ok(err);
7498-
assert.deepEqual(Object.keys(err.errors), ['name.last']);
7499-
7500-
obj = { comments: [{ name: 'test' }, {}] };
7501-
err = await Model.validate(obj, ['comments']).
7502-
then(() => null, err => err);
7503-
assert.ok(err);
7504-
assert.deepEqual(Object.keys(err.errors), ['comments.name']);
7505-
7506-
obj = { age: '42' };
7507-
await Model.validate(obj, ['age']);
7508-
assert.strictEqual(obj.age, 42);
7509-
});
7510-
7511-
it('Model.validate(...) validates paths in arrays (gh-8821)', async function() {
7512-
const userSchema = new Schema({
7513-
friends: [{ type: String, required: true, minlength: 3 }]
7514-
});
7515-
7516-
const User = db.model('User', userSchema);
7517-
7518-
const err = await User.validate({ friends: [null, 'A'] }).catch(err => err);
7519-
7520-
assert.ok(err.errors['friends.0']);
7521-
assert.ok(err.errors['friends.1']);
7522-
7523-
});
7524-
7525-
it('Model.validate() works with arrays (gh-10669)', async function() {
7526-
const testSchema = new Schema({
7527-
docs: [String]
7528-
});
7529-
7530-
const Test = db.model('Test', testSchema);
7531-
7532-
const test = { docs: ['6132655f2cdb9d94eaebc09b'] };
7533-
7534-
const err = await Test.validate(test);
7535-
assert.ifError(err);
7536-
});
7537-
7538-
it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() {
7539-
const userSchema = new Schema({
7540-
name: {
7541-
type: String,
7542-
required: function() {
7543-
return this.nameRequired;
7544-
}
7545-
},
7546-
nameRequired: Boolean
7547-
});
7548-
7549-
const User = db.model('User', userSchema);
7550-
7551-
const user = new User({ name: 'test', nameRequired: false });
7552-
const err = await User.validate(user).catch(err => err);
7553-
7554-
assert.ifError(err);
7555-
7556-
});
7557-
it('Model.validate(...) uses object as context by default (gh-10346)', async() => {
7558-
7559-
const userSchema = new mongoose.Schema({
7560-
name: { type: String, required: true },
7561-
age: { type: Number, required() {return this && this.name === 'John';} }
7562-
});
7563-
7564-
const User = db.model('User', userSchema);
7565-
7566-
const err1 = await User.validate({ name: 'John' }).then(() => null, err => err);
7567-
assert.ok(err1);
7568-
7569-
const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err);
7570-
assert.ok(err2 === null);
7571-
7572-
});
7573-
75747461
it('sets correct `Document#op` with `save()` (gh-8439)', function() {
75757462
const schema = Schema({ name: String });
75767463
const ops = [];

‎test/model.validate.test.js

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
'use strict';
2+
3+
const start = require('./common');
4+
5+
const assert = require('assert');
6+
7+
const mongoose = start.mongoose;
8+
const Schema = mongoose.Schema;
9+
10+
describe('model: validate: ', function() {
11+
beforeEach(() => mongoose.deleteModel(/.*/));
12+
after(() => mongoose.deleteModel(/.*/));
13+
14+
it('Model.validate() (gh-7587)', async function() {
15+
const Model = mongoose.model('Test', new Schema({
16+
name: {
17+
first: {
18+
type: String,
19+
required: true
20+
},
21+
last: {
22+
type: String,
23+
required: true
24+
}
25+
},
26+
age: {
27+
type: Number,
28+
required: true
29+
},
30+
comments: [{ name: { type: String, required: true } }]
31+
}));
32+
33+
34+
let err = null;
35+
let obj = null;
36+
37+
err = await Model.validate({ age: null }, ['age']).
38+
then(() => null, err => err);
39+
assert.ok(err);
40+
assert.deepEqual(Object.keys(err.errors), ['age']);
41+
42+
err = await Model.validate({ name: {} }, ['name']).
43+
then(() => null, err => err);
44+
assert.ok(err);
45+
assert.deepEqual(Object.keys(err.errors), ['name.first', 'name.last']);
46+
47+
obj = { name: { first: 'foo' } };
48+
err = await Model.validate(obj, ['name']).
49+
then(() => null, err => err);
50+
assert.ok(err);
51+
assert.deepEqual(Object.keys(err.errors), ['name.last']);
52+
53+
obj = { comments: [{ name: 'test' }, {}] };
54+
err = await Model.validate(obj, ['comments']).
55+
then(() => null, err => err);
56+
assert.ok(err);
57+
assert.deepEqual(Object.keys(err.errors), ['comments.name']);
58+
59+
obj = { age: '42' };
60+
await Model.validate(obj, ['age']);
61+
assert.strictEqual(obj.age, 42);
62+
});
63+
64+
it('Model.validate(...) validates paths in arrays (gh-8821)', async function() {
65+
const userSchema = new Schema({
66+
friends: [{ type: String, required: true, minlength: 3 }]
67+
});
68+
69+
const User = mongoose.model('User', userSchema);
70+
71+
const err = await User.validate({ friends: [null, 'A'] }).catch(err => err);
72+
73+
assert.ok(err.errors['friends.0']);
74+
assert.ok(err.errors['friends.1']);
75+
});
76+
77+
it('Model.validate(...) respects discriminators (gh-12621)', async function() {
78+
const CatSchema = new Schema({ meows: { type: Boolean, required: true } });
79+
const DogSchema = new Schema({ barks: { type: Boolean, required: true } });
80+
const AnimalSchema = new Schema(
81+
{ id: String },
82+
{ discriminatorKey: 'kind' }
83+
);
84+
AnimalSchema.discriminator('cat', CatSchema);
85+
AnimalSchema.discriminator('dog', DogSchema);
86+
87+
const Animal = mongoose.model('Test', AnimalSchema);
88+
89+
const invalidPet1 = new Animal({
90+
id: '123',
91+
kind: 'dog',
92+
meows: true
93+
});
94+
95+
const err = await Animal.validate(invalidPet1).then(() => null, err => err);
96+
assert.ok(err);
97+
assert.ok(err.errors['barks']);
98+
});
99+
100+
it('Model.validate() works with arrays (gh-10669)', async function() {
101+
const testSchema = new Schema({
102+
docs: [String]
103+
});
104+
105+
const Test = mongoose.model('Test', testSchema);
106+
107+
const test = { docs: ['6132655f2cdb9d94eaebc09b'] };
108+
109+
const err = await Test.validate(test);
110+
assert.ifError(err);
111+
});
112+
113+
it('Model.validate(...) uses document instance as context by default (gh-10132)', async function() {
114+
const userSchema = new Schema({
115+
name: {
116+
type: String,
117+
required: function() {
118+
return this.nameRequired;
119+
}
120+
},
121+
nameRequired: Boolean
122+
});
123+
124+
const User = mongoose.model('User', userSchema);
125+
126+
const user = new User({ name: 'test', nameRequired: false });
127+
const err = await User.validate(user).catch(err => err);
128+
129+
assert.ifError(err);
130+
131+
});
132+
it('Model.validate(...) uses object as context by default (gh-10346)', async() => {
133+
134+
const userSchema = new mongoose.Schema({
135+
name: { type: String, required: true },
136+
age: { type: Number, required() {return this && this.name === 'John';} }
137+
});
138+
139+
const User = mongoose.model('User', userSchema);
140+
141+
const err1 = await User.validate({ name: 'John' }).then(() => null, err => err);
142+
assert.ok(err1);
143+
144+
const err2 = await User.validate({ name: 'Sam' }).then(() => null, err => err);
145+
assert.ok(err2 === null);
146+
});
147+
});

0 commit comments

Comments
 (0)
Please sign in to comment.