UNPKG

23.3 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 EmbeddedDocument = require('../index').EmbeddedDocument;
9const isDocument = require('../lib/validate').isDocument;
10const ValidationError = require('../lib/errors').ValidationError;
11const Data = require('./data');
12const getData1 = require('./util').data1;
13const getData2 = require('./util').data2;
14const validateId = require('./util').validateId;
15
16describe('Embedded', function() {
17
18 // TODO: Should probably use mock database client...
19 const url = 'nedb://memory';
20 //const url = 'mongodb://localhost/camo_test';
21 let database = null;
22
23 before(function(done) {
24 connect(url).then(function(db) {
25 database = db;
26 return database.dropDatabase();
27 }).then(function() {
28 return done();
29 });
30 });
31
32 beforeEach(function(done) {
33 done();
34 });
35
36 afterEach(function(done) {
37 database.dropDatabase().then(function() {}).then(done, done);
38 });
39
40 after(function(done) {
41 database.dropDatabase().then(function() {}).then(done, done);
42 });
43
44 describe('general', function() {
45 it('should not have an _id', function(done) {
46
47 class EmbeddedModel extends EmbeddedDocument {
48 constructor() {
49 super();
50 this.str = String;
51 }
52 }
53
54 class DocumentModel extends Document {
55 constructor() {
56 super();
57 this.mod = EmbeddedModel;
58 this.num = { type: Number };
59 }
60 }
61
62 let data = DocumentModel.create();
63 data.mod = EmbeddedModel.create();
64 data.mod.str = 'some data';
65 data.num = 1;
66
67 data.save().then(function() {
68 expect(data.mod._id).to.be.undefined;
69 return DocumentModel.findOne({ num: 1 });
70 }).then(function(d) {
71 expect(d.mod._id).to.be.undefined;
72 }).then(done, done);
73 });
74 });
75
76 describe('types', function() {
77 it('should allow embedded types', function(done) {
78
79 class EmbeddedModel extends EmbeddedDocument {
80 constructor() {
81 super();
82 this.str = String;
83 }
84 }
85
86 class DocumentModel extends Document {
87 constructor() {
88 super();
89 this.mod = EmbeddedModel;
90 this.num = { type: Number };
91 }
92 }
93
94 let data = DocumentModel.create();
95 data.mod = EmbeddedModel.create();
96 data.mod.str = 'some data';
97 data.num = 1;
98
99 data.save().then(function() {
100 validateId(data);
101 return DocumentModel.findOne({ num: 1 });
102 }).then(function(d) {
103 validateId(d);
104 expect(d.num).to.be.equal(1);
105 expect(d.mod).to.be.a('object');
106 expect(d.mod).to.be.an.instanceof(EmbeddedModel);
107 expect(d.mod.str).to.be.equal('some data');
108 }).then(done, done);
109 });
110
111 it('should allow array of embedded types', function(done) {
112
113 class Limb extends EmbeddedDocument {
114 constructor() {
115 super();
116 this.type = String;
117 }
118 }
119
120 class Person extends Document {
121 constructor() {
122 super();
123 this.limbs = [Limb];
124 this.name = String;
125 }
126
127 static collectionName() {
128 return 'people';
129 }
130 }
131
132 let person = Person.create();
133 person.name = 'Scott';
134 person.limbs.push(Limb.create());
135 person.limbs[0].type = 'left arm';
136 person.limbs.push(Limb.create());
137 person.limbs[1].type = 'right arm';
138 person.limbs.push(Limb.create());
139 person.limbs[2].type = 'left leg';
140 person.limbs.push(Limb.create());
141 person.limbs[3].type = 'right leg';
142
143 person.save().then(function() {
144 validateId(person);
145 expect(person.limbs).to.have.length(4);
146 return Person.findOne({ name: 'Scott' });
147 }).then(function(p) {
148 validateId(p);
149 expect(p.name).to.be.equal('Scott');
150 expect(p.limbs).to.be.a('array');
151 expect(p.limbs).to.have.length(4);
152 expect(p.limbs[0].type).to.be.equal('left arm');
153 expect(p.limbs[1].type).to.be.equal('right arm');
154 expect(p.limbs[2].type).to.be.equal('left leg');
155 expect(p.limbs[3].type).to.be.equal('right leg');
156 }).then(done, done);
157 });
158
159 it('should save nested array of embeddeds', function(done) {
160 class Point extends EmbeddedDocument {
161 constructor() {
162 super();
163 this.x = Number;
164 this.y = Number;
165 }
166 }
167
168 class Polygon extends EmbeddedDocument {
169 constructor() {
170 super();
171 this.points = [Point];
172 }
173 }
174
175 class WorldMap extends Document {
176 constructor() {
177 super();
178 this.polygons = [Polygon];
179 }
180 }
181
182 let map = WorldMap.create();
183 let polygon1 = Polygon.create();
184 let polygon2 = Polygon.create();
185 let point1 = Point.create({ x: 123.45, y: 678.90 });
186 let point2 = Point.create({ x: 543.21, y: 987.60 });
187
188 map.polygons.push(polygon1);
189 map.polygons.push(polygon2);
190 polygon2.points.push(point1);
191 polygon2.points.push(point2);
192
193 map.save().then(function() {
194 return WorldMap.findOne();
195 }).then(function(m) {
196 expect(m.polygons).to.have.length(2);
197 expect(m.polygons[0]).to.be.instanceof(Polygon);
198 expect(m.polygons[1]).to.be.instanceof(Polygon);
199 expect(m.polygons[1].points).to.have.length(2);
200 expect(m.polygons[1].points[0]).to.be.instanceof(Point);
201 expect(m.polygons[1].points[1]).to.be.instanceof(Point);
202 }).then(done, done);
203 });
204
205 it('should allow nested initialization of embedded types', function(done) {
206
207 class Discount extends EmbeddedDocument {
208 constructor() {
209 super();
210 this.authorized = Boolean;
211 this.amount = Number;
212 }
213 }
214
215 class Product extends Document {
216 constructor() {
217 super();
218 this.name = String;
219 this.discount = Discount;
220 }
221 }
222
223 let product = Product.create({
224 name: 'bike',
225 discount: {
226 authorized: true,
227 amount: 9.99
228 }
229 });
230
231 product.save().then(function() {
232 validateId(product);
233 expect(product.name).to.be.equal('bike');
234 expect(product.discount).to.be.a('object');
235 expect(product.discount instanceof Discount).to.be.true;
236 expect(product.discount.authorized).to.be.equal(true);
237 expect(product.discount.amount).to.be.equal(9.99);
238 }).then(done, done);
239 });
240
241 it('should allow initialization of array of embedded documents', function(done) {
242
243 class Discount extends EmbeddedDocument {
244 constructor() {
245 super();
246 this.authorized = Boolean;
247 this.amount = Number;
248 }
249 }
250
251 class Product extends Document {
252 constructor() {
253 super();
254 this.name = String;
255 this.discounts = [Discount];
256 }
257 }
258
259 let product = Product.create({
260 name: 'bike',
261 discounts: [{
262 authorized: true,
263 amount: 9.99
264 },
265 {
266 authorized: false,
267 amount: 187.44
268 }]
269 });
270
271 product.save().then(function() {
272 validateId(product);
273 expect(product.name).to.be.equal('bike');
274 expect(product.discounts).to.have.length(2);
275 expect(product.discounts[0] instanceof Discount).to.be.true;
276 expect(product.discounts[1] instanceof Discount).to.be.true;
277 expect(product.discounts[0].authorized).to.be.equal(true);
278 expect(product.discounts[0].amount).to.be.equal(9.99);
279 expect(product.discounts[1].authorized).to.be.equal(false);
280 expect(product.discounts[1].amount).to.be.equal(187.44);
281 }).then(done, done);
282 });
283 });
284
285 describe('defaults', function() {
286 it('should assign defaults to embedded types', function(done) {
287
288 class EmbeddedModel extends EmbeddedDocument {
289 constructor() {
290 super();
291 this.str = { type: String, default: 'hello' };
292 }
293 }
294
295 class DocumentModel extends Document {
296 constructor() {
297 super();
298 this.emb = EmbeddedModel;
299 this.num = { type: Number };
300 }
301 }
302
303 let data = DocumentModel.create();
304 data.emb = EmbeddedModel.create();
305 data.num = 1;
306
307 data.save().then(function() {
308 validateId(data);
309 return DocumentModel.findOne({ num: 1 });
310 }).then(function(d) {
311 validateId(d);
312 expect(d.emb.str).to.be.equal('hello');
313 }).then(done, done);
314 });
315
316 it('should assign defaults to array of embedded types', function(done) {
317
318 class Money extends EmbeddedDocument {
319 constructor() {
320 super();
321 this.value = { type: Number, default: 100 };
322 }
323 }
324
325 class Wallet extends Document {
326 constructor() {
327 super();
328 this.contents = [Money];
329 this.owner = String;
330 }
331 }
332
333 let wallet = Wallet.create();
334 wallet.owner = 'Scott';
335 wallet.contents.push(Money.create());
336 wallet.contents.push(Money.create());
337 wallet.contents.push(Money.create());
338
339 wallet.save().then(function() {
340 validateId(wallet);
341 return Wallet.findOne({ owner: 'Scott' });
342 }).then(function(w) {
343 validateId(w);
344 expect(w.owner).to.be.equal('Scott');
345 expect(w.contents[0].value).to.be.equal(100);
346 expect(w.contents[1].value).to.be.equal(100);
347 expect(w.contents[2].value).to.be.equal(100);
348 }).then(done, done);
349 });
350 });
351
352 describe('validate', function() {
353
354 it('should validate embedded values', function(done) {
355
356 class EmbeddedModel extends EmbeddedDocument {
357 constructor() {
358 super();
359 this.num = { type: Number, max: 10 };
360 }
361 }
362
363 class DocumentModel extends Document {
364 constructor() {
365 super();
366 this.emb = EmbeddedModel;
367 }
368 }
369
370 let data = DocumentModel.create();
371 data.emb = EmbeddedModel.create();
372 data.emb.num = 26;
373
374 data.save().then(function() {
375 expect.fail(null, Error, 'Expected error, but got none.');
376 }).catch(function(error) {
377 expect(error).to.be.instanceof(ValidationError);
378 expect(error.message).to.contain('max');
379 }).then(done, done);
380 });
381
382 it('should validate array of embedded values', function(done) {
383
384 class Money extends EmbeddedDocument {
385 constructor() {
386 super();
387 this.value = { type: Number, choices: [1, 5, 10, 20, 50, 100] };
388 }
389 }
390
391 class Wallet extends Document {
392 constructor() {
393 super();
394 this.contents = [Money];
395 }
396 }
397
398 let wallet = Wallet.create();
399 wallet.contents.push(Money.create());
400 wallet.contents[0].value = 5;
401 wallet.contents.push(Money.create());
402 wallet.contents[1].value = 26;
403
404 wallet.save().then(function() {
405 expect.fail(null, Error, 'Expected error, but got none.');
406 }).catch(function(error) {
407 expect(error).to.be.instanceof(ValidationError);
408 expect(error.message).to.contain('choices');
409 }).then(done, done);
410 });
411
412 });
413
414 describe('canonicalize', function() {
415 it('should ensure timestamp dates are converted to Date objects', function(done) {
416 class Education extends EmbeddedDocument {
417 constructor() {
418 super();
419
420 this.school = String;
421 this.major = String;
422 this.dateGraduated = Date;
423 }
424
425 static collectionName() {
426 return 'people';
427 }
428 }
429
430 class Person extends Document {
431 constructor() {
432 super();
433
434 this.gradSchool = Education;
435 }
436
437 static collectionName() {
438 return 'people';
439 }
440 }
441
442 let now = new Date();
443
444 let person = Person.create({
445 gradSchool: {
446 school: 'CMU',
447 major: 'ECE',
448 dateGraduated: now
449 }
450 });
451
452 person.save().then(function() {
453 validateId(person);
454 expect(person.gradSchool.school).to.be.equal('CMU');
455 expect(person.gradSchool.dateGraduated.getFullYear()).to.be.equal(now.getFullYear());
456 expect(person.gradSchool.dateGraduated.getHours()).to.be.equal(now.getHours());
457 expect(person.gradSchool.dateGraduated.getMinutes()).to.be.equal(now.getMinutes());
458 expect(person.gradSchool.dateGraduated.getMonth()).to.be.equal(now.getMonth());
459 expect(person.gradSchool.dateGraduated.getSeconds()).to.be.equal(now.getSeconds());
460 }).then(done, done);
461 });
462 });
463
464 describe('hooks', function() {
465
466 it('should call all pre and post functions on embedded models', function(done) {
467
468 let preValidateCalled = false;
469 let preSaveCalled = false;
470 let preDeleteCalled = false;
471
472 let postValidateCalled = false;
473 let postSaveCalled = false;
474 let postDeleteCalled = false;
475
476 class Coffee extends EmbeddedDocument {
477 constructor() {
478 super();
479 }
480
481 preValidate() {
482 preValidateCalled = true;
483 }
484
485 postValidate() {
486 postValidateCalled = true;
487 }
488
489 preSave() {
490 preSaveCalled = true;
491 }
492
493 postSave() {
494 postSaveCalled = true;
495 }
496
497 preDelete() {
498 preDeleteCalled = true;
499 }
500
501 postDelete() {
502 postDeleteCalled = true;
503 }
504 }
505
506 class Cup extends Document {
507 constructor() {
508 super();
509
510 this.contents = Coffee;
511 }
512 }
513
514 let cup = Cup.create();
515 cup.contents = Coffee.create();
516
517 cup.save().then(function() {
518 validateId(cup);
519
520 // Pre/post save and validate should be called
521 expect(preValidateCalled).to.be.equal(true);
522 expect(preSaveCalled).to.be.equal(true);
523 expect(postValidateCalled).to.be.equal(true);
524 expect(postSaveCalled).to.be.equal(true);
525
526 // Pre/post delete should not have been called yet
527 expect(preDeleteCalled).to.be.equal(false);
528 expect(postDeleteCalled).to.be.equal(false);
529
530 return cup.delete();
531 }).then(function(numDeleted) {
532 expect(numDeleted).to.be.equal(1);
533
534 expect(preDeleteCalled).to.be.equal(true);
535 expect(postDeleteCalled).to.be.equal(true);
536 }).then(done, done);
537 });
538
539 it('should call all pre and post functions on array of embedded models', function(done) {
540
541 let preValidateCalled = false;
542 let preSaveCalled = false;
543 let preDeleteCalled = false;
544
545 let postValidateCalled = false;
546 let postSaveCalled = false;
547 let postDeleteCalled = false;
548
549 class Money extends EmbeddedDocument {
550 constructor() {
551 super();
552 }
553
554 preValidate() {
555 preValidateCalled = true;
556 }
557
558 postValidate() {
559 postValidateCalled = true;
560 }
561
562 preSave() {
563 preSaveCalled = true;
564 }
565
566 postSave() {
567 postSaveCalled = true;
568 }
569
570 preDelete() {
571 preDeleteCalled = true;
572 }
573
574 postDelete() {
575 postDeleteCalled = true;
576 }
577 }
578
579 class Wallet extends Document {
580 constructor() {
581 super();
582
583 this.contents = [Money];
584 }
585 }
586
587 let wallet = Wallet.create();
588 wallet.contents.push(Money.create());
589 wallet.contents.push(Money.create());
590
591 wallet.save().then(function() {
592 validateId(wallet);
593
594 // Pre/post save and validate should be called
595 expect(preValidateCalled).to.be.equal(true);
596 expect(postValidateCalled).to.be.equal(true);
597 expect(preSaveCalled).to.be.equal(true);
598 expect(postSaveCalled).to.be.equal(true);
599
600 // Pre/post delete should not have been called yet
601 expect(preDeleteCalled).to.be.equal(false);
602 expect(postDeleteCalled).to.be.equal(false);
603
604 return wallet.delete();
605 }).then(function(numDeleted) {
606 expect(numDeleted).to.be.equal(1);
607
608 expect(preDeleteCalled).to.be.equal(true);
609 expect(postDeleteCalled).to.be.equal(true);
610 }).then(done, done);
611 });
612 });
613
614 describe('serialize', function() {
615 it('should serialize data to JSON', function(done) {
616 class Address extends EmbeddedDocument {
617 constructor() {
618 super();
619
620 this.street = String;
621 this.city = String;
622 this.zipCode = Number;
623 this.isPoBox = Boolean;
624 }
625 }
626
627 class Person extends Document {
628 constructor() {
629 super();
630
631 this.name = String;
632 this.age = Number;
633 this.isAlive = Boolean;
634 this.children = [String];
635 this.address = Address;
636 }
637
638 static collectionName() {
639 return 'people';
640 }
641 }
642
643 let person = Person.create({
644 name: 'Scott',
645 address: {
646 street: '123 Fake St.',
647 city: 'Cityville',
648 zipCode: 12345,
649 isPoBox: false
650 }
651 });
652
653 person.save().then(function() {
654 validateId(person);
655 expect(person.name).to.be.equal('Scott');
656 expect(person.address).to.be.an.instanceof(Address);
657 expect(person.address.street).to.be.equal('123 Fake St.');
658 expect(person.address.city).to.be.equal('Cityville');
659 expect(person.address.zipCode).to.be.equal(12345);
660 expect(person.address.isPoBox).to.be.equal(false);
661
662 let json = person.toJSON();
663
664 expect(json.name).to.be.equal('Scott');
665 expect(json.address).to.not.be.an.instanceof(Address);
666 expect(json.address.street).to.be.equal('123 Fake St.');
667 expect(json.address.city).to.be.equal('Cityville');
668 expect(json.address.zipCode).to.be.equal(12345);
669 expect(json.address.isPoBox).to.be.equal(false);
670 }).then(done, done);
671 });
672
673 it('should serialize data to JSON and ignore methods', function(done) {
674 class Address extends EmbeddedDocument {
675 constructor() {
676 super();
677 this.street = String;
678 }
679
680 getBar() {
681 return 'bar';
682 }
683 }
684
685 class Person extends Document {
686 constructor() {
687 super();
688
689 this.name = String;
690 this.address = Address;
691 }
692
693 static collectionName() {
694 return 'people';
695 }
696
697 getFoo() {
698 return 'foo';
699 }
700 }
701
702 let person = Person.create({
703 name: 'Scott',
704 address : {
705 street : 'Bar street'
706 }
707 });
708
709 let json = person.toJSON();
710 expect(json).to.have.keys(['_id', 'name', 'address']);
711 expect(json.address).to.have.keys(['street']);
712
713 done();
714 });
715 });
716});
\No newline at end of file