From f3b849bb472ed25ea7bb8ef88ffe555b21556402 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Thu, 27 Jul 2017 10:50:25 -0400 Subject: [PATCH] castPropertyValue: throw on malformed types --- lib/connectors/memory.js | 26 ++++++++-- test/memory.test.js | 101 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 54b440fb..baf140da 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -350,9 +350,17 @@ Memory.prototype.fromDb = function(model, data) { if (!data) return null; data = deserialize(data); var props = this._models[model].properties; - for (var key in data) { - data[key] = this._castPropertyValue(key, data[key], props); + try { + for (var key in data) { + data[key] = this._castPropertyValue(key, data[key], props); + } + } catch (err) { + // Modify error message and re-throw + err.message = g.f('Unable to convert to instance of "%s": %s', model, + err); + throw err; } + return data; }; @@ -370,7 +378,15 @@ Memory.prototype._castPropertyValue = function(prop, val, props) { var isArray = Array.isArray(props[prop].type); var propType = isArray ? props[prop].type[0] : props[prop].type; - + if (!propType || !propType.name) { + if (isArray) + throw new Error(g.f( + 'Property definition "%s" did not specify any sub-types!', prop)); + else + throw new Error(g.f( + 'Property definition "%s" was null or undefined!', prop + )); + } switch (propType.name) { case 'Date': val = new Date(val.toString().replace(/GMT.*$/, 'GMT')); @@ -383,7 +399,9 @@ Memory.prototype._castPropertyValue = function(prop, val, props) { break; case 'ModelConstructor': for (var subProp in val) { - val[subProp] = this._castPropertyValue(subProp, val[subProp], propType.definition.properties); + if (propType.definition && propType.definition.properties) + val[subProp] = this._castPropertyValue(subProp, val[subProp], + propType.definition.properties); } break; } diff --git a/test/memory.test.js b/test/memory.test.js index 68c75ff3..53b3ed46 100644 --- a/test/memory.test.js +++ b/test/memory.test.js @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; +var g = require('strong-globalize')(); var jdb = require('../'); var DataSource = jdb.DataSource; var path = require('path'); @@ -889,6 +890,106 @@ describe('Memory connector', function() { }); }); }); + + describe('_castPropertyValue', function() { + var mem = new Memory(); + var ds = new DataSource({ + connector: mem, + }); + + var Kwyjibo = ds.createModel('Kwyjibo', { + modelNumber: Number, + purpose: String, + }); + + testHappyPath('handles strings', 'name', 'foo', 'foo'); + testHappyPath('handles numbers', 'age', '20', 20); + var hobbies = [ + 'swimming', 'biking', 'extreme bear fighting', + ]; + testHappyPath('handles boolean values', 'isAwesome', 'true', true); + var now = new Date(); + testHappyPath('handles Dates', 'createdAt', now.toISOString(), + new Date(now)); + testHappyPath('handles arrays', 'hobbies', hobbies, hobbies); + + it('handles ModelConstructors', function() { + var samoflange = { + modelNumber: 12345, + purpose: 'To annoy others', + }; + var kwyjibo = new Kwyjibo(); + for (var item in samoflange) { + kwyjibo[item] = samoflange[item]; + } + var result = mem._castPropertyValue('samoflange', samoflange, kwyjibo); + should.exist(result); + should.deepEqual(result, kwyjibo.__data); + }); + + var nullUndef = 'Property definition "%s" was null or undefined!'; + var noSubTypes = 'Property definition "%s" did not specify any sub-types!'; + testUnhappyPath('throws on empty array property def', 'hobbies', hobbies, + g.f(noSubTypes, 'hobbies')); + testUnhappyPath('throws on empty object property def', 'age', 20, + g.f(nullUndef, 'age')); + testUnhappyPath('throws on null property def', 'name', 'foo', + g.f(nullUndef, 'name')); + testUnhappyPath('throws on undefined property def', 'title', 'peon', + g.f(nullUndef, 'title')); + + function testHappyPath(testName, prop, val, expected) { + var props = { + name: { + type: String, + }, + age: { + type: Number, + }, + hobbies: { + type: [ + String, + ], + }, + isAwesome: { + type: Boolean, + }, + createdAt: { + type: Date, + }, + samoflange: { + type: Kwyjibo, + }, + }; + + it(testName, function() { + var result = mem._castPropertyValue(prop, val, props); + result.should.deepEqual(expected); + }); + } + + function testUnhappyPath(testName, prop, val, expected) { + var props = { + name: { + type: null, + }, + title: { + type: undefined, + }, + age: { + type: {}, + }, + hobbies: { + type: [], + }, + }; + it(testName, function() { + (function() { + mem._castPropertyValue(prop, val, props); + }).should.throw(expected); + }); + } + }); }); describe('Optimized connector', function() {