UNPKG

51 kBJavaScriptView Raw
1// Copyright IBM Corp. 2013,2019. All Rights Reserved.
2// Node module: loopback-datasource-juggler
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6// This test written in mocha+should.js
7'use strict';
8
9/* global getSchema:false */
10const should = require('./init.js');
11const async = require('async');
12
13const j = require('../');
14let db, User;
15const ValidationError = j.ValidationError;
16
17function getValidAttributes() {
18 return {
19 name: 'Anatoliy',
20 email: 'email@example.com',
21 state: '',
22 age: 26,
23 gender: 'male',
24 createdByAdmin: false,
25 createdByScript: true,
26 };
27}
28
29describe('validations', function() {
30 let User, Entry, Employee;
31
32 before(function(done) {
33 db = getSchema();
34 User = db.define('User', {
35 email: String,
36 name: String,
37 password: String,
38 state: String,
39 age: Number,
40 gender: String,
41 domain: String,
42 pendingPeriod: Number,
43 createdByAdmin: Boolean,
44 createdByScript: Boolean,
45 updatedAt: Date,
46 });
47 Entry = db.define('Entry', {
48 id: {type: 'string', id: true, generated: false},
49 name: {type: 'string'},
50 });
51 Employee = db.define('Employee', {
52 id: {type: Number, id: true, generated: false},
53 name: {type: String},
54 age: {type: Number},
55 }, {
56 validateUpdate: true,
57 });
58 Entry.validatesUniquenessOf('id');
59 db.automigrate(function(err) {
60 should.not.exist(err);
61 Employee.create(empData, done);
62 });
63 });
64
65 beforeEach(function(done) {
66 User.destroyAll(function() {
67 delete User.validations;
68 done();
69 });
70 });
71
72 after(function(done) {
73 Employee.destroyAll(done);
74 });
75
76 describe('commons', function() {
77 describe('skipping', function() {
78 it('should NOT skip when `if` is fulfilled', function() {
79 User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
80 const user = new User;
81 user.createdByAdmin = true;
82 user.isValid().should.be.false();
83 user.errors.pendingPeriod.should.eql(['can\'t be blank']);
84 user.pendingPeriod = 1;
85 user.isValid().should.be.true();
86 });
87
88 it('should skip when `if` is NOT fulfilled', function() {
89 User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
90 const user = new User;
91 user.createdByAdmin = false;
92 user.isValid().should.be.true();
93 user.errors.should.be.false();
94 user.pendingPeriod = 1;
95 user.isValid().should.be.true();
96 });
97
98 it('should NOT skip when `unless` is fulfilled', function() {
99 User.validatesPresenceOf('pendingPeriod', {unless: 'createdByAdmin'});
100 const user = new User;
101 user.createdByAdmin = false;
102 user.isValid().should.be.false();
103 user.errors.pendingPeriod.should.eql(['can\'t be blank']);
104 user.pendingPeriod = 1;
105 user.isValid().should.be.true();
106 });
107
108 it('should skip when `unless` is NOT fulfilled', function() {
109 User.validatesPresenceOf('pendingPeriod', {unless: 'createdByAdmin'});
110 const user = new User;
111 user.createdByAdmin = true;
112 user.isValid().should.be.true();
113 user.errors.should.be.false();
114 user.pendingPeriod = 1;
115 user.isValid().should.be.true();
116 });
117 });
118
119 describe('skipping in async validation', function() {
120 it('should skip when `if` is NOT fulfilled', function(done) {
121 User.validateAsync('pendingPeriod', function(err, done) {
122 if (!this.pendingPeriod) err();
123 done();
124 }, {if: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
125 const user = new User;
126 user.createdByAdmin = false;
127 user.isValid(function(valid) {
128 valid.should.be.true();
129 user.errors.should.be.false();
130 done();
131 });
132 });
133
134 it('should NOT skip when `if` is fulfilled', function(done) {
135 User.validateAsync('pendingPeriod', function(err, done) {
136 if (!this.pendingPeriod) err();
137 done();
138 }, {if: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
139 const user = new User;
140 user.createdByAdmin = true;
141 user.isValid(function(valid) {
142 valid.should.be.false();
143 user.errors.pendingPeriod.should.eql(['can\'t be blank']);
144 done();
145 });
146 });
147
148 it('should skip when `unless` is NOT fulfilled', function(done) {
149 User.validateAsync('pendingPeriod', function(err, done) {
150 if (!this.pendingPeriod) err();
151 done();
152 }, {unless: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
153 const user = new User;
154 user.createdByAdmin = true;
155 user.isValid(function(valid) {
156 valid.should.be.true();
157 user.errors.should.be.false();
158 done();
159 });
160 });
161
162 it('should NOT skip when `unless` is fulfilled', function(done) {
163 User.validateAsync('pendingPeriod', function(err, done) {
164 if (!this.pendingPeriod) err();
165 done();
166 }, {unless: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
167 const user = new User;
168 user.createdByAdmin = false;
169 user.isValid(function(valid) {
170 valid.should.be.false();
171 user.errors.pendingPeriod.should.eql(['can\'t be blank']);
172 done();
173 });
174 });
175 });
176
177 describe('lifecycle', function() {
178 it('should work on create', function(done) {
179 delete User.validations;
180 User.validatesPresenceOf('name');
181 User.create(function(e, u) {
182 should.exist(e);
183 User.create({name: 'Valid'}, function(e, d) {
184 should.not.exist(e);
185 done();
186 });
187 });
188 });
189
190 it('should work on update', function(done) {
191 delete User.validations;
192 User.validatesPresenceOf('name');
193 User.create({name: 'Valid'}, function(e, d) {
194 d.updateAttribute('name', null, function(e) {
195 should.exist(e);
196 e.should.be.instanceOf(Error);
197 e.should.be.instanceOf(ValidationError);
198 d.updateAttribute('name', 'Vasiliy', function(e) {
199 should.not.exist(e);
200 done();
201 });
202 });
203 });
204 });
205
206 it('should ignore errors on upsert by default', function(done) {
207 delete User.validations;
208 User.validatesPresenceOf('name');
209 // It's important to pass an existing id value to updateOrCreate,
210 // otherwise DAO falls back to regular create()
211 User.create({name: 'a-name'}, (err, u) => {
212 if (err) return done(err);
213 User.updateOrCreate({id: u.id}, done);
214 });
215 });
216
217 it('should be skipped by upsert when disabled via settings', function(done) {
218 const Customer = User.extend('Customer');
219 Customer.attachTo(db);
220 db.autoupdate(function(err) {
221 if (err) return done(err);
222 // It's important to pass an existing id value,
223 // otherwise DAO falls back to regular create()
224 Customer.create({name: 'a-name'}, (err, u) => {
225 if (err) return done(err);
226
227 Customer.prototype.isValid = function() {
228 throw new Error('isValid() should not be called at all');
229 };
230 Customer.settings.validateUpsert = false;
231
232 Customer.updateOrCreate({id: u.id, name: ''}, done);
233 });
234 });
235 });
236
237 it('should work on upsert when enabled via settings', function(done) {
238 User.validatesPresenceOf('name');
239 User.settings.validateUpsert = true;
240 // It's important to pass an existing id value,
241 // otherwise DAO falls back to regular create()
242 User.create({name: 'a-name'}, (err, u) => {
243 if (err) return done(err);
244 User.upsert({id: u.id, name: ''}, function(err, u) {
245 if (!err) return done(new Error('Validation should have failed.'));
246 err.should.be.instanceOf(ValidationError);
247 done();
248 });
249 });
250 });
251
252 it('should return error code', function(done) {
253 delete User.validations;
254 User.validatesPresenceOf('name');
255 User.create(function(e, u) {
256 should.exist(e);
257 e.details.codes.name.should.eql(['presence']);
258 done();
259 });
260 });
261
262 it('should allow to modify error after validation', function(done) {
263 User.afterValidate = function(next) {
264 next();
265 };
266 done();
267 });
268
269 it('should include validation messages in err.message', function(done) {
270 delete User.validations;
271 User.validatesPresenceOf('name');
272 User.create(function(e, u) {
273 should.exist(e);
274 e.message.should.match(/`name` can't be blank/);
275 done();
276 });
277 });
278
279 it('should include property value in err.message', function(done) {
280 delete User.validations;
281 User.validatesPresenceOf('name');
282 User.create(function(e, u) {
283 should.exist(e);
284 e.message.should.match(/`name` can't be blank \(value: undefined\)/);
285 done();
286 });
287 });
288
289 it('should include model name in err.message', function(done) {
290 delete User.validations;
291 User.validatesPresenceOf('name');
292 User.create(function(e, u) {
293 should.exist(e);
294 e.message.should.match(/`User` instance/i);
295 done();
296 });
297 });
298
299 it('should return validation metadata', function() {
300 const expected = {name: [{validation: 'presence', options: {}}]};
301 delete User.validations;
302 User.validatesPresenceOf('name');
303 const validations = User.validations;
304 validations.should.eql(expected);
305 });
306 });
307 });
308
309 describe('validation with or without options', function() {
310 it('should work on update with options', function(done) {
311 delete User.validations;
312 User.validatesPresenceOf('name');
313 User.create({name: 'Valid'}, function(e, d) {
314 d.updateAttribute('name', null, {options: 'options'}, function(e) {
315 should.exist(e);
316 e.should.be.instanceOf(Error);
317 e.should.be.instanceOf(ValidationError);
318 d.updateAttribute('name', 'Vasiliy', {options: 'options'}, err => {
319 if (err) return done(err);
320 // test passed
321 done();
322 });
323 });
324 });
325 });
326
327 it('passes options to custom sync validator', done => {
328 delete User.validations;
329 User.validate('name', function(err, options) {
330 if (options.testFlag !== 'someValue') err();
331 });
332 User.create({name: 'Valid'}, {testFlag: 'someValue'}, function(e, d) {
333 d.updateAttribute('name', null, {testFlag: 'otherValue'}, function(e) {
334 should.exist(e);
335 e.should.be.instanceOf(ValidationError);
336 d.updateAttribute('name', 'Vasiliy', {testFlag: 'someValue'}, err => {
337 if (err) return done(err);
338 // test passed
339 done();
340 });
341 });
342 });
343 });
344
345 it('passes options to async validator', done => {
346 delete User.validations;
347 User.validateAsync('name', function(err, options, done) {
348 if (options.testFlag !== 'someValue') {
349 console.error(
350 'Unexpected validation options: %j Expected %j',
351 options, {testFlag: 'someValue'},
352 );
353 err();
354 }
355 process.nextTick(function() { done(); });
356 });
357 User.create({name: 'Valid'}, {testFlag: 'someValue'}, function(e, d) {
358 if (e) return done(e);
359 d.updateAttribute('name', null, {testFlag: 'otherValue'}, function(e) {
360 should.exist(e);
361 e.should.be.instanceOf(ValidationError);
362 d.updateAttribute('name', 'Vasiliy', {testFlag: 'someValue'}, err => {
363 if (err) return done(err);
364 // test passed
365 done();
366 });
367 });
368 });
369 });
370
371 it('should work on update without options', function(done) {
372 delete User.validations;
373 User.validatesPresenceOf('name');
374 User.create({name: 'Valid'}, function(e, d) {
375 d.updateAttribute('name', null, function(e) {
376 should.exist(e);
377 e.should.be.instanceOf(Error);
378 e.should.be.instanceOf(ValidationError);
379 d.updateAttribute('name', 'Vasiliy', function(e) {
380 should.not.exist(e);
381 done();
382 });
383 });
384 });
385 });
386
387 it('should work on create with options', function(done) {
388 delete User.validations;
389 User.validatesPresenceOf('name');
390 User.create(function(e, u) {
391 should.exist(e);
392 User.create({name: 'Valid'}, {options: 'options'}, function(e, d) {
393 should.not.exist(e);
394 done();
395 });
396 });
397 });
398
399 it('should work on create without options', function(done) {
400 delete User.validations;
401 User.validatesPresenceOf('name');
402 User.create(function(e, u) {
403 should.exist(e);
404 User.create({name: 'Valid'}, function(e, d) {
405 should.not.exist(e);
406 done();
407 });
408 });
409 });
410 });
411
412 describe('presence', function() {
413 it('should validate presence', function() {
414 User.validatesPresenceOf('name', 'email');
415
416 const validations = User.validations;
417 validations.name.should.eql([{validation: 'presence', options: {}}]);
418 validations.email.should.eql([{validation: 'presence', options: {}}]);
419
420 const u = new User;
421 u.isValid().should.not.be.true();
422 u.name = 1;
423 u.isValid().should.not.be.true();
424 u.email = 2;
425 u.isValid().should.be.true();
426 });
427
428 it('should reject NaN value as a number', function() {
429 User.validatesPresenceOf('age');
430 const u = new User();
431 u.isValid().should.be.false();
432 u.age = NaN;
433 u.isValid().should.be.false();
434 u.age = 1;
435 u.isValid().should.be.true();
436 });
437
438 it('should allow "NaN" value as a string', function() {
439 User.validatesPresenceOf('name');
440 const u = new User();
441 u.isValid().should.be.false();
442 u.name = 'NaN';
443 u.isValid().should.be.true();
444 });
445
446 it('should skip validation by property (if/unless)', function() {
447 User.validatesPresenceOf('domain', {unless: 'createdByScript'});
448
449 const user = new User(getValidAttributes());
450 user.isValid().should.be.true();
451
452 user.createdByScript = false;
453 user.isValid().should.be.false();
454 user.errors.domain.should.eql(['can\'t be blank']);
455
456 user.domain = 'domain';
457 user.isValid().should.be.true();
458 });
459
460 describe('validate presence on update', function() {
461 before(function(done) {
462 Employee.destroyAll(function(err) {
463 should.not.exist(err);
464 delete Employee.validations;
465 db.automigrate('Employee', function(err) {
466 should.not.exist(err);
467 Employee.create(empData, function(err, inst) {
468 should.not.exist(err);
469 should.exist(inst);
470 Employee.validatesPresenceOf('name', 'age');
471 done();
472 });
473 });
474 });
475 });
476
477 it('succeeds when validate condition is met', function(done) {
478 const data = {name: 'Foo-new', age: 5};
479 Employee.updateAll({id: 1}, data,
480 function(err, emp) {
481 should.not.exist(err);
482 should.exist(emp);
483 should.equal(emp.count, 1);
484 Employee.find({where: {id: 1}}, function(err, emp) {
485 should.not.exist(err);
486 should.exist(emp);
487 data.id = 1;
488 should.deepEqual(data, emp[0].toObject());
489 done();
490 });
491 });
492 });
493
494 it('throws err when validate condition is not met', function(done) {
495 Employee.updateAll({where: {id: 1}}, {name: 'Foo-new'},
496 function(err, emp) {
497 should.exist(err);
498 should.not.exist(emp);
499 should.equal(err.statusCode, 422);
500 should.equal(err.details.messages.age[0], 'can\'t be blank');
501 done();
502 });
503 });
504 });
505 });
506
507 describe('absence', function() {
508 it('should validate absence', function() {
509 User.validatesAbsenceOf('reserved', {if: 'locked'});
510 let u = new User({reserved: 'foo', locked: true});
511 u.isValid().should.not.be.true();
512 u.reserved = null;
513 u.isValid().should.be.true();
514 u = new User({reserved: 'foo', locked: false});
515 u.isValid().should.be.true();
516 });
517
518 describe('validate absence on update', function() {
519 before(function(done) {
520 Employee.destroyAll(function(err) {
521 should.not.exist(err);
522 delete Employee.validations;
523 db.automigrate('Employee', function(err) {
524 should.not.exist(err);
525 Employee.create(empData, function(err, inst) {
526 should.not.exist(err);
527 should.exist(inst);
528 Employee.validatesAbsenceOf('name');
529 done();
530 });
531 });
532 });
533 });
534
535 it('succeeds when validate condition is met', function(done) {
536 const data = {age: 5};
537 Employee.updateAll({id: 1}, data,
538 function(err, emp) {
539 should.not.exist(err);
540 should.exist(emp);
541 should.equal(emp.count, 1);
542 Employee.find({where: {id: 1}}, function(err, emp) {
543 should.not.exist(err);
544 should.exist(emp);
545 data.id = 1;
546 data.name = 'Foo';
547 should.deepEqual(data, emp[0].toObject());
548 done();
549 });
550 });
551 });
552
553 it('throws err when validate condition is not met', function(done) {
554 Employee.updateAll({where: {id: 1}}, {name: 'Foo-new', age: 5},
555 function(err, emp) {
556 should.exist(err);
557 should.not.exist(emp);
558 should.equal(err.statusCode, 422);
559 should.equal(err.details.messages.name[0], 'can\'t be set');
560 done();
561 });
562 });
563 });
564 });
565
566 describe('uniqueness', function() {
567 it('should validate uniqueness', function(done) {
568 User.validatesUniquenessOf('email');
569 const u = new User({email: 'hey'});
570 Boolean(u.isValid(function(valid) {
571 valid.should.be.true();
572 u.save(function() {
573 const u2 = new User({email: 'hey'});
574 u2.isValid(function(valid) {
575 valid.should.be.false();
576 done();
577 });
578 });
579 })).should.be.false();
580 });
581
582 it('should handle same object modification', function(done) {
583 User.validatesUniquenessOf('email');
584 const u = new User({email: 'hey'});
585 Boolean(u.isValid(function(valid) {
586 valid.should.be.true();
587 u.save(function() {
588 u.name = 'Goghi';
589 u.isValid(function(valid) {
590 valid.should.be.true();
591 u.save(done);
592 });
593 });
594 // async validations always falsy when called as sync
595 })).should.not.be.ok;
596 });
597
598 it('should support multi-key constraint', function(done) {
599 const EMAIL = 'user@xample.com';
600 const SiteUser = db.define('SiteUser', {
601 siteId: String,
602 email: String,
603 });
604 SiteUser.validatesUniquenessOf('email', {scopedTo: ['siteId']});
605 async.waterfall([
606 function automigrate(next) {
607 db.automigrate(next);
608 },
609 function createSite1User(next) {
610 SiteUser.create(
611 {siteId: 1, email: EMAIL},
612 next,
613 );
614 },
615 function createSite2User(user1, next) {
616 SiteUser.create(
617 {siteId: 2, email: EMAIL},
618 next,
619 );
620 },
621 function validateDuplicateUser(user2, next) {
622 const user3 = new SiteUser({siteId: 1, email: EMAIL});
623 user3.isValid(function(valid) {
624 valid.should.be.false();
625 next();
626 });
627 },
628 ], function(err) {
629 if (err && err.name == 'ValidationError') {
630 console.error('ValidationError:', err.details.messages);
631 }
632 done(err);
633 });
634 });
635
636 it('should skip blank values', function(done) {
637 User.validatesUniquenessOf('email');
638 const u = new User({email: ' '});
639 Boolean(u.isValid(function(valid) {
640 valid.should.be.true();
641 u.save(function() {
642 const u2 = new User({email: null});
643 u2.isValid(function(valid) {
644 valid.should.be.true();
645 done();
646 });
647 });
648 })).should.be.false();
649 });
650
651 it('should work with if/unless', function(done) {
652 User.validatesUniquenessOf('email', {
653 if: function() { return true; },
654 unless: function() { return false; },
655 });
656 const u = new User({email: 'hello'});
657 Boolean(u.isValid(function(valid) {
658 valid.should.be.true();
659 done();
660 })).should.be.false();
661 });
662
663 it('should work with id property on create', function(done) {
664 Entry.create({id: 'entry'}, function(err, entry) {
665 const e = new Entry({id: 'entry'});
666 Boolean(e.isValid(function(valid) {
667 valid.should.be.false();
668 done();
669 })).should.be.false();
670 });
671 });
672
673 it('should work with id property after create', function(done) {
674 Entry.findById('entry', function(err, e) {
675 Boolean(e.isValid(function(valid) {
676 valid.should.be.true();
677 done();
678 })).should.be.false();
679 });
680 });
681
682 it('passes case insensitive validation', function(done) {
683 User.validatesUniquenessOf('email', {ignoreCase: true});
684 const u = new User({email: 'hey'});
685 Boolean(u.isValid(function(valid) {
686 valid.should.be.true();
687 u.save(function(err) {
688 if (err) return done(err);
689 const u2 = new User({email: 'HEY'});
690 u2.isValid(function(valid) {
691 valid.should.be.false();
692 done();
693 });
694 });
695 })).should.be.false();
696 });
697
698 it('passed case sensitive validation', function(done) {
699 User.validatesUniquenessOf('email', {ignoreCase: false});
700 const u = new User({email: 'hey'});
701 Boolean(u.isValid(function(valid) {
702 valid.should.be.true();
703 u.save(function(err) {
704 if (err) return done(err);
705 const u2 = new User({email: 'HEY'});
706 u2.isValid(function(valid) {
707 valid.should.be.true();
708 done();
709 });
710 });
711 })).should.be.false();
712 });
713
714 it('passes case insensitive validation with string that needs escaping', function(done) {
715 User.validatesUniquenessOf('email', {ignoreCase: true});
716 const u = new User({email: 'me+me@my.com'});
717 Boolean(u.isValid(function(valid) {
718 valid.should.be.true();
719 u.save(function(err) {
720 if (err) return done(err);
721 const u2 = new User({email: 'ME+ME@MY.COM'});
722 u2.isValid(function(valid) {
723 valid.should.be.false();
724 done();
725 });
726 });
727 })).should.be.false();
728 });
729
730 it('passed case sensitive validation with string that needs escaping', function(done) {
731 User.validatesUniquenessOf('email', {ignoreCase: false});
732 const u = new User({email: 'me+me@my.com'});
733 Boolean(u.isValid(function(valid) {
734 valid.should.be.true();
735 u.save(function(err) {
736 if (err) return done(err);
737 const u2 = new User({email: 'ME+ME@MY.COM'});
738 u2.isValid(function(valid) {
739 valid.should.be.true();
740 done();
741 });
742 });
743 })).should.be.false();
744 });
745
746 it('passes partial case insensitive validation with string that needs escaping', function(done) {
747 User.validatesUniquenessOf('email', {ignoreCase: true});
748 const u = new User({email: 'also+me@my.com'});
749 Boolean(u.isValid(function(valid) {
750 valid.should.be.true();
751 u.save(function(err) {
752 if (err) return done(err);
753 const u2 = new User({email: 'Me@My.com'});
754 u2.isValid(function(valid) {
755 valid.should.be.true();
756 done();
757 });
758 });
759 })).should.be.false();
760 });
761
762 it('passes partial case sensitive validation with string that needs escaping', function(done) {
763 User.validatesUniquenessOf('email', {ignoreCase: false});
764 const u = new User({email: 'also+me@my.com'});
765 Boolean(u.isValid(function(valid) {
766 valid.should.be.true();
767 u.save(function(err) {
768 if (err) return done(err);
769 const u2 = new User({email: 'Me@My.com'});
770 u2.isValid(function(valid) {
771 valid.should.be.true();
772 done();
773 });
774 });
775 })).should.be.false();
776 });
777
778 describe('validate uniqueness on update', function() {
779 before(function(done) {
780 Employee.destroyAll(function(err) {
781 should.not.exist(err);
782 delete Employee.validations;
783 db.automigrate('Employee', function(err) {
784 should.not.exist(err);
785 Employee.create(empData, function(err, inst) {
786 should.not.exist(err);
787 should.exist(inst);
788 Employee.validatesUniquenessOf('name');
789 done();
790 });
791 });
792 });
793 });
794
795 it('succeeds when validate condition is met', function(done) {
796 const data = {name: 'Foo-new', age: 5};
797 Employee.updateAll({id: 1}, data,
798 function(err, emp) {
799 should.not.exist(err);
800 should.exist(emp);
801 should.equal(emp.count, 1);
802 Employee.find({where: {id: 1}}, function(err, emp) {
803 should.not.exist(err);
804 should.exist(emp);
805 data.id = 1;
806 should.deepEqual(data, emp[0].toObject());
807 done();
808 });
809 });
810 });
811
812 it('throws err when validate condition is not met', function(done) {
813 Employee.updateAll({where: {id: 1}}, {name: 'Bar', age: 5},
814 function(err, emp) {
815 should.exist(err);
816 should.not.exist(emp);
817 should.equal(err.statusCode, 422);
818 should.equal(err.details.messages.name[0], 'is not unique');
819 done();
820 });
821 });
822 });
823 });
824
825 describe('format', function() {
826 it('should validate the format of valid strings', function() {
827 User.validatesFormatOf('name', {with: /[a-z][A-Z]*$/});
828 const u = new User({name: 'valid name'});
829 u.isValid().should.be.true();
830 });
831
832 it('should validate the format of invalid strings', function() {
833 User.validatesFormatOf('name', {with: /[a-z][A-Z]*$/});
834 const u = new User({name: 'invalid name!'});
835 u.isValid().should.be.false();
836 });
837
838 it('should validate the format of valid numbers', function() {
839 User.validatesFormatOf('age', {with: /^\d+$/});
840 const u = new User({age: 30});
841 u.isValid().should.be.true();
842 });
843
844 it('should validate the format of invalid numbers', function() {
845 User.validatesFormatOf('age', {with: /^\d+$/});
846 const u = new User({age: 'thirty'});
847 u.isValid().should.be.false();
848 });
849
850 it('should overwrite default blank message with custom format message', function() {
851 const CUSTOM_MESSAGE = 'custom validation message';
852 User.validatesFormatOf('name', {with: /[a-z][A-Z]*$/, message: CUSTOM_MESSAGE});
853 const u = new User({name: 'invalid name string 123'});
854 u.isValid().should.be.false();
855 u.errors.should.containEql({
856 name: [CUSTOM_MESSAGE],
857 codes: {
858 name: ['format'],
859 },
860 });
861 });
862
863 it('should skip missing values when allowing blank', function() {
864 User.validatesFormatOf('email', {with: /^\S+@\S+\.\S+$/, allowBlank: true});
865 const u = new User({});
866 u.isValid().should.be.true();
867 });
868
869 it('should skip null values when allowing null', function() {
870 User.validatesFormatOf('email', {with: /^\S+@\S+\.\S+$/, allowNull: true});
871 const u = new User({email: null});
872 u.isValid().should.be.true();
873 });
874
875 it('should not skip missing values', function() {
876 User.validatesFormatOf('email', {with: /^\S+@\S+\.\S+$/});
877 const u = new User({});
878 u.isValid().should.be.false();
879 });
880
881 it('should not skip null values', function() {
882 User.validatesFormatOf('email', {with: /^\S+@\S+\.\S+$/});
883 const u = new User({email: null});
884 u.isValid().should.be.false();
885 });
886
887 describe('validate format correctly on bulk creation with global flag enabled in RegExp', function() {
888 before(function(done) {
889 Employee.destroyAll(function(err) {
890 should.not.exist(err);
891 delete Employee.validations;
892 db.automigrate('Employee', function(err) {
893 should.not.exist(err);
894 Employee.create(empData, function(err, inst) {
895 should.not.exist(err);
896 should.exist(inst);
897 Employee.validatesFormatOf('name', {with: /^[a-z]+$/g, allowNull: false});
898 done();
899 });
900 });
901 });
902 });
903
904 it('succeeds when validate condition is met for all items', function(done) {
905 Employee.create([
906 {name: 'test'},
907 {name: 'test'},
908 {name: 'test'},
909 {name: 'test'},
910 {name: 'test'},
911 {name: 'test'},
912 ], (err, instances) => {
913 should.not.exist(err);
914 should.exist(instances);
915 instances.should.have.lengthOf(6);
916 done();
917 });
918 });
919 });
920
921 describe('validate format on update', function() {
922 before(function(done) {
923 Employee.destroyAll(function(err) {
924 should.not.exist(err);
925 delete Employee.validations;
926 db.automigrate('Employee', function(err) {
927 should.not.exist(err);
928 Employee.create(empData, function(err, inst) {
929 should.not.exist(err);
930 should.exist(inst);
931 Employee.validatesFormatOf('name', {with: /^\w+\s\w+$/, allowNull: false});
932 done();
933 });
934 });
935 });
936 });
937
938 it('succeeds when validate condition is met', function(done) {
939 const data = {name: 'Foo Mo', age: 5};
940 Employee.updateAll({id: 1}, data,
941 function(err, emp) {
942 should.not.exist(err);
943 should.exist(emp);
944 should.equal(emp.count, 1);
945 Employee.find({where: {id: 1}}, function(err, emp) {
946 should.not.exist(err);
947 should.exist(emp);
948 data.id = 1;
949 should.deepEqual(data, emp[0].toObject());
950 done();
951 });
952 });
953 });
954
955 it('throws err when validate condition is not met', function(done) {
956 Employee.updateAll({where: {id: 1}}, {name: '45foo', age: 5},
957 function(err, emp) {
958 should.exist(err);
959 should.not.exist(emp);
960 should.equal(err.statusCode, 422);
961 should.equal(err.details.messages.name[0], 'is invalid');
962 done();
963 });
964 });
965 });
966 });
967
968 describe('numericality', function() {
969 it('passes when given numeric values', function() {
970 User.validatesNumericalityOf('age');
971 const user = new User({age: 10});
972 user.isValid().should.be.true();
973 });
974
975 it('fails when given non-numeric values', function() {
976 User.validatesNumericalityOf('age');
977 const user = new User({age: 'notanumber'});
978 user.isValid().should.be.false();
979 user.errors.should.containEql({age: ['is not a number']});
980 });
981
982 it('fails when given undefined values', function() {
983 User.validatesNumericalityOf('age');
984 const user = new User({});
985 user.isValid().should.be.false();
986 user.errors.should.containEql({age: ['is blank']});
987 });
988
989 it('skips undefined values when allowBlank option is true', function() {
990 User.validatesNumericalityOf('age', {allowBlank: true});
991 const user = new User({});
992 user.isValid().should.be.true();
993 });
994
995 it('fails when given non-numeric values when allowBlank option is true', function() {
996 User.validatesNumericalityOf('age', {allowBlank: true});
997 const user = new User({age: 'test'});
998 user.isValid().should.be.false();
999 user.errors.should.containEql({age: ['is not a number']});
1000 });
1001
1002 it('fails when given null values', function() {
1003 User.validatesNumericalityOf('age');
1004 const user = new User({age: null});
1005 user.isValid().should.be.false();
1006 user.errors.should.containEql({age: ['is null']});
1007 });
1008
1009 it('passes when given null values when allowNull option is true', function() {
1010 User.validatesNumericalityOf('age', {allowNull: true});
1011 const user = new User({age: null});
1012 user.isValid().should.be.true();
1013 });
1014
1015 it('passes when given float values', function() {
1016 User.validatesNumericalityOf('age');
1017 const user = new User({age: 13.37});
1018 user.isValid().should.be.true();
1019 });
1020
1021 it('fails when given non-integer values when int option is true', function() {
1022 User.validatesNumericalityOf('age', {int: true});
1023 const user = new User({age: 13.37});
1024 user.isValid().should.be.false();
1025 user.errors.should.match({age: /is not an integer/});
1026 });
1027
1028 describe('validate numericality on update', function() {
1029 before(function(done) {
1030 Employee.destroyAll(function(err) {
1031 should.not.exist(err);
1032 delete Employee.validations;
1033 db.automigrate('Employee', function(err) {
1034 should.not.exist(err);
1035 Employee.create(empData, function(err, inst) {
1036 should.not.exist(err);
1037 should.exist(inst);
1038 Employee.validatesNumericalityOf('age');
1039 done();
1040 });
1041 });
1042 });
1043 });
1044
1045 it('succeeds when validate condition is met', function(done) {
1046 const data = {name: 'Foo-new', age: 5};
1047 Employee.updateAll({id: 1}, data,
1048 function(err, emp) {
1049 should.not.exist(err);
1050 should.exist(emp);
1051 should.equal(emp.count, 1);
1052 Employee.find({where: {id: 1}}, function(err, emp) {
1053 should.not.exist(err);
1054 should.exist(emp);
1055 data.id = 1;
1056 should.deepEqual(data, emp[0].toObject());
1057 done();
1058 });
1059 });
1060 });
1061
1062 it('throws err when validate condition is not met', function(done) {
1063 Employee.updateAll({where: {id: 1}}, {age: {someAge: 5}},
1064 function(err, emp) {
1065 should.exist(err);
1066 should.not.exist(emp);
1067 should.equal(err.statusCode, 422);
1068 should.equal(err.details.messages.age[0], 'is not a number');
1069 done();
1070 });
1071 });
1072 });
1073 });
1074
1075 describe('inclusion', function() {
1076 it('fails when included value is not used for property', function(done) {
1077 User.validatesInclusionOf('name', {in: ['bob', 'john']});
1078 User.create({name: 'bobby'}, function(err) {
1079 err.should.be.instanceof(Error);
1080 err.details.messages.should.match({name: /is not included in the list/});
1081 done();
1082 });
1083 });
1084
1085 it('passes when included value is used for property', function(done) {
1086 User.validatesInclusionOf('name', {in: ['bob', 'john']});
1087 User.create({name: 'bob'}, function(err, user) {
1088 if (err) return done(err);
1089 user.name.should.eql('bob');
1090 done();
1091 });
1092 });
1093
1094 it('fails with a custom error message', function(done) {
1095 User.validatesInclusionOf('name', {in: ['bob', 'john'], message: 'not used'});
1096 User.create({name: 'dude'}, function(err) {
1097 err.should.be.instanceof(Error);
1098 err.details.messages.should.match({name: /not used/});
1099 done();
1100 });
1101 });
1102
1103 it('fails with a null value when allowNull is false', function(done) {
1104 User.validatesInclusionOf('name', {in: ['bob'], allowNull: false});
1105 User.create({name: null}, function(err) {
1106 err.should.be.instanceof(Error);
1107 err.details.messages.should.match({name: /is null/});
1108 done();
1109 });
1110 });
1111
1112 it('passes with a null value when allowNull is true', function(done) {
1113 User.validatesInclusionOf('name', {in: ['bob'], allowNull: true});
1114 User.create({name: null}, done);
1115 });
1116
1117 it('fails if value is used for integer property', function(done) {
1118 User.validatesInclusionOf('age', {in: [123, 456]});
1119 User.create({age: 789}, function(err) {
1120 err.should.be.instanceof(Error);
1121 err.details.messages.should.match({age: /is not included in the list/});
1122 done();
1123 });
1124 });
1125
1126 it('passes with an empty value when allowBlank option is true', function(done) {
1127 User.validatesInclusionOf('gender', {in: ['male', 'female'], allowBlank: true});
1128 User.create({gender: ''}, done);
1129 });
1130
1131 it('fails with an empty value when allowBlank option is false', function(done) {
1132 User.validatesInclusionOf('gender', {in: ['male', 'female'], allowBlank: false});
1133 User.create({gender: ''}, function(err) {
1134 err.should.be.instanceOf(ValidationError);
1135 getErrorDetails(err)
1136 .should.equal('`gender` is blank (value: "").');
1137 done();
1138 });
1139 });
1140
1141 function getErrorDetails(err) {
1142 return err.message.replace(/^.*Details: /, '');
1143 }
1144
1145 describe('validate inclusion on update', function() {
1146 before(function(done) {
1147 Employee.destroyAll(function(err) {
1148 should.not.exist(err);
1149 delete Employee.validations;
1150 db.automigrate('Employee', function(err) {
1151 should.not.exist(err);
1152 Employee.create(empData, function(err, inst) {
1153 should.not.exist(err);
1154 should.exist(inst);
1155 Employee.validatesInclusionOf('name', {in: ['Foo-new']});
1156 done();
1157 });
1158 });
1159 });
1160 });
1161
1162 it('succeeds when validate condition is met', function(done) {
1163 const data = {name: 'Foo-new', age: 5};
1164 Employee.updateAll({id: 1}, data,
1165 function(err, emp) {
1166 should.not.exist(err);
1167 should.exist(emp);
1168 should.equal(emp.count, 1);
1169 Employee.find({where: {id: 1}}, function(err, emp) {
1170 should.not.exist(err);
1171 should.exist(emp);
1172 data.id = 1;
1173 should.deepEqual(data, emp[0].toObject());
1174 done();
1175 });
1176 });
1177 });
1178
1179 it('throws err when validate condition is not met', function(done) {
1180 Employee.updateAll({where: {id: 1}}, {name: 'Foo-new2', age: 5},
1181 function(err, emp) {
1182 should.exist(err);
1183 should.not.exist(emp);
1184 should.equal(err.statusCode, 422);
1185 should.equal(err.details.messages.name[0], 'is not included in ' +
1186 'the list');
1187 done();
1188 });
1189 });
1190 });
1191 });
1192
1193 describe('exclusion', function() {
1194 it('fails when excluded value is used for property', function(done) {
1195 User.validatesExclusionOf('name', {in: ['bob']});
1196 User.create({name: 'bob'}, function(err, user) {
1197 err.should.be.instanceof(Error);
1198 err.details.messages.should.match({name: /is reserved/});
1199 done();
1200 });
1201 });
1202
1203 it('passes when excluded value not found for property', function(done) {
1204 User.validatesExclusionOf('name', {in: ['dude']});
1205 User.create({name: 'bob'}, function(err, user) {
1206 if (err) return done(err);
1207 user.name.should.eql('bob');
1208 done();
1209 });
1210 });
1211
1212 it('fails with a custom error message', function(done) {
1213 User.validatesExclusionOf('name', {in: ['bob'], message: 'cannot use this'});
1214 User.create({name: 'bob'}, function(err) {
1215 err.should.be.instanceof(Error);
1216 err.details.messages.should.match({name: /cannot use this/});
1217 done();
1218 });
1219 });
1220
1221 it('fails with a null value when allowNull is false', function(done) {
1222 User.validatesExclusionOf('name', {in: ['bob'], allowNull: false});
1223 User.create({name: null}, function(err) {
1224 err.should.be.instanceof(Error);
1225 err.details.messages.should.match({name: /is null/});
1226 done();
1227 });
1228 });
1229
1230 it('passes with a null value when allowNull is true', function(done) {
1231 User.validatesExclusionOf('name', {in: ['bob'], allowNull: true});
1232 User.create({name: null}, done);
1233 });
1234
1235 it('fails if value is used for integer property', function(done) {
1236 User.validatesExclusionOf('age', {in: [123, 456]});
1237 User.create({age: 123}, function(err) {
1238 err.should.be.instanceof(Error);
1239 err.details.messages.should.match({age: /is reserved/});
1240 done();
1241 });
1242 });
1243
1244 describe('validate exclusion on update', function() {
1245 before(function(done) {
1246 Employee.destroyAll(function(err) {
1247 should.not.exist(err);
1248 delete Employee.validations;
1249 db.automigrate('Employee', function(err) {
1250 should.not.exist(err);
1251 Employee.create(empData, function(err, inst) {
1252 should.not.exist(err);
1253 should.exist(inst);
1254 Employee.validatesExclusionOf('name', {in: ['Bob']});
1255 done();
1256 });
1257 });
1258 });
1259 });
1260
1261 it('succeeds when validate condition is met', function(done) {
1262 const data = {name: 'Foo-new', age: 5};
1263 Employee.updateAll({id: 1}, data,
1264 function(err, emp) {
1265 should.not.exist(err);
1266 should.exist(emp);
1267 should.equal(emp.count, 1);
1268 Employee.find({where: {id: 1}}, function(err, emp) {
1269 should.not.exist(err);
1270 should.exist(emp);
1271 data.id = 1;
1272 should.deepEqual(data, emp[0].toObject());
1273 done();
1274 });
1275 });
1276 });
1277
1278 it('throws err when validate condition is not met', function(done) {
1279 Employee.updateAll({where: {id: 1}}, {name: 'Bob', age: 5},
1280 function(err, emp) {
1281 should.exist(err);
1282 should.not.exist(emp);
1283 should.equal(err.statusCode, 422);
1284 should.equal(err.details.messages.name[0], 'is reserved');
1285 done();
1286 });
1287 });
1288 });
1289 });
1290
1291 describe('length', function() {
1292 it('should validate length');
1293
1294 describe('validate length on update', function() {
1295 before(function(done) {
1296 Employee.destroyAll(function(err) {
1297 should.not.exist(err);
1298 delete Employee.validations;
1299 db.automigrate('Employee', function(err) {
1300 should.not.exist(err);
1301 Employee.create(empData, function(err, inst) {
1302 should.not.exist(err);
1303 should.exist(inst);
1304 Employee.validatesLengthOf('name', {min: 5});
1305 done();
1306 });
1307 });
1308 });
1309 });
1310
1311 it('succeeds when validate condition is met', function(done) {
1312 const data = {name: 'Foo-new', age: 5};
1313 Employee.updateAll({id: 1}, data,
1314 function(err, emp) {
1315 should.not.exist(err);
1316 should.exist(emp);
1317 should.equal(emp.count, 1);
1318 Employee.find({where: {id: 1}}, function(err, emp) {
1319 should.not.exist(err);
1320 should.exist(emp);
1321 data.id = 1;
1322 should.deepEqual(data, emp[0].toObject());
1323 done();
1324 });
1325 });
1326 });
1327
1328 it('throws err when validate condition is not met', function(done) {
1329 Employee.updateAll({where: {id: 1}}, {name: 'Bob', age: 5},
1330 function(err, emp) {
1331 should.exist(err);
1332 should.not.exist(emp);
1333 should.equal(err.statusCode, 422);
1334 should.equal(err.details.messages.name[0], 'too short');
1335 done();
1336 });
1337 });
1338 });
1339 });
1340
1341 describe('custom', function() {
1342 it('should validate using custom sync validation', function() {
1343 User.validate('email', function(err) {
1344 if (this.email === 'hello') err();
1345 }, {code: 'invalid-email'});
1346 const u = new User({email: 'hello'});
1347 Boolean(u.isValid()).should.be.false();
1348 u.errors.codes.should.eql({email: ['invalid-email']});
1349 });
1350
1351 it('should validate and return detailed error messages', function() {
1352 User.validate('global', function(err) {
1353 if (this.email === 'hello' || this.email === 'hey') {
1354 this.errors.add('email', 'Cannot be `' + this.email + '`', 'invalid-email');
1355 err(false); // false: prevent global error message
1356 }
1357 });
1358 const u = new User({email: 'hello'});
1359 Boolean(u.isValid()).should.be.false();
1360 u.errors.should.containEql({email: ['Cannot be `hello`']});
1361 u.errors.codes.should.eql({email: ['invalid-email']});
1362 });
1363
1364 it('should validate using custom async validation', function(done) {
1365 User.validateAsync('email', function(err, next) {
1366 process.nextTick(next);
1367 }, {
1368 if: function() { return true; },
1369 unless: function() { return false; },
1370 });
1371 const u = new User({email: 'hello'});
1372 Boolean(u.isValid(function(valid) {
1373 valid.should.be.true();
1374 done();
1375 })).should.be.false();
1376 });
1377 });
1378
1379 describe('invalid value formatting', function() {
1380 let origMaxLen;
1381 beforeEach(function saveAndSetMaxLen() {
1382 origMaxLen = ValidationError.maxPropertyStringLength;
1383 });
1384
1385 afterEach(function restoreMaxLen() {
1386 ValidationError.maxPropertyStringLength = origMaxLen;
1387 });
1388
1389 it('should truncate long strings', function() {
1390 ValidationError.maxPropertyStringLength = 9;
1391 const err = givenValidationError('prop', '1234567890abc', 'is invalid');
1392 getErrorDetails(err)
1393 .should.equal('`prop` is invalid (value: "12...abc").');
1394 });
1395
1396 it('should truncate long objects', function() {
1397 ValidationError.maxPropertyStringLength = 12;
1398 const err = givenValidationError('prop', {foo: 'bar'}, 'is invalid');
1399 getErrorDetails(err)
1400 .should.equal('`prop` is invalid (value: { foo:... }).');
1401 });
1402
1403 it('should truncate long arrays', function() {
1404 ValidationError.maxPropertyStringLength = 12;
1405 const err = givenValidationError('prop', [{a: 1, b: 2}], 'is invalid');
1406 getErrorDetails(err)
1407 .should.equal('`prop` is invalid (value: [ { a...} ]).');
1408 });
1409
1410 it('should print only top-level object properties', function() {
1411 const err = givenValidationError('prop', {a: {b: 'c'}}, 'is invalid');
1412 getErrorDetails(err)
1413 .should.equal('`prop` is invalid (value: { a: [Object] }).');
1414 });
1415
1416 it('should print only top-level props of objects in array', function() {
1417 const err = givenValidationError('prop', [{a: {b: 'c'}}], 'is invalid');
1418 getErrorDetails(err)
1419 .should.equal('`prop` is invalid (value: [ { a: [Object] } ]).');
1420 });
1421
1422 it('should exclude colors from Model values', function() {
1423 const obj = new User();
1424 obj.email = 'test@example.com';
1425 const err = givenValidationError('user', obj, 'is invalid');
1426 getErrorDetails(err).should.equal(
1427 '`user` is invalid (value: { email: \'test@example.com\' }).',
1428 );
1429 });
1430
1431 function givenValidationError(propertyName, propertyValue, errorMessage) {
1432 const jsonVal = {};
1433 jsonVal[propertyName] = propertyValue;
1434 const errorVal = {};
1435 errorVal[propertyName] = [errorMessage];
1436
1437 const obj = {
1438 errors: errorVal,
1439 toJSON: function() { return jsonVal; },
1440 };
1441 return new ValidationError(obj);
1442 }
1443
1444 function getErrorDetails(err) {
1445 return err.message.replace(/^.*Details: /, '');
1446 }
1447 });
1448
1449 describe('date', function() {
1450 it('should validate a date object', function() {
1451 User.validatesDateOf('updatedAt');
1452 const u = new User({updatedAt: new Date()});
1453 u.isValid().should.be.true();
1454 });
1455
1456 it('should validate a date string', function() {
1457 User.validatesDateOf('updatedAt');
1458 const u = new User({updatedAt: '2000-01-01'});
1459 u.isValid().should.be.true();
1460 });
1461
1462 it('should validate a null date', function() {
1463 User.validatesDateOf('updatedAt');
1464 const u = new User({updatedAt: null});
1465 u.isValid().should.be.true();
1466 });
1467
1468 it('should validate an undefined date', function() {
1469 User.validatesDateOf('updatedAt');
1470 const u = new User({updatedAt: undefined});
1471 u.isValid().should.be.true();
1472 });
1473
1474 it('should validate an invalid date string', function() {
1475 User.validatesDateOf('updatedAt');
1476 const u = new User({updatedAt: 'invalid date string'});
1477 u.isValid().should.not.be.true();
1478 u.errors.should.containEql({
1479 updatedAt: ['is not a valid date'],
1480 codes: {
1481 updatedAt: ['date'],
1482 },
1483 });
1484 });
1485
1486 it('should attach validation by default to all date properties', function() {
1487 const AnotherUser = db.define('User', {
1488 email: String,
1489 name: String,
1490 password: String,
1491 state: String,
1492 age: Number,
1493 gender: String,
1494 domain: String,
1495 pendingPeriod: Number,
1496 createdByAdmin: Boolean,
1497 createdByScript: Boolean,
1498 updatedAt: Date,
1499 });
1500 const u = new AnotherUser({updatedAt: 'invalid date string'});
1501 u.isValid().should.not.be.true();
1502 u.errors.should.containEql({
1503 updatedAt: ['is not a valid date'],
1504 codes: {
1505 updatedAt: ['date'],
1506 },
1507 });
1508 });
1509
1510 it('should overwrite default blank message with custom format message', function() {
1511 const CUSTOM_MESSAGE = 'custom validation message';
1512 User.validatesDateOf('updatedAt', {message: CUSTOM_MESSAGE});
1513 const u = new User({updatedAt: 'invalid date string'});
1514 u.isValid().should.not.be.true();
1515 u.errors.should.containEql({
1516 updatedAt: [CUSTOM_MESSAGE],
1517 codes: {
1518 updatedAt: ['date'],
1519 },
1520 });
1521 });
1522 });
1523});
1524
1525const empData = [{
1526 id: 1,
1527 name: 'Foo',
1528 age: 1,
1529}, {
1530 id: 2,
1531 name: 'Bar',
1532 age: 2,
1533}, {
1534 id: 3,
1535 name: 'Baz',
1536 age: 3,
1537}];