loopback-connector-mysql/test/mysql.test.js

887 lines
28 KiB
JavaScript

// Copyright IBM Corp. 2013,2016. All Rights Reserved.
// Node module: loopback-connector-mysql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var should = require('./init.js');
var sinon = require('sinon');
var Post, PostWithStringId, PostWithUniqueTitle, PostWithNumId, db;
// Mock up mongodb ObjectID
function ObjectID(id) {
if (!(this instanceof ObjectID)) {
return new ObjectID(id);
}
this.id1 = id.substring(0, 2);
this.id2 = id.substring(2);
}
ObjectID.prototype.toJSON = function() {
return this.id1 + this.id2;
};
describe('mysql', function() {
before(function(done) {
db = global.getDataSource();
Post = db.define('PostWithDefaultId', {
title: {type: String, length: 255, index: true},
content: {type: String},
comments: [String],
history: Object,
stars: Number,
userId: ObjectID,
}, {
forceId: false,
});
PostWithStringId = db.define('PostWithStringId', {
id: {type: String, id: true},
title: {type: String, length: 255, index: true},
content: {type: String},
});
PostWithNumId = db.define('PostWithNumId', {
id: {type: Number, id: true, null: false},
title: {type: String, length: 255, null: false, index: true},
content: {type: String, null: false},
buffProp: {type: Buffer, null: false},
objProp: {type: Object, null: false},
arrProp: {type: [Number], null: false},
dateProp: {type: Date, null: false},
pointProp: {type: 'GeoPoint', null: false},
});
PostWithUniqueTitle = db.define('PostWithUniqueTitle', {
title: {type: String, length: 255, index: {unique: true}},
content: {type: String},
});
db.automigrate(['PostWithDefaultId', 'PostWithStringId', 'PostWithUniqueTitle', 'PostWithNumId'], function(err) {
should.not.exist(err);
done(err);
});
});
beforeEach(function(done) {
Post.destroyAll(function() {
PostWithStringId.destroyAll(function() {
PostWithUniqueTitle.destroyAll(done);
});
});
});
it('should allow array or object', function(done) {
Post.create({title: 'a', content: 'AAA', comments: ['1', '2'],
history: {a: 1, b: 'b'}}, function(err, post) {
should.not.exist(err);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('a');
p.comments.should.eql(['1', '2']);
p.history.should.eql({a: 1, b: 'b'});
done();
});
});
});
it('should allow ObjectID', function(done) {
var uid = new ObjectID('123');
Post.create({title: 'a', content: 'AAA', userId: uid},
function(err, post) {
should.not.exist(err);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('a');
p.userId.should.eql(uid);
done();
});
});
});
it('updateOrCreate should update the instance', function(done) {
Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post.title = 'b';
Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('b');
done();
});
});
});
});
it('updateOrCreate should update the instance without removing existing properties', function(done) {
Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post = post.toObject();
delete post.title;
Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('a');
done();
});
});
});
});
it('updateOrCreate should create a new instance if it does not exist', function(done) {
var post = {id: 123, title: 'a', content: 'AAA'};
Post.updateOrCreate(post, function(err, p) {
should.not.exist(err);
p.title.should.be.equal(post.title);
p.content.should.be.equal(post.content);
p.id.should.be.equal(post.id);
Post.findById(p.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal(post.title);
p.id.should.be.equal(post.id);
done();
});
});
});
context('replaceOrCreate', function() {
it('should replace the instance', function(done) {
Post.create({title: 'a', content: 'AAA'}, function(err, post) {
if (err) return done(err);
post = post.toObject();
delete post.content;
Post.replaceOrCreate(post, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
p.title.should.equal('a');
should.not.exist(p.content);
should.not.exist(p._id);
Post.findById(post.id, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
p.title.should.equal('a');
should.not.exist(post.content);
should.not.exist(p._id);
done();
});
});
});
});
it('should replace with new data', function(done) {
Post.create({title: 'a', content: 'AAA', comments: ['Comment1']},
function(err, post) {
if (err) return done(err);
post = post.toObject();
delete post.comments;
delete post.content;
post.title = 'b';
Post.replaceOrCreate(post, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
should.not.exist(p._id);
p.title.should.equal('b');
should.not.exist(p.content);
should.not.exist(p.comments);
Post.findById(post.id, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
should.not.exist(p._id);
p.title.should.equal('b');
should.not.exist(p.content);
should.not.exist(p.comments);
done();
});
});
});
});
it('should create a new instance if it does not exist', function(done) {
var post = {id: 123, title: 'a', content: 'AAA'};
Post.replaceOrCreate(post, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
should.not.exist(p._id);
p.title.should.equal(post.title);
p.content.should.equal(post.content);
Post.findById(p.id, function(err, p) {
if (err) return done(err);
p.id.should.equal(post.id);
should.not.exist(p._id);
p.title.should.equal(post.title);
p.content.should.equal(post.content);
done();
});
});
});
});
it('save should update the instance with the same id', function(done) {
Post.create({title: 'a', content: 'AAA'}, function(err, post) {
post.title = 'b';
post.save(function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('b');
done();
});
});
});
});
it('save should update the instance without removing existing properties',
function(done) {
Post.create({title: 'a', content: 'AAA'}, function(err, post) {
delete post.title;
post.save(function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
Post.findById(post.id, function(err, p) {
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal('a');
done();
});
});
});
});
it('save should create a new instance if it does not exist', function(done) {
var post = new Post({id: 123, title: 'a', content: 'AAA'});
post.save(post, function(err, p) {
should.not.exist(err);
p.title.should.be.equal(post.title);
p.content.should.be.equal(post.content);
p.id.should.be.equal(post.id);
Post.findById(p.id, function(err, p) {
should.not.exist(err);
p.id.should.be.equal(post.id);
p.content.should.be.equal(post.content);
p.title.should.be.equal(post.title);
p.id.should.be.equal(post.id);
done();
});
});
});
it('all return should honor filter.fields', function(done) {
var post = new Post({title: 'b', content: 'BBB'});
post.save(function(err, post) {
Post.all({fields: ['title'], where: {title: 'b'}}, function(err, posts) {
should.not.exist(err);
posts.should.have.lengthOf(1);
post = posts[0];
post.should.have.property('title', 'b');
post.should.have.property('content', undefined);
should.not.exist(post.id);
done();
});
});
});
it('find should order by id if the order is not set for the query filter',
function(done) {
PostWithStringId.create({id: '2', title: 'c', content: 'CCC'}, function(err, post) {
PostWithStringId.create({id: '1', title: 'd', content: 'DDD'}, function(err, post) {
PostWithStringId.find(function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(2);
posts[0].id.should.be.equal('1');
PostWithStringId.find({limit: 1, offset: 0}, function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(1);
posts[0].id.should.be.equal('1');
PostWithStringId.find({limit: 1, offset: 1}, function(err, posts) {
should.not.exist(err);
posts.length.should.be.equal(1);
posts[0].id.should.be.equal('2');
done();
});
});
});
});
});
});
it('should allow to find using like', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {title: {like: 'M%st'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
});
});
});
it('should support like for no match', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {title: {like: 'M%XY'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
it('should allow to find using nlike', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {title: {nlike: 'M%st'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
it('should support nlike for no match', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {title: {nlike: 'M%XY'}}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
});
});
});
it('should support "and" operator that is satisfied', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {and: [
{title: 'My Post'},
{content: 'Hello'},
]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
});
});
});
it('should support "and" operator that is not satisfied', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {and: [
{title: 'My Post'},
{content: 'Hello1'},
]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
it('should support "or" that is satisfied', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {or: [
{title: 'My Post'},
{content: 'Hello1'},
]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
});
});
});
it('should support "or" operator that is not satisfied', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.find({where: {or: [
{title: 'My Post1'},
{content: 'Hello1'},
]}}, function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
context('null vals in different operators', function() {
var defaultPost = {
id: 3,
title: 'defTitle',
content: 'defContent',
buffProp: new Buffer('defBuffer'),
objProp: {defKey: 'defVal'},
arrProp: [0],
dateProp: new Date('2017-06-14'),
pointProp: {lng: 4.51515, lat: 57.2314},
};
beforeEach(function(done) {
PostWithNumId.destroyAll(done);
});
after(function(done) {
PostWithNumId.destroyAll(done);
});
it('should handle null in inq operator', function(done) {
defaultPost.id = 1;
defaultPost.title = 'Foo';
defaultPost.content = 'Bar';
PostWithNumId.create(defaultPost, function(err, post) {
should.not.exist(err);
post.id.should.equal(defaultPost.id);
PostWithNumId.find({where: {id: {inq: [null, 1]}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].title.should.equal('Foo');
posts[0].id.should.equal(1);
done();
});
});
});
it('should handle null in nin operator', function(done) {
defaultPost.id = 2;
defaultPost.title = 'Make';
defaultPost.content = 'Toyota';
PostWithNumId.create(defaultPost, function(err, post) {
should.not.exist(err);
post.id.should.equal(defaultPost.id);
PostWithNumId.find({where: {id: {nin: [null, 3]}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('Toyota');
posts[0].id.should.equal(2);
done();
});
});
});
it('should handle null in neq operator', function(done) {
defaultPost.id = 3;
defaultPost.title = 'Model';
defaultPost.content = 'Corolla';
PostWithNumId.create(defaultPost, function(err, post) {
should.not.exist(err);
post.id.should.equal(defaultPost.id);
PostWithNumId.find({where: {id: {neq: null}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('Corolla');
posts[0].id.should.equal(3);
done();
});
});
});
it('should handle null in nin op for different datatypes', function(done) {
PostWithNumId.create(defaultPost, function(err, post) {
should.not.exist(err);
post.id.should.equal(defaultPost.id);
PostWithNumId.find({where: {and: [
{id: {nin: [null]}},
{title: {nin: [null]}},
{content: {nin: [null]}},
{buffProp: {nin: [null]}},
{objProp: {nin: [null]}},
{arrProp: {nin: [null]}},
{dateProp: {nin: [null]}},
{pointProp: {nin: [null]}},
]}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].toObject().should.deepEqual(defaultPost);
done();
});
});
});
});
// The where object should be parsed by the connector
it('should support where for count', function(done) {
Post.create({title: 'My Post', content: 'Hello'}, function(err, post) {
Post.count({and: [
{title: 'My Post'},
{content: 'Hello'},
]}, function(err, count) {
should.not.exist(err);
count.should.be.equal(1);
Post.count({and: [
{title: 'My Post1'},
{content: 'Hello'},
]}, function(err, count) {
should.not.exist(err);
count.should.be.equal(0);
done();
});
});
});
});
// The where object should be parsed by the connector
it('should support where for destroyAll', function(done) {
Post.create({title: 'My Post1', content: 'Hello'}, function(err, post) {
Post.create({title: 'My Post2', content: 'Hello'}, function(err, post) {
Post.destroyAll({and: [
{title: 'My Post1'},
{content: 'Hello'},
]}, function(err) {
should.not.exist(err);
Post.count(function(err, count) {
should.not.exist(err);
count.should.be.equal(1);
done();
});
});
});
});
});
it('should not allow SQL injection for inq operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {title: {inq: ['SELECT title from PostWithDefaultId']}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
});
it('should not allow SQL injection for lt operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {stars: {lt: 'SELECT title from PostWithDefaultId'}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
});
it('should not allow SQL injection for nin operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {title: {nin: ['SELECT title from PostWithDefaultId']}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 2);
done();
});
});
});
});
it('should not allow SQL injection for inq operator with number column', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {stars: {inq: ['SELECT title from PostWithDefaultId']}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
});
it('should not allow SQL injection for inq operator with array value', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {stars: {inq: [5, 'SELECT title from PostWithDefaultId']}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 1);
done();
});
});
});
});
it('should not allow SQL injection for between operator', function(done) {
Post.create({title: 'My Post1', content: 'Hello', stars: 5},
function(err, post) {
Post.create({title: 'My Post2', content: 'Hello', stars: 20},
function(err, post) {
Post.find({where: {stars: {between: [5, 'SELECT title from PostWithDefaultId']}}},
function(err, posts) {
should.not.exist(err);
posts.should.have.property('length', 0);
done();
});
});
});
});
it('should not allow duplicate titles', function(done) {
var data = {title: 'a', content: 'AAA'};
PostWithUniqueTitle.create(data, function(err, post) {
should.not.exist(err);
PostWithUniqueTitle.create(data, function(err, post) {
should.exist(err);
done();
});
});
});
context('regexp operator', function() {
beforeEach(function deleteExistingTestFixtures(done) {
Post.destroyAll(done);
});
beforeEach(function createTestFixtures(done) {
Post.create([
{title: 'a', content: 'AAA'},
{title: 'b', content: 'BBB'},
], done);
});
after(function deleteTestFixtures(done) {
Post.destroyAll(done);
});
context('with regex strings', function() {
context('using no flags', function() {
it('should work', function(done) {
Post.find({where: {content: {regexp: '^A'}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
});
context('using flags', function() {
beforeEach(function addSpy() {
sinon.stub(console, 'warn');
});
afterEach(function removeSpy() {
console.warn.restore();
});
it('should work', function(done) {
Post.find({where: {content: {regexp: '^a/i'}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
it('should print a warning when the ignore flag is set',
function(done) {
Post.find({where: {content: {regexp: '^a/i'}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the global flag is set',
function(done) {
Post.find({where: {content: {regexp: '^a/g'}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the multiline flag is set',
function(done) {
Post.find({where: {content: {regexp: '^a/m'}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
});
it('filter with case sensitive regex string', function(done) {
Post.find({where: {content: {regexp: '^a'}}}, function(err, posts) {
should.not.exist(err);
should.exist(posts);
posts.length.should.equal(0);
done();
});
});
});
context('with regex literals', function() {
context('using no flags', function() {
it('should work', function(done) {
Post.find({where: {content: {regexp: /^A/}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
});
context('using flags', function() {
beforeEach(function addSpy() {
sinon.stub(console, 'warn');
});
afterEach(function removeSpy() {
console.warn.restore();
});
it('should work', function(done) {
Post.find({where: {content: {regexp: /^a/i}}}, function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
it('should print a warning when the ignore flag is set',
function(done) {
Post.find({where: {content: {regexp: /^a/i}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the global flag is set',
function(done) {
Post.find({where: {content: {regexp: /^a/g}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the multiline flag is set',
function(done) {
Post.find({where: {content: {regexp: /^a/m}}}, function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
});
it('filter with case sensitive regex literal', function(done) {
Post.find({where: {content: {regexp: /^B/}}}, function(err, posts) {
should.not.exist(err);
should.exist(posts);
posts.length.should.equal(1);
posts[0].content.should.equal('BBB');
done();
});
});
});
context('with regex objects', function() {
beforeEach(function addSpy() {
sinon.stub(console, 'warn');
});
afterEach(function removeSpy() {
console.warn.restore();
});
context('using no flags', function() {
it('should work', function(done) {
Post.find({where: {content: {regexp: new RegExp(/^A/)}}},
function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
});
context('using flags', function() {
it('should work', function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/i)}}},
function(err, posts) {
should.not.exist(err);
posts.length.should.equal(1);
posts[0].content.should.equal('AAA');
done();
});
});
it('should print a warning when the ignore flag is set',
function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/i)}}},
function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the global flag is set',
function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/g)}}},
function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('should print a warning when the multiline flag is set',
function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/m)}}},
function(err, posts) {
console.warn.calledOnce.should.be.ok;
done();
});
});
it('filter with case sensitive regex object', function(done) {
Post.find({where: {content: {regexp: new RegExp(/^a/)}}}, function(err, posts) {
should.not.exist(err);
should.exist(posts);
posts.length.should.equal(0);
done();
});
});
});
});
});
after(function(done) {
Post.destroyAll(function() {
PostWithStringId.destroyAll(function() {
PostWithUniqueTitle.destroyAll(done);
});
});
});
});