1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 |
|
10 | const should = require('./init.js');
|
11 |
|
12 | let db, Model, modelWithDecimalArray, dateArrayModel, numArrayModel;
|
13 |
|
14 | class NestedClass {
|
15 | constructor(roleName) {
|
16 | this.roleName = roleName;
|
17 | }
|
18 | }
|
19 |
|
20 | describe('datatypes', function() {
|
21 | before(function(done) {
|
22 | db = getSchema();
|
23 | const Nested = db.define('Nested', {});
|
24 | const modelTableSchema = {
|
25 | str: String,
|
26 | date: Date,
|
27 | num: Number,
|
28 | bool: Boolean,
|
29 | list: {type: [String]},
|
30 | arr: Array,
|
31 | nested: Nested,
|
32 | nestedClass: NestedClass,
|
33 | };
|
34 | Model = db.define('Model', modelTableSchema);
|
35 |
|
36 | modelWithDecimalArray = db.define('modelWithDecArr', {
|
37 | randomReview: {
|
38 | type: [String],
|
39 | mongodb: {
|
40 | dataType: 'Decimal128',
|
41 | },
|
42 | },
|
43 | });
|
44 | dateArrayModel = db.define('dateArrayModel', {
|
45 | bunchOfDates: [Date],
|
46 | bunchOfOtherDates: {
|
47 | type: [Date],
|
48 | },
|
49 | });
|
50 | numArrayModel = db.define('numArrayModel', {
|
51 | bunchOfNums: [Number],
|
52 | });
|
53 | db.automigrate(['Model', 'modelWithDecArr', 'dateArrayModel', 'numArrayModel'], done);
|
54 | });
|
55 |
|
56 | it('should resolve top-level "type" property correctly', function() {
|
57 | const Account = db.define('Account', {
|
58 | type: String,
|
59 | id: String,
|
60 | });
|
61 | Account.definition.properties.type.type.should.equal(String);
|
62 | });
|
63 |
|
64 | it('should resolve "type" sub-property correctly', function() {
|
65 | const Account = db.define('Account', {
|
66 | item: {type: {
|
67 | itemname: {type: String},
|
68 | type: {type: String},
|
69 | }},
|
70 | });
|
71 | Account.definition.properties.item.type.should.not.equal(String);
|
72 | });
|
73 | it('should resolve array prop with connector specific metadata', function() {
|
74 | const props = modelWithDecimalArray.definition.properties;
|
75 | props.randomReview.type.should.deepEqual(Array(String));
|
76 | props.randomReview.mongodb.should.deepEqual({dataType: 'Decimal128'});
|
77 | });
|
78 |
|
79 | it('should coerce array of dates from string', async () => {
|
80 | const dateVal = new Date('2019-02-21T12:00:00').toISOString();
|
81 | const created = await dateArrayModel.create({
|
82 | bunchOfDates: [dateVal,
|
83 | dateVal,
|
84 | dateVal],
|
85 | bunchOfOtherDates: [dateVal,
|
86 | dateVal,
|
87 | dateVal],
|
88 | });
|
89 | created.bunchOfDates[0].should.be.an.instanceOf(Date);
|
90 | created.bunchOfDates[0].should.deepEqual(new Date(dateVal));
|
91 | created.bunchOfOtherDates[0].should.be.an.instanceOf(Date);
|
92 | created.bunchOfOtherDates[0].should.deepEqual(new Date(dateVal));
|
93 | });
|
94 |
|
95 | it('should coerce array of numbers from string', async () => {
|
96 | const dateVal = new Date('2019-02-21T12:00:00').toISOString();
|
97 | const created = await numArrayModel.create({
|
98 | bunchOfNums: ['1',
|
99 | '2',
|
100 | '3'],
|
101 | });
|
102 | created.bunchOfNums[0].should.be.an.instanceOf(Number);
|
103 | created.bunchOfNums[0].should.equal(1);
|
104 | });
|
105 |
|
106 | it('should return 400 when property of type array is set to string value',
|
107 | function(done) {
|
108 | const myModel = db.define('myModel', {
|
109 | list: {type: ['object']},
|
110 | });
|
111 |
|
112 | myModel.create({list: 'This string will crash the server'}, function(err) {
|
113 | (err.statusCode).should.equal(400);
|
114 | done();
|
115 | });
|
116 | });
|
117 |
|
118 | it('should return 400 when property of type array is set to object value',
|
119 | function(done) {
|
120 | const myModel = db.define('myModel', {
|
121 | list: {type: ['object']},
|
122 | });
|
123 |
|
124 | myModel.create({list: {key: 'This string will crash the server'}}, function(err) {
|
125 | (err.statusCode).should.equal(400);
|
126 | done();
|
127 | });
|
128 | });
|
129 |
|
130 | it('should keep types when get read data from db', function(done) {
|
131 | const d = new Date('2015-01-01T12:00:00');
|
132 | let id;
|
133 |
|
134 | Model.create({
|
135 | str: 'hello', date: d, num: '3', bool: 1, list: ['test'], arr: [1, 'str'],
|
136 | }, function(err, m) {
|
137 | should.not.exists(err);
|
138 | should.exist(m && m.id);
|
139 | m.str.should.be.type('string');
|
140 | m.num.should.be.type('number');
|
141 | m.bool.should.be.type('boolean');
|
142 | m.list[0].should.be.equal('test');
|
143 | m.arr[0].should.be.equal(1);
|
144 | m.arr[1].should.be.equal('str');
|
145 | id = m.id;
|
146 | testFind(testAll);
|
147 | });
|
148 |
|
149 | function testFind(next) {
|
150 | Model.findById(id, function(err, m) {
|
151 | should.not.exist(err);
|
152 | should.exist(m);
|
153 | m.str.should.be.type('string');
|
154 | m.num.should.be.type('number');
|
155 | m.bool.should.be.type('boolean');
|
156 | m.list[0].should.be.equal('test');
|
157 | m.arr[0].should.be.equal(1);
|
158 | m.arr[1].should.be.equal('str');
|
159 | m.date.should.be.an.instanceOf(Date);
|
160 | m.date.toString().should.equal(d.toString(), 'Time must match');
|
161 | next();
|
162 | });
|
163 | }
|
164 |
|
165 | function testAll() {
|
166 | Model.findOne(function(err, m) {
|
167 | should.not.exist(err);
|
168 | should.exist(m);
|
169 | m.str.should.be.type('string');
|
170 | m.num.should.be.type('number');
|
171 | m.bool.should.be.type('boolean');
|
172 | m.date.should.be.an.instanceOf(Date);
|
173 | m.date.toString().should.equal(d.toString(), 'Time must match');
|
174 | done();
|
175 | });
|
176 | }
|
177 | });
|
178 |
|
179 | it('should create nested object defined by a class when reading data from db', async () => {
|
180 | const d = new Date('2015-01-01T12:00:00');
|
181 | let id;
|
182 | const created = await Model.create({
|
183 | date: d,
|
184 | list: ['test'],
|
185 | arr: [1, 'str'],
|
186 | nestedClass: new NestedClass('admin'),
|
187 | });
|
188 | created.list.toJSON().should.deepEqual(['test']);
|
189 | created.arr.toJSON().should.deepEqual([1, 'str']);
|
190 | created.date.should.be.an.instanceOf(Date);
|
191 | created.date.toString().should.equal(d.toString(), 'Time must match');
|
192 | created.nestedClass.should.have.property('roleName', 'admin');
|
193 |
|
194 | const found = await Model.findById(created.id);
|
195 | should.exist(found);
|
196 | found.list.toJSON().should.deepEqual(['test']);
|
197 | found.arr.toJSON().should.deepEqual([1, 'str']);
|
198 | found.date.should.be.an.instanceOf(Date);
|
199 | found.date.toString().should.equal(d.toString(), 'Time must match');
|
200 | found.nestedClass.should.have.property('roleName', 'admin');
|
201 | });
|
202 |
|
203 | it('should respect data types when updating attributes', function(done) {
|
204 | const d = new Date;
|
205 | let id;
|
206 |
|
207 | Model.create({
|
208 | str: 'hello', date: d, num: '3', bool: 1}, function(err, m) {
|
209 | should.not.exist(err);
|
210 | should.exist(m && m.id);
|
211 |
|
212 |
|
213 | m.str.should.be.type('string');
|
214 | m.num.should.be.type('number');
|
215 | m.bool.should.be.type('boolean');
|
216 | id = m.id;
|
217 | testDataInDB(function() {
|
218 | testUpdate(function() {
|
219 | testDataInDB(done);
|
220 | });
|
221 | });
|
222 | });
|
223 |
|
224 | function testUpdate(done) {
|
225 | Model.findById(id, function(err, m) {
|
226 | should.not.exist(err);
|
227 |
|
228 | m.updateAttributes({
|
229 | id: m.id, num: 10,
|
230 | }, function(err, m) {
|
231 | should.not.exist(err);
|
232 | m.num.should.be.type('number');
|
233 | done();
|
234 | });
|
235 | });
|
236 | }
|
237 |
|
238 | function testDataInDB(done) {
|
239 |
|
240 | function cb(err, data) {
|
241 | should.exist(data);
|
242 | data.num.should.be.type('number');
|
243 | done();
|
244 | }
|
245 |
|
246 | if (db.connector.find.length === 4) {
|
247 | db.connector.find(Model.modelName, id, {}, cb);
|
248 | } else {
|
249 | db.connector.find(Model.modelName, id, cb);
|
250 | }
|
251 | }
|
252 | });
|
253 |
|
254 | it('should not coerce nested objects into ModelConstructor types', function() {
|
255 | const coerced = Model._coerce({nested: {foo: 'bar'}});
|
256 | coerced.nested.constructor.name.should.equal('Object');
|
257 | });
|
258 |
|
259 | it('rejects array value converted to NaN for a required property',
|
260 | function(done) {
|
261 | db = getSchema();
|
262 | Model = db.define('RequiredNumber', {
|
263 | num: {type: Number, required: true},
|
264 | });
|
265 | db.automigrate(['Model'], function() {
|
266 | Model.create({num: [1, 2, 3]}, function(err, inst) {
|
267 | should.exist(err);
|
268 | err.should.have.property('name').equal('ValidationError');
|
269 | done();
|
270 | });
|
271 | });
|
272 | });
|
273 |
|
274 | it('handles null data', (done) => {
|
275 | db = getSchema();
|
276 | Model = db.define('HandleNullModel', {
|
277 | data: {type: 'string'},
|
278 | });
|
279 | db.automigrate(['HandleNullModel'], function() {
|
280 | const a = new Model(null);
|
281 | done();
|
282 | });
|
283 | });
|
284 |
|
285 | describe('model option persistUndefinedAsNull', function() {
|
286 | let TestModel, isStrict;
|
287 | before(function(done) {
|
288 | db = getSchema();
|
289 | TestModel = db.define(
|
290 | 'TestModel',
|
291 | {
|
292 | name: {type: String, required: false},
|
293 | desc: {type: String, required: false},
|
294 | stars: {type: Number, required: false},
|
295 | },
|
296 | {
|
297 | persistUndefinedAsNull: true,
|
298 | },
|
299 | );
|
300 |
|
301 | isStrict = TestModel.definition.settings.strict;
|
302 |
|
303 | db.automigrate(['TestModel'], done);
|
304 | });
|
305 |
|
306 | it('should set missing optional properties to null', function(done) {
|
307 | const EXPECTED = {desc: null, stars: null};
|
308 | TestModel.create({name: 'a-test-name'}, function(err, created) {
|
309 | if (err) return done(err);
|
310 | created.should.have.properties(EXPECTED);
|
311 |
|
312 | TestModel.findById(created.id, function(err, found) {
|
313 | if (err) return done(err);
|
314 | found.should.have.properties(EXPECTED);
|
315 | done();
|
316 | });
|
317 | });
|
318 | });
|
319 |
|
320 | it('should convert property value undefined to null', function(done) {
|
321 | const EXPECTED = {desc: null, extra: null};
|
322 | const data = {desc: undefined, extra: undefined};
|
323 | if (isStrict) {
|
324 |
|
325 | delete EXPECTED.extra;
|
326 | delete data.extra;
|
327 | }
|
328 | TestModel.create(data, function(err, created) {
|
329 | if (err) return done(err);
|
330 |
|
331 | created.should.have.properties(EXPECTED);
|
332 |
|
333 | TestModel.findById(created.id, function(err, found) {
|
334 | if (err) return done(err);
|
335 | found.should.have.properties(EXPECTED);
|
336 | done();
|
337 | });
|
338 | });
|
339 | });
|
340 |
|
341 | it('should convert undefined to null in the setter', function() {
|
342 | const inst = new TestModel();
|
343 | inst.desc = undefined;
|
344 | inst.should.have.property('desc', null);
|
345 | inst.toObject().should.have.property('desc', null);
|
346 | });
|
347 |
|
348 | it('should use null in unsetAttribute()', function() {
|
349 | const inst = new TestModel();
|
350 | inst.unsetAttribute('stars');
|
351 | inst.should.have.property('stars', null);
|
352 | inst.toObject().should.have.property('stars', null);
|
353 | });
|
354 |
|
355 | it('should convert undefined to null on save', function(done) {
|
356 | const EXPECTED = {desc: null, stars: null, extra: null, dx: null};
|
357 | if (isStrict) {
|
358 |
|
359 | delete EXPECTED.extra;
|
360 | delete EXPECTED.dx;
|
361 | }
|
362 |
|
363 | TestModel.create({}, function(err, created) {
|
364 | if (err) return done(err);
|
365 | created.desc = undefined;
|
366 | created.unsetAttribute('stars');
|
367 | created.extra = undefined;
|
368 | created.__data.dx = undefined;
|
369 |
|
370 | created.save(function(err, saved) {
|
371 | if (err) return done(err);
|
372 |
|
373 | created.should.have.properties(EXPECTED);
|
374 | saved.should.have.properties(EXPECTED);
|
375 |
|
376 | function cb(err, found) {
|
377 | if (err) return done(err);
|
378 | should.exist(found[0]);
|
379 | found[0].should.have.properties(EXPECTED);
|
380 | done();
|
381 | }
|
382 |
|
383 | if (TestModel.dataSource.connector.all.length === 4) {
|
384 | TestModel.dataSource.connector.all(
|
385 | TestModel.modelName,
|
386 | {where: {id: created.id}},
|
387 | {},
|
388 | cb,
|
389 | );
|
390 | } else {
|
391 | TestModel.dataSource.connector.all(
|
392 | TestModel.modelName,
|
393 | {where: {id: created.id}},
|
394 | cb,
|
395 | );
|
396 | }
|
397 | });
|
398 | });
|
399 | });
|
400 |
|
401 | it('should convert undefined to null in toObject()', function() {
|
402 | const inst = new TestModel();
|
403 | inst.desc = undefined;
|
404 | inst.unsetAttribute('stars');
|
405 | inst.extra = undefined;
|
406 | inst.__data.dx = undefined;
|
407 |
|
408 | inst.toObject(false).should.have.properties({
|
409 | desc: null, stars: null, extra: null, dx: null,
|
410 | });
|
411 | });
|
412 | });
|
413 | });
|