1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 | const should = require('./init.js');
|
9 | const assert = require('assert');
|
10 | const async = require('async');
|
11 |
|
12 | const jdb = require('../');
|
13 | const ModelBuilder = jdb.ModelBuilder;
|
14 | const DataSource = jdb.DataSource;
|
15 |
|
16 | describe('ModelBuilder', function() {
|
17 | it('supports plain models', function(done) {
|
18 | const modelBuilder = new ModelBuilder();
|
19 |
|
20 | const User = modelBuilder.define('User', {
|
21 | name: String,
|
22 | bio: ModelBuilder.Text,
|
23 | approved: Boolean,
|
24 | joinedAt: Date,
|
25 | age: Number,
|
26 | });
|
27 |
|
28 |
|
29 | User.prototype.getNameAndAge = function() {
|
30 | return this.name + ', ' + this.age;
|
31 | };
|
32 |
|
33 | modelBuilder.models.should.be.type('object').and.have.property('User').exactly(User);
|
34 | modelBuilder.definitions.should.be.type('object').and.have.property('User');
|
35 |
|
36 | const user = new User({name: 'Joe', age: 20, xyz: false});
|
37 |
|
38 | User.modelName.should.equal('User');
|
39 | user.should.be.type('object').and.have.property('name', 'Joe');
|
40 | user.should.have.property('name', 'Joe');
|
41 | user.should.have.property('age', 20);
|
42 | user.should.have.property('xyz', false);
|
43 | user.should.have.property('bio', undefined);
|
44 | done(null, User);
|
45 | });
|
46 |
|
47 | it('ignores unknown properties in strict mode', function(done) {
|
48 | const modelBuilder = new ModelBuilder();
|
49 |
|
50 | const User = modelBuilder.define('User', {name: String, bio: String}, {strict: true});
|
51 |
|
52 | const user = new User({name: 'Joe', age: 20});
|
53 |
|
54 | User.modelName.should.equal('User');
|
55 | user.should.be.type('object');
|
56 | user.should.have.property('name', 'Joe');
|
57 | user.should.not.have.property('age');
|
58 | user.toObject().should.not.have.property('age');
|
59 | user.toObject(true).should.not.have.property('age');
|
60 | user.should.have.property('bio', undefined);
|
61 | done(null, User);
|
62 | });
|
63 |
|
64 | it('ignores non-predefined properties in strict mode', function(done) {
|
65 | const modelBuilder = new ModelBuilder();
|
66 |
|
67 | const User = modelBuilder.define('User', {name: String, bio: String}, {strict: true});
|
68 |
|
69 | const user = new User({name: 'Joe'});
|
70 | user.age = 10;
|
71 | user.bio = 'me';
|
72 |
|
73 | user.should.have.property('name', 'Joe');
|
74 | user.should.have.property('bio', 'me');
|
75 |
|
76 |
|
77 | user.toObject().should.not.have.property('age');
|
78 | user.toObject(true).should.not.have.property('age');
|
79 | user.toObject(false).should.have.property('age', 10);
|
80 |
|
81 |
|
82 | user.toObject().should.have.property('bio', 'me');
|
83 | user.toObject(true).should.have.property('bio', 'me');
|
84 | user.toObject(false).should.have.property('bio', 'me');
|
85 | done(null, User);
|
86 | });
|
87 |
|
88 | it('throws an error when unknown properties are used if strict=throw', function(done) {
|
89 | const modelBuilder = new ModelBuilder();
|
90 |
|
91 | const User = modelBuilder.define('User', {name: String, bio: String}, {strict: 'throw'});
|
92 |
|
93 | try {
|
94 | const user = new User({name: 'Joe', age: 20});
|
95 | assert(false, 'The code should have thrown an error');
|
96 | } catch (e) {
|
97 | assert(true, 'The code is expected to throw an error');
|
98 | }
|
99 | done(null, User);
|
100 | });
|
101 |
|
102 | it('supports open models', function(done) {
|
103 | const modelBuilder = new ModelBuilder();
|
104 |
|
105 | const User = modelBuilder.define('User', {}, {strict: false});
|
106 |
|
107 | const user = new User({name: 'Joe', age: 20});
|
108 |
|
109 | User.modelName.should.equal('User');
|
110 | user.should.be.type('object').and.have.property('name', 'Joe');
|
111 | user.should.have.property('name', 'Joe');
|
112 | user.should.have.property('age', 20);
|
113 | user.should.not.have.property('bio');
|
114 | done(null, User);
|
115 | });
|
116 |
|
117 | it('accepts non-predefined properties in non-strict mode', function(done) {
|
118 | const modelBuilder = new ModelBuilder();
|
119 |
|
120 | const User = modelBuilder.define('User', {name: String, bio: String}, {strict: false});
|
121 |
|
122 | const user = new User({name: 'Joe'});
|
123 | user.age = 10;
|
124 | user.bio = 'me';
|
125 |
|
126 | user.should.have.property('name', 'Joe');
|
127 | user.should.have.property('bio', 'me');
|
128 |
|
129 |
|
130 | user.toObject().should.have.property('age', 10);
|
131 | user.toObject(true).should.have.property('age', 10);
|
132 | user.toObject(false).should.have.property('age', 10);
|
133 |
|
134 |
|
135 | user.toObject().should.have.property('bio', 'me');
|
136 | user.toObject({onlySchema: true}).should.have.property('bio', 'me');
|
137 | user.toObject({onlySchema: false}).should.have.property('bio', 'me');
|
138 |
|
139 | done(null, User);
|
140 | });
|
141 |
|
142 | it('uses non-strict mode by default', function(done) {
|
143 | const modelBuilder = new ModelBuilder();
|
144 |
|
145 | const User = modelBuilder.define('User', {});
|
146 |
|
147 | const user = new User({name: 'Joe', age: 20});
|
148 |
|
149 | User.modelName.should.equal('User');
|
150 | user.should.be.type('object').and.have.property('name', 'Joe');
|
151 | user.should.have.property('name', 'Joe');
|
152 | user.should.have.property('age', 20);
|
153 | user.should.not.have.property('bio');
|
154 | done(null, User);
|
155 | });
|
156 |
|
157 | it('supports nested model definitions', function(done) {
|
158 | const modelBuilder = new ModelBuilder();
|
159 |
|
160 |
|
161 | const User = modelBuilder.define('User', {
|
162 | name: String,
|
163 | bio: ModelBuilder.Text,
|
164 | approved: Boolean,
|
165 | joinedAt: Date,
|
166 | age: Number,
|
167 | address: {
|
168 | street: String,
|
169 | city: String,
|
170 | state: String,
|
171 | zipCode: String,
|
172 | country: String,
|
173 | },
|
174 | emails: [
|
175 | {
|
176 | label: String,
|
177 | email: String,
|
178 | },
|
179 | ],
|
180 | friends: [String],
|
181 | });
|
182 |
|
183 |
|
184 | User.prototype.getNameAndAge = function() {
|
185 | return this.name + ', ' + this.age;
|
186 | };
|
187 |
|
188 | modelBuilder.models.should.be.type('object').and.have.property('User', User);
|
189 | modelBuilder.definitions.should.be.type('object').and.have.property('User');
|
190 |
|
191 | let user = new User({
|
192 | name: 'Joe', age: 20,
|
193 | address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'},
|
194 | emails: [
|
195 | {label: 'work', email: 'xyz@sample.com'},
|
196 | ],
|
197 | friends: ['Mary', 'John'],
|
198 | });
|
199 |
|
200 | User.modelName.should.equal('User');
|
201 | user.should.be.type('object').and.have.property('name', 'Joe');
|
202 | user.should.have.property('name', 'Joe');
|
203 | user.should.have.property('age', 20);
|
204 | user.should.have.property('bio', undefined);
|
205 | user.should.have.property('address');
|
206 | user.address.should.have.property('city', 'San Jose');
|
207 | user.address.should.have.property('state', 'CA');
|
208 |
|
209 | user = user.toObject();
|
210 | user.emails.should.have.property('length', 1);
|
211 | user.emails[0].should.have.property('label', 'work');
|
212 | user.emails[0].should.have.property('email', 'xyz@sample.com');
|
213 | user.friends.should.have.property('length', 2);
|
214 | assert.equal(user.friends[0], 'Mary');
|
215 | assert.equal(user.friends[1], 'John');
|
216 | done(null, User);
|
217 | });
|
218 |
|
219 | it('allows models to be referenced by name before they are defined', function(done) {
|
220 | const modelBuilder = new ModelBuilder();
|
221 |
|
222 | const User = modelBuilder.define('User', {name: String, address: 'Address'});
|
223 |
|
224 | let user;
|
225 | try {
|
226 | user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
|
227 | assert(false, 'An exception should have been thrown');
|
228 | } catch (e) {
|
229 |
|
230 | }
|
231 |
|
232 | const Address = modelBuilder.define('Address', {
|
233 | street: String,
|
234 | city: String,
|
235 | state: String,
|
236 | zipCode: String,
|
237 | country: String,
|
238 | });
|
239 |
|
240 | user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
|
241 |
|
242 | User.modelName.should.equal('User');
|
243 | User.definition.properties.address.should.have.property('type', Address);
|
244 | user.should.be.type('object');
|
245 | assert(user.name === 'Joe');
|
246 | user.address.should.have.property('city', 'San Jose');
|
247 | user.address.should.have.property('state', 'CA');
|
248 | done(null, User);
|
249 | });
|
250 |
|
251 | it('defines an id property for composite ids', function() {
|
252 | const modelBuilder = new ModelBuilder();
|
253 | const Follow = modelBuilder.define('Follow', {
|
254 | followerId: {type: String, id: 1},
|
255 | followeeId: {type: String, id: 2},
|
256 | followAt: Date,
|
257 | });
|
258 | const follow = new Follow({followerId: 1, followeeId: 2});
|
259 |
|
260 | follow.should.have.property('id');
|
261 | assert.deepEqual(follow.id, {followerId: 1, followeeId: 2});
|
262 | });
|
263 |
|
264 | it('instantiates model from data with no constructor', function(done) {
|
265 | const modelBuilder = new ModelBuilder();
|
266 |
|
267 | const User = modelBuilder.define('User', {name: String, age: Number});
|
268 |
|
269 | try {
|
270 | const data = Object.create(null);
|
271 | data.name = 'Joe';
|
272 | data.age = 20;
|
273 | const user = new User(data);
|
274 | assert(true, 'The code is expected to pass');
|
275 | } catch (e) {
|
276 | assert(false, 'The code should have not thrown an error');
|
277 | }
|
278 | done();
|
279 | });
|
280 |
|
281 | it('instantiates model from data with non function constructor', function(done) {
|
282 | const modelBuilder = new ModelBuilder();
|
283 |
|
284 | const User = modelBuilder.define('User', {name: String, age: Number});
|
285 |
|
286 | try {
|
287 | const Person = function(name, age) {
|
288 | this.name = name;
|
289 | this.age = age;
|
290 | };
|
291 |
|
292 | Person.prototype.constructor = 'constructor';
|
293 |
|
294 | const data = new Person('Joe', 20);
|
295 |
|
296 | const user = new User(data);
|
297 | assert(false, 'The code should have thrown an error');
|
298 | } catch (e) {
|
299 | e.message.should.equal('Property name "constructor" is not allowed in User data');
|
300 | assert(true, 'The code is expected to throw an error');
|
301 | }
|
302 | done();
|
303 | });
|
304 | });
|
305 |
|
306 | describe('DataSource ping', function() {
|
307 | const ds = new DataSource('memory');
|
308 | ds.settings.connectionTimeout = 50;
|
309 | ds.connector.connect = function(cb) {
|
310 |
|
311 | setTimeout(cb, 100);
|
312 | };
|
313 | ds.connector.ping = function(cb) {
|
314 | cb(new Error('bad connection 2'));
|
315 | };
|
316 |
|
317 | it('reports connection errors during ping', function(done) {
|
318 | ds.ping(function(err) {
|
319 | (!!err).should.be.true;
|
320 | err.message.should.be.eql('bad connection 2');
|
321 | done();
|
322 | });
|
323 | });
|
324 |
|
325 | it('cancels invocation after timeout', function(done) {
|
326 | ds.connected = false;
|
327 | const Post = ds.define('Post', {
|
328 | title: {type: String, length: 255},
|
329 | });
|
330 | Post.create(function(err) {
|
331 | (!!err).should.be.true;
|
332 | err.message.should.be.eql('Timeout in connecting after 50 ms');
|
333 | done();
|
334 | });
|
335 | });
|
336 | });
|
337 |
|
338 | describe('DataSource define model', function() {
|
339 | it('supports plain model definitions', function() {
|
340 | const ds = new DataSource('memory');
|
341 |
|
342 |
|
343 | const Post = ds.define('Post', {
|
344 | title: {type: String, length: 255},
|
345 | content: {type: ModelBuilder.Text},
|
346 | date: {type: Date, default: function() {
|
347 | return new Date();
|
348 | }},
|
349 | timestamp: {type: Number, default: Date.now},
|
350 | published: {type: Boolean, default: false, index: true},
|
351 | });
|
352 |
|
353 |
|
354 | const User = ds.define('User', {
|
355 | name: String,
|
356 | bio: ModelBuilder.Text,
|
357 | approved: Boolean,
|
358 | joinedAt: {type: Date, default: Date},
|
359 | age: Number,
|
360 | });
|
361 |
|
362 | const Group = ds.define('Group', {group: String});
|
363 | User.mixin(Group);
|
364 |
|
365 |
|
366 | User.prototype.getNameAndAge = function() {
|
367 | return this.name + ', ' + this.age;
|
368 | };
|
369 |
|
370 | const user = new User({name: 'Joe', group: 'G1'});
|
371 | assert.equal(user.name, 'Joe');
|
372 | assert.equal(user.group, 'G1');
|
373 |
|
374 | assert(user.joinedAt instanceof Date);
|
375 |
|
376 |
|
377 | User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
378 |
|
379 | Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
380 |
|
381 | User.hasAndBelongsToMany('groups');
|
382 |
|
383 | const user2 = new User({name: 'Smith'});
|
384 | user2.save(function(err) {
|
385 | const post = user2.posts.build({title: 'Hello world'});
|
386 | post.save(function(err, data) {
|
387 |
|
388 | });
|
389 | });
|
390 |
|
391 | Post.findOne({where: {published: false}, order: 'date DESC'}, function(err, data) {
|
392 |
|
393 | });
|
394 |
|
395 | User.create({name: 'Jeff'}, function(err, data) {
|
396 | if (err) {
|
397 | return;
|
398 | }
|
399 | const post = data.posts.build({title: 'My Post'});
|
400 | });
|
401 |
|
402 | User.create({name: 'Ray'}, function(err, data) {
|
403 |
|
404 | });
|
405 |
|
406 | const Article = ds.define('Article', {title: String});
|
407 | const Tag = ds.define('Tag', {name: String});
|
408 | Article.hasAndBelongsToMany('tags');
|
409 |
|
410 | Article.create(function(e, article) {
|
411 | article.tags.create({name: 'popular'}, function(err, data) {
|
412 | Article.findOne(function(e, article) {
|
413 | article.tags(function(e, tags) {
|
414 |
|
415 | });
|
416 | });
|
417 | });
|
418 | });
|
419 |
|
420 |
|
421 | const modelBuilder = new ModelBuilder();
|
422 |
|
423 | const Color = modelBuilder.define('Color', {
|
424 | name: String,
|
425 | });
|
426 |
|
427 | Color.should.not.have.property('create');
|
428 |
|
429 |
|
430 | ds.attach(Color);
|
431 | Color.should.have.property('create');
|
432 |
|
433 | Color.create({name: 'red'});
|
434 | Color.create({name: 'green'});
|
435 | Color.create({name: 'blue'});
|
436 |
|
437 | Color.all(function(err, colors) {
|
438 | colors.should.have.lengthOf(3);
|
439 | });
|
440 | });
|
441 |
|
442 | it('emits events during attach', function() {
|
443 | const ds = new DataSource('memory');
|
444 | const modelBuilder = new ModelBuilder();
|
445 |
|
446 | const User = modelBuilder.define('User', {
|
447 | name: String,
|
448 | });
|
449 |
|
450 | let seq = 0;
|
451 | let dataAccessConfigured = -1;
|
452 | let dataSourceAttached = -1;
|
453 |
|
454 | User.on('dataAccessConfigured', function(model) {
|
455 | dataAccessConfigured = seq++;
|
456 | assert(User.create);
|
457 | assert(User.hasMany);
|
458 | });
|
459 |
|
460 | User.on('dataSourceAttached', function(model) {
|
461 | assert(User.dataSource instanceof DataSource);
|
462 | dataSourceAttached = seq++;
|
463 | });
|
464 |
|
465 | ds.attach(User);
|
466 | assert.equal(dataAccessConfigured, 0);
|
467 | assert.equal(dataSourceAttached, 1);
|
468 | });
|
469 |
|
470 | it('ignores unknown properties in strict mode', function(done) {
|
471 | const ds = new DataSource('memory');
|
472 |
|
473 | const User = ds.define('User', {name: String, bio: String}, {strict: true});
|
474 |
|
475 | User.create({name: 'Joe', age: 20}, function(err, user) {
|
476 | User.modelName.should.equal('User');
|
477 | user.should.be.type('object');
|
478 | assert(user.name === 'Joe');
|
479 | assert(user.age === undefined);
|
480 | assert(user.toObject().age === undefined);
|
481 | assert(user.toObject(true).age === undefined);
|
482 | assert(user.bio === undefined);
|
483 | done(null, User);
|
484 | });
|
485 | });
|
486 |
|
487 | it('throws an error when unknown properties are used if strict=throw', function(done) {
|
488 | const ds = new DataSource('memory');
|
489 |
|
490 | const User = ds.define('User', {name: String, bio: String}, {strict: 'throw'});
|
491 |
|
492 | try {
|
493 | const user = new User({name: 'Joe', age: 20});
|
494 | assert(false, 'The code should have thrown an error');
|
495 | } catch (e) {
|
496 | assert(true, 'The code is expected to throw an error');
|
497 | }
|
498 | done(null, User);
|
499 | });
|
500 |
|
501 | describe('strict mode "validate"', function() {
|
502 | it('reports validation errors for unknown properties', function() {
|
503 | const ds = new DataSource('memory');
|
504 | const User = ds.define('User', {name: String}, {strict: 'validate'});
|
505 | const user = new User({name: 'Joe', age: 20});
|
506 | user.isValid().should.be.false;
|
507 | const codes = user.errors && user.errors.codes || {};
|
508 | codes.should.have.property('age').eql(['unknown-property']);
|
509 | });
|
510 | });
|
511 |
|
512 | it('supports open model definitions', function(done) {
|
513 | const ds = new DataSource('memory');
|
514 |
|
515 | const User = ds.define('User', {}, {strict: false});
|
516 | User.modelName.should.equal('User');
|
517 |
|
518 | User.create({name: 'Joe', age: 20}, function(err, user) {
|
519 | user.should.be.type('object').and.have.property('name', 'Joe');
|
520 | user.should.have.property('name', 'Joe');
|
521 | user.should.have.property('age', 20);
|
522 | user.should.not.have.property('bio');
|
523 |
|
524 | User.findById(user.id, function(err, user) {
|
525 | user.should.be.type('object').and.have.property('name', 'Joe');
|
526 | user.should.have.property('name', 'Joe');
|
527 | user.should.have.property('age', 20);
|
528 | user.should.not.have.property('bio');
|
529 | done(null, User);
|
530 | });
|
531 | });
|
532 | });
|
533 |
|
534 | it('uses non-strict mode by default', function(done) {
|
535 | const ds = new DataSource('memory');
|
536 |
|
537 | const User = ds.define('User', {});
|
538 |
|
539 | User.create({name: 'Joe', age: 20}, function(err, user) {
|
540 | User.modelName.should.equal('User');
|
541 | user.should.be.type('object').and.have.property('name', 'Joe');
|
542 | user.should.have.property('name', 'Joe');
|
543 | user.should.have.property('age', 20);
|
544 | user.should.not.have.property('bio');
|
545 | done(null, User);
|
546 | });
|
547 | });
|
548 |
|
549 | it('uses strict mode by default for relational DBs', function(done) {
|
550 | const ds = new DataSource('memory');
|
551 | ds.connector.relational = true;
|
552 |
|
553 | const User = ds.define('User', {name: String, bio: String}, {strict: true});
|
554 |
|
555 | const user = new User({name: 'Joe', age: 20});
|
556 |
|
557 | User.modelName.should.equal('User');
|
558 | user.should.be.type('object');
|
559 | assert(user.name === 'Joe');
|
560 | assert(user.age === undefined);
|
561 | assert(user.toObject().age === undefined);
|
562 | assert(user.toObject(true).age === undefined);
|
563 | assert(user.bio === undefined);
|
564 | done(null, User);
|
565 | });
|
566 |
|
567 | it('throws an error with unknown properties in non-strict mode for relational DBs', function(done) {
|
568 | const ds = new DataSource('memory');
|
569 | ds.connector.relational = true;
|
570 |
|
571 | const User = ds.define('User', {name: String, bio: String}, {strict: 'throw'});
|
572 |
|
573 | try {
|
574 | const user = new User({name: 'Joe', age: 20});
|
575 | assert(false, 'The code should have thrown an error');
|
576 | } catch (e) {
|
577 | assert(true, 'The code is expected to throw an error');
|
578 | }
|
579 | done(null, User);
|
580 | });
|
581 |
|
582 | it('changes the property value for save in non-strict mode', function(done) {
|
583 | const ds = new DataSource('memory');
|
584 | const Post = ds.define('Post');
|
585 |
|
586 | Post.create({price: 900}, function(err, post) {
|
587 | assert.equal(post.price, 900);
|
588 | post.price = 1000;
|
589 | post.save(function(err, result) {
|
590 | assert.equal(1000, result.price);
|
591 | done(err, result);
|
592 | });
|
593 | });
|
594 | });
|
595 |
|
596 | it('supports instance level strict mode', function() {
|
597 | const ds = new DataSource('memory');
|
598 |
|
599 | const User = ds.define('User', {name: String, bio: String}, {strict: true});
|
600 |
|
601 | const user = new User({name: 'Joe', age: 20}, {strict: false});
|
602 |
|
603 | user.should.have.property('__strict', false);
|
604 | user.should.be.type('object');
|
605 | user.should.have.property('name', 'Joe');
|
606 | user.should.have.property('age', 20);
|
607 | user.toObject().should.have.property('age', 20);
|
608 | user.toObject(true).should.have.property('age', 20);
|
609 |
|
610 | user.setStrict(true);
|
611 | user.toObject().should.not.have.property('age');
|
612 | user.toObject(true).should.not.have.property('age');
|
613 | user.toObject(false).should.have.property('age', 20);
|
614 | });
|
615 |
|
616 | it('updates instances with unknown properties in non-strict mode', function(done) {
|
617 | const ds = new DataSource('memory');
|
618 | const Post = ds.define('Post', {
|
619 | title: {type: String, length: 255, index: true},
|
620 | content: {type: String},
|
621 | });
|
622 |
|
623 | Post.create({title: 'a', content: 'AAA'}, function(err, post) {
|
624 | post.updateAttributes({title: 'b', xyz: 'xyz'}, function(err, p) {
|
625 | should.not.exist(err);
|
626 | p.id.should.be.equal(post.id);
|
627 | p.content.should.be.equal(post.content);
|
628 | p.xyz.should.be.equal('xyz');
|
629 |
|
630 | Post.findById(post.id, function(err, p) {
|
631 | p.id.should.be.equal(post.id);
|
632 | p.content.should.be.equal(post.content);
|
633 | p.xyz.should.be.equal('xyz');
|
634 | p.title.should.be.equal('b');
|
635 | done();
|
636 | });
|
637 | });
|
638 | });
|
639 | });
|
640 |
|
641 | it('injects id by default', function(done) {
|
642 | const ds = new ModelBuilder();
|
643 |
|
644 | const User = ds.define('User', {});
|
645 | assert.deepEqual(User.definition.properties.id,
|
646 | {type: Number, id: 1, generated: true, updateOnly: true});
|
647 |
|
648 | done();
|
649 | });
|
650 |
|
651 | it('injects id with useDefaultIdType to false', function(done) {
|
652 | const ds = new ModelBuilder();
|
653 |
|
654 | const User = ds.define('User', {id: {type: String, generated: true, id: true, useDefaultIdType: false}});
|
655 | assert.deepEqual(User.definition.properties.id,
|
656 | {type: String, id: true, generated: true, updateOnly: true, useDefaultIdType: false});
|
657 |
|
658 | done();
|
659 | });
|
660 |
|
661 | it('disables idInjection if the value is false', function(done) {
|
662 | const ds = new ModelBuilder();
|
663 |
|
664 | const User1 = ds.define('User', {}, {idInjection: false});
|
665 | assert(!User1.definition.properties.id);
|
666 | done();
|
667 | });
|
668 |
|
669 | it('updates generated id type by the connector', function(done) {
|
670 | const builder = new ModelBuilder();
|
671 |
|
672 | const User = builder.define('User', {id: {type: String, generated: true, id: true}});
|
673 | assert.deepEqual(User.definition.properties.id,
|
674 | {type: String, id: 1, generated: true, updateOnly: true});
|
675 |
|
676 | const ds = new DataSource('memory');
|
677 | User.attachTo(ds);
|
678 |
|
679 | assert.deepEqual(User.definition.properties.id,
|
680 | {type: Number, id: 1, generated: true, updateOnly: true});
|
681 |
|
682 | done();
|
683 | });
|
684 |
|
685 | it('allows an explicit remoting path', function() {
|
686 | const ds = new DataSource('memory');
|
687 |
|
688 | const User = ds.define('User', {name: String, bio: String}, {
|
689 | http: {path: 'accounts'},
|
690 | });
|
691 | User.http.path.should.equal('/accounts');
|
692 | });
|
693 |
|
694 | it('allows an explicit remoting path with leading /', function() {
|
695 | const ds = new DataSource('memory');
|
696 |
|
697 | const User = ds.define('User', {name: String, bio: String}, {
|
698 | http: {path: '/accounts'},
|
699 | });
|
700 | User.http.path.should.equal('/accounts');
|
701 | });
|
702 | });
|
703 |
|
704 | describe('Model loaded with a base', function() {
|
705 | it('has a base class according to the base option', function() {
|
706 | const ds = new ModelBuilder();
|
707 |
|
708 | const User = ds.define('User', {name: String});
|
709 |
|
710 | User.staticMethod = function staticMethod() {
|
711 | };
|
712 | User.prototype.instanceMethod = function instanceMethod() {
|
713 | };
|
714 |
|
715 | const Customer = ds.define('Customer', {vip: Boolean}, {base: 'User'});
|
716 |
|
717 | assert(Customer.prototype instanceof User);
|
718 | assert(Customer.staticMethod === User.staticMethod);
|
719 | assert(Customer.prototype.instanceMethod === User.prototype.instanceMethod);
|
720 | assert.equal(Customer.base, User);
|
721 | assert.equal(Customer.base, Customer.super_);
|
722 |
|
723 | try {
|
724 | const Customer1 = ds.define('Customer1', {vip: Boolean}, {base: 'User1'});
|
725 | } catch (e) {
|
726 | assert(e);
|
727 | }
|
728 | });
|
729 |
|
730 | it('inherits properties from base model', function() {
|
731 | const ds = new ModelBuilder();
|
732 |
|
733 | const User = ds.define('User', {name: String});
|
734 |
|
735 | const Customer = ds.define('Customer', {vip: Boolean}, {base: 'User'});
|
736 |
|
737 | Customer.definition.properties.should.have.property('name');
|
738 | Customer.definition.properties.name.should.have.property('type', String);
|
739 | });
|
740 |
|
741 | it('inherits properties by clone from base model', function() {
|
742 | const ds = new ModelBuilder();
|
743 |
|
744 | const User = ds.define('User', {name: String});
|
745 |
|
746 | const Customer1 = ds.define('Customer1', {vip: Boolean}, {base: 'User'});
|
747 | const Customer2 = ds.define('Customer2', {vip: Boolean}, {base: 'User'});
|
748 |
|
749 | Customer1.definition.properties.should.have.property('name');
|
750 | Customer2.definition.properties.should.have.property('name');
|
751 | Customer1.definition.properties.name.should.not.be.equal(
|
752 | Customer2.definition.properties.name,
|
753 | );
|
754 | Customer1.definition.properties.name.should.eql(
|
755 | Customer2.definition.properties.name,
|
756 | );
|
757 | });
|
758 |
|
759 | it('can remove properties from base model', function() {
|
760 | const ds = new ModelBuilder();
|
761 |
|
762 | const User = ds.define('User', {username: String, email: String});
|
763 |
|
764 | const Customer = ds.define('Customer',
|
765 | {name: String, username: null, email: false},
|
766 | {base: 'User'});
|
767 |
|
768 | Customer.definition.properties.should.have.property('name');
|
769 |
|
770 | Customer.definition.properties.should.not.have.property('username');
|
771 | Customer.definition.properties.should.not.have.property('email');
|
772 | const c = new Customer({name: 'John'});
|
773 | c.should.have.property('username', undefined);
|
774 | c.should.have.property('email', undefined);
|
775 | c.should.have.property('name', 'John');
|
776 | const u = new User({username: 'X', email: 'x@y.com'});
|
777 | u.should.not.have.property('name');
|
778 | u.should.have.property('username', 'X');
|
779 | u.should.have.property('email', 'x@y.com');
|
780 | });
|
781 |
|
782 | it('can configure base class via parent argument', function() {
|
783 | const ds = new ModelBuilder();
|
784 |
|
785 | const User = ds.define('User', {name: String});
|
786 |
|
787 | User.staticMethod = function staticMethod() {
|
788 | };
|
789 | User.prototype.instanceMethod = function instanceMethod() {
|
790 | };
|
791 |
|
792 | const Customer = ds.define('Customer', {vip: Boolean}, {}, User);
|
793 |
|
794 | Customer.definition.properties.should.have.property('name');
|
795 | Customer.definition.properties.name.should.have.property('type', String);
|
796 |
|
797 | assert(Customer.prototype instanceof User);
|
798 | assert(Customer.staticMethod === User.staticMethod);
|
799 | assert(Customer.prototype.instanceMethod === User.prototype.instanceMethod);
|
800 | assert.equal(Customer.base, User);
|
801 | assert.equal(Customer.base, Customer.super_);
|
802 | });
|
803 | });
|
804 |
|
805 | describe('Models attached to a dataSource', function() {
|
806 | let Post;
|
807 | before(function() {
|
808 | const ds = new DataSource('memory');
|
809 | Post = ds.define('Post', {
|
810 | title: {type: String, length: 255, index: true},
|
811 | content: {type: String},
|
812 | comments: [String],
|
813 | }, {forceId: false});
|
814 | });
|
815 |
|
816 | beforeEach(function(done) {
|
817 | Post.destroyAll(done);
|
818 | });
|
819 |
|
820 | describe('updateOrCreate', function() {
|
821 | it('updates instances', function(done) {
|
822 | Post.create({title: 'a', content: 'AAA'}, function(err, post) {
|
823 | post.title = 'b';
|
824 | Post.updateOrCreate(post, function(err, p) {
|
825 | should.not.exist(err);
|
826 | p.id.should.be.equal(post.id);
|
827 | p.content.should.be.equal(post.content);
|
828 | should.not.exist(p._id);
|
829 |
|
830 | Post.findById(post.id, function(err, p) {
|
831 | p.id.should.be.equal(post.id);
|
832 | should.not.exist(p._id);
|
833 | p.content.should.be.equal(post.content);
|
834 | p.title.should.be.equal('b');
|
835 | done();
|
836 | });
|
837 | });
|
838 | });
|
839 | });
|
840 |
|
841 | it('updates instances without removing existing properties', function(done) {
|
842 | Post.create({title: 'a', content: 'AAA', comments: ['Comment1']}, function(err, post) {
|
843 | post = post.toObject();
|
844 | delete post.title;
|
845 | delete post.comments;
|
846 | Post.updateOrCreate(post, function(err, p) {
|
847 | should.not.exist(err);
|
848 | p.id.should.be.equal(post.id);
|
849 | p.content.should.be.equal(post.content);
|
850 | should.not.exist(p._id);
|
851 |
|
852 | Post.findById(post.id, function(err, p) {
|
853 | p.id.should.be.equal(post.id);
|
854 | should.not.exist(p._id);
|
855 | p.content.should.be.equal(post.content);
|
856 | p.title.should.be.equal('a');
|
857 | p.comments.length.should.be.equal(1);
|
858 | p.comments[0].should.be.equal('Comment1');
|
859 | done();
|
860 | });
|
861 | });
|
862 | });
|
863 | });
|
864 |
|
865 | it('creates a new instance if it does not exist', function(done) {
|
866 | const post = {id: 123, title: 'a', content: 'AAA'};
|
867 | Post.updateOrCreate(post, function(err, p) {
|
868 | should.not.exist(err);
|
869 | p.title.should.be.equal(post.title);
|
870 | p.content.should.be.equal(post.content);
|
871 | p.id.should.be.equal(post.id);
|
872 |
|
873 | Post.findById(p.id, function(err, p) {
|
874 | p.id.should.be.equal(post.id);
|
875 | should.not.exist(p._id);
|
876 | p.content.should.be.equal(post.content);
|
877 | p.title.should.be.equal(post.title);
|
878 | p.id.should.be.equal(post.id);
|
879 | done();
|
880 | });
|
881 | });
|
882 | });
|
883 | });
|
884 |
|
885 | describe('save', function() {
|
886 | it('updates instance with the same id', function(done) {
|
887 | Post.create({title: 'a', content: 'AAA'}, function(err, post) {
|
888 | post.title = 'b';
|
889 | post.save(function(err, p) {
|
890 | should.not.exist(err);
|
891 | p.id.should.be.equal(post.id);
|
892 | p.content.should.be.equal(post.content);
|
893 | should.not.exist(p._id);
|
894 |
|
895 | Post.findById(post.id, function(err, p) {
|
896 | p.id.should.be.equal(post.id);
|
897 | should.not.exist(p._id);
|
898 | p.content.should.be.equal(post.content);
|
899 | p.title.should.be.equal('b');
|
900 | done();
|
901 | });
|
902 | });
|
903 | });
|
904 | });
|
905 |
|
906 | it('updates the instance without removing existing properties', function(done) {
|
907 | Post.create({title: 'a', content: 'AAA'}, function(err, post) {
|
908 | delete post.title;
|
909 | post.save(function(err, p) {
|
910 | should.not.exist(err);
|
911 | p.id.should.be.equal(post.id);
|
912 | p.content.should.be.equal(post.content);
|
913 | should.not.exist(p._id);
|
914 |
|
915 | Post.findById(post.id, function(err, p) {
|
916 | p.id.should.be.equal(post.id);
|
917 | should.not.exist(p._id);
|
918 | p.content.should.be.equal(post.content);
|
919 | p.title.should.be.equal('a');
|
920 | done();
|
921 | });
|
922 | });
|
923 | });
|
924 | });
|
925 |
|
926 | it('creates a new instance if it does not exist', function(done) {
|
927 | const post = new Post({id: '123', title: 'a', content: 'AAA'});
|
928 | post.save(post, function(err, p) {
|
929 | should.not.exist(err);
|
930 | p.title.should.be.equal(post.title);
|
931 | p.content.should.be.equal(post.content);
|
932 | p.id.should.be.equal(post.id);
|
933 |
|
934 | Post.findById(p.id, function(err, p) {
|
935 | p.id.should.be.equal(post.id);
|
936 | should.not.exist(p._id);
|
937 | p.content.should.be.equal(post.content);
|
938 | p.title.should.be.equal(post.title);
|
939 | p.id.should.be.equal(post.id);
|
940 | done();
|
941 | });
|
942 | });
|
943 | });
|
944 | });
|
945 | });
|
946 |
|
947 | describe('DataSource connector types', function() {
|
948 | it('returns an array of types using getTypes', function() {
|
949 | const ds = new DataSource('memory');
|
950 | const types = ds.getTypes();
|
951 | assert.deepEqual(types, ['db', 'nosql', 'memory']);
|
952 | });
|
953 |
|
954 | describe('supportTypes', function() {
|
955 | it('tests supported types by string', function() {
|
956 | const ds = new DataSource('memory');
|
957 | const result = ds.supportTypes('db');
|
958 | assert(result);
|
959 | });
|
960 |
|
961 | it('tests supported types by array', function() {
|
962 | const ds = new DataSource('memory');
|
963 | const result = ds.supportTypes(['db', 'memory']);
|
964 | assert(result);
|
965 | });
|
966 |
|
967 | it('tests unsupported types by string', function() {
|
968 | const ds = new DataSource('memory');
|
969 | const result = ds.supportTypes('rdbms');
|
970 | assert(!result);
|
971 | });
|
972 |
|
973 | it('tests unsupported types by array', function() {
|
974 | const ds = new DataSource('memory');
|
975 | let result = ds.supportTypes(['rdbms', 'memory']);
|
976 | assert(!result);
|
977 |
|
978 | result = ds.supportTypes(['rdbms']);
|
979 | assert(!result);
|
980 | });
|
981 | });
|
982 | });
|
983 |
|
984 | describe('DataSource._resolveConnector', function() {
|
985 |
|
986 | const loader = function(name) {
|
987 | if (name.indexOf('./connectors/') !== -1) {
|
988 |
|
989 | return null;
|
990 | }
|
991 | if (name === 'loopback-connector-abc') {
|
992 |
|
993 | return null;
|
994 | }
|
995 | return {
|
996 | name: name,
|
997 | };
|
998 | };
|
999 |
|
1000 | it('resolves connector by path', function() {
|
1001 | const connector = DataSource._resolveConnector(__dirname + '/../lib/connectors/memory');
|
1002 | assert(connector.connector);
|
1003 | });
|
1004 | it('resolves connector by internal name', function() {
|
1005 | const connector = DataSource._resolveConnector('memory');
|
1006 | assert(connector.connector);
|
1007 | });
|
1008 | it('resolves connector by module name starting with loopback-connector-', function() {
|
1009 | const connector = DataSource._resolveConnector('loopback-connector-xyz', loader);
|
1010 | assert(connector.connector);
|
1011 | });
|
1012 | it('resolves connector by short module name with full name first', function() {
|
1013 | const connector = DataSource._resolveConnector('xyz', loader);
|
1014 | assert(connector.connector);
|
1015 | assert.equal(connector.connector.name, 'loopback-connector-xyz');
|
1016 | });
|
1017 | it('resolves connector by short module name', function() {
|
1018 | const connector = DataSource._resolveConnector('abc', loader);
|
1019 | assert(connector.connector);
|
1020 | assert.equal(connector.connector.name, 'abc');
|
1021 | });
|
1022 | it('resolves connector by short module name for known connectors', function() {
|
1023 | const connector = DataSource._resolveConnector('oracle', loader);
|
1024 | assert(connector.connector);
|
1025 | assert.equal(connector.connector.name, 'loopback-connector-oracle');
|
1026 | });
|
1027 | it('resolves connector by full module name', function() {
|
1028 | const connector = DataSource._resolveConnector('loopback-xyz', loader);
|
1029 | assert(connector.connector);
|
1030 | });
|
1031 | it('fails to resolve connector by module name starting with loopback-connector-', function() {
|
1032 | const connector = DataSource._resolveConnector('loopback-connector-xyz');
|
1033 | assert(!connector.connector);
|
1034 | assert(connector.error.indexOf('loopback-connector-xyz') !== -1);
|
1035 | });
|
1036 | it('fails resolve invalid connector by short module name', function() {
|
1037 | const connector = DataSource._resolveConnector('xyz');
|
1038 | assert(!connector.connector);
|
1039 | assert(connector.error.indexOf('loopback-connector-xyz') !== -1);
|
1040 | });
|
1041 | it('fails to resolve invalid connector by full module name', function() {
|
1042 | const connector = DataSource._resolveConnector('loopback-xyz');
|
1043 | assert(!connector.connector);
|
1044 | assert(connector.error.indexOf('loopback-connector-loopback-xyz') !== -1);
|
1045 | });
|
1046 | });
|
1047 |
|
1048 | describe('Model define with relations configuration', function() {
|
1049 | it('sets up hasMany relations', function(done) {
|
1050 | const ds = new DataSource('memory');
|
1051 |
|
1052 | const Post = ds.define('Post', {userId: Number, content: String});
|
1053 | const User = ds.define('User', {name: String}, {
|
1054 | relations: {posts: {type: 'hasMany', model: 'Post'}},
|
1055 | });
|
1056 |
|
1057 | assert(User.relations['posts']);
|
1058 | done();
|
1059 | });
|
1060 |
|
1061 | it('sets up belongsTo relations', function(done) {
|
1062 | const ds = new DataSource('memory');
|
1063 |
|
1064 | const User = ds.define('User', {name: String});
|
1065 | const Post = ds.define('Post', {userId: Number, content: String}, {
|
1066 | relations: {user: {type: 'belongsTo', model: 'User'}},
|
1067 | });
|
1068 |
|
1069 | assert(Post.relations['user']);
|
1070 | done();
|
1071 | });
|
1072 |
|
1073 | it('sets up referencesMany relations', function(done) {
|
1074 | const ds = new DataSource('memory');
|
1075 |
|
1076 | const Post = ds.define('Post', {userId: Number, content: String});
|
1077 | const User = ds.define('User', {name: String}, {
|
1078 | relations: {posts: {type: 'referencesMany', model: 'Post'}},
|
1079 | });
|
1080 |
|
1081 | assert(User.relations['posts']);
|
1082 | done();
|
1083 | });
|
1084 |
|
1085 | it('sets up embedsMany relations', function(done) {
|
1086 | const ds = new DataSource('memory');
|
1087 |
|
1088 | const Post = ds.define('Post', {userId: Number, content: String});
|
1089 | const User = ds.define('User', {name: String}, {
|
1090 | relations: {posts: {type: 'embedsMany', model: 'Post'}},
|
1091 | });
|
1092 |
|
1093 | assert(User.relations['posts']);
|
1094 | done();
|
1095 | });
|
1096 |
|
1097 | it('sets up belongsTo polymorphic relation with `{polymorphic: true}`', function(done) {
|
1098 | const ds = new DataSource('memory');
|
1099 |
|
1100 | const Product = ds.define('Product', {name: String}, {relations: {
|
1101 | pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'},
|
1102 | }});
|
1103 | const Picture = ds.define('Picture', {name: String}, {relations: {
|
1104 | imageable: {type: 'belongsTo', polymorphic: true},
|
1105 | }});
|
1106 |
|
1107 | assert(Picture.relations['imageable']);
|
1108 | assert.deepEqual(Picture.relations['imageable'].toJSON(), {
|
1109 | name: 'imageable',
|
1110 | type: 'belongsTo',
|
1111 | modelFrom: 'Picture',
|
1112 | keyFrom: 'imageableId',
|
1113 | modelTo: '<polymorphic>',
|
1114 | keyTo: 'id',
|
1115 | multiple: false,
|
1116 | polymorphic: {
|
1117 | selector: 'imageable',
|
1118 | foreignKey: 'imageableId',
|
1119 | discriminator: 'imageableType',
|
1120 | },
|
1121 | });
|
1122 | done();
|
1123 | });
|
1124 |
|
1125 | it('sets up hasMany polymorphic relation with `{polymorphic: belongsToRelationName}`', function(done) {
|
1126 | const ds = new DataSource('memory');
|
1127 |
|
1128 | const Picture = ds.define('Picture', {name: String}, {relations: {
|
1129 | imageable: {type: 'belongsTo', polymorphic: true},
|
1130 | }});
|
1131 | const Product = ds.define('Product', {name: String}, {relations: {
|
1132 | pictures: {type: 'hasMany', model: 'Picture', polymorphic: 'imageable'},
|
1133 | }});
|
1134 |
|
1135 | assert(Product.relations['pictures']);
|
1136 | assert.deepEqual(Product.relations['pictures'].toJSON(), {
|
1137 | name: 'pictures',
|
1138 | type: 'hasMany',
|
1139 | modelFrom: 'Product',
|
1140 | keyFrom: 'id',
|
1141 | modelTo: 'Picture',
|
1142 | keyTo: 'imageableId',
|
1143 | multiple: true,
|
1144 | polymorphic: {
|
1145 | selector: 'imageable',
|
1146 | foreignKey: 'imageableId',
|
1147 | discriminator: 'imageableType',
|
1148 | },
|
1149 | });
|
1150 | done();
|
1151 | });
|
1152 |
|
1153 | it('creates a foreign key with the correct type', function(done) {
|
1154 | const ds = new DataSource('memory');
|
1155 |
|
1156 | const User = ds.define('User', {name: String, id: {type: String, id: true}});
|
1157 | const Post = ds.define('Post', {content: String}, {relations: {
|
1158 | user: {type: 'belongsTo', model: 'User'}},
|
1159 | });
|
1160 |
|
1161 | const fk = Post.definition.properties['userId'];
|
1162 | assert(fk, 'The foreign key should be added');
|
1163 | assert(fk.type === String, 'The foreign key should be the same type as primary key');
|
1164 | assert(Post.relations['user'], 'User relation should be set');
|
1165 | done();
|
1166 | });
|
1167 |
|
1168 | it('sets up related hasMany and belongsTo relations', function(done) {
|
1169 | const ds = new DataSource('memory');
|
1170 |
|
1171 | const User = ds.define('User', {name: String}, {
|
1172 | relations: {
|
1173 | posts: {type: 'hasMany', model: 'Post'},
|
1174 | accounts: {type: 'hasMany', model: 'Account'},
|
1175 | },
|
1176 | });
|
1177 |
|
1178 | assert(!User.relations['posts']);
|
1179 | assert(!User.relations['accounts']);
|
1180 |
|
1181 | const Post = ds.define('Post', {userId: Number, content: String}, {
|
1182 | relations: {user: {type: 'belongsTo', model: 'User'}},
|
1183 | });
|
1184 |
|
1185 | const Account = ds.define('Account', {userId: Number, type: String}, {
|
1186 | relations: {user: {type: 'belongsTo', model: 'User'}},
|
1187 | });
|
1188 |
|
1189 | assert(Post.relations['user']);
|
1190 | assert.deepEqual(Post.relations['user'].toJSON(), {
|
1191 | name: 'user',
|
1192 | type: 'belongsTo',
|
1193 | modelFrom: 'Post',
|
1194 | keyFrom: 'userId',
|
1195 | modelTo: 'User',
|
1196 | keyTo: 'id',
|
1197 | multiple: false,
|
1198 | });
|
1199 | assert(User.relations['posts']);
|
1200 | assert.deepEqual(User.relations['posts'].toJSON(), {
|
1201 | name: 'posts',
|
1202 | type: 'hasMany',
|
1203 | modelFrom: 'User',
|
1204 | keyFrom: 'id',
|
1205 | modelTo: 'Post',
|
1206 | keyTo: 'userId',
|
1207 | multiple: true,
|
1208 | });
|
1209 | assert(User.relations['accounts']);
|
1210 | assert.deepEqual(User.relations['accounts'].toJSON(), {
|
1211 | name: 'accounts',
|
1212 | type: 'hasMany',
|
1213 | modelFrom: 'User',
|
1214 | keyFrom: 'id',
|
1215 | modelTo: 'Account',
|
1216 | keyTo: 'userId',
|
1217 | multiple: true,
|
1218 | });
|
1219 |
|
1220 | done();
|
1221 | });
|
1222 |
|
1223 | it('throws an error if a relation is missing type', function(done) {
|
1224 | const ds = new DataSource('memory');
|
1225 |
|
1226 | const Post = ds.define('Post', {userId: Number, content: String});
|
1227 |
|
1228 | try {
|
1229 | const User = ds.define('User', {name: String}, {
|
1230 | relations: {posts: {model: 'Post'}},
|
1231 | });
|
1232 | } catch (e) {
|
1233 | done();
|
1234 | }
|
1235 | });
|
1236 |
|
1237 | it('throws an error if a relation type is invalid', function(done) {
|
1238 | const ds = new DataSource('memory');
|
1239 |
|
1240 | const Post = ds.define('Post', {userId: Number, content: String});
|
1241 |
|
1242 | try {
|
1243 | const User = ds.define('User', {name: String}, {
|
1244 | relations: {posts: {type: 'hasXYZ', model: 'Post'}},
|
1245 | });
|
1246 | } catch (e) {
|
1247 | done();
|
1248 | }
|
1249 | });
|
1250 |
|
1251 | it('sets up hasMany through relations', function(done) {
|
1252 | const ds = new DataSource('memory');
|
1253 | const Physician = ds.createModel('Physician', {
|
1254 | name: String,
|
1255 | }, {
|
1256 | relations: {
|
1257 | patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'},
|
1258 | },
|
1259 | });
|
1260 |
|
1261 | const Patient = ds.createModel('Patient', {
|
1262 | name: String,
|
1263 | }, {
|
1264 | relations: {
|
1265 | physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'},
|
1266 | },
|
1267 | });
|
1268 |
|
1269 | assert(!Physician.relations['patients']);
|
1270 | assert(!Patient.relations['physicians']);
|
1271 |
|
1272 | const Appointment = ds.createModel('Appointment', {
|
1273 | physicianId: Number,
|
1274 | patientId: Number,
|
1275 | appointmentDate: Date,
|
1276 | }, {
|
1277 | relations: {
|
1278 | patient: {type: 'belongsTo', model: 'Patient'},
|
1279 | physician: {type: 'belongsTo', model: 'Physician'},
|
1280 | },
|
1281 | });
|
1282 |
|
1283 | assert(Physician.relations['patients']);
|
1284 | assert(Patient.relations['physicians']);
|
1285 | done();
|
1286 | });
|
1287 |
|
1288 | it('sets up hasMany through relations with options', function(done) {
|
1289 | const ds = new DataSource('memory');
|
1290 | const Physician = ds.createModel('Physician', {
|
1291 | name: String,
|
1292 | }, {
|
1293 | relations: {
|
1294 | patients: {model: 'Patient', type: 'hasMany', foreignKey: 'leftId', through: 'Appointment'},
|
1295 | },
|
1296 | });
|
1297 |
|
1298 | const Patient = ds.createModel('Patient', {
|
1299 | name: String,
|
1300 | }, {
|
1301 | relations: {
|
1302 | physicians: {model: 'Physician', type: 'hasMany', foreignKey: 'rightId', through: 'Appointment'},
|
1303 | },
|
1304 | });
|
1305 |
|
1306 | const Appointment = ds.createModel('Appointment', {
|
1307 | physicianId: Number,
|
1308 | patientId: Number,
|
1309 | appointmentDate: Date,
|
1310 | }, {
|
1311 | relations: {
|
1312 | patient: {type: 'belongsTo', model: 'Patient'},
|
1313 | physician: {type: 'belongsTo', model: 'Physician'},
|
1314 | },
|
1315 | });
|
1316 |
|
1317 | assert(Physician.relations['patients'].keyTo === 'leftId');
|
1318 | assert(Patient.relations['physicians'].keyTo === 'rightId');
|
1319 | done();
|
1320 | });
|
1321 |
|
1322 | it('sets up relations after attach', function(done) {
|
1323 | const ds = new DataSource('memory');
|
1324 | const modelBuilder = new ModelBuilder();
|
1325 |
|
1326 | const Post = modelBuilder.define('Post', {userId: Number, content: String});
|
1327 | const User = modelBuilder.define('User', {name: String}, {
|
1328 | relations: {posts: {type: 'hasMany', model: 'Post'},
|
1329 | }});
|
1330 |
|
1331 | assert(!User.relations['posts']);
|
1332 | Post.attachTo(ds);
|
1333 | User.attachTo(ds);
|
1334 | assert(User.relations['posts']);
|
1335 | done();
|
1336 | });
|
1337 | });
|
1338 |
|
1339 | describe('Model define with scopes configuration', function() {
|
1340 | it('creates scopes', function(done) {
|
1341 | const ds = new DataSource('memory');
|
1342 | const User = ds.define('User', {name: String, vip: Boolean, age: Number},
|
1343 | {scopes: {vips: {where: {vip: true}}, top5: {limit: 5, order: 'age'}}});
|
1344 |
|
1345 | const users = [];
|
1346 | for (let i = 0; i < 10; i++) {
|
1347 | users.push({name: 'User' + i, vip: i % 3 === 0, age: 20 + i * 2});
|
1348 | }
|
1349 | async.each(users, function(user, callback) {
|
1350 | User.create(user, callback);
|
1351 | }, function(err) {
|
1352 | User.vips(function(err, vips) {
|
1353 | if (err) {
|
1354 | return done(err);
|
1355 | }
|
1356 | assert.equal(vips.length, 4);
|
1357 | User.top5(function(err, top5) {
|
1358 | assert.equal(top5.length, 5);
|
1359 | done(err);
|
1360 | });
|
1361 | });
|
1362 | });
|
1363 | });
|
1364 | });
|
1365 |
|
1366 | describe('DataAccessObject', function() {
|
1367 | let ds, model, where, error, filter;
|
1368 |
|
1369 | before(function() {
|
1370 | ds = new DataSource('memory');
|
1371 | model = ds.createModel('M1', {
|
1372 | id: {type: String, id: true},
|
1373 | age: Number,
|
1374 | string: 'string',
|
1375 | vip: Boolean,
|
1376 | date: Date,
|
1377 | location: 'GeoPoint',
|
1378 | scores: [Number],
|
1379 | array: 'array',
|
1380 | object: 'object',
|
1381 | });
|
1382 | });
|
1383 |
|
1384 | beforeEach(function() {
|
1385 | error = null;
|
1386 | });
|
1387 |
|
1388 | it('coerces where clause for string types', function() {
|
1389 | where = model._coerce({id: 1});
|
1390 | assert.deepEqual(where, {id: '1'});
|
1391 | where = model._coerce({id: '1'});
|
1392 | assert.deepEqual(where, {id: '1'});
|
1393 |
|
1394 |
|
1395 | function ObjectID(id) {
|
1396 | this.id = id;
|
1397 | }
|
1398 |
|
1399 | ObjectID.prototype.toString = function() {
|
1400 | return this.id;
|
1401 | };
|
1402 |
|
1403 | where = model._coerce({id: new ObjectID('1')});
|
1404 | assert.deepEqual(where, {id: '1'});
|
1405 | });
|
1406 |
|
1407 | it('coerces where clause for number types', function() {
|
1408 | where = model._coerce({age: '10'});
|
1409 | assert.deepEqual(where, {age: 10});
|
1410 |
|
1411 | where = model._coerce({age: 10});
|
1412 | assert.deepEqual(where, {age: 10});
|
1413 |
|
1414 | where = model._coerce({age: {gt: 10}});
|
1415 | assert.deepEqual(where, {age: {gt: 10}});
|
1416 |
|
1417 | where = model._coerce({age: {gt: '10'}});
|
1418 | assert.deepEqual(where, {age: {gt: 10}});
|
1419 |
|
1420 | where = model._coerce({age: {between: ['10', '20']}});
|
1421 | assert.deepEqual(where, {age: {between: [10, 20]}});
|
1422 | });
|
1423 |
|
1424 | it('coerces where clause for array types', function() {
|
1425 | where = model._coerce({scores: ['10', '20']});
|
1426 | assert.deepEqual(where, {scores: [10, 20]});
|
1427 | });
|
1428 |
|
1429 | it('coerces where clause for date types', function() {
|
1430 | const d = new Date();
|
1431 | where = model._coerce({date: d});
|
1432 | assert.deepEqual(where, {date: d});
|
1433 |
|
1434 | where = model._coerce({date: d.toISOString()});
|
1435 | assert.deepEqual(where, {date: d});
|
1436 | });
|
1437 |
|
1438 | it('coerces where clause for boolean types', function() {
|
1439 | where = model._coerce({vip: 'true'});
|
1440 | assert.deepEqual(where, {vip: true});
|
1441 |
|
1442 | where = model._coerce({vip: true});
|
1443 | assert.deepEqual(where, {vip: true});
|
1444 |
|
1445 | where = model._coerce({vip: 'false'});
|
1446 | assert.deepEqual(where, {vip: false});
|
1447 |
|
1448 | where = model._coerce({vip: false});
|
1449 | assert.deepEqual(where, {vip: false});
|
1450 |
|
1451 | where = model._coerce({vip: '1'});
|
1452 | assert.deepEqual(where, {vip: true});
|
1453 |
|
1454 | where = model._coerce({vip: 0});
|
1455 | assert.deepEqual(where, {vip: false});
|
1456 |
|
1457 | where = model._coerce({vip: ''});
|
1458 | assert.deepEqual(where, {vip: false});
|
1459 | });
|
1460 |
|
1461 | it('coerces where clause with and operators', function() {
|
1462 | where = model._coerce({and: [{age: '10'}, {vip: 'true'}]});
|
1463 | assert.deepEqual(where, {and: [{age: 10}, {vip: true}]});
|
1464 | });
|
1465 |
|
1466 | it('coerces where clause with or operators', function() {
|
1467 | where = model._coerce({or: [{age: '10'}, {vip: 'true'}]});
|
1468 | assert.deepEqual(where, {or: [{age: 10}, {vip: true}]});
|
1469 | });
|
1470 |
|
1471 | it('continues to coerce properties after a logical operator', function() {
|
1472 | const clause = {and: [{age: '10'}], vip: 'true'};
|
1473 |
|
1474 |
|
1475 | assert(Object.keys(clause)[0] === 'and', 'Unexpected key order.');
|
1476 |
|
1477 | where = model._coerce(clause);
|
1478 | assert.deepEqual(where, {and: [{age: 10}], vip: true});
|
1479 | });
|
1480 |
|
1481 | const COERCIONS = [
|
1482 | {
|
1483 | in: {scores: {0: '10', 1: '20'}},
|
1484 | out: {scores: [10, 20]},
|
1485 | },
|
1486 | {
|
1487 | in: {and: {0: {age: '10'}, 1: {vip: 'true'}}},
|
1488 | out: {and: [{age: 10}, {vip: true}]},
|
1489 | },
|
1490 | {
|
1491 | in: {or: {0: {age: '10'}, 1: {vip: 'true'}}},
|
1492 | out: {or: [{age: 10}, {vip: true}]},
|
1493 | },
|
1494 | {
|
1495 | in: {id: {inq: {0: 'aaa', 1: 'bbb'}}},
|
1496 | out: {id: {inq: ['aaa', 'bbb']}},
|
1497 | },
|
1498 | {
|
1499 | in: {id: {nin: {0: 'aaa', 1: 'bbb'}}},
|
1500 | out: {id: {nin: ['aaa', 'bbb']}},
|
1501 | },
|
1502 | {
|
1503 | in: {scores: {between: {0: '0', 1: '42'}}},
|
1504 | out: {scores: {between: [0, 42]}},
|
1505 | },
|
1506 | ];
|
1507 |
|
1508 | COERCIONS.forEach(coercion => {
|
1509 | const inStr = JSON.stringify(coercion.in);
|
1510 | it('coerces where clause with array-like objects ' + inStr, () => {
|
1511 | assert.deepEqual(model._coerce(coercion.in), coercion.out);
|
1512 | });
|
1513 | });
|
1514 |
|
1515 | const INVALID_CLAUSES = [
|
1516 | {scores: {inq: {0: '10', 1: '20', 4: '30'}}},
|
1517 | {scores: {inq: {0: '10', 1: '20', bogus: 'true'}}},
|
1518 | {scores: {between: {0: '10', 1: '20', 2: '30'}}},
|
1519 | ];
|
1520 |
|
1521 | INVALID_CLAUSES.forEach((where) => {
|
1522 | const whereStr = JSON.stringify(where);
|
1523 | it('throws an error on malformed array-like object ' + whereStr, () => {
|
1524 | assert.throws(() => model._coerce(where), /property has invalid clause/);
|
1525 | });
|
1526 | });
|
1527 |
|
1528 | it('throws an error if the where property is not an object', function() {
|
1529 | try {
|
1530 |
|
1531 | model._coerce('abc');
|
1532 | } catch (err) {
|
1533 | error = err;
|
1534 | }
|
1535 | assert(error, 'An error should have been thrown');
|
1536 | });
|
1537 |
|
1538 | it('throws an error if the where property is an array', function() {
|
1539 | try {
|
1540 |
|
1541 | model._coerce([
|
1542 | {vip: true},
|
1543 | ]);
|
1544 | } catch (err) {
|
1545 | error = err;
|
1546 | }
|
1547 | assert(error, 'An error should have been thrown');
|
1548 | });
|
1549 |
|
1550 | it('throws an error if the and operator is not configured with an array', function() {
|
1551 | try {
|
1552 |
|
1553 | model._coerce({and: {x: 1}});
|
1554 | } catch (err) {
|
1555 | error = err;
|
1556 | }
|
1557 | assert(error, 'An error should have been thrown');
|
1558 | });
|
1559 |
|
1560 | it('throws an error if the or operator does not take an array', function() {
|
1561 | try {
|
1562 |
|
1563 | model._coerce({or: {x: 1}});
|
1564 | } catch (err) {
|
1565 | error = err;
|
1566 | }
|
1567 | assert(error, 'An error should have been thrown');
|
1568 | });
|
1569 |
|
1570 | it('throws an error if the or operator not configured with an array of objects', function() {
|
1571 | try {
|
1572 |
|
1573 | model._coerce({or: ['x']});
|
1574 | } catch (err) {
|
1575 | error = err;
|
1576 | }
|
1577 | assert(error, 'An error should have been thrown');
|
1578 | });
|
1579 |
|
1580 | it('throws an error when malformed logical operators follow valid logical clauses', function() {
|
1581 | const invalid = {and: [{x: 1}], or: 'bogus'};
|
1582 |
|
1583 |
|
1584 | assert(Object.keys(invalid)[0] !== 'or', 'Unexpected key order.');
|
1585 |
|
1586 | try {
|
1587 | model._coerce(invalid);
|
1588 | } catch (err) {
|
1589 | error = err;
|
1590 | }
|
1591 | assert(error, 'An error should have been thrown');
|
1592 | });
|
1593 |
|
1594 | it('throws an error if the filter property is not an object', function() {
|
1595 | let filter = null;
|
1596 | try {
|
1597 |
|
1598 | filter = model._normalize('abc');
|
1599 | } catch (err) {
|
1600 | error = err;
|
1601 | }
|
1602 | assert(error, 'An error should have been thrown');
|
1603 | });
|
1604 |
|
1605 | it('throws an error if the filter.limit property is not a number', function() {
|
1606 | try {
|
1607 |
|
1608 | filter = model._normalize({limit: 'x'});
|
1609 | } catch (err) {
|
1610 | error = err;
|
1611 | }
|
1612 | assert(error, 'An error should have been thrown');
|
1613 | });
|
1614 |
|
1615 | it('throws an error if the filter.limit property is negative', function() {
|
1616 | try {
|
1617 |
|
1618 | filter = model._normalize({limit: -1});
|
1619 | } catch (err) {
|
1620 | error = err;
|
1621 | }
|
1622 | assert(error, 'An error should have been thrown');
|
1623 | });
|
1624 |
|
1625 | it('throws an error if the filter.limit property is not an integer', function() {
|
1626 | try {
|
1627 |
|
1628 | filter = model._normalize({limit: 5.8});
|
1629 | } catch (err) {
|
1630 | error = err;
|
1631 | }
|
1632 | assert(error, 'An error should have been thrown');
|
1633 | });
|
1634 |
|
1635 | it('throws an error if filter.offset property is not a number', function() {
|
1636 | try {
|
1637 |
|
1638 | filter = model._normalize({offset: 'x'});
|
1639 | } catch (err) {
|
1640 | error = err;
|
1641 | }
|
1642 | assert(error, 'An error should have been thrown');
|
1643 | });
|
1644 |
|
1645 | it('throws an error if the filter.skip property is not a number', function() {
|
1646 | try {
|
1647 |
|
1648 | filter = model._normalize({skip: '_'});
|
1649 | } catch (err) {
|
1650 | error = err;
|
1651 | }
|
1652 | assert(error, 'An error should have been thrown');
|
1653 | });
|
1654 |
|
1655 | it('normalizes limit/offset/skip', function() {
|
1656 | filter = model._normalize({limit: '10', skip: 5});
|
1657 | assert.deepEqual(filter, {limit: 10, offset: 5, skip: 5});
|
1658 | });
|
1659 |
|
1660 | it('uses a default value for limit', function() {
|
1661 | filter = model._normalize({skip: 5});
|
1662 | assert.deepEqual(filter, {limit: 100, offset: 5, skip: 5});
|
1663 | });
|
1664 |
|
1665 | it('applies settings for handling undefined', function() {
|
1666 | filter = model._normalize({filter: {x: undefined}});
|
1667 | assert.deepEqual(filter, {filter: {}});
|
1668 |
|
1669 | ds.settings.normalizeUndefinedInQuery = 'ignore';
|
1670 | filter = model._normalize({filter: {x: undefined}});
|
1671 | assert.deepEqual(filter, {filter: {}}, 'Should ignore undefined');
|
1672 |
|
1673 | ds.settings.normalizeUndefinedInQuery = 'nullify';
|
1674 | filter = model._normalize({filter: {x: undefined}});
|
1675 | assert.deepEqual(filter, {filter: {x: null}}, 'Should nullify undefined');
|
1676 |
|
1677 | ds.settings.normalizeUndefinedInQuery = 'throw';
|
1678 | (function() { model._normalize({filter: {x: undefined}}); }).should.throw(/`undefined` in query/);
|
1679 | });
|
1680 |
|
1681 | it('does not coerce GeoPoint', function() {
|
1682 | where = model._coerce({location: {near: {lng: 10, lat: 20}, maxDistance: 20}});
|
1683 | assert.deepEqual(where, {location: {near: {lng: 10, lat: 20}, maxDistance: 20}});
|
1684 | });
|
1685 |
|
1686 | it('does not coerce null values', function() {
|
1687 | where = model._coerce({date: null});
|
1688 | assert.deepEqual(where, {date: null});
|
1689 | });
|
1690 |
|
1691 | it('does not coerce undefined values', function() {
|
1692 | where = model._coerce({date: undefined});
|
1693 | assert.deepEqual(where, {date: undefined});
|
1694 | });
|
1695 |
|
1696 | it('does not coerce empty objects to arrays', function() {
|
1697 | where = model._coerce({object: {}});
|
1698 | where.object.should.not.be.an.Array();
|
1699 | where.object.should.be.an.Object();
|
1700 | });
|
1701 |
|
1702 | it('does not coerce an empty array', function() {
|
1703 | where = model._coerce({array: []});
|
1704 | where.array.should.be.an.Array();
|
1705 | where.array.should.have.length(0);
|
1706 | });
|
1707 |
|
1708 | it('does not coerce to a number for a simple value that produces NaN',
|
1709 | function() {
|
1710 | where = model._coerce({age: 'xyz'});
|
1711 | assert.deepEqual(where, {age: 'xyz'});
|
1712 | });
|
1713 |
|
1714 | it('does not coerce to a number for a simple value in an array that produces NaN',
|
1715 | function() {
|
1716 | where = model._coerce({age: {inq: ['xyz', '12']}});
|
1717 | assert.deepEqual(where, {age: {inq: ['xyz', 12]}});
|
1718 | });
|
1719 |
|
1720 | it('does not coerce to a string for a regexp value in an array ',
|
1721 | function() {
|
1722 | where = model._coerce({string: {inq: [/xyz/i, new RegExp(/xyz/i)]}});
|
1723 | assert.deepEqual(where, {string: {inq: [/xyz/i, /xyz/i]}});
|
1724 | });
|
1725 |
|
1726 |
|
1727 | it('gets settings in priority',
|
1728 | function() {
|
1729 | ds.settings.test = 'test';
|
1730 | assert.equal(model._getSetting('test'), ds.settings.test, 'Should get datasource setting');
|
1731 | ds.settings.test = undefined;
|
1732 |
|
1733 | model.settings.test = 'test';
|
1734 | assert.equal(model._getSetting('test'), model.settings.test, 'Should get model settings');
|
1735 |
|
1736 | ds.settings.test = 'willNotGet';
|
1737 | assert.notEqual(model._getSetting('test'), ds.settings.test, 'Should not get datasource setting');
|
1738 | });
|
1739 | });
|
1740 |
|
1741 | describe('ModelBuilder processing json files', function() {
|
1742 | const path = require('path'),
|
1743 | fs = require('fs');
|
1744 |
|
1745 | |
1746 |
|
1747 |
|
1748 |
|
1749 |
|
1750 | function loadSchemasSync(schemaFile, dataSource) {
|
1751 | let modelBuilder, createModel;
|
1752 |
|
1753 | if (!dataSource) {
|
1754 | modelBuilder = new ModelBuilder();
|
1755 | } else {
|
1756 | modelBuilder = dataSource.modelBuilder;
|
1757 | createModel = dataSource.createModel.bind(dataSource);
|
1758 | }
|
1759 |
|
1760 |
|
1761 | const schemas = JSON.parse(fs.readFileSync(schemaFile));
|
1762 | return modelBuilder.buildModels(schemas, createModel);
|
1763 | }
|
1764 |
|
1765 | it('defines models', function() {
|
1766 | let models = loadSchemasSync(path.join(__dirname, 'test1-schemas.json'));
|
1767 |
|
1768 | models.should.have.property('AnonymousModel_0');
|
1769 | models.AnonymousModel_0.should.have.property('modelName', 'AnonymousModel_0');
|
1770 |
|
1771 | const m1 = new models.AnonymousModel_0({title: 'Test'});
|
1772 | m1.should.have.property('title', 'Test');
|
1773 | m1.should.have.property('author', 'Raymond');
|
1774 |
|
1775 | models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json'));
|
1776 | models.should.have.property('Address');
|
1777 | models.should.have.property('Account');
|
1778 | models.should.have.property('Customer');
|
1779 | for (const s in models) {
|
1780 | const m = models[s];
|
1781 | assert(new m());
|
1782 | }
|
1783 | });
|
1784 |
|
1785 | it('attaches models to a specified dataSource', function() {
|
1786 | const ds = new DataSource('memory');
|
1787 |
|
1788 | const models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json'), ds);
|
1789 | models.should.have.property('Address');
|
1790 | models.should.have.property('Account');
|
1791 | models.should.have.property('Customer');
|
1792 | assert.equal(models.Address.dataSource, ds);
|
1793 | });
|
1794 |
|
1795 | it('allows customization of default model base class', function() {
|
1796 | const modelBuilder = new ModelBuilder();
|
1797 |
|
1798 | const User = modelBuilder.define('User', {
|
1799 | name: String,
|
1800 | bio: ModelBuilder.Text,
|
1801 | approved: Boolean,
|
1802 | joinedAt: Date,
|
1803 | age: Number,
|
1804 | });
|
1805 |
|
1806 | modelBuilder.defaultModelBaseClass = User;
|
1807 |
|
1808 | const Customer = modelBuilder.define('Customer', {customerId: {type: String, id: true}});
|
1809 | assert(Customer.prototype instanceof User);
|
1810 | });
|
1811 |
|
1812 | it('accepts a model base class', function() {
|
1813 | const modelBuilder = new ModelBuilder();
|
1814 |
|
1815 | const User = modelBuilder.define('User', {
|
1816 | name: String,
|
1817 | bio: ModelBuilder.Text,
|
1818 | approved: Boolean,
|
1819 | joinedAt: Date,
|
1820 | age: Number,
|
1821 | });
|
1822 |
|
1823 | const Customer = modelBuilder.define('Customer',
|
1824 | {customerId: {type: String, id: true}}, {}, User);
|
1825 | assert(Customer.prototype instanceof User);
|
1826 | });
|
1827 | });
|
1828 |
|
1829 | describe('DataSource constructor', function() {
|
1830 | it('takes url as the settings', function() {
|
1831 | const ds = new DataSource('memory://localhost/mydb?x=1');
|
1832 | assert.equal(ds.connector.name, 'memory');
|
1833 | });
|
1834 |
|
1835 | it('takes connector name', function() {
|
1836 | const ds = new DataSource('memory');
|
1837 | assert.equal(ds.connector.name, 'memory');
|
1838 | });
|
1839 |
|
1840 | it('takes settings object', function() {
|
1841 | const ds = new DataSource({connector: 'memory'});
|
1842 | assert.equal(ds.connector.name, 'memory');
|
1843 | });
|
1844 |
|
1845 | it('takes settings object and name', function() {
|
1846 | const ds = new DataSource('x', {connector: 'memory'});
|
1847 | assert.equal(ds.connector.name, 'memory');
|
1848 | });
|
1849 | });
|
1850 |
|
1851 | describe('ModelBuilder options.models', function() {
|
1852 | it('injects model classes from models', function() {
|
1853 | const builder = new ModelBuilder();
|
1854 | const M1 = builder.define('M1');
|
1855 | const M2 = builder.define('M2', {}, {models: {
|
1856 | 'M1': M1,
|
1857 | }});
|
1858 |
|
1859 | assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
1860 | });
|
1861 |
|
1862 | it('injects model classes by name in the models', function() {
|
1863 | const builder = new ModelBuilder();
|
1864 | const M1 = builder.define('M1');
|
1865 | const M2 = builder.define('M2', {}, {models: {
|
1866 | 'M1': 'M1',
|
1867 | }});
|
1868 |
|
1869 | assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
1870 | });
|
1871 |
|
1872 | it('injects model classes by name in the models before the class is defined',
|
1873 | function() {
|
1874 | const builder = new ModelBuilder();
|
1875 | const M2 = builder.define('M2', {}, {models: {
|
1876 | 'M1': 'M1',
|
1877 | }});
|
1878 | assert(M2.M1, 'M1 should be injected to M2');
|
1879 | assert(M2.M1.settings.unresolved, 'M1 is still a proxy');
|
1880 | const M1 = builder.define('M1');
|
1881 | assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
1882 | });
|
1883 |
|
1884 | it('uses non-strict mode for embedded models by default', function() {
|
1885 | const builder = new ModelBuilder();
|
1886 | const M1 = builder.define('testEmbedded', {
|
1887 | name: 'string',
|
1888 | address: {
|
1889 | street: 'string',
|
1890 | },
|
1891 | });
|
1892 | const m1 = new M1({
|
1893 | name: 'Jim',
|
1894 | address: {
|
1895 | street: 'washington st',
|
1896 | number: 5512,
|
1897 | },
|
1898 | });
|
1899 | assert.equal(m1.address.number, 5512, 'm1 should contain number property in address');
|
1900 | });
|
1901 |
|
1902 | it('uses the strictEmbeddedModels setting (true) when applied on modelBuilder', function() {
|
1903 | const builder = new ModelBuilder();
|
1904 | builder.settings.strictEmbeddedModels = true;
|
1905 | const M1 = builder.define('testEmbedded', {
|
1906 | name: 'string',
|
1907 | address: {
|
1908 | street: 'string',
|
1909 | },
|
1910 | });
|
1911 | const m1 = new M1({
|
1912 | name: 'Jim',
|
1913 | address: {
|
1914 | street: 'washington st',
|
1915 | number: 5512,
|
1916 | },
|
1917 | });
|
1918 | assert.equal(m1.address.number, undefined, 'm1 should not contain number property in address');
|
1919 | assert.equal(m1.address.isValid(), false, 'm1 address should not validate with extra property');
|
1920 | const codes = m1.address.errors && m1.address.errors.codes || {};
|
1921 | assert.deepEqual(codes.number, ['unknown-property']);
|
1922 | });
|
1923 | });
|
1924 |
|
1925 | describe('updateOnly', function() {
|
1926 | it('sets forceId to true when model id is generated', function(done) {
|
1927 | const ds = new DataSource('memory');
|
1928 | const Post = ds.define('Post', {
|
1929 | title: {type: String, length: 255},
|
1930 | date: {type: Date, default: function() {
|
1931 | return new Date();
|
1932 | }},
|
1933 | });
|
1934 |
|
1935 |
|
1936 |
|
1937 | Post.settings.should.have.property('forceId').eql('auto');
|
1938 | done();
|
1939 | });
|
1940 |
|
1941 | it('flags id as updateOnly when forceId is undefined', function(done) {
|
1942 | const ds = new DataSource('memory');
|
1943 | const Post = ds.define('Post', {
|
1944 | title: {type: String, length: 255},
|
1945 | date: {type: Date, default: function() {
|
1946 | return new Date();
|
1947 | }},
|
1948 | });
|
1949 |
|
1950 |
|
1951 | Post.should.have.property('getUpdateOnlyProperties');
|
1952 | Post.getUpdateOnlyProperties().should.eql(['id']);
|
1953 | done();
|
1954 | });
|
1955 |
|
1956 | it('does not flag id as updateOnly when forceId is false', function(done) {
|
1957 | const ds = new DataSource('memory');
|
1958 | const Person = ds.define('Person', {
|
1959 | name: String,
|
1960 | gender: String,
|
1961 | }, {forceId: false});
|
1962 |
|
1963 |
|
1964 | Person.should.have.property('getUpdateOnlyProperties');
|
1965 | Person.getUpdateOnlyProperties().should.eql([]);
|
1966 | done();
|
1967 | });
|
1968 |
|
1969 | it('flags id as updateOnly when forceId is true', function(done) {
|
1970 | const ds = new DataSource('memory');
|
1971 | const Person = ds.define('Person', {
|
1972 | name: String,
|
1973 | gender: String,
|
1974 | }, {forceId: true});
|
1975 |
|
1976 |
|
1977 | Person.should.have.property('getUpdateOnlyProperties');
|
1978 | Person.getUpdateOnlyProperties().should.eql(['id']);
|
1979 | done();
|
1980 | });
|
1981 | });
|