UNPKG

16.3 kBJavaScriptView Raw
1'use strict';
2
3const expect = require('chai').expect;
4const connect = require('../index').connect;
5const Document = require('../index').Document;
6const EmbeddedDocument = require('../index').EmbeddedDocument;
7const ValidationError = require('../lib/errors').ValidationError;
8const validateId = require('./util').validateId;
9
10describe('Issues', function() {
11
12 // TODO: Should probably use mock database client...
13 const url = 'nedb://memory';
14 //const url = 'mongodb://localhost/camo_test';
15 let database = null;
16
17 before(function(done) {
18 connect(url).then(function(db) {
19 database = db;
20 return database.dropDatabase();
21 }).then(function() {
22 return done();
23 });
24 });
25
26 beforeEach(function(done) {
27 done();
28 });
29
30 afterEach(function(done) {
31 database.dropDatabase().then(function() {}).then(done, done);
32 });
33
34 after(function(done) {
35 database.dropDatabase().then(function() {}).then(done, done);
36 });
37
38 describe('#4', function() {
39 it('should not load duplicate references in array when only one reference is present', function(done) {
40 /*
41 * This issue happens when there are multiple objects in the database,
42 * each object has an array of references, and at least two of the
43 * object's arrays contain the same reference.
44
45 * In this case, both user1 and user2 have a reference to eye1. So
46 * when we call `.find()`, both user1 and user2 will have a
47 * duplicate reference to eye1, which is not correct.
48 */
49
50 class Eye extends Document {
51 constructor() {
52 super();
53 this.color = String;
54 }
55 }
56
57 class User extends Document {
58 constructor() {
59 super();
60 this.eyes = [Eye];
61 }
62 }
63
64 let user1 = User.create();
65 let user2 = User.create();
66 let eye1 = Eye.create({color: 'blue'});
67 let eye2 = Eye.create({color: 'brown'});
68
69 let id;
70
71 eye1.save().then(function(e) {
72 validateId(e);
73 return eye2.save();
74 }).then(function(e) {
75 validateId(e);
76 user1.eyes.push(eye1, eye2);
77 return user1.save();
78 }).then(function(u) {
79 validateId(u);
80 user2.eyes.push(eye1);
81 return user2.save();
82 }).then(function(u) {
83 validateId(u);
84 return User.find({});
85 }).then(function(users) {
86 expect(users).to.have.length(2);
87
88 // Get user1
89 let u1 = String(users[0]._id) === String(user1._id) ? users[0] : users[1];
90
91 // Ensure we have correct number of eyes...
92 expect(u1.eyes).to.have.length(2);
93
94 let e1 = String(u1.eyes[0]._id) === String(eye1._id) ? u1.eyes[0] : u1.eyes[1];
95 let e2 = String(u1.eyes[1]._id) === String(eye2._id) ? u1.eyes[1] : u1.eyes[0];
96
97 // ...and that we have the correct eyes
98 expect(String(e1._id)).to.be.equal(String(eye1._id));
99 expect(String(e2._id)).to.be.equal(String(eye2._id));
100 }).then(done, done);
101 });
102 });
103
104 describe('#5', function() {
105 it('should allow multiple references to the same object in same array', function(done) {
106 /*
107 * This issue happens when an object has an array of
108 * references and there are multiple references to the
109 * same object in the array.
110 *
111 * In the code below, we give the user two references
112 * to the same Eye, but when we load the user there is
113 * only one reference there.
114 */
115
116 class Eye extends Document {
117 constructor() {
118 super();
119 this.color = String;
120 }
121 }
122
123 class User extends Document {
124 constructor() {
125 super();
126 this.eyes = [Eye];
127 }
128 }
129
130 let user = User.create();
131 let eye = Eye.create({color: 'blue'});
132
133 eye.save().then(function(e) {
134 validateId(e);
135 user.eyes.push(eye, eye);
136 return user.save();
137 }).then(function(u) {
138 validateId(u);
139 return User.find({});
140 }).then(function(users) {
141 expect(users).to.have.length(1);
142 expect(users[0].eyes).to.have.length(2);
143
144 let eyeRefs = users[0].eyes.map(function(e) {return e._id;});
145
146 expect(eyeRefs).to.include(eye._id);
147 }).then(done, done);
148 });
149 });
150
151 describe('#8', function() {
152 it('should use virtuals when initializing instance with data', function(done) {
153 /*
154 * This issue happens when a model has virtual setters
155 * and the caller tries to use those setters during
156 * initialization via `create()`. The setters are
157 * never called, but they should be.
158 */
159
160 class User extends Document {
161 constructor() {
162 super();
163 this.firstName = String;
164 this.lastName = String;
165 }
166
167 set fullName(name) {
168 let split = name.split(' ');
169 this.firstName = split[0];
170 this.lastName = split[1];
171 }
172
173 get fullName() {
174 return this.firstName + ' ' + this.lastName;
175 }
176 }
177
178 let user = User.create({
179 fullName: 'Billy Bob'
180 });
181
182 expect(user.firstName).to.be.equal('Billy');
183 expect(user.lastName).to.be.equal('Bob');
184
185 done();
186 });
187 });
188
189 describe('#20', function() {
190 it('should not alias _id to id in queries and returned documents', function(done) {
191 /*
192 * Camo inconsistently aliases the '_id' field to 'id'. When
193 * querying, we must use '_id', but documents are returned
194 * with '_id' AND 'id'. 'id' alias should be removed.
195 *
196 * TODO: Uncomment lines below once '_id' is fully
197 * deprecated and removed.
198 */
199
200 class User extends Document {
201 constructor() {
202 super();
203 this.name = String;
204 }
205 }
206
207 let user = User.create({
208 name: 'Billy Bob'
209 });
210
211 user.save().then(function() {
212 validateId(user);
213
214 //expect(user.id).to.not.exist;
215 expect(user._id).to.exist;
216
217 // Should NOT be able to use 'id' to query
218 return User.findOne({ id: user._id });
219 }).then(function(u) {
220 expect(u).to.not.exist;
221
222 // SHOULD be able to use '_id' to query
223 return User.findOne({ _id: user._id });
224 }).then(function(u) {
225 //expect(u.id).to.not.exist;
226 expect(u).to.exist;
227 validateId(user);
228 }).then(done, done);
229 });
230 });
231
232 describe('#43', function() {
233 /*
234 * Changes made to the model in postValidate and preSave hooks
235 * should be saved to the database
236 */
237 it('should save changes made in postValidate hook', function(done) {
238 class Person extends Document {
239 constructor() {
240 super();
241
242 this.postValidateChange = {
243 type: Boolean,
244 default: false
245 };
246 this.pet = Pet;
247 this.pets = [Pet];
248 }
249
250 static collectionName() {
251 return 'people';
252 }
253
254 postValidate() {
255 this.postValidateChange = true;
256 this.pet.postValidateChange = true;
257 this.pets[0].postValidateChange = true;
258
259 this.pets.push(Pet.create({
260 postValidateChange: true
261 }));
262 }
263 }
264
265 class Pet extends EmbeddedDocument {
266 constructor() {
267 super();
268
269 this.postValidateChange = Boolean;
270 }
271
272 static collectionName() {
273 return 'pets';
274 }
275 }
276
277 let person = Person.create();
278 person.pet = Pet.create();
279 person.pets.push(Pet.create());
280
281 person.save().then(function() {
282 validateId(person);
283 return Person
284 .findOne({ _id: person._id }, { populate: true })
285 .then((p) => {
286 expect(p.postValidateChange).to.be.equal(true);
287 expect(p.pet.postValidateChange).to.be.equal(true);
288 expect(p.pets[0].postValidateChange).to.be.equal(true);
289 expect(p.pets[1].postValidateChange).to.be.equal(true);
290 });
291 }).then(done, done);
292 });
293
294 it('should save changes made in preSave hook', function(done) {
295 class Person extends Document {
296 constructor() {
297 super();
298
299 this.preSaveChange = {
300 type: Boolean,
301 default: false
302 };
303 this.pet = Pet;
304 this.pets = [Pet];
305 }
306
307 static collectionName() {
308 return 'people';
309 }
310
311 postValidate() {
312 this.preSaveChange = true;
313 this.pet.preSaveChange = true;
314 this.pets[0].preSaveChange = true;
315
316 this.pets.push(Pet.create({
317 preSaveChange: true
318 }));
319 }
320 }
321
322 class Pet extends EmbeddedDocument {
323 constructor() {
324 super();
325
326 this.preSaveChange = Boolean;
327 }
328
329 static collectionName() {
330 return 'pets';
331 }
332 }
333
334 let person = Person.create();
335 person.pet = Pet.create();
336 person.pets.push(Pet.create());
337
338 person.save().then(function() {
339 validateId(person);
340 return Person
341 .findOne({ _id: person._id }, { populate: true })
342 .then((p) => {
343 expect(p.preSaveChange).to.be.equal(true);
344 expect(p.pet.preSaveChange).to.be.equal(true);
345 expect(p.pets[0].preSaveChange).to.be.equal(true);
346 expect(p.pets[1].preSaveChange).to.be.equal(true);
347 });
348 }).then(done, done);
349 });
350 });
351
352 describe('#53', function() {
353 /*
354 * Camo should validate that all properties conform to
355 * the type they were given in the schema. However,
356 * array types are not properly validated due to not
357 * properly checking for 'type === Array' and
358 * 'type === []' in validator code.
359 */
360
361 it('should validate Array types properly', function(done) {
362 class Foo extends Document {
363 constructor() {
364 super();
365
366 this.bar = Array;
367 }
368 }
369
370 let foo = Foo.create({bar: [1, 2, 3]});
371
372 foo.save().then(function(f) {
373 expect(f.bar).to.have.length(3);
374 expect(f.bar).to.include(1);
375 expect(f.bar).to.include(2);
376 expect(f.bar).to.include(3);
377
378 foo.bar = 1;
379 return foo.save();
380 }).then(function(f){
381 expect.fail(null, Error, 'Expected error, but got none.');
382 }).catch(function(error) {
383 expect(error).to.be.instanceof(ValidationError);
384 }).then(done, done);
385 });
386
387 it('should validate [] types properly', function(done) {
388
389 class Foo extends Document {
390 constructor() {
391 super();
392
393 this.bar = [];
394 }
395 }
396
397 let foo = Foo.create({bar: [1, 2, 3]});
398
399 foo.save().then(function(f) {
400 expect(f.bar).to.have.length(3);
401 expect(f.bar).to.include(1);
402 expect(f.bar).to.include(2);
403 expect(f.bar).to.include(3);
404
405 foo.bar = 2;
406 return foo.save();
407 }).then(function(f){
408 expect.fail(null, Error, 'Expected error, but got none.');
409 }).catch(function(error) {
410 expect(error).to.be.instanceof(ValidationError);
411 }).then(done, done);
412 });
413 });
414
415 describe('#55', function() {
416 it('should return updated data on findOneAndUpdate when updating nested data', function(done) {
417 /*
418 * When updating nested data with findOneAndUpdate,
419 * the document returned to you should contain
420 * all of the updated data. But due to lack of
421 * support in NeDB versions < 1.8, I had to use
422 * a hack (_.assign) to update the document. This
423 * doesn't properly update nested data.
424 *
425 * Temporary fix is to just reload the document
426 * with findOne.
427 */
428
429 class Contact extends EmbeddedDocument {
430 constructor() {
431 super();
432
433 this.email = String;
434 this.phone = String;
435 }
436 }
437
438 class Person extends Document {
439 constructor() {
440 super();
441 this.name = String;
442 this.contact = Contact;
443 }
444 }
445
446 let person = Person.create({
447 name: 'John Doe',
448 contact: {
449 email: 'john@doe.info',
450 phone: 'NA'
451 }
452 });
453
454 person.save().then(function(person) {
455 return Person.findOneAndUpdate({_id: person._id}, {name: 'John Derp', 'contact.phone': '0123456789'});
456 }).then(function(person) {
457 expect(person.name).to.be.equal('John Derp');
458 expect(person.contact.email).to.be.equal('john@doe.info');
459 expect(person.contact.phone).to.be.equal('0123456789');
460 }).then(done, done);
461 });
462 });
463
464 describe('#57', function() {
465 it('should not save due to Promise.reject in hook', function(done) {
466 /*
467 * Rejecting a Promise inside of a pre-save hook should
468 * cause the save to be aborted, and the .caught() method
469 * should be invoked on the Promise chain. This wasn't
470 * happening due to how the hooks were being collected
471 * and executed.
472 */
473
474 class Foo extends Document {
475 constructor() {
476 super();
477
478 this.bar = String;
479 }
480
481 preValidate() {
482 return Promise.reject('DO NOT SAVE');
483 }
484 }
485
486 Foo.create({bar: 'bar'}).save().then(function(foo) {
487 expect.fail(null, Error, 'Expected error, but got none.');
488 }).catch(function(error) {
489 expect(error).to.be.equal('DO NOT SAVE');
490 }).then(done, done);
491 });
492 });
493});
\No newline at end of file