UNPKG

50.6 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const fs = require('fs');
5const expect = require('chai').expect;
6const connect = require('../index').connect;
7const Document = require('../index').Document;
8const isDocument = require('../lib/validate').isDocument;
9const ValidationError = require('../lib/errors').ValidationError;
10const Data = require('./data');
11const getData1 = require('./util').data1;
12const getData2 = require('./util').data2;
13const validateId = require('./util').validateId;
14const fail = require('./util').fail;
15const expectError = require('./util').expectError;
16
17describe('Document', function() {
18
19 // TODO: Should probably use mock database client...
20 const url = 'nedb://memory';
21 //const url = 'mongodb://localhost/camo_test';
22 let database = null;
23
24 before(function(done) {
25 connect(url).then(function(db) {
26 database = db;
27 return database.dropDatabase();
28 }).then(function() {
29 return done();
30 });
31 });
32
33 beforeEach(function(done) {
34 done();
35 });
36
37 afterEach(function(done) {
38 database.dropDatabase().then(function() {}).then(done, done);
39 });
40
41 after(function(done) {
42 database.dropDatabase().then(function() {}).then(done, done);
43 });
44
45 describe('instantiation', function() {
46 it('should allow creation of instance', function(done) {
47
48 class User extends Document {
49 constructor() {
50 super();
51 this.firstName = String;
52 this.lastName = String;
53 }
54 }
55
56 let user = User.create();
57 user.firstName = 'Billy';
58 user.lastName = 'Bob';
59
60 user.save().then(function() {
61 validateId(user);
62 }).then(done, done);
63 });
64
65 it('should allow schema declaration via method', function(done) {
66
67 class User extends Document {
68 constructor() {
69 super();
70
71 this.schema({
72 firstName: String,
73 lastName: String
74 });
75 }
76 }
77
78 let user = User.create();
79 user.firstName = 'Billy';
80 user.lastName = 'Bob';
81
82 user.save().then(function() {
83 validateId(user);
84 }).then(done, done);
85 });
86
87 it('should allow creation of instance with data', function(done) {
88
89 class User extends Document {
90 constructor() {
91 super();
92 this.firstName = String;
93 this.lastName = String;
94 this.nicknames = [String];
95 }
96 }
97
98 let user = User.create({
99 firstName: 'Billy',
100 lastName: 'Bob',
101 nicknames: ['Bill', 'William', 'Will']
102 });
103
104 expect(user.firstName).to.be.equal('Billy');
105 expect(user.lastName).to.be.equal('Bob');
106 expect(user.nicknames).to.have.length(3);
107 expect(user.nicknames).to.include('Bill');
108 expect(user.nicknames).to.include('William');
109 expect(user.nicknames).to.include('Will');
110
111 done();
112 });
113
114 it('should allow creation of instance with references', function(done) {
115
116 class Coffee extends Document {
117 constructor() {
118 super();
119 this.temp = Number;
120 }
121 }
122
123 class User extends Document {
124 constructor() {
125 super();
126 this.drinks = [Coffee];
127 }
128 }
129
130 let coffee = Coffee.create();
131 coffee.temp = 105;
132
133 coffee.save().then(function() {
134 let user = User.create({ drinks: [coffee] });
135 expect(user.drinks).to.have.length(1);
136 }).then(done, done);
137 });
138 });
139
140 describe('class', function() {
141 it('should allow use of member variables in getters', function(done) {
142
143 class User extends Document {
144 constructor() {
145 super();
146 this.firstName = String;
147 this.lastName = String;
148 }
149
150 get fullName() {
151 return this.firstName + ' ' + this.lastName;
152 }
153 }
154
155 let user = User.create();
156 user.firstName = 'Billy';
157 user.lastName = 'Bob';
158
159 user.save().then(function() {
160 validateId(user);
161 expect(user.fullName).to.be.equal('Billy Bob');
162 }).then(done, done);
163 });
164
165 it('should allow use of member variables in setters', function(done) {
166
167 class User extends Document {
168 constructor() {
169 super();
170 this.firstName = String;
171 this.lastName = String;
172 }
173
174 get fullName() {
175 return this.firstName + ' ' + this.lastName;
176 }
177
178 set fullName(name) {
179 let nameArr = name.split(' ');
180 this.firstName = nameArr[0];
181 this.lastName = nameArr[1];
182 }
183 }
184
185 let user = User.create();
186 user.fullName = 'Billy Bob';
187
188 user.save().then(function() {
189 validateId(user);
190 expect(user.firstName).to.be.equal('Billy');
191 expect(user.lastName).to.be.equal('Bob');
192 }).then(done, done);
193 });
194
195 it('should allow use of member variables in methods', function(done) {
196
197 class User extends Document {
198 constructor() {
199 super();
200 this.firstName = String;
201 this.lastName = String;
202 }
203
204 fullName() {
205 return this.firstName + ' ' + this.lastName;
206 }
207 }
208
209 let user = User.create();
210 user.firstName = 'Billy';
211 user.lastName = 'Bob';
212
213 user.save().then(function() {
214 validateId(user);
215 expect(user.fullName()).to.be.equal('Billy Bob');
216 }).then(done, done);
217 });
218
219 it('should allow schemas to be extended', function(done) {
220
221 class User extends Document {
222 constructor(collection) {
223 super(collection);
224 this.firstName = String;
225 this.lastName = String;
226 }
227 }
228
229 class ProUser extends User {
230 constructor() {
231 super();
232 this.paymentMethod = String;
233 }
234 }
235
236 let user = ProUser.create();
237 user.firstName = 'Billy';
238 user.lastName = 'Bob';
239 user.paymentMethod = 'cash';
240
241 user.save().then(function() {
242 validateId(user);
243 expect(user.firstName).to.be.equal('Billy');
244 expect(user.lastName).to.be.equal('Bob');
245 expect(user.paymentMethod).to.be.equal('cash');
246 }).then(done, done);
247 });
248
249 it('should allow schemas to be overridden', function(done) {
250
251 class Vehicle extends Document {
252 constructor(collection) {
253 super(collection);
254 this.numWheels = {
255 type: Number,
256 default: 4
257 };
258 }
259 }
260
261 class Motorcycle extends Vehicle {
262 constructor() {
263 super();
264 this.numWheels = {
265 type: Number,
266 default: 2
267 };
268 }
269 }
270
271 let bike = Motorcycle.create();
272
273 bike.save().then(function() {
274 validateId(bike);
275 expect(bike.numWheels).to.be.equal(2);
276 }).then(done, done);
277 });
278
279 it('should provide default collection name based on class name', function(done) {
280
281 class User extends Document {
282 constructor() {
283 super();
284 }
285 }
286
287 let user = User.create();
288
289 expect(user.collectionName()).to.be.equal('users');
290 expect(User.collectionName()).to.be.equal('users');
291
292 done();
293 });
294
295 it('should provide default collection name based on subclass name', function(done) {
296
297 class User extends Document {
298 constructor() {
299 super();
300 }
301 }
302
303 class ProUser extends User {
304 constructor() {
305 super();
306 }
307 }
308
309 let pro = ProUser.create();
310
311 expect(pro.collectionName()).to.be.equal('prousers');
312 expect(ProUser.collectionName()).to.be.equal('prousers');
313
314 done();
315 });
316
317 it('should allow custom collection name', function(done) {
318
319 class User extends Document {
320 constructor() {
321 super();
322 }
323
324 static collectionName() {
325 return 'sheeple';
326 }
327 }
328
329 let user = User.create();
330
331 expect(user.collectionName()).to.be.equal('sheeple');
332 expect(User.collectionName()).to.be.equal('sheeple');
333
334 done();
335 });
336 });
337
338 describe('types', function() {
339 it('should allow reference types', function(done) {
340
341 class ReferenceeModel extends Document {
342 constructor() {
343 super();
344 this.str = String;
345 }
346
347 static collectionName() {
348 return 'referencee1';
349 }
350 }
351
352 class ReferencerModel extends Document {
353 constructor() {
354 super();
355 this.ref = ReferenceeModel;
356 this.num = { type: Number };
357 }
358
359 static collectionName() {
360 return 'referencer1';
361 }
362 }
363
364 let data = ReferencerModel.create();
365 data.ref = ReferenceeModel.create();
366 data.ref.str = 'some data';
367 data.num = 1;
368
369 data.ref.save().then(function() {
370 validateId(data.ref);
371 return data.save();
372 }).then(function() {
373 validateId(data);
374 return ReferencerModel.findOne({ num: 1 });
375 }).then(function(d) {
376 validateId(d);
377 validateId(d.ref);
378 expect(d.ref).to.be.an.instanceof(ReferenceeModel);
379 expect(d.ref.str).to.be.equal('some data');
380 }).then(done, done);
381 });
382
383 it('should allow array of references', function(done) {
384
385 class ReferenceeModel extends Document {
386 constructor() {
387 super();
388 this.schema({ str: { type: String } });
389 }
390
391 static collectionName() {
392 return 'referencee2';
393 }
394 }
395
396 class ReferencerModel extends Document {
397 constructor() {
398 super();
399 this.refs = [ReferenceeModel];
400 this.num = Number;
401 }
402
403 static collectionName() {
404 return 'referencer2';
405 }
406 }
407
408 let data = ReferencerModel.create();
409 data.refs.push(ReferenceeModel.create());
410 data.refs.push(ReferenceeModel.create());
411 data.refs[0].str = 'string1';
412 data.refs[1].str = 'string2';
413 data.num = 1;
414
415 data.refs[0].save().then(function() {
416 validateId(data.refs[0]);
417 return data.refs[1].save();
418 }).then(function() {
419 validateId(data.refs[1]);
420 return data.save();
421 }).then(function() {
422 validateId(data);
423 return ReferencerModel.findOne({ num: 1 });
424 }).then(function(d) {
425 validateId(d);
426 validateId(d.refs[0]);
427 validateId(d.refs[1]);
428 expect(d.refs[0]).to.be.an.instanceof(ReferenceeModel);
429 expect(d.refs[1]).to.be.an.instanceof(ReferenceeModel);
430 expect(d.refs[0].str).to.be.equal('string1');
431 expect(d.refs[1].str).to.be.equal('string2');
432 }).then(done, done);
433 });
434
435 it('should allow references to be saved using the object or its id', function(done) {
436 class ReferenceeModel extends Document {
437 constructor() {
438 super();
439 this.str = String;
440 }
441
442 static collectionName() {
443 return 'referencee3';
444 }
445 }
446
447 class ReferencerModel extends Document {
448 constructor() {
449 super();
450 this.ref1 = ReferenceeModel;
451 this.ref2 = ReferenceeModel;
452 this.num = { type: Number };
453 }
454
455 static collectionName() {
456 return 'referencer3';
457 }
458 }
459
460 let data = ReferencerModel.create();
461 data.ref1 = ReferenceeModel.create();
462 let ref2 = ReferenceeModel.create();
463 data.ref1.str = 'string1';
464 ref2.str = 'string2';
465 data.num = 1;
466
467 data.ref1.save().then(function() {
468 validateId(data.ref1);
469 return data.save();
470 }).then(function() {
471 validateId(data);
472 return ref2.save();
473 }).then(function() {
474 validateId(ref2);
475 data.ref2 = ref2._id;
476 return data.save();
477 }).then(function() {
478 return ReferencerModel.findOne({num: 1});
479 }).then(function(d) {
480 validateId(d.ref1);
481 validateId(d.ref2);
482 expect(d.ref1.str).to.be.equal('string1');
483 expect(d.ref2.str).to.be.equal('string2');
484 }).then(done, done);
485 });
486
487 it('should allow array of references to be saved using the object or its id', function(done) {
488 class ReferenceeModel extends Document {
489 constructor() {
490 super();
491 this.schema({ str: { type: String } });
492 }
493
494 static collectionName() {
495 return 'referencee4';
496 }
497 }
498
499 class ReferencerModel extends Document {
500 constructor() {
501 super();
502 this.refs = [ReferenceeModel];
503 this.num = Number;
504 }
505
506 static collectionName() {
507 return 'referencer4';
508 }
509 }
510
511 let data = ReferencerModel.create();
512 data.refs.push(ReferenceeModel.create());
513 let ref2 = ReferenceeModel.create();
514 data.refs[0].str = 'string1';
515 ref2.str = 'string2';
516 data.num = 1;
517
518 data.refs[0].save().then(function() {
519 validateId(data.refs[0]);
520 return data.save();
521 }).then(function() {
522 validateId(data);
523 return ref2.save();
524 }).then(function() {
525 validateId(ref2);
526 data.refs.push(ref2._id);
527 return data.save();
528 }).then(function() {
529 return ReferencerModel.findOne({num: 1});
530 }).then(function(d) {
531 validateId(d.refs[0]);
532 validateId(d.refs[1]);
533 expect(d.refs[1].str).to.be.equal('string2');
534 }).then(done, done);
535 });
536
537 it('should allow circular references', function(done) {
538
539 class Employee extends Document {
540 constructor() {
541 super();
542 this.name = String;
543 this.boss = Boss;
544 }
545 }
546
547 class Boss extends Document {
548 constructor() {
549 super();
550 this.salary = Number;
551 this.employees = [Employee];
552 }
553
554 static collectionName() {
555 return 'bosses';
556 }
557 }
558
559 let employee = Employee.create();
560 employee.name = 'Scott';
561
562 let boss = Boss.create();
563 boss.salary = 10000000;
564
565 employee.boss = boss;
566
567 boss.save().then(function() {
568 validateId(boss);
569
570 return employee.save();
571 }).then(function() {
572 validateId(employee);
573 validateId(employee.boss);
574
575 boss.employees.push(employee);
576
577 return boss.save();
578 }).then(function() {
579 validateId(boss);
580 validateId(boss.employees[0]);
581 validateId(boss.employees[0].boss);
582
583 return Boss.findOne({ salary: 10000000 });
584 }).then(function(b) {
585 // If we had an issue with an infinite loop
586 // of loading circular dependencies then the
587 // test probably would have crashed by now,
588 // so we're good.
589
590 validateId(b);
591
592 // Validate that boss employee ref was loaded
593 validateId(b.employees[0]);
594
595 // .findOne should have only loaded 1 level
596 // of references, so the boss's reference
597 // to the employee is still the ID.
598 expect(b.employees[0].boss).to.not.be.null;
599 expect(!isDocument(b.employees[0].boss)).to.be.true;
600 }).then(done, done);
601 });
602
603 it('should allow string types', function(done) {
604
605 class StringModel extends Document {
606 constructor() {
607 super();
608 this.schema({ str: { type: String } });
609 }
610 }
611
612 let data = StringModel.create();
613 data.str = 'hello';
614
615 data.save().then(function() {
616 validateId(data);
617 expect(data.str).to.be.equal('hello');
618 }).then(done, done);
619 });
620
621 it('should allow number types', function(done) {
622
623 class NumberModel extends Document {
624 constructor() {
625 super();
626 this.schema({ num: { type: Number } });
627 }
628
629 static collectionName() {
630 return 'numbers1';
631 }
632 }
633
634 let data = NumberModel.create();
635 data.num = 26;
636
637 data.save().then(function() {
638 validateId(data);
639 expect(data.num).to.be.equal(26);
640 }).then(done, done);
641 });
642
643 it('should allow boolean types', function(done) {
644
645 class BooleanModel extends Document {
646 constructor() {
647 super();
648 this.schema({ bool: { type: Boolean } });
649 }
650 }
651
652 let data = BooleanModel.create();
653 data.bool = true;
654
655 data.save().then(function() {
656 validateId(data);
657 expect(data.bool).to.be.equal(true);
658 }).then(done, done);
659 });
660
661 it('should allow date types', function(done) {
662
663 class DateModel extends Document {
664 constructor() {
665 super();
666 this.schema({ date: { type: Date } });
667 }
668 }
669
670 let data = DateModel.create();
671 let date = new Date();
672 data.date = date;
673
674 data.save().then(function() {
675 validateId(data);
676 expect(data.date.valueOf()).to.be.equal(date.valueOf());
677 }).then(done, done);
678 });
679
680 it('should allow object types', function(done) {
681
682 class ObjectModel extends Document {
683 constructor() {
684 super();
685 this.schema({ obj: { type: Object } });
686 }
687 }
688
689 let data = ObjectModel.create();
690 data.obj = { hi: 'bye'};
691
692 data.save().then(function() {
693 validateId(data);
694 expect(data.obj.hi).to.not.be.null;
695 expect(data.obj.hi).to.be.equal('bye');
696 }).then(done, done);
697 });
698
699 it('should allow buffer types', function(done) {
700
701 class BufferModel extends Document {
702 constructor() {
703 super();
704 this.schema({ buf: { type: Buffer } });
705 }
706 }
707
708 let data = BufferModel.create();
709 data.buf = new Buffer('hello');
710
711 data.save().then(function() {
712 validateId(data);
713 expect(data.buf.toString('ascii')).to.be.equal('hello');
714 }).then(done, done);
715 });
716
717 it('should allow array types', function(done) {
718
719 class ArrayModel extends Document {
720 constructor() {
721 super();
722 this.schema({ arr: { type: Array } });
723 }
724 }
725
726 let data = ArrayModel.create();
727 data.arr = [1, 'number', true];
728
729 data.save().then(function() {
730 validateId(data);
731 expect(data.arr).to.have.length(3);
732 expect(data.arr).to.include(1);
733 expect(data.arr).to.include('number');
734 expect(data.arr).to.include(true);
735 }).then(done, done);
736 });
737
738 it('should allow typed-array types', function(done) {
739
740 class ArrayModel extends Document {
741 constructor() {
742 super();
743 this.schema({ arr: { type: [String] } });
744 }
745 }
746
747 let data = ArrayModel.create();
748 data.arr = ['1', '2', '3'];
749
750 data.save().then(function() {
751 validateId(data);
752 expect(data.arr).to.have.length(3);
753 expect(data.arr).to.include('1');
754 expect(data.arr).to.include('2');
755 expect(data.arr).to.include('3');
756 }).then(done, done);
757 });
758
759 it('should reject objects containing values with different types', function(done) {
760
761 class NumberModel extends Document {
762 constructor() {
763 super();
764 this.schema({ num: { type: Number } });
765 }
766
767 static collectionName() {
768 return 'numbers2';
769 }
770 }
771
772 let data = NumberModel.create();
773 data.num = '1';
774
775 data.save().then(function() {
776 expect.fail(null, Error, 'Expected error, but got none.');
777 }).catch(function(error) {
778 expect(error).to.be.instanceof(ValidationError);
779 }).then(done, done);
780 });
781
782 it('should reject typed-arrays containing different types', function(done) {
783
784 class ArrayModel extends Document {
785 constructor() {
786 super();
787 this.schema({ arr: { type: [String] } });
788 }
789 }
790
791 let data = ArrayModel.create();
792 data.arr = [1, 2, 3];
793
794 data.save().then(function() {
795 expect.fail(null, Error, 'Expected error, but got none.');
796 }).catch(function(error) {
797 expect(error).to.be.instanceof(ValidationError);
798 }).then(done, done);
799 });
800 });
801
802 describe('defaults', function() {
803 it('should assign default value if unassigned', function(done) {
804
805 let data = Data.create();
806
807 data.save().then(function() {
808 validateId(data);
809 expect(data.source).to.be.equal('reddit');
810 }).then(done, done);
811 });
812
813 it('should assign default value via function if unassigned', function(done) {
814
815 let data = Data.create();
816
817 data.save().then(function() {
818 validateId(data);
819 expect(data.date).to.be.lessThan(Date.now());
820 }).then(done, done);
821 });
822
823 it('should be undefined if unassigned and no default is given', function(done) {
824
825 class Person extends Document {
826 constructor() {
827 super();
828 this.name = String;
829 this.age = Number;
830 }
831
832 static collectionName() {
833 return 'people';
834 }
835 }
836
837 let person = Person.create({
838 name: 'Scott'
839 });
840
841 person.save().then(function() {
842 validateId(person);
843 return Person.findOne({name: 'Scott'});
844 }).then(function(p) {
845 validateId(p);
846 expect(p.name).to.be.equal('Scott');
847 expect(p.age).to.be.undefined;
848 }).then(done, done);
849 });
850 });
851
852 describe('choices', function() {
853 it('should accept value specified in choices', function(done) {
854
855 let data = Data.create();
856 data.source = 'wired';
857
858 data.save().then(function() {
859 validateId(data);
860 expect(data.source).to.be.equal('wired');
861 }).then(done, done);
862 });
863
864 it('should reject values not specified in choices', function(done) {
865
866 let data = Data.create();
867 data.source = 'google';
868
869 data.save().then(function() {
870 expect.fail(null, Error, 'Expected error, but got none.');
871 }).catch(function(error) {
872 expect(error).to.be.instanceof(ValidationError);
873 }).then(done, done);
874 });
875 });
876
877 describe('min', function() {
878 it('should accept value > min', function(done) {
879
880 let data = Data.create();
881 data.item = 1;
882
883 data.save().then(function() {
884 validateId(data);
885 expect(data.item).to.be.equal(1);
886 }).then(done, done);
887 });
888
889 it('should accept value == min', function(done) {
890
891 let data = Data.create();
892 data.item = 0;
893
894 data.save().then(function() {
895 validateId(data);
896 expect(data.item).to.be.equal(0);
897 }).then(done, done);
898 });
899
900 it('should reject value < min', function(done) {
901
902 let data = Data.create();
903 data.item = -1;
904
905 data.save().then(function() {
906 expect.fail(null, Error, 'Expected error, but got none.');
907 }).catch(function(error) {
908 expect(error).to.be.instanceof(ValidationError);
909 }).then(done, done);
910 });
911 });
912
913 describe('max', function() {
914 it('should accept value < max', function(done) {
915
916 let data = Data.create();
917 data.item = 99;
918
919 data.save().then(function() {
920 validateId(data);
921 expect(data.item).to.be.equal(99);
922 }).then(done, done);
923 });
924
925 it('should accept value == max', function(done) {
926
927 let data = Data.create();
928 data.item = 100;
929
930 data.save().then(function() {
931 validateId(data);
932 expect(data.item).to.be.equal(100);
933 }).then(done, done);
934 });
935
936 it('should reject value > max', function(done) {
937
938 let data = Data.create();
939 data.item = 101;
940
941 data.save().then(function() {
942 expect.fail(null, Error, 'Expected error, but got none.');
943 }).catch(function(error) {
944 expect(error).to.be.instanceof(ValidationError);
945 }).then(done, done);
946 });
947 });
948
949 describe('match', function() {
950 it('should accept value matching regex', function(done) {
951
952 class Product extends Document {
953 constructor() {
954 super();
955 this.name = String;
956 this.cost = {
957 type: String,
958 match: /^\$?[\d,]+(\.\d*)?$/
959 };
960 }
961 }
962
963 let product = Product.create();
964 product.name = 'Dark Roast Coffee';
965 product.cost = '$1.39';
966
967 product.save().then(function() {
968 validateId(product);
969 expect(product.name).to.be.equal('Dark Roast Coffee');
970 expect(product.cost).to.be.equal('$1.39');
971 }).then(done, done);
972 });
973
974 it('should reject value not matching regex', function(done) {
975
976 class Product extends Document {
977 constructor() {
978 super();
979 this.name = String;
980 this.cost = {
981 type: String,
982 match: /^\$?[\d,]+(\.\d*)?$/
983 };
984 }
985 }
986
987 let product = Product.create();
988 product.name = 'Light Roast Coffee';
989 product.cost = '$1..39';
990
991 product.save().then(function() {
992 fail(null, Error, 'Expected error, but got none.');
993 }).catch(function(error) {
994 expectError(error);
995 }).then(done, done);
996 });
997 });
998
999 describe('validate', function() {
1000 it('should accept value that passes custom validator', function(done) {
1001
1002 class Person extends Document {
1003 constructor() {
1004 super();
1005
1006 this.name = {
1007 type: String,
1008 validate: function(value) {
1009 return value.length > 4;
1010 }
1011 };
1012 }
1013
1014 static collectionName() {
1015 return 'people';
1016 }
1017 }
1018
1019 let person = Person.create({
1020 name: 'Scott'
1021 });
1022
1023 person.save().then(function() {
1024 validateId(person);
1025 expect(person.name).to.be.equal('Scott');
1026 }).then(done, done);
1027 });
1028
1029 it('should reject value that fails custom validator', function(done) {
1030
1031 class Person extends Document {
1032 constructor() {
1033 super();
1034
1035 this.name = {
1036 type: String,
1037 validate: function(value) {
1038 return value.length > 4;
1039 }
1040 };
1041 }
1042
1043 static collectionName() {
1044 return 'people';
1045 }
1046 }
1047
1048 let person = Person.create({
1049 name: 'Matt'
1050 });
1051
1052 person.save().then(function() {
1053 fail(null, Error, 'Expected error, but got none.');
1054 }).catch(function(error) {
1055 expectError(error);
1056 }).then(done, done);
1057 });
1058 });
1059
1060 describe('canonicalize', function() {
1061 it('should ensure timestamp dates are converted to Date objects', function(done) {
1062
1063 class Person extends Document {
1064 constructor() {
1065 super();
1066
1067 this.birthday = Date;
1068 }
1069
1070 static collectionName() {
1071 return 'people';
1072 }
1073 }
1074
1075 let now = new Date();
1076
1077 let person = Person.create({
1078 birthday: now
1079 });
1080
1081 person.save().then(function() {
1082 validateId(person);
1083 expect(person.birthday.valueOf()).to.be.equal(now.valueOf());
1084 }).then(done, done);
1085 });
1086
1087 it('should ensure date strings are converted to Date objects', function(done) {
1088
1089 class Person extends Document {
1090 constructor() {
1091 super();
1092 this.birthday = Date;
1093 this.graduationDate = Date;
1094 this.weddingDate = Date;
1095 }
1096
1097 static collectionName() {
1098 return 'people';
1099 }
1100 }
1101
1102 let birthday = new Date(Date.UTC(2016, 1, 17, 5, 6, 8, 0));
1103 let graduationDate = new Date(2016, 1, 17, 0, 0, 0, 0);
1104 let weddingDate = new Date(2016, 1, 17, 0, 0, 0, 0);
1105
1106 let person = Person.create({
1107 birthday: '2016-02-17T05:06:08+00:00',
1108 graduationDate: 'February 17, 2016',
1109 weddingDate: '2016/02/17'
1110 });
1111
1112 person.save().then(function() {
1113 validateId(person);
1114 expect(person.birthday.valueOf()).to.be.equal(birthday.valueOf());
1115 expect(person.graduationDate.valueOf()).to.be.equal(graduationDate.valueOf());
1116 expect(person.weddingDate.valueOf()).to.be.equal(weddingDate.valueOf());
1117 }).then(done, done);
1118 });
1119 });
1120
1121 describe('required', function() {
1122 it('should accept empty value that is not reqired', function(done) {
1123
1124 class Person extends Document {
1125 constructor() {
1126 super();
1127
1128 this.name = {
1129 type: String,
1130 required: false
1131 };
1132 }
1133
1134 static collectionName() {
1135 return 'people';
1136 }
1137 }
1138
1139 let person = Person.create({
1140 name: ''
1141 });
1142
1143 person.save().then(function() {
1144 validateId(person);
1145 expect(person.name).to.be.equal('');
1146 }).then(done, done);
1147 });
1148
1149 it('should accept value that is not undefined', function(done) {
1150
1151 class Person extends Document {
1152 constructor() {
1153 super();
1154
1155 this.name = {
1156 type: String,
1157 required: true
1158 };
1159 }
1160
1161 static collectionName() {
1162 return 'people';
1163 }
1164 }
1165
1166 let person = Person.create({
1167 name: 'Scott'
1168 });
1169
1170 person.save().then(function() {
1171 validateId(person);
1172 expect(person.name).to.be.equal('Scott');
1173 }).then(done, done);
1174 });
1175
1176 it('should accept an empty value if default is specified', function(done) {
1177
1178 class Person extends Document {
1179 constructor() {
1180 super();
1181
1182 this.name = {
1183 type: String,
1184 required: true,
1185 default: 'Scott'
1186 };
1187 }
1188
1189 static collectionName() {
1190 return 'people';
1191 }
1192 }
1193
1194 let person = Person.create();
1195
1196 person.save().then(function() {
1197 validateId(person);
1198 expect(person.name).to.be.equal('Scott');
1199 }).then(done, done);
1200 });
1201
1202 it('should accept boolean value', function(done) {
1203
1204 class Person extends Document {
1205 constructor() {
1206 super();
1207
1208 this.isSingle = {
1209 type: Boolean,
1210 required: true
1211 };
1212 this.isMerried = {
1213 type: Boolean,
1214 required: true
1215 };
1216 }
1217
1218 static collectionName() {
1219 return 'people';
1220 }
1221 }
1222
1223 let person = Person.create({
1224 isMerried: true,
1225 isSingle: false
1226 });
1227
1228 person.save().then(function() {
1229 validateId(person);
1230 expect(person.isMerried).to.be.true;
1231 expect(person.isSingle).to.be.false;
1232 }).then(done, done);
1233 });
1234
1235 it('should accept date value', function(done) {
1236
1237 class Person extends Document {
1238 constructor() {
1239 super();
1240
1241 this.birthDate = {
1242 type: Date,
1243 required: true
1244 };
1245 }
1246
1247 static collectionName() {
1248 return 'people';
1249 }
1250 }
1251
1252 let myBirthDate = new Date();
1253
1254 let person = Person.create({
1255 birthDate: myBirthDate
1256 });
1257
1258 person.save().then(function(savedPerson) {
1259 validateId(person);
1260 expect(savedPerson.birthDate.valueOf()).to.equal(myBirthDate.valueOf());
1261 }).then(done, done);
1262 });
1263
1264 it('should accept any number value', function(done) {
1265
1266 class Person extends Document {
1267 constructor() {
1268 super();
1269
1270 this.age = {
1271 type: Number,
1272 required: true
1273 };
1274 this.level = {
1275 type: Number,
1276 required: true
1277 };
1278 }
1279
1280 static collectionName() {
1281 return 'people';
1282 }
1283 }
1284
1285 let person = Person.create({
1286 age: 21,
1287 level: 0
1288 });
1289
1290 person.save().then(function(savedPerson) {
1291 validateId(person);
1292 expect(savedPerson.age).to.equal(21);
1293 expect(savedPerson.level).to.equal(0);
1294 }).then(done, done);
1295 });
1296
1297 it('should reject value that is undefined', function(done) {
1298
1299 class Person extends Document {
1300 constructor() {
1301 super();
1302
1303 this.name = {
1304 type: String,
1305 required: true
1306 };
1307 }
1308
1309 static collectionName() {
1310 return 'people';
1311 }
1312 }
1313
1314 let person = Person.create();
1315
1316 person.save().then(function() {
1317 fail(null, Error, 'Expected error, but got none.');
1318 }).catch(function(error) {
1319 expectError(error);
1320 }).then(done, done);
1321 });
1322
1323 it('should reject value if specified default empty value', function(done) {
1324
1325 class Person extends Document {
1326 constructor() {
1327 super();
1328
1329 this.name = {
1330 type: String,
1331 required: true,
1332 default: ''
1333 };
1334 }
1335
1336 static collectionName() {
1337 return 'people';
1338 }
1339 }
1340
1341 let person = Person.create();
1342
1343 person.save().then(function() {
1344 fail(null, Error, 'Expected error, but got none.');
1345 }).catch(function(error) {
1346 expectError(error);
1347 }).then(done, done);
1348 });
1349
1350 it('should reject value that is null', function(done) {
1351
1352 class Person extends Document {
1353 constructor() {
1354 super();
1355
1356 this.name = {
1357 type: Object,
1358 required: true
1359 };
1360 }
1361
1362 static collectionName() {
1363 return 'people';
1364 }
1365 }
1366
1367 let person = Person.create({
1368 name: null
1369 });
1370
1371 person.save().then(function() {
1372 fail(null, Error, 'Expected error, but got none.');
1373 }).catch(function(error) {
1374 expectError(error);
1375 }).then(done, done);
1376 });
1377
1378 it('should reject value that is an empty array', function(done) {
1379
1380 class Person extends Document {
1381 constructor() {
1382 super();
1383
1384 this.names = {
1385 type: Array,
1386 required: true
1387 };
1388 }
1389
1390 static collectionName() {
1391 return 'people';
1392 }
1393 }
1394
1395 let person = Person.create({
1396 names: []
1397 });
1398
1399 person.save().then(function() {
1400 fail(null, Error, 'Expected error, but got none.');
1401 }).catch(function(error) {
1402 expectError(error);
1403 }).then(done, done);
1404 });
1405
1406 it('should reject value that is an empty string', function(done) {
1407
1408 class Person extends Document {
1409 constructor() {
1410 super();
1411
1412 this.name = {
1413 type: String,
1414 required: true
1415 };
1416 }
1417
1418 static collectionName() {
1419 return 'people';
1420 }
1421 }
1422
1423 let person = Person.create({
1424 name: ''
1425 });
1426
1427 person.save().then(function() {
1428 fail(null, Error, 'Expected error, but got none.');
1429 }).catch(function(error) {
1430 expectError(error);
1431 }).then(done, done);
1432 });
1433
1434 it('should reject value that is an empty object', function(done) {
1435
1436 class Person extends Document {
1437 constructor() {
1438 super();
1439
1440 this.names = {
1441 type: Object,
1442 required: true
1443 };
1444 }
1445
1446 static collectionName() {
1447 return 'people';
1448 }
1449 }
1450
1451 let person = Person.create({
1452 names: {}
1453 });
1454
1455 person.save().then(function() {
1456 fail(null, Error, 'Expected error, but got none.');
1457 }).catch(function(error) {
1458 expectError(error);
1459 }).then(done, done);
1460 });
1461 });
1462
1463 describe('hooks', function() {
1464 it('should call all pre and post functions', function(done) {
1465
1466 let preValidateCalled = false;
1467 let preSaveCalled = false;
1468 let preDeleteCalled = false;
1469
1470 let postValidateCalled = false;
1471 let postSaveCalled = false;
1472 let postDeleteCalled = false;
1473
1474 class Person extends Document {
1475 constructor() {
1476 super();
1477 }
1478
1479 static collectionName() {
1480 return 'people';
1481 }
1482
1483 preValidate() {
1484 preValidateCalled = true;
1485 }
1486
1487 postValidate() {
1488 postValidateCalled = true;
1489 }
1490
1491 preSave() {
1492 preSaveCalled = true;
1493 }
1494
1495 postSave() {
1496 postSaveCalled = true;
1497 }
1498
1499 preDelete() {
1500 preDeleteCalled = true;
1501 }
1502
1503 postDelete() {
1504 postDeleteCalled = true;
1505 }
1506 }
1507
1508 let person = Person.create();
1509
1510 person.save().then(function() {
1511 validateId(person);
1512
1513 // Pre/post save and validate should be called
1514 expect(preValidateCalled).to.be.equal(true);
1515 expect(preSaveCalled).to.be.equal(true);
1516 expect(postValidateCalled).to.be.equal(true);
1517 expect(postSaveCalled).to.be.equal(true);
1518
1519 // Pre/post delete should not have been called yet
1520 expect(preDeleteCalled).to.be.equal(false);
1521 expect(postDeleteCalled).to.be.equal(false);
1522
1523 return person.delete();
1524 }).then(function(numDeleted) {
1525 expect(numDeleted).to.be.equal(1);
1526
1527 expect(preDeleteCalled).to.be.equal(true);
1528 expect(postDeleteCalled).to.be.equal(true);
1529 }).then(done, done);
1530 });
1531 });
1532
1533 describe('serialize', function() {
1534 it('should serialize data to JSON', function(done) {
1535 class Person extends Document {
1536 constructor() {
1537 super();
1538
1539 this.name = String;
1540 this.age = Number;
1541 this.isAlive = Boolean;
1542 this.children = [String];
1543 this.spouse = {
1544 type: String,
1545 default: null
1546 };
1547 }
1548
1549 static collectionName() {
1550 return 'people';
1551 }
1552 }
1553
1554 let person = Person.create({
1555 name: 'Scott',
1556 age: 28,
1557 isAlive: true,
1558 children: ['Billy', 'Timmy'],
1559 spouse: null
1560 });
1561
1562 person.save().then(function() {
1563 validateId(person);
1564 expect(person.name).to.be.equal('Scott');
1565 expect(person.age).to.be.equal(28);
1566 expect(person.isAlive).to.be.equal(true);
1567 expect(person.children).to.have.length(2);
1568 expect(person.spouse).to.be.null;
1569
1570 let json = person.toJSON();
1571
1572 expect(json.name).to.be.equal('Scott');
1573 expect(json.age).to.be.equal(28);
1574 expect(json.isAlive).to.be.equal(true);
1575 expect(json.children).to.have.length(2);
1576 expect(json.spouse).to.be.null;
1577 expect(json._id).to.be.equal(person._id.toString());
1578 }).then(done, done);
1579 });
1580
1581 it('should serialize data to JSON', function(done) {
1582 class Person extends Document {
1583 constructor() {
1584 super();
1585
1586 this.name = String;
1587 this.children = [Person];
1588 this.spouse = {
1589 type: Person,
1590 default: null
1591 };
1592 }
1593
1594 static collectionName() {
1595 return 'people';
1596 }
1597 }
1598
1599 let person = Person.create({
1600 name: 'Scott'
1601 });
1602
1603 let spouse = Person.create({
1604 name: 'Jane'
1605 });
1606
1607 let kid1 = Person.create({
1608 name: 'Billy'
1609 });
1610
1611 let kid2 = Person.create({
1612 name: 'Timmy'
1613 });
1614
1615 spouse.save().then(function() {
1616 return kid1.save();
1617 }).then(function() {
1618 return kid2.save();
1619 }).then(function() {
1620 person.spouse = spouse;
1621 person.children.push(kid1);
1622 person.children.push(kid2);
1623
1624 return person.save();
1625 }).then(function() {
1626 validateId(person);
1627 validateId(spouse);
1628 validateId(kid1);
1629 validateId(kid2);
1630
1631 expect(person.name).to.be.equal('Scott');
1632 expect(person.children).to.have.length(2);
1633 expect(person.spouse.name).to.be.equal('Jane');
1634 expect(person.children[0].name).to.be.equal('Billy');
1635 expect(person.children[1].name).to.be.equal('Timmy');
1636 expect(person.spouse).to.be.an.instanceof(Person);
1637 expect(person.children[0]).to.be.an.instanceof(Person);
1638 expect(person.children[1]).to.be.an.instanceof(Person);
1639
1640 let json = person.toJSON();
1641
1642 expect(json.name).to.be.equal('Scott');
1643 expect(json.children).to.have.length(2);
1644 expect(json.spouse.name).to.be.equal('Jane');
1645 expect(json.children[0].name).to.be.equal('Billy');
1646 expect(json.children[1].name).to.be.equal('Timmy');
1647 expect(json.spouse).to.not.be.an.instanceof(Person);
1648 expect(json.children[0]).to.not.be.an.instanceof(Person);
1649 expect(json.children[1]).to.not.be.an.instanceof(Person);
1650 }).then(done, done);
1651 });
1652
1653 it('should serialize data to JSON and ignore methods', function(done) {
1654 class Person extends Document {
1655 constructor() {
1656 super();
1657
1658 this.name = String;
1659 }
1660
1661 static collectionName() {
1662 return 'people';
1663 }
1664
1665 getFoo() {
1666 return 'foo';
1667 }
1668 }
1669
1670 let person = Person.create({
1671 name: 'Scott'
1672 });
1673
1674 let json = person.toJSON();
1675 expect(json).to.have.keys(['_id', 'name']);
1676 done();
1677 });
1678 });
1679});
\No newline at end of file