UNPKG

39.4 kBJavaScriptView Raw
1// Copyright IBM Corp. 2011,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'use strict';
7
8const Schema = require('../index').Schema;
9const Text = Schema.Text;
10
11let nbSchemaRequests = 0;
12
13let batch;
14let schemaName;
15
16function it(name, cases) {
17 batch[schemaName][name] = cases;
18}
19
20function skip(name) {
21 delete batch[schemaName][name];
22}
23
24module.exports = function testSchema(exportCasesHere, dataSource) {
25 batch = exportCasesHere;
26 schemaName = dataSource.name;
27 if (dataSource.name.match(/^\/.*\/test\/\.\.$/)) {
28 schemaName = schemaName.split('/').slice(-3).shift();
29 }
30 let start;
31
32 batch['should connect to database'] = function(test) {
33 start = Date.now();
34 if (dataSource.connected) return test.done();
35 dataSource.on('connected', test.done);
36 };
37
38 dataSource.log = function(a) {
39 console.log(a);
40 nbSchemaRequests++;
41 };
42
43 batch[schemaName] = {};
44
45 testOrm(dataSource);
46
47 batch['all tests done'] = function(test) {
48 test.done();
49 process.nextTick(allTestsDone);
50 };
51
52 function allTestsDone() {
53 // dataSource.disconnect();
54 console.log('Test done in %dms\n', Date.now() - start);
55 }
56};
57
58Object.defineProperty(module.exports, 'it', {
59 writable: true,
60 enumerable: false,
61 configurable: true,
62 value: it,
63});
64
65Object.defineProperty(module.exports, 'skip', {
66 writable: true,
67 enumerable: false,
68 configurable: true,
69 value: skip,
70});
71
72function clearAndCreate(model, data, callback) {
73 const createdItems = [];
74 model.destroyAll(function() {
75 nextItem(null, null);
76 });
77
78 let itemIndex = 0;
79
80 function nextItem(err, lastItem) {
81 if (lastItem !== null) {
82 createdItems.push(lastItem);
83 }
84 if (itemIndex >= data.length) {
85 callback(createdItems);
86 return;
87 }
88 model.create(data[itemIndex], nextItem);
89 itemIndex++;
90 }
91}
92
93/* eslint-disable mocha/handle-done-callback */
94function testOrm(dataSource) {
95 const requestsAreCounted = dataSource.name !== 'mongodb';
96
97 let Post, User, Passport, Log, Dog;
98
99 it('should define class', function(test) {
100 User = dataSource.define('User', {
101 name: {type: String, index: true},
102 email: {type: String, index: true},
103 bio: Text,
104 approved: Boolean,
105 joinedAt: Date,
106 age: Number,
107 passwd: {type: String, index: true},
108 });
109
110 Dog = dataSource.define('Dog', {
111 name: {type: String, limit: 64, allowNull: false},
112 });
113
114 Log = dataSource.define('Log', {
115 ownerId: {type: Number, allowNull: true},
116 name: {type: String, limit: 64, allowNull: false},
117 });
118
119 Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'});
120
121 dataSource.extendModel('User', {
122 settings: {type: Schema.JSON},
123 extra: Object,
124 });
125
126 const newuser = new User({settings: {hey: 'you'}});
127 test.ok(newuser.settings);
128
129 Post = dataSource.define('Post', {
130 title: {type: String, length: 255, index: true},
131 subject: {type: String},
132 content: {type: Text},
133 date: {type: Date, default: function() {
134 return new Date;
135 }, index: true},
136 published: {type: Boolean, default: false, index: true},
137 likes: [],
138 related: [RelatedPost],
139 }, {table: 'posts'});
140
141 function RelatedPost() {
142 }
143
144 RelatedPost.prototype.someMethod = function() {
145 return this.parent;
146 };
147
148 Post.validateAsync('title', function(err, done) {
149 process.nextTick(done);
150 });
151
152 User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
153 // creates instance methods:
154 // user.posts(conds)
155 // user.posts.build(data) // like new Post({userId: user.id});
156 // user.posts.create(data) // build and save
157 // user.posts.find
158
159 // User.hasOne('latestPost', {model: Post, foreignKey: 'postId'});
160
161 // User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'});
162 // creates instance methods:
163 // user.latestPost()
164 // user.latestPost.build(data)
165 // user.latestPost.create(data)
166
167 Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
168 // creates instance methods:
169 // post.author(callback) -- getter when called with function
170 // post.author() -- sync getter when called without params
171 // post.author(user) -- setter when called with object
172
173 Passport = dataSource.define('Passport', {
174 number: String,
175 });
176
177 Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'});
178 User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'});
179
180 const user = new User;
181
182 test.ok(User instanceof Function);
183
184 // class methods
185 test.ok(User.find instanceof Function);
186 test.ok(User.create instanceof Function);
187
188 // instance methods
189 test.ok(user.save instanceof Function);
190
191 dataSource.automigrate(function(err) {
192 if (err) {
193 console.log('Error while migrating');
194 console.log(err);
195 } else {
196 test.done();
197 }
198 });
199 });
200
201 it('should initialize object properly', function(test) {
202 const hw = 'Hello word',
203 now = Date.now(),
204 post = new Post({title: hw}),
205 anotherPost = Post({title: 'Resig style constructor'});
206
207 test.equal(post.title, hw);
208 test.ok(!post.propertyChanged('title'), 'property changed: title');
209 post.title = 'Goodbye, Lenin';
210 test.equal(post.title_was, hw);
211 test.ok(post.propertyChanged('title'));
212 test.strictEqual(post.published, false);
213 test.ok(post.date >= now);
214 test.ok(post.isNewRecord());
215 test.ok(anotherPost instanceof Post);
216 test.ok(anotherPost.title, 'Resig style constructor');
217 test.done();
218 });
219
220 it('should save object', function(test) {
221 const title = 'Initial title', title2 = 'Hello world',
222 date = new Date;
223
224 Post.create({
225 title: title,
226 date: date,
227 }, function(err, obj) {
228 test.ok(obj.id, 'Object id should present');
229 test.equals(obj.title, title);
230 // test.equals(obj.date, date);
231 obj.title = title2;
232 test.ok(obj.propertyChanged('title'), 'Title changed');
233 obj.save(function(err, obj) {
234 test.equal(obj.title, title2);
235 test.ok(!obj.propertyChanged('title'));
236
237 const p = new Post({title: 1});
238 p.title = 2;
239 p.save(function(err, obj) {
240 test.ok(!p.propertyChanged('title'));
241 p.title = 3;
242 test.ok(p.propertyChanged('title'));
243 test.equal(p.title_was, 2);
244 p.save(function() {
245 test.equal(p.title_was, 3);
246 test.ok(!p.propertyChanged('title'));
247 test.done();
248 });
249 });
250 });
251 });
252 });
253
254 it('should create object with initial data', function(test) {
255 const title = 'Initial title',
256 date = new Date;
257
258 Post.create({
259 title: title,
260 date: date,
261 }, function(err, obj) {
262 test.ok(obj.id);
263 test.equals(obj.title, title);
264 test.equals(obj.date, date);
265 Post.findById(obj.id, function() {
266 test.equal(obj.title, title);
267 test.equal(obj.date.toString(), date.toString());
268 test.done();
269 });
270 });
271 });
272
273 it('should save only dataSource-defined field in database', function(test) {
274 Post.create({title: '1602', nonSchemaField: 'some value'}, function(err, post) {
275 test.ok(!post.nonSchemaField);
276 post.a = 1;
277 post.save(function() {
278 test.ok(post.a);
279 post.reload(function(err, psto) {
280 test.ok(!psto.a);
281 test.done();
282 });
283 });
284 });
285 });
286
287 /*
288 it('should not create new instances for the same object', function (test) {
289 var title = 'Initial title';
290 Post.create({ title: title }, function (err, post) {
291 test.ok(post.id, 'Object should have id');
292 test.equals(post.title, title);
293 Post.findById(post.id, function (err, foundPost) {
294 if (err) throw err;
295 test.equal(post.title, title);
296 test.strictEqual(post, foundPost);
297 test.done();
298 });
299 });
300 });
301 */
302
303 it('should not re-instantiate object on saving', function(test) {
304 const title = 'Initial title';
305 const post = new Post({title: title});
306 post.save(function(err, savedPost) {
307 test.strictEqual(post, savedPost);
308 test.done();
309 });
310 });
311
312 it('should destroy object', function(test) {
313 Post.create(function(err, post) {
314 Post.exists(post.id, function(err, exists) {
315 test.ok(exists, 'Object exists');
316 post.destroy(function() {
317 Post.exists(post.id, function(err, exists) {
318 if (err) console.log(err);
319 test.ok(!exists, 'Hey! ORM told me that object exists, ' +
320 ' but it looks like it doesn\'t. Something went wrong...');
321 Post.findById(post.id, function(err, obj) {
322 test.equal(obj, null, 'Param obj should be null');
323 test.done();
324 });
325 });
326 });
327 });
328 });
329 });
330
331 it('should handle virtual attributes', function(test) {
332 const salt = 's0m3s3cr3t5a1t';
333
334 User.setter.passwd = function(password) {
335 this._passwd = calcHash(password, salt);
336 };
337
338 function calcHash(pass, salt) {
339 const crypto = require('crypto');
340 const hash = crypto.createHash('sha256');
341 hash.update(pass);
342 hash.update(salt);
343 return hash.digest('base64');
344 }
345
346 const u = new User;
347 u.passwd = 's3cr3t';
348 test.equal(u.passwd, calcHash('s3cr3t', salt));
349 test.done();
350 });
351
352 // it('should serialize JSON type', function (test) {
353 // User.create({settings: {hello: 'world'}}, function (err, user) {
354 // test.ok(user.id);
355 // test.equal(user.settings.hello, 'world');
356 // User.find(user.id, function (err, u) {
357 // console.log(u.settings);
358 // test.equal(u.settings.hello, 'world');
359 // test.done();
360 // });
361 // });
362 // });
363
364 it('should update single attribute', function(test) {
365 Post.create({title: 'title', content: 'content', published: true}, function(err, post) {
366 post.content = 'New content';
367 post.updateAttribute('title', 'New title', function() {
368 test.equal(post.title, 'New title');
369 test.ok(!post.propertyChanged('title'));
370 test.equal(post.content, 'New content', 'dirty state saved');
371 test.ok(post.propertyChanged('content'));
372 post.reload(function(err, post) {
373 test.equal(post.title, 'New title');
374 test.ok(!post.propertyChanged('title'), 'title not changed');
375 test.equal(post.content, 'content', 'real value turned back');
376 test.ok(!post.propertyChanged('content'), 'content unchanged');
377 test.done();
378 });
379 });
380 });
381 });
382
383 let countOfposts, countOfpostsFiltered;
384 it('should fetch collection', function(test) {
385 Post.all(function(err, posts) {
386 countOfposts = posts.length;
387 test.ok(countOfposts > 0);
388 test.ok(posts[0] instanceof Post);
389 countOfpostsFiltered = posts.filter(function(p) {
390 return p.title === 'title';
391 }).length;
392 test.done();
393 });
394 });
395
396 it('should find records filtered with multiple attributes', function(test) {
397 const d = new Date;
398 Post.create({title: 'title', content: 'content', published: true, date: d}, function(err, post) {
399 Post.all({where: {title: 'title', date: d, published: true}}, function(err, res) {
400 test.equals(res.length, 1, 'Filtering Posts returns one post');
401 test.done();
402 });
403 });
404 });
405
406 if (
407 !dataSource.name.match(/redis/) &&
408 dataSource.name !== 'memory' &&
409 dataSource.name !== 'neo4j' &&
410 dataSource.name !== 'cradle'
411 )
412 it('relations key is working', function(test) {
413 test.ok(User.relations, 'Relations key should be defined');
414 test.ok(User.relations.posts, 'posts relation should exist on User');
415 test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany');
416 test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple');
417 test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table');
418 test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table');
419
420 test.ok(Post.relations, 'Relations key should be defined');
421 test.ok(Post.relations.author, 'author relation should exist on Post');
422 test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo');
423 test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple');
424 test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table');
425 test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table');
426 test.done();
427 });
428
429 it('should handle hasMany relationship', function(test) {
430 User.create(function(err, u) {
431 if (err) return console.log(err);
432 test.ok(u.posts, 'Method defined: posts');
433 test.ok(u.posts.build, 'Method defined: posts.build');
434 test.ok(u.posts.create, 'Method defined: posts.create');
435 u.posts.create(function(err, post) {
436 if (err) return console.log(err);
437 u.posts(function(err, posts) {
438 test.equal(posts.pop().id.toString(), post.id.toString());
439 test.done();
440 });
441 });
442 });
443 });
444
445 it('should navigate variations of belongsTo regardless of column name', function(test) {
446 Dog.create({name: 'theDog'}, function(err, obj) {
447 test.ok(obj instanceof Dog);
448 Log.create({name: 'theLog', ownerId: obj.id}, function(err, obj) {
449 test.ok(obj instanceof Log);
450 obj.owner(function(err, obj) {
451 test.ok(!err, 'Should not have an error.'); // Before cba174b this would be 'Error: Permission denied'
452 if (err) {
453 console.log('Found: ' + err);
454 }
455 test.ok(obj, 'Should not find null or undefined.'); // Before cba174b this could be null or undefined.
456 test.ok(obj instanceof Dog, 'Should find a Dog.');
457 if (obj) { // Since test won't stop on fail, have to check before accessing obj.name.
458 test.ok(obj.name, 'Should have a name.');
459 }
460 if (obj && obj.name) {
461 test.equal(obj.name, 'theDog', 'The owner of theLog is theDog.');
462 }
463 test.done();
464 });
465 });
466 });
467 });
468
469 it('hasMany should support additional conditions', function(test) {
470 User.create(function(e, u) {
471 u.posts.create({}, function(e, p) {
472 u.posts({where: {id: p.id}}, function(e, posts) {
473 test.equal(posts.length, 1, 'There should be only 1 post.');
474 test.done();
475 });
476 });
477 });
478 });
479
480 /* eslint-disable max-len */
481 it('hasMany should be cached', function(test) {
482 // User.create(function (e, u) {
483 // u.posts.create({}, function (e, p) {
484 // find all posts for a user.
485 // Finding one post with an existing author associated
486 Post.all(function(err, posts) {
487 // We try to get the first post with a userId != NULL
488 for (let i = 0; i < posts.length; i++) {
489 const post = posts[i];
490 if (post.userId) {
491 // We could get the user with belongs to relationship but it is better if there is no interactions.
492 User.findById(post.userId, function(err, user) {
493 User.create(function(err, voidUser) {
494 Post.create({userId: user.id}, function() {
495 // There can't be any concurrency because we are counting requests
496 // We are first testing cases when user has posts
497 user.posts(function(err, data) {
498 const nbInitialRequests = nbSchemaRequests;
499 user.posts(function(err, data2) {
500 test.equal(data.length, 2, 'There should be 2 posts.');
501 test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.');
502 requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
503
504 if (dataSource.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above)
505 test.done();
506 } else {
507 user.posts({where: {id: data[0].id}}, function(err, data) {
508 test.equal(data.length, 1, 'There should be only one post.');
509 requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.');
510
511 user.posts(function(err, data) {
512 test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.');
513 requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.');
514
515 // We are now testing cases when user doesn't have any post
516 voidUser.posts(function(err, data) {
517 const nbInitialRequests = nbSchemaRequests;
518 voidUser.posts(function(err, data2) {
519 test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).');
520 test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).');
521 requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
522
523 voidUser.posts(true, function(err, data3) {
524 test.equal(data3.length, 0, 'There shouldn\'t be any posts.');
525 requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.');
526
527 test.done();
528 });
529 });
530 });
531 });
532 });
533 }
534 });
535 });
536 });
537 });
538 });
539 break;
540 }
541 }
542 });
543 });
544 /* eslint-enable max-len */
545
546 // it('should handle hasOne relationship', function (test) {
547 // User.create(function (err, u) {
548 // if (err) return console.log(err);
549 // });
550 // });
551
552 it('should support scopes', function(test) {
553 let wait = 2;
554
555 test.ok(Post.scope, 'Scope supported');
556 Post.scope('published', {where: {published: true}});
557 test.ok(typeof Post.published === 'function');
558 test.ok(Post.published._scope.where.published === true);
559 const post = Post.published.build();
560 test.ok(post.published, 'Can build');
561 test.ok(post.isNewRecord());
562 Post.published.create(function(err, psto) {
563 if (err) return console.log(err);
564 test.ok(psto.published);
565 test.ok(!psto.isNewRecord());
566 done();
567 });
568
569 User.create(function(err, u) {
570 if (err) return console.log(err);
571 test.ok(typeof u.posts.published == 'function');
572 test.ok(u.posts.published._scope.where.published);
573 console.log(u.posts.published._scope);
574 test.equal(u.posts.published._scope.where.userId, u.id);
575 done();
576 });
577
578 function done() {
579 if (--wait === 0) test.done();
580 }
581 });
582
583 it('should return type of property', function(test) {
584 test.equal(Post.getPropertyType('title'), 'String');
585 test.equal(Post.getPropertyType('content'), 'Text');
586 const p = new Post;
587 test.equal(p.getPropertyType('title'), 'String');
588 test.equal(p.getPropertyType('content'), 'Text');
589 test.done();
590 });
591
592 it('should handle ORDER clause', function(test) {
593 const titles = [
594 {title: 'Title A', subject: 'B'},
595 {title: 'Title Z', subject: 'A'},
596 {title: 'Title M', subject: 'C'},
597 {title: 'Title A', subject: 'A'},
598 {title: 'Title B', subject: 'A'},
599 {title: 'Title C', subject: 'D'},
600 ];
601 const isRedis = Post.dataSource.name === 'redis';
602 const dates = isRedis ? [5, 9, 0, 17, 10, 9] : [
603 new Date(1000 * 5),
604 new Date(1000 * 9),
605 new Date(1000 * 0),
606 new Date(1000 * 17),
607 new Date(1000 * 10),
608 new Date(1000 * 9),
609 ];
610 titles.forEach(function(t, i) {
611 Post.create({title: t.title, subject: t.subject, date: dates[i]}, done);
612 });
613
614 let i = 0, tests = 0;
615
616 function done(err, obj) {
617 if (++i === titles.length) {
618 doFilterAndSortTest();
619 doFilterAndSortReverseTest();
620 doStringTest();
621 doNumberTest();
622
623 if (dataSource.name == 'mongoose') {
624 doMultipleSortTest();
625 doMultipleReverseSortTest();
626 }
627 }
628 }
629
630 function compare(a, b) {
631 if (a.title < b.title) return -1;
632 if (a.title > b.title) return 1;
633 return 0;
634 }
635
636 // Post.dataSource.log = console.log;
637
638 function doStringTest() {
639 tests += 1;
640 Post.all({order: 'title'}, function(err, posts) {
641 if (err) console.log(err);
642 test.equal(posts.length, 6);
643 titles.sort(compare).forEach(function(t, i) {
644 if (posts[i]) test.equal(posts[i].title, t.title);
645 });
646 finished();
647 });
648 }
649
650 function doNumberTest() {
651 tests += 1;
652 Post.all({order: 'date'}, function(err, posts) {
653 if (err) console.log(err);
654 test.equal(posts.length, 6);
655 dates.sort(numerically).forEach(function(d, i) {
656 if (posts[i])
657 test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest');
658 });
659 finished();
660 });
661 }
662
663 function doFilterAndSortTest() {
664 tests += 1;
665 Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function(err, posts) {
666 if (err) console.log(err);
667 console.log(posts.length);
668 test.equal(posts.length, 2, 'Exactly 2 posts returned by query');
669 ['Title C', 'Title Z'].forEach(function(t, i) {
670 if (posts[i]) {
671 test.equal(posts[i].title, t, 'doFilterAndSortTest');
672 }
673 });
674 finished();
675 });
676 }
677
678 function doFilterAndSortReverseTest() {
679 tests += 1;
680 Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function(err, posts) {
681 if (err) console.log(err);
682 test.equal(posts.length, 2, 'Exactly 2 posts returned by query');
683 ['Title Z', 'Title C'].forEach(function(t, i) {
684 if (posts[i]) {
685 test.equal(posts[i].title, t, 'doFilterAndSortReverseTest');
686 }
687 });
688 finished();
689 });
690 }
691
692 function doMultipleSortTest() {
693 tests += 1;
694 Post.all({order: 'title ASC, subject ASC'}, function(err, posts) {
695 if (err) console.log(err);
696 test.equal(posts.length, 6);
697 test.equal(posts[0].title, 'Title A');
698 test.equal(posts[0].subject, 'A');
699 test.equal(posts[1].title, 'Title A');
700 test.equal(posts[1].subject, 'B');
701 test.equal(posts[5].title, 'Title Z');
702 finished();
703 });
704 }
705
706 function doMultipleReverseSortTest() {
707 tests += 1;
708 Post.all({order: 'title ASC, subject DESC'}, function(err, posts) {
709 if (err) console.log(err);
710 test.equal(posts.length, 6);
711 test.equal(posts[0].title, 'Title A');
712 test.equal(posts[0].subject, 'B');
713 test.equal(posts[1].title, 'Title A');
714 test.equal(posts[1].subject, 'A');
715 test.equal(posts[5].title, 'Title Z');
716 finished();
717 });
718 }
719
720 let fin = 0;
721
722 function finished() {
723 if (++fin === tests) {
724 test.done();
725 }
726 }
727
728 // TODO: do mixed test, do real dates tests, ensure that dates stored in UNIX timestamp format
729
730 function numerically(a, b) {
731 return a - b;
732 }
733 });
734
735 // if (
736 // !dataSource.name.match(/redis/) &&
737 // dataSource.name !== 'memory' &&
738 // dataSource.name !== 'neo4j' &&
739 // dataSource.name !== 'cradle' &&
740 // dataSource.name !== 'nano'
741 // )
742 // it('should allow advanced queying: lt, gt, lte, gte, between', function (test) {
743 // Post.destroyAll(function () {
744 // Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done);
745 // Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done);
746 // Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done);
747 // Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done);
748 // Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done);
749 // Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done);
750 // Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done);
751 // Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done);
752 // Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done);
753 // });
754
755 // var posts = 9;
756 // function done() {
757 // if (--posts === 0) makeTest();
758 // }
759
760 // function makeTest() {
761 // // gt
762 // Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
763 // test.equal(posts.length, 2, 'gt');
764 // ok();
765 // });
766
767 // // gte
768 // Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
769 // test.equal(posts.length, 3, 'gte');
770 // ok();
771 // });
772
773 // // lte
774 // Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
775 // test.equal(posts.length, 7, 'lte');
776 // ok();
777 // });
778
779 // // lt
780 // Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
781 // test.equal(posts.length, 6, 'lt');
782 // ok();
783 // });
784
785 // // between
786 // Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) {
787 // test.equal(posts.length, 5, 'between');
788 // ok();
789 // });
790 // }
791
792 // var tests = 5;
793 // function ok() {
794 // if (--tests === 0) test.done();
795 // }
796 // });
797
798 // if (
799 // dataSource.name === 'mysql' ||
800 // dataSource.name === 'postgres'
801 // )
802 // it('should allow IN or NOT IN', function (test) {
803 // User.destroyAll(function () {
804 // User.create({name: 'User A', age: 21}, done);
805 // User.create({name: 'User B', age: 22}, done);
806 // User.create({name: 'User C', age: 23}, done);
807 // User.create({name: 'User D', age: 24}, done);
808 // User.create({name: 'User E', age: 25}, done);
809 // });
810
811 // var users = 5;
812 // function done() {
813 // if (--users === 0) makeTest();
814 // }
815
816 // function makeTest() {
817 // // IN with empty array should return nothing
818 // User.all({where: {name: {inq: []}}}, function (err, users) {
819 // test.equal(users.length, 0, 'IN with empty array returns nothing');
820 // ok();
821 // });
822
823 // // NOT IN with empty array should return everything
824 // User.all({where: {name: {nin: []}}}, function (err, users) {
825 // test.equal(users.length, 5, 'NOT IN with empty array returns everything');
826 // ok();
827 // });
828
829 // // IN [User A] returns user with name = User A
830 // User.all({where: {name: {inq: ['User A']}}}, function (err, users) {
831 // test.equal(users.length, 1, 'IN searching one existing value returns 1 user');
832 // test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A');
833 // ok();
834 // });
835
836 // // NOT IN [User A] returns users with name != User A
837 // User.all({where: {name: {nin: ['User A']}}}, function (err, users) {
838 // test.equal(users.length, 4, 'IN [User A] returns users with name != User A');
839 // ok();
840 // });
841
842 // // IN [User A, User B] returns users with name = User A OR name = User B
843 // User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) {
844 // test.equal(users.length, 2, 'IN searching two existing values returns 2 users');
845 // ok();
846 // });
847
848 // // NOT IN [User A, User B] returns users with name != User A AND name != User B
849 // User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) {
850 // test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B');
851 // ok();
852 // });
853
854 // // IN works with numbers too
855 // User.all({where: {age: {inq: [21, 22]}}}, function (err, users) {
856 // test.equal(users.length, 2, 'IN works with numbers too');
857 // ok();
858 // });
859
860 // // NOT IN works with numbers too
861 // User.all({where: {age: {nin: [21, 22]}}}, function (err, users) {
862 // test.equal(users.length, 3, 'NOT IN works with numbers too');
863 // ok();
864 // });
865 // }
866
867 // var tests = 8;
868 // function ok() {
869 // if (--tests === 0) test.done();
870 // }
871 // });
872
873 it('should handle order clause with direction', function(test) {
874 let wait = 0;
875 const emails = [
876 'john@hcompany.com',
877 'tom@hcompany.com',
878 'admin@hcompany.com',
879 'tin@hcompany.com',
880 'mike@hcompany.com',
881 'susan@hcompany.com',
882 'test@hcompany.com',
883 ];
884 User.destroyAll(function() {
885 emails.forEach(function(email) {
886 wait += 1;
887 User.create({email: email, name: 'Nick'}, done);
888 });
889 });
890 let tests = 2;
891
892 function done() {
893 process.nextTick(function() {
894 if (--wait === 0) {
895 doSortTest();
896 doReverseSortTest();
897 }
898 });
899 }
900
901 function doSortTest() {
902 User.all({order: 'email ASC', where: {name: 'Nick'}}, function(err, users) {
903 const _emails = emails.sort();
904 users.forEach(function(user, i) {
905 test.equal(_emails[i], user.email, 'ASC sorting');
906 });
907 testDone();
908 });
909 }
910
911 function doReverseSortTest() {
912 User.all({order: 'email DESC', where: {name: 'Nick'}}, function(err, users) {
913 const _emails = emails.sort().reverse();
914 users.forEach(function(user, i) {
915 test.equal(_emails[i], user.email, 'DESC sorting');
916 });
917 testDone();
918 });
919 }
920
921 function testDone() {
922 if (--tests === 0) test.done();
923 }
924 });
925
926 it('should return id in find result even after updateAttributes', function(test) {
927 Post.create(function(err, post) {
928 const id = post.id;
929 test.ok(post.published === false);
930 post.updateAttributes({title: 'hey', published: true}, function() {
931 Post.find(id, function(err, post) {
932 test.ok(!!post.published, 'Update boolean field');
933 test.ok(post.id);
934 test.done();
935 });
936 });
937 });
938 });
939
940 it('should handle belongsTo correctly', function(test) {
941 const passport = new Passport({ownerId: 16});
942 // sync getter
943 test.equal(passport.owner(), 16);
944 // sync setter
945 passport.owner(18);
946 test.equal(passport.owner(), 18);
947 test.done();
948 });
949
950 it('should query one record', function(test) {
951 test.expect(4);
952 Post.findOne(function(err, post) {
953 test.ok(post && post.id);
954 Post.findOne({where: {title: 'hey'}}, function(err, post) {
955 if (err) {
956 console.log(err);
957 return test.done();
958 }
959 test.equal(post && post.constructor.modelName, 'Post');
960 test.equal(post && post.title, 'hey');
961 Post.findOne({where: {title: 'not exists'}}, function(err, post) {
962 test.ok(post === null);
963 test.done();
964 });
965 });
966 });
967 });
968
969 // if (
970 // !dataSource.name.match(/redis/) &&
971 // dataSource.name !== 'memory' &&
972 // dataSource.name !== 'neo4j' &&
973 // dataSource.name !== 'cradle' &&
974 // dataSource.name !== 'nano'
975 // )
976 // it('belongsTo should be cached', function (test) {
977 // User.findOne(function(err, user) {
978
979 // var passport = new Passport({ownerId: user.id});
980 // var passport2 = new Passport({ownerId: null});
981
982 // // There can't be any concurrency because we are counting requests
983 // // We are first testing cases when passport has an owner
984 // passport.owner(function(err, data) {
985 // var nbInitialRequests = nbSchemaRequests;
986 // passport.owner(function(err, data2) {
987 // test.equal(data.id, data2.id, 'The value should remain the same');
988 // requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
989
990 // // We are now testing cases when passport has not an owner
991 // passport2.owner(function(err, data) {
992 // var nbInitialRequests2 = nbSchemaRequests;
993 // passport2.owner(function(err, data2) {
994 // test.equal(data, null, 'The value should be null since there is no owner');
995 // test.equal(data, data2, 'The value should remain the same (null)');
996 // requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.');
997
998 // passport2.owner(user.id);
999 // passport2.owner(function(err, data3) {
1000 // test.equal(data3.id, user.id, 'Owner should now be the user.');
1001 // requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.');
1002
1003 // passport2.owner(true, function(err, data4) {
1004 // test.equal(data3.id, data3.id, 'The value should remain the same');
1005 // requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.');
1006 // test.done();
1007 // });
1008 // });
1009 // });
1010 // });
1011
1012 // });
1013 // });
1014 // });
1015
1016 // });
1017
1018 if (dataSource.name !== 'mongoose' && dataSource.name !== 'neo4j')
1019 it('should update or create record', function(test) {
1020 const newData = {
1021 id: 1,
1022 title: 'New title (really new)',
1023 content: 'Some example content (updated)',
1024 };
1025 Post.updateOrCreate(newData, function(err, updatedPost) {
1026 if (err) throw err;
1027 test.ok(updatedPost);
1028 if (!updatedPost) throw Error('No post!');
1029
1030 if (dataSource.name !== 'mongodb') {
1031 test.equal(newData.id, updatedPost.toObject().id);
1032 }
1033 test.equal(newData.title, updatedPost.toObject().title);
1034 test.equal(newData.content, updatedPost.toObject().content);
1035
1036 Post.findById(updatedPost.id, function(err, post) {
1037 if (err) throw err;
1038 if (!post) throw Error('No post!');
1039 if (dataSource.name !== 'mongodb') {
1040 test.equal(newData.id, post.toObject().id);
1041 }
1042 test.equal(newData.title, post.toObject().title);
1043 test.equal(newData.content, post.toObject().content);
1044 Post.updateOrCreate({id: 100001, title: 'hey'}, function(err, post) {
1045 if (dataSource.name !== 'mongodb') test.equal(post.id, 100001);
1046 test.equal(post.title, 'hey');
1047 Post.findById(post.id, function(err, post) {
1048 if (!post) throw Error('No post!');
1049 test.done();
1050 });
1051 });
1052 });
1053 });
1054 });
1055
1056 it('should work with custom setters and getters', function(test) {
1057 User.dataSource.defineForeignKey('User', 'passwd');
1058 User.setter.passwd = function(pass) {
1059 this._passwd = pass + 'salt';
1060 };
1061 const u = new User({passwd: 'qwerty'});
1062 test.equal(u.passwd, 'qwertysalt');
1063 u.save(function(err, user) {
1064 User.findById(user.id, function(err, user) {
1065 test.ok(user !== u);
1066 test.equal(user.passwd, 'qwertysalt');
1067 User.all({where: {passwd: 'qwertysalt'}}, function(err, users) {
1068 test.ok(users[0] !== user);
1069 test.equal(users[0].passwd, 'qwertysalt');
1070 User.create({passwd: 'asalat'}, function(err, usr) {
1071 test.equal(usr.passwd, 'asalatsalt');
1072 User.upsert({passwd: 'heyman'}, function(err, us) {
1073 test.equal(us.passwd, 'heymansalt');
1074 User.findById(us.id, function(err, user) {
1075 test.equal(user.passwd, 'heymansalt');
1076 test.done();
1077 });
1078 });
1079 });
1080 });
1081 });
1082 });
1083 });
1084
1085 it('should work with typed and untyped nested collections', function(test) {
1086 const post = new Post;
1087 const like = post.likes.push({foo: 'bar'});
1088 test.equal(like.constructor.name, 'ListItem');
1089 const related = post.related.push({hello: 'world'});
1090 test.ok(related.someMethod);
1091 post.save(function(err, p) {
1092 test.equal(p.likes.nextid, 2);
1093 p.likes.push({second: 2});
1094 p.likes.push({third: 3});
1095 p.save(function(err) {
1096 Post.findById(p.id, function(err, pp) {
1097 test.equal(pp.likes.length, 3);
1098 test.ok(pp.likes[3].third);
1099 test.ok(pp.likes[2].second);
1100 test.ok(pp.likes[1].foo);
1101 pp.likes.remove(2);
1102 test.equal(pp.likes.length, 2);
1103 test.ok(!pp.likes[2]);
1104 pp.likes.remove(pp.likes[1]);
1105 test.equal(pp.likes.length, 1);
1106 test.ok(!pp.likes[1]);
1107 test.ok(pp.likes[3]);
1108 pp.save(function() {
1109 Post.findById(p.id, function(err, pp) {
1110 test.equal(pp.likes.length, 1);
1111 test.ok(!pp.likes[1]);
1112 test.ok(pp.likes[3]);
1113 test.done();
1114 });
1115 });
1116 });
1117 });
1118 });
1119 });
1120
1121 it('should find or create', function(test) {
1122 const email = 'some email ' + Math.random();
1123 User.findOrCreate({where: {email: email}}, function(err, u, created) {
1124 test.ok(u);
1125 test.ok(!u.age);
1126 test.ok(created);
1127 User.findOrCreate({where: {email: email}}, {age: 21}, function(err, u2, created) {
1128 test.equals(u.id.toString(), u2.id.toString(), 'Same user ids');
1129 test.ok(!u2.age);
1130 test.ok(!created);
1131 test.done();
1132 });
1133 });
1134 });
1135}