Merge pull request #1719 from strongloop/fix/primitive-datatypes-coercion-3x

Fix primitive datatypes coercion [3.x]

Make sure we coerce Date values properly when creating new
model instances. Also make sure we use the coerced data values
for model instance for update operation.
This commit is contained in:
Biniam Admikew 2019-04-18 10:44:46 -04:00 committed by GitHub
commit 730918f23d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 18 deletions

View File

@ -2303,6 +2303,7 @@ DataAccessObject.updateAll = function(where, data, options, cb) {
if (!(data instanceof Model)) {
try {
inst = new Model(data, {applyDefaultValues: false});
ctx.data = inst.toObject(true);
} catch (err) {
return cb(err);
}

View File

@ -67,8 +67,14 @@ function List(items, itemType, parent) {
if (isClass(this.itemType)) {
return new this.itemType(item);
} else {
if (Array.isArray(item)) return item;
else return this.itemType(item);
if (Array.isArray(item)) {
return item;
} else if (this.itemType === Date) {
if (item === null) return null;
return new Date(item);
} else {
return this.itemType(item);
}
}
};

View File

@ -1095,21 +1095,89 @@ describe('basic-querying', function() {
});
});
context('regexp operator', function() {
const invalidDataTypes = [0, true, {}, [], Function, null];
describe('updateAll', function() {
let numAndDateModel, numAndDateArrayModel;
before(seed);
it('should return an error for invalid data types', function(done) {
// `undefined` is not tested because the `removeUndefined` function
// in `lib/dao.js` removes it before coercion
async.each(invalidDataTypes, function(v, cb) {
User.find({where: {name: {regexp: v}}}, function(err, users) {
should.exist(err);
cb();
});
}, done);
before(function() {
numAndDateModel = db.define('numAndDateModel', {
dateProp: Date,
numProp: Number,
});
numAndDateArrayModel = db.define('numAndDateArrayModel', {
dateArray: [Date],
numArray: [Number],
});
return db.automigrate(['numAndDateModel', 'numAndDateArrayModel']);
});
it('coerces primitive datatypes on update', function() {
const createDate = new Date('2019-02-21T12:00:00').toISOString();
const createData = {
dateProp: createDate,
numProp: '1',
};
const updateDate = new Date('2019-04-15T12:00:00').toISOString();
const updateData = {
dateProp: updateDate,
numProp: '3',
};
let createdId;
return numAndDateModel.create(createData)
.then((createdInstance) => {
createdId = createdInstance.id;
return numAndDateModel.updateAll({id: createdId}, updateData);
}).then(() => {
return numAndDateModel.findById(createdId);
}).then((found) => {
found.dateProp.should.deepEqual(new Date(updateDate));
found.numProp.should.equal(3);
});
});
// PostgreSQL connector does not support arrays at the moment
bdd.itIf(connectorCapabilities.supportsArrays !== false,
'coerces primitive array datatypes on update', function() {
const createDate = new Date('2019-02-21T12:00:00').toISOString();
const createData = {
dateArray: [createDate, createDate],
numArray: ['1', '2'],
};
const updateDate = new Date('2019-04-15T12:00:00').toISOString();
const updateData = {
dateArray: [updateDate, updateDate],
numArray: ['3', '4'],
};
let createdId;
return numAndDateArrayModel.create(createData)
.then((createdInstance) => {
createdId = createdInstance.id;
return numAndDateArrayModel.updateAll({id: createdId}, updateData);
}).then(() => {
return numAndDateArrayModel.findById(createdId);
}).then((found) => {
found.dateArray[0].should.deepEqual(new Date(updateDate));
found.dateArray[1].should.deepEqual(new Date(updateDate));
found.numArray[0].should.equal(3);
found.numArray[1].should.equal(4);
});
});
});
});
context('regexp operator', function() {
const invalidDataTypes = [0, true, {}, [], Function, null];
before(seed);
it('should return an error for invalid data types', function(done) {
// `undefined` is not tested because the `removeUndefined` function
// in `lib/dao.js` removes it before coercion
async.each(invalidDataTypes, function(v, cb) {
User.find({where: {name: {regexp: v}}}, function(err, users) {
should.exist(err);
cb();
});
}, done);
});
});

View File

@ -9,7 +9,7 @@
/* global getSchema:false */
const should = require('./init.js');
let db, Model;
let db, Model, modelWithDecimalArray, dateArrayModel, numArrayModel;
describe('datatypes', function() {
before(function(done) {
@ -25,7 +25,24 @@ describe('datatypes', function() {
nested: Nested,
};
Model = db.define('Model', modelTableSchema);
db.automigrate(['Model'], done);
modelWithDecimalArray = db.define('modelWithDecimalArray', {
randomReview: {
type: [String],
mongodb: {
dataType: 'Decimal128',
},
},
});
dateArrayModel = db.define('dateArrayModel', {
bunchOfDates: [Date],
bunchOfOtherDates: {
type: [Date],
},
});
numArrayModel = db.define('numArrayModel', {
bunchOfNums: [Number],
});
db.automigrate(['Model', 'modelWithDecimalArray', 'dateArrayModel', 'numArrayModel'], done);
});
it('should resolve top-level "type" property correctly', function() {
@ -45,6 +62,41 @@ describe('datatypes', function() {
});
Account.definition.properties.item.type.should.not.equal(String);
});
it('should resolve array prop with connector specific metadata', function() {
const props = modelWithDecimalArray.definition.properties;
props.randomReview.type.should.deepEqual(Array(String));
props.randomReview.mongodb.should.deepEqual({dataType: 'Decimal128'});
});
it('should coerce array of dates from string', function() {
const dateVal = new Date('2019-02-21T12:00:00').toISOString();
return dateArrayModel.create({
bunchOfDates: [dateVal,
dateVal,
dateVal],
bunchOfOtherDates: [dateVal,
dateVal,
dateVal],
}).then((created) => {
created.bunchOfDates[0].should.be.an.instanceOf(Date);
created.bunchOfDates[0].should.deepEqual(new Date(dateVal));
created.bunchOfOtherDates[0].should.be.an.instanceOf(Date);
created.bunchOfOtherDates[0].should.deepEqual(new Date(dateVal));
});
});
it('should coerce array of numbers from string', function() {
const dateVal = new Date('2019-02-21T12:00:00').toISOString();
return numArrayModel.create({
bunchOfNums: ['1',
'2',
'3'],
})
.then((created) => {
created.bunchOfNums[0].should.be.an.instanceOf(Number);
created.bunchOfNums[0].should.equal(1);
});
});
it('should return 400 when property of type array is set to string value',
function(done) {

View File

@ -2390,7 +2390,7 @@ describe('manipulation', function() {
it('should not coerce invalid values provided in where conditions', function(done) {
Person.update({name: 'Brett Boe'}, {dob: 'notadate'}, function(err) {
should.exist(err);
err.message.should.equal('Invalid date: notadate');
err.message.should.equal('Invalid date: Invalid Date');
done();
});
});