it('is possible to use casting multiple times mixed in with other utilities', function (done) { var self = this , type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer') , _done = _.after(2, function() { done() }) if (Support.dialectIsMySQL()) { type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed') } this.User.create({ intVal: type }).on('sql', function (sql) { if (Support.dialectIsMySQL()) { expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)') } else { expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)') } _done() }).success(function (user) { self.User.find(user.id).success(function (user) { expect(user.intVal).to.equal(-1) _done() }) }) })
User.sync({ force: true }).success(function() { var sql = '' if (Support.dialectIsMySQL()) { sql = 'SELECT COLUMN_COMMENT as cmt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = \'' + self.sequelize.config.database + '\' AND TABLE_NAME = \'Users\' AND COLUMN_NAME = \'username\''; } else if (dialect === "postgres" || dialect === "postgres-native") { sql = 'SELECT com.description as cmt FROM pg_attribute a JOIN pg_class pgc ON pgc.oid = a.attrelid \ LEFT JOIN pg_index i ON (pgc.oid = i.indrelid AND i.indkey[0] = a.attnum) \ LEFT JOIN pg_description com on (pgc.oid = com.objoid AND a.attnum = com.objsubid) \ WHERE a.attnum > 0 AND pgc.oid = a.attrelid AND pg_table_is_visible(pgc.oid) \ AND NOT a.attisdropped AND pgc.relname = \'Users\' AND a.attname = \'username\''; } else if (dialect === "sqlite") { // sqlite doesn't support comments except for explicit comments in the file expect(true).to.be.true return done() } else { console.log('FIXME: This dialect is not supported :('); expect(true).to.be.true return done() } self.sequelize.query(sql, null, {raw: true}).success(function(result) { expect(result[0].cmt).to.equal('Some lovely info for my DBA'); done() }) })
UserNull.create({ username: '******', smth: null }).error(function(err) { expect(err).to.exist if (Support.dialectIsMySQL()) { // We need to allow two different errors for MySQL, see: // http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables expect(err.message).to.match(/(Column 'smth' cannot be null|Field 'smth' doesn't have a default value)/) } else if (dialect === "sqlite") { expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/) } else { expect(err.message).to.match(/.*column "smth" violates not-null.*/) } UserNull.create({ username: '******', smth: 'foo' }).success(function() { UserNull.create({ username: '******', smth: 'bar' }).error(function(err) { expect(err).to.exist if (dialect === "sqlite") { expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/) } else if (Support.dialectIsMySQL()) { expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/) } else { expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/) } done() }) }) })
var qq = function(str) { if (dialect == 'postgres' || dialect == 'sqlite') { return '"' + str + '"' } else if (Support.dialectIsMySQL()) { return '`' + str + '`' } else { return str } }
}).on('sql', function (sql) { if (Support.dialectIsMySQL()) { expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)') } else { expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)') } _done() }).success(function (user) {
}).then(function(users){ if (Support.dialectIsMySQL()) { // MySQL will return NULL, becuase they lack EMPTY geometry data support. expect(users[0].field).to.be.eql(null); } else if (dialect === 'postgres' || dialect === 'postgres-native') { //Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996 expect(users[0].field).to.be.deep.eql({ type: "Point", coordinates: [0,0] }); } else { expect(users[0].field).to.be.deep.eql(point); } });
it('is possible for .literal() to contain other utility functions', function (done) { var self = this this.User.create({ intVal: this.sequelize.literal(this.sequelize.cast('1-2', (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER'))) }).success(function (user) { self.User.find(user.id).success(function (user) { expect(user.intVal).to.equal(-1) done() }) }) })
UserNull.create({ username: '******', smth: 'bar' }).error(function(err) { expect(err).to.exist if (dialect === "sqlite") { expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/) } else if (Support.dialectIsMySQL()) { expect(err.message).to.match(/Duplicate entry 'foo' for key 'username'/) } else { expect(err.message).to.match(/.*duplicate key value violates unique constraint.*/) } done() })
it('is possible to just use .literal() to bypass escaping', function (done) { var self = this this.User.create({ intVal: this.sequelize.literal('CAST(1-2 AS ' + (Support.dialectIsMySQL() ? 'SIGNED' : 'INTEGER') + ')') }).success(function (user) { self.User.find(user.id).success(function (user) { expect(user.intVal).to.equal(-1) done() }) }) })
User.create({ username: '******' }).error(function(err) { expect(err).to.exist if (dialect === "sqlite") { expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/) } else if (Support.dialectIsMySQL()) { expect(err.message).to.match(/.*Duplicate\ entry.*/) } else { expect(err.message).to.match(/.*duplicate\ key\ value.*/) } done() })
UserNull.create({ username: '******', smth: null }).error(function(err) { expect(err).to.exist expect(err.smth[0].path).to.equal('smth'); if (Support.dialectIsMySQL()) { // We need to allow two different errors for MySQL, see: // http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_trans_tables expect(err.smth[0].message).to.match(/notNull Violation/) } else if (dialect === "sqlite") { expect(err.smth[0].message).to.match(/notNull Violation/) } else { expect(err.smth[0].message).to.match(/notNull Violation/) } done() })
it('should use the default port when no other is specified', function() { var sequelize = new Sequelize('dbname', 'root', 'pass', { dialect: dialect }) , config = sequelize.config , port; if (Support.dialectIsMySQL()) { port = 3306; } else if (dialect === 'postgres' || dialect === 'postgres-native') { port = 5432; } else { // sqlite has no concept of ports when connecting return; } expect(config.port).to.equal(port); });
describe(Support.getTestDialectTeaser('Timezone'), function() { beforeEach(function() { this.sequelizeWithTimezone = Support.createSequelizeInstance({ timezone: '+07:00' }); }); it('returns the same value for current timestamp', function() { var now = 'now()' , startQueryTime = Date.now(); if (dialect === 'mssql') { now = 'GETDATE()'; } var query = 'SELECT ' + now + ' as now'; return Promise.all([ this.sequelize.query(query), this.sequelizeWithTimezone.query(query) ]).spread(function(now1, now2) { var elapsedQueryTime = (Date.now() - startQueryTime) + 20; expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); }); }); if (Support.dialectIsMySQL()) { it('handles existing timestamps', function() { var NormalUser = this.sequelize.define('user', {}) , TimezonedUser = this.sequelizeWithTimezone.define('user', {}); return this.sequelize.sync({ force: true }).bind(this).then(function() { return TimezonedUser.create({}); }).then(function(timezonedUser) { this.timezonedUser = timezonedUser; return NormalUser.find(timezonedUser.id); }).then(function(normalUser) { // Expect 7 hours difference, in milliseconds. // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp // this test does not apply to PG, since it stores the timezone along with the timestamp. expect(normalUser.createdAt.getTime() - this.timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 50); }); }); } });
it('is possible to use casting when creating an instance', function (done) { var self = this , type = Support.dialectIsMySQL() ? 'signed' : 'integer' , _done = _.after(2, function() { done() }) this.User.create({ intVal: this.sequelize.cast('1', type) }).on('sql', function (sql) { expect(sql).to.match(new RegExp('CAST\\(1 AS ' + type.toUpperCase() + '\\)')) _done() }) .success(function (user) { self.User.find(user.id).success(function (user) { expect(user.intVal).to.equal(1) _done() }) }) })
describe(Support.getTestDialectTeaser('db:drop'), () => { if (Support.dialectIsPostgres()) { it('correctly drops database', function (done) { const databaseName = `my_test_db_${_.random(10000, 99999)}`; prepare( 'db:drop', () => { this.sequelize.query(`SELECT 1 as exists FROM pg_database WHERE datname = '${databaseName}';`, { type: this.sequelize.QueryTypes.SELECT }).then(result => { expect(result).to.be.empty; done(); }); }, { config: { database: databaseName } }); }); } if (Support.dialectIsMySQL()) { it('correctly drops database', function (done) { const databaseName = `my_test_db_${_.random(10000, 99999)}`; prepare( 'db:drop', () => { this.sequelize.query(`SELECT IF('${databaseName}' IN(SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA), 1, 0) AS found;`, { type: this.sequelize.QueryTypes.SELECT }).then(result => { expect(result[0].found).to.eql(0); done(); }); }, { config: { database: databaseName } }); }); } });
var chai = require('chai') , expect = chai.expect , Support = require(__dirname + '/../support') , dialect = Support.getTestDialect() , sinon = require('sinon') , DataTypes = require(__dirname + "/../../lib/data-types") chai.Assertion.includeStack = true if (Support.dialectIsMySQL()) { describe('[MYSQL Specific] Connector Manager', function() { it('works correctly after being idle', function(done) { var User = this.sequelize.define('User', { username: DataTypes.STRING }) , spy = sinon.spy() User.sync({force: true}).on('success', function() { User.create({username: '******'}).on('success', function() { User.count().on('success', function(count) { expect(count).to.equal(1) spy() setTimeout(function() { User.count().on('success', function(count) { expect(count).to.equal(1) spy() if (spy.calledTwice) { done() } }) }, 1000) })
describe('general / basic function', function() { beforeEach(function() { var self = this; return this.User.create({username: '******'}).then(function(user) { self.UserPrimary = self.sequelize.define('UserPrimary', { specialkey: { type: DataTypes.STRING, primaryKey: true } }); return self.UserPrimary.sync({force: true}).then(function() { return self.UserPrimary.create({specialkey: 'a string'}).then(function() { self.user = user; }); }); }); }); if (Support.dialectIsMySQL()) { // Bit fields interpreted as boolean need conversion from buffer / bool. // Sqlite returns the inserted value as is, and postgres really should the built in bool type instead it('allows bit fields as booleans', function() { var self = this, bitUser = this.sequelize.define('bituser', { bool: 'BIT(1)' }, { timestamps: false }); // First use a custom data type def to create the bit field return bitUser.sync({ force: true }).then(function() { // Then change the definition to BOOLEAN bitUser = self.sequelize.define('bituser', { bool: DataTypes.BOOLEAN }, { timestamps: false }); return bitUser.bulkCreate([ { bool: 0 }, { bool: 1 } ]); }).then(function() { return bitUser.findAll(); }).then(function(bitUsers) { expect(bitUsers[0].bool).not.to.be.ok; expect(bitUsers[1].bool).to.be.ok; }); }); } it('does not modify the passed arguments', function() { var options = { where: ['specialkey = ?', 'awesome']}; return this.UserPrimary.find(options).then(function() { expect(options).to.deep.equal({ where: ['specialkey = ?', 'awesome']}); }); }); it('treats questionmarks in an array', function() { var test = false; return this.UserPrimary.find({ where: ['specialkey = ?', 'awesome'] }, { logging: function(sql) { test = true; expect(sql).to.contain("WHERE specialkey = 'awesome'"); } }).then(function() { expect(test).to.be.true; }); }); it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function() { return this.UserPrimary.find('a string').then(function(user) { expect(user.specialkey).to.equal('a string'); }); }); it.skip('doesn\'t throw an error when entering in a non integer value', function() { return this.User.find('a string value').then(function(user) { expect(user).to.be.null; }); }); it('returns a single dao', function() { var self = this; return this.User.find(this.user.id).then(function(user) { expect(Array.isArray(user)).to.not.be.ok; expect(user.id).to.equal(self.user.id); expect(user.id).to.equal(1); }); }); it('returns a single dao given a string id', function() { var self = this; return this.User.find(this.user.id + '').then(function(user) { expect(Array.isArray(user)).to.not.be.ok; expect(user.id).to.equal(self.user.id); expect(user.id).to.equal(1); }); }); it('should make aliased attributes available', function() { return this.User.find({ where: { id: 1 }, attributes: ['id', ['username', 'name']] }).then(function(user) { expect(user.dataValues.name).to.equal('barfooz'); }); }); it('should not try to convert boolean values if they are not selected', function() { var UserWithBoolean = this.sequelize.define('UserBoolean', { active: Sequelize.BOOLEAN }); return UserWithBoolean.sync({force: true}).then(function() { return UserWithBoolean.create({ active: true }).then(function(user) { return UserWithBoolean.find({ where: { id: user.id }, attributes: ['id'] }).then(function(user) { expect(user.active).not.to.exist; }); }); }); }); it('finds a specific user via where option', function() { return this.User.find({ where: { username: '******' } }).then(function(user) { expect(user.username).to.equal('barfooz'); }); }); it('doesn\'t find a user if conditions are not matching', function() { return this.User.find({ where: { username: '******' } }).then(function(user) { expect(user).to.be.null; }); }); it('allows sql logging', function() { var test = false; return this.User.find({ where: { username: '******' } }, { logging: function(sql) { test = true; expect(sql).to.exist; expect(sql.toUpperCase().indexOf('SELECT')).to.be.above(-1); } }).then(function() { expect(test).to.be.true; }); }); it('ignores passed limit option', function() { return this.User.find({ limit: 10 }).then(function(user) { // it returns an object instead of an array expect(Array.isArray(user)).to.not.be.ok; expect(user.dataValues.hasOwnProperty('username')).to.be.ok; }); }); it('finds entries via primary keys', function() { var self = this , UserPrimary = self.sequelize.define('UserWithPrimaryKey', { identifier: {type: Sequelize.STRING, primaryKey: true}, name: Sequelize.STRING }); return UserPrimary.sync({ force: true }).then(function() { return UserPrimary.create({ identifier: 'an identifier', name: 'John' }).then(function(u) { expect(u.id).not.to.exist; return UserPrimary.find('an identifier').then(function(u2) { expect(u2.identifier).to.equal('an identifier'); expect(u2.name).to.equal('John'); }); }); }); }); it('finds entries via a string primary key called id', function() { var self = this , UserPrimary = self.sequelize.define('UserWithPrimaryKey', { id: {type: Sequelize.STRING, primaryKey: true}, name: Sequelize.STRING }); return UserPrimary.sync({ force: true }).then(function() { return UserPrimary.create({ id: 'a string based id', name: 'Johnno' }).then(function() { return UserPrimary.find('a string based id').then(function(u2) { expect(u2.id).to.equal('a string based id'); expect(u2.name).to.equal('Johnno'); }); }); }); }); it('always honors ZERO as primary key', function() { var self = this , permutations = [ 0, '0', {where: {id: 0}}, {where: {id: '0'}} ] , count = 0; return this.User.bulkCreate([{username: '******'}, {username: '******'}]).then(function() { return self.sequelize.Promise.map(permutations, function(perm) { return self.User.find(perm, { logging: function(s) { expect(s.indexOf(0)).not.to.equal(-1); count++; } }).then(function(user) { expect(user).to.be.null; }); }); }).then(function() { expect(count).to.be.equal(permutations.length); }); }); it('should allow us to find IDs using capital letters', function() { var User = this.sequelize.define('User' + config.rand(), { ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, Login: { type: Sequelize.STRING } }); return User.sync({ force: true }).then(function() { return User.create({Login: '******'}).then(function() { return User.find(1).then(function(user) { expect(user).to.exist; expect(user.ID).to.equal(1); }); }); }); }); });
describe('query', function() { afterEach(function(done) { this.sequelize.options.quoteIdentifiers = true done() }) beforeEach(function(done) { this.User = this.sequelize.define('User', { username: DataTypes.STRING }) this.insertQuery = "INSERT INTO " + qq(this.User.tableName) + " (username, " + qq("createdAt") + ", " + qq("updatedAt") + ") VALUES ('john', '2012-01-01 10:10:10', '2012-01-01 10:10:10')" this.User.sync({ force: true }).success(function() { done() }) }) it('executes a query the internal way', function(done) { this.sequelize.query(this.insertQuery, null, { raw: true }) .complete(function(err, result) { expect(err).to.be.null expect(result).to.be.null done() }) }) it('executes a query if only the sql is passed', function(done) { this.sequelize.query(this.insertQuery) .complete(function(err, result) { expect(err).to.be.null expect(result).to.not.exist done() }) }) it('executes select queries correctly', function(done) { var self = this self.sequelize.query(this.insertQuery).success(function() { self.sequelize .query("select * from " + qq(self.User.tableName) + "") .complete(function(err, users) { expect(err).to.be.null expect(users.map(function(u){ return u.username })).to.include('john') done() }) }) }) it('executes select queries correctly when quoteIdentifiers is false', function(done) { var self = this , seq = Object.create(self.sequelize) seq.options.quoteIdentifiers = false seq.query(this.insertQuery).success(function() { seq.query("select * from " + qq(self.User.tableName) + "") .complete(function(err, users) { expect(err).to.be.null expect(users.map(function(u){ return u.username })).to.include('john') done() }) }) }) it('executes select query with dot notation results', function(done) { var self = this self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).complete(function() { self.sequelize.query(self.insertQuery).success(function() { self.sequelize .query("select username as " + qq("user.username") + " from " + qq(self.User.tableName) + "") .complete(function(err, users) { expect(err).to.be.null expect(users).to.deep.equal([{'user.username':'******'}]) done() }) }) }) }) it('executes select query with dot notation results and nest it', function(done) { var self = this self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).complete(function() { self.sequelize.query(self.insertQuery).success(function() { self.sequelize .query("select username as " + qq("user.username") + " from " + qq(self.User.tableName) + "", null, { raw: true, nest: true }) .complete(function(err, users) { expect(err).to.be.null expect(users.map(function(u){ return u.user })).to.deep.equal([{'username':'******'}]) done() }) }) }) }) if (Support.dialectIsMySQL()) { it('executes stored procedures', function(done) { var self = this self.sequelize.query(this.insertQuery).success(function() { self.sequelize.query('DROP PROCEDURE IF EXISTS foo').success(function() { self.sequelize.query( "CREATE PROCEDURE foo()\nSELECT * FROM " + self.User.tableName + ";" ).success(function() { self.sequelize.query('CALL foo()').success(function(users) { expect(users.map(function(u){ return u.username })).to.include('john') done() }) }) }) }) }) } else { console.log('FIXME: I want to be supported in this dialect as well :-(') } it('uses the passed DAOFactory', function(done) { var self = this self.sequelize.query(this.insertQuery).success(function() { self.sequelize.query("SELECT * FROM " + qq(self.User.tableName) + ";", self.User).success(function(users) { expect(users[0].Model).to.equal(self.User) done() }) }) }) it('dot separated attributes when doing a raw query without nest', function(done) { var tickChar = (dialect === 'postgres') ? '"' : '`' , sql = "select 1 as " + Sequelize.Utils.addTicks('foo.bar.baz', tickChar) this.sequelize.query(sql, null, { raw: true, nest: false }).success(function(result) { expect(result).to.deep.equal([ { 'foo.bar.baz': 1 } ]) done() }) }) it('destructs dot separated attributes when doing a raw query using nest', function(done) { var tickChar = (dialect === 'postgres') ? '"' : '`' , sql = "select 1 as " + Sequelize.Utils.addTicks('foo.bar.baz', tickChar) this.sequelize.query(sql, null, { raw: true, nest: true }).success(function(result) { expect(result).to.deep.equal([ { foo: { bar: { baz: 1 } } } ]) done() }) }) it('replaces token with the passed array', function(done) { this.sequelize.query('select ? as foo, ? as bar', null, { raw: true }, [ 1, 2 ]).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]) done() }) }) it('replaces named parameters with the passed object', function(done) { this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: 2 }).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]) done() }) }) it('replaces named parameters with the passed object and ignore those which does not qualify', function(done) { this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]) done() }) }) it('replaces named parameters with the passed object using the same key twice', function(done) { this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]) done() }) }) it('replaces named parameters with the passed object having a null property', function(done) { this.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, { one: 1, two: null }).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: null }]) done() }) }) it('throw an exception when key is missing in the passed object', function(done) { var self = this expect(function() { self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true }, { one: 1, two: 2 }) }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g) done() }) it('throw an exception with the passed number', function(done) { var self = this expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 2) }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g) done() }) it('throw an exception with the passed empty object', function(done) { var self = this expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, {}) }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g) done() }) it('throw an exception with the passed string', function(done) { var self = this expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, 'foobar') }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g) done() }) it('throw an exception with the passed date', function(done) { var self = this expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true }, new Date()) }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g) done() }) it('handles AS in conjunction with functions just fine', function(done) { this.sequelize.query('SELECT ' + (dialect === "sqlite" ? 'date(\'now\')' : 'NOW()') + ' AS t').success(function(result) { expect(moment(result[0].t).isValid()).to.be.true done() }) }) if (Support.getTestDialect() === 'postgres') { it('replaces named parameters with the passed object and ignores casts', function(done) { this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', null, { raw: true }, { one: 1, two: 2 }).success(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]) done() }) }) it('supports WITH queries', function(done) { this .sequelize .query("WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t") .success(function(results) { expect(results).to.deep.equal([ { "sum": "5050" } ]) done() }) }) } })
describe('[MYSQL Specific] Connector Manager', function() { it('works correctly after being idle', function() { var User = this.sequelize.define('User', { username: DataTypes.STRING }) , spy = sinon.spy() , self = this; return User.sync({force: true}).then(function() { return User.create({username: '******'}).then(function() { return User.count().then(function(count) { expect(count).to.equal(1); spy(); return self.sequelize.Promise.delay(1000).then(function() { return User.count().then(function(count) { expect(count).to.equal(1); spy(); if (!spy.calledTwice) { throw new Error('Spy was not called twice'); } }); }); }); }); }); }); it('accepts new queries after shutting down a connection', function() { // Create a sequelize instance with pooling disabled var sequelize = Support.createSequelizeInstance({ pool: false }); var User = sequelize.define('User', { username: DataTypes.STRING }); return User.sync({force: true}).then(function() { return User.create({username: '******'}); }).then(function() { // After 100 ms the DB connection will be disconnected for inactivity return sequelize.Promise.delay(100); }).then(function () { // This query will be queued just after the `client.end` is executed and before its callback is called return sequelize.query('SELECT COUNT(*) AS count FROM Users', { type: sequelize.QueryTypes.SELECT }); }).then(function(count) { expect(count[0].count).to.equal(1); }); }); // This should run only on direct mysql if (Support.dialectIsMySQL(true)) { it('should maintain connection', function() { var sequelize = Support.createSequelizeInstance({pool: {min: 1, max: 1, handleDisconnects: true, idle: 5000}}) , cm = sequelize.connectionManager , conn; return sequelize.sync() .then(function() { return cm.getConnection(); }) .then(function(connection) { // Save current connection conn = connection; }) .then(function() { return cm.releaseConnection(conn); }) .then(function() { // Get next available connection return cm.getConnection(); }) .then(function(connection) { // Old threadId should be different from current new one expect(conn.threadId).to.be.equal(connection.threadId); expect(cm.validate(conn)).to.be.ok; return cm.releaseConnection(connection); }); }); it('should work with handleDisconnects', function() { var sequelize = Support.createSequelizeInstance({pool: {min: 1, max: 1, handleDisconnects: true, idle: 5000}}) , cm = sequelize.connectionManager , conn; return sequelize.sync() .then(function() { return cm.getConnection(); }) .then(function(connection) { // Save current connection conn = connection; // simulate a unexpected end connection._protocol.end(); }) .then(function() { return cm.releaseConnection(conn); }) .then(function() { // Get next available connection return cm.getConnection(); }) .then(function(connection) { // Old threadId should be different from current new one expect(conn.threadId).to.not.be.equal(connection.threadId); expect(cm.validate(conn)).to.not.be.ok; return cm.releaseConnection(connection); }); }); } });
describe('query', function() { afterEach(function() { this.sequelize.options.quoteIdentifiers = true; }); beforeEach(function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.insertQuery = 'INSERT INTO ' + qq(this.User.tableName) + ' (username, ' + qq('createdAt') + ', ' + qq('updatedAt') + ") VALUES ('john', '2012-01-01 10:10:10', '2012-01-01 10:10:10')"; return this.User.sync({ force: true }); }); it('executes a query the internal way', function() { return this.sequelize.query(this.insertQuery, null, { raw: true }); }); it('executes a query if only the sql is passed', function() { return this.sequelize.query(this.insertQuery); }); it('executes select queries correctly', function() { var self = this; return self.sequelize.query(this.insertQuery).then(function() { return self.sequelize.query('select * from ' + qq(self.User.tableName) + ''); }).spread(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); it('executes select queries correctly when quoteIdentifiers is false', function() { var self = this , seq = Object.create(self.sequelize); seq.options.quoteIdentifiers = false; return seq.query(this.insertQuery).then(function() { return seq.query('select * from ' + qq(self.User.tableName) + ''); }).spread(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); it('executes select query with dot notation results', function() { var self = this; return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() { return self.sequelize.query(self.insertQuery); }).then(function() { return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + ''); }).spread(function( users) { expect(users).to.deep.equal([{'user.username': '******'}]); }); }); it('executes select query with dot notation results and nest it', function() { var self = this; return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() { return self.sequelize.query(self.insertQuery); }).then(function() { return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '', null, { raw: true, nest: true }); }).then(function(users) { expect(users.map(function(u) { return u.user; })).to.deep.equal([{'username': '******'}]); }); }); if (Support.dialectIsMySQL()) { it('executes stored procedures', function() { var self = this; return self.sequelize.query(this.insertQuery).then(function() { return self.sequelize.query('DROP PROCEDURE IF EXISTS foo').then(function() { return self.sequelize.query( 'CREATE PROCEDURE foo()\nSELECT * FROM ' + self.User.tableName + ';' ).then(function() { return self.sequelize.query('CALL foo()').then(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); }); }); }); } else { console.log('FIXME: I want to be supported in this dialect as well :-('); } it('uses the passed model', function() { return this.sequelize.query(this.insertQuery).bind(this).then(function() { return this.sequelize.query('SELECT * FROM ' + qq(this.User.tableName) + ';', this.User); }).then(function(users) { expect(users[0].Model).to.equal(this.User); }); }); it('throw an exception if `values` and `options.replacements` are both passed', function() { var self = this; expect(function() { return self.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, null, { raw: true, replacements: [1, 2] }); }).to.throw(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); }); it('uses properties `query` and `values` if query is tagged', function() { return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, null, { type: this.sequelize.QueryTypes.SELECT }).then(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); }); }); it('dot separated attributes when doing a raw query without nest', function() { var tickChar = (dialect === 'postgres' || dialect === 'mssql') ? '"' : '`' , sql = 'select 1 as ' + Sequelize.Utils.addTicks('foo.bar.baz', tickChar); return expect(this.sequelize.query(sql, null, { raw: true, nest: false }).get(0)).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); }); it('destructs dot separated attributes when doing a raw query using nest', function() { var tickChar = (dialect === 'postgres' || dialect === 'mssql') ? '"' : '`' , sql = 'select 1 as ' + Sequelize.Utils.addTicks('foo.bar.baz', tickChar); return this.sequelize.query(sql, null, { raw: true, nest: true }).then(function(result) { expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); }); }); it('replaces token with the passed array', function() { return this.sequelize.query('select ? as foo, ? as bar', null, { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }).then(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); }); }); it('replaces named parameters with the passed object', function() { return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); }); it('replaces named parameters with the passed object and ignore those which does not qualify', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); }); it('replaces named parameters with the passed object using the same key twice', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); }); it('replaces named parameters with the passed object having a null property', function() { return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: null }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: null }]); }); it('throw an exception when key is missing in the passed object', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true, replacements: { one: 1, two: 2 }}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed number', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 2 }); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed empty object', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: {}}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed string', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 'foobar'}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed date', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: new Date()}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('handles AS in conjunction with functions just fine', function() { var datetime = (dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'); if (dialect === 'mssql') { datetime = 'GETDATE()'; } return this.sequelize.query('SELECT ' + datetime + ' AS t').spread(function(result) { expect(moment(result[0].t).isValid()).to.be.true; }); }); if (Support.getTestDialect() === 'postgres') { it('replaces named parameters with the passed object and ignores casts', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', null, { raw: true }, { one: 1, two: 2 }).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); }); it('supports WITH queries', function() { return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').get(0)) .to.eventually.deep.equal([{ 'sum': '5050' }]); }); } });
describe(Support.getTestDialectTeaser('Sequelize'), function() { describe('constructor', function() { if (dialect !== 'sqlite') { it.skip('should work with minConnections', function() { var ConnectionManager = current.dialect.connectionManager , connectionSpy = ConnectionManager.connect = chai.spy(ConnectionManager.connect); var sequelize = Support.createSequelizeInstance({ pool: { minConnections: 2 } }); expect(connectionSpy).to.have.been.called.twice; }); } it('should pass the global options correctly', function() { var sequelize = Support.createSequelizeInstance({ logging: false, define: { underscored: true } }) , DAO = sequelize.define('dao', {name: DataTypes.STRING}); expect(DAO.options.underscored).to.be.ok; }); it('should correctly set the host and the port', function() { var sequelize = Support.createSequelizeInstance({ host: '127.0.0.1', port: 1234 }); expect(sequelize.config.port).to.equal(1234); expect(sequelize.config.host).to.equal('127.0.0.1'); }); if (dialect === 'sqlite') { it('should work with connection strings (1)', function() { var sequelize = new Sequelize('sqlite://test.sqlite'); }); it('should work with connection strings (2)', function() { var sequelize = new Sequelize('sqlite://test.sqlite/'); }); it('should work with connection strings (3)', function() { var sequelize = new Sequelize('sqlite://test.sqlite/lol?reconnect=true'); }); } if (dialect === 'postgres') { var getConnectionUri = _.template('<%= protocol %>://<%= username %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>'); it('should work with connection strings (postgres protocol)', function() { var connectionUri = getConnectionUri(_.extend(config[dialect], {protocol: 'postgres'})); var sequelize = new Sequelize(connectionUri); // postgres://... }); it('should work with connection strings (postgresql protocol)', function() { var connectionUri = getConnectionUri(_.extend(config[dialect], {protocol: 'postgresql'})); var sequelize = new Sequelize(connectionUri); // postgresql://... }); } }); if (dialect !== 'sqlite') { describe('authenticate', function() { describe('with valid credentials', function() { it('triggers the success event', function() { return this.sequelize.authenticate(); }); }); describe('with an invalid connection', function() { beforeEach(function() { var options = _.extend({}, this.sequelize.options, { port: '99999' }); this.sequelizeWithInvalidConnection = new Sequelize('wat', 'trololo', 'wow', options); }); it('triggers the error event', function() { return this .sequelizeWithInvalidConnection .authenticate() .catch(function(err) { expect(err).to.not.be.null; }); }); it('triggers the actual adapter error', function() { return this .sequelizeWithInvalidConnection .authenticate() .catch(function(err) { console.log(err.message); expect( err.message.match(/connect ECONNREFUSED/) || err.message.match(/invalid port number/) || err.message.match(/RangeError: Port should be > 0 and < 65536/) || err.message.match(/RangeError: port should be > 0 and < 65536/) || err.message.match(/RangeError: port should be >= 0 and < 65536: 99999/) || err.message.match(/ConnectionError: Login failed for user/) ).to.be.ok; }); }); }); describe('with invalid credentials', function() { beforeEach(function() { this.sequelizeWithInvalidCredentials = new Sequelize('localhost', 'wtf', 'lol', this.sequelize.options); }); it('triggers the error event', function() { return this .sequelizeWithInvalidCredentials .authenticate() .catch(function(err) { expect(err).to.not.be.null; }); }); it('triggers the error event when using replication', function() { return new Sequelize('sequelize', null, null, { replication: { read: { host: 'localhost', username: '******', password: '******' } } }).authenticate() .catch(function(err) { expect(err).to.not.be.null; }); }); }); }); describe('validate', function() { it('is an alias for .authenticate()', function() { expect(this.sequelize.validate).to.equal(this.sequelize.authenticate); }); }); } describe('getDialect', function() { it('returns the defined dialect', function() { expect(this.sequelize.getDialect()).to.equal(dialect); }); }); describe('isDefined', function() { it("returns false if the dao wasn't defined before", function() { expect(this.sequelize.isDefined('Project')).to.be.false; }); it('returns true if the dao was defined before', function() { this.sequelize.define('Project', { name: DataTypes.STRING }); expect(this.sequelize.isDefined('Project')).to.be.true; }); }); describe('model', function() { it('throws an error if the dao being accessed is undefined', function() { var self = this; expect(function() { self.sequelize.model('Project'); }).to.throw(/project has not been defined/i); }); it('returns the dao factory defined by daoName', function() { var project = this.sequelize.define('Project', { name: DataTypes.STRING }); expect(this.sequelize.model('Project')).to.equal(project); }); }); describe('query', function() { afterEach(function() { this.sequelize.options.quoteIdentifiers = true; }); beforeEach(function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.insertQuery = 'INSERT INTO ' + qq(this.User.tableName) + ' (username, ' + qq('createdAt') + ', ' + qq('updatedAt') + ") VALUES ('john', '2012-01-01 10:10:10', '2012-01-01 10:10:10')"; return this.User.sync({ force: true }); }); it('executes a query the internal way', function() { return this.sequelize.query(this.insertQuery, null, { raw: true }); }); it('executes a query if only the sql is passed', function() { return this.sequelize.query(this.insertQuery); }); it('executes select queries correctly', function() { var self = this; return self.sequelize.query(this.insertQuery).then(function() { return self.sequelize.query('select * from ' + qq(self.User.tableName) + ''); }).spread(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); it('executes select queries correctly when quoteIdentifiers is false', function() { var self = this , seq = Object.create(self.sequelize); seq.options.quoteIdentifiers = false; return seq.query(this.insertQuery).then(function() { return seq.query('select * from ' + qq(self.User.tableName) + ''); }).spread(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); it('executes select query with dot notation results', function() { var self = this; return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() { return self.sequelize.query(self.insertQuery); }).then(function() { return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + ''); }).spread(function( users) { expect(users).to.deep.equal([{'user.username': '******'}]); }); }); it('executes select query with dot notation results and nest it', function() { var self = this; return self.sequelize.query('DELETE FROM ' + qq(self.User.tableName)).then(function() { return self.sequelize.query(self.insertQuery); }).then(function() { return self.sequelize.query('select username as ' + qq('user.username') + ' from ' + qq(self.User.tableName) + '', null, { raw: true, nest: true }); }).then(function(users) { expect(users.map(function(u) { return u.user; })).to.deep.equal([{'username': '******'}]); }); }); if (Support.dialectIsMySQL()) { it('executes stored procedures', function() { var self = this; return self.sequelize.query(this.insertQuery).then(function() { return self.sequelize.query('DROP PROCEDURE IF EXISTS foo').then(function() { return self.sequelize.query( 'CREATE PROCEDURE foo()\nSELECT * FROM ' + self.User.tableName + ';' ).then(function() { return self.sequelize.query('CALL foo()').then(function(users) { expect(users.map(function(u) { return u.username; })).to.include('john'); }); }); }); }); }); } else { console.log('FIXME: I want to be supported in this dialect as well :-('); } it('uses the passed model', function() { return this.sequelize.query(this.insertQuery).bind(this).then(function() { return this.sequelize.query('SELECT * FROM ' + qq(this.User.tableName) + ';', this.User); }).then(function(users) { expect(users[0].Model).to.equal(this.User); }); }); it('throw an exception if `values` and `options.replacements` are both passed', function() { var self = this; expect(function() { return self.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, null, { raw: true, replacements: [1, 2] }); }).to.throw(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); }); it('uses properties `query` and `values` if query is tagged', function() { return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, null, { type: this.sequelize.QueryTypes.SELECT }).then(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); }); }); it('dot separated attributes when doing a raw query without nest', function() { var tickChar = (dialect === 'postgres' || dialect === 'mssql') ? '"' : '`' , sql = 'select 1 as ' + Sequelize.Utils.addTicks('foo.bar.baz', tickChar); return expect(this.sequelize.query(sql, null, { raw: true, nest: false }).get(0)).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); }); it('destructs dot separated attributes when doing a raw query using nest', function() { var tickChar = (dialect === 'postgres' || dialect === 'mssql') ? '"' : '`' , sql = 'select 1 as ' + Sequelize.Utils.addTicks('foo.bar.baz', tickChar); return this.sequelize.query(sql, null, { raw: true, nest: true }).then(function(result) { expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); }); }); it('replaces token with the passed array', function() { return this.sequelize.query('select ? as foo, ? as bar', null, { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }).then(function(result) { expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); }); }); it('replaces named parameters with the passed object', function() { return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); }); it('replaces named parameters with the passed object and ignore those which does not qualify', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); }); it('replaces named parameters with the passed object using the same key twice', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', null, { raw: true, replacements: { one: 1, two: 2 }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); }); it('replaces named parameters with the passed object having a null property', function() { return expect(this.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: { one: 1, two: null }}).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: null }]); }); it('throw an exception when key is missing in the passed object', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar, :three as baz', null, { raw: true, replacements: { one: 1, two: 2 }}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed number', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 2 }); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed empty object', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: {}}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed string', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: 'foobar'}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('throw an exception with the passed date', function() { var self = this; expect(function() { self.sequelize.query('select :one as foo, :two as bar', null, { raw: true, replacements: new Date()}); }).to.throw(Error, /Named parameter ":\w+" has no value in the given object\./g); }); it('handles AS in conjunction with functions just fine', function() { var datetime = (dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'); if (dialect === 'mssql') { datetime = 'GETDATE()'; } return this.sequelize.query('SELECT ' + datetime + ' AS t').spread(function(result) { expect(moment(result[0].t).isValid()).to.be.true; }); }); if (Support.getTestDialect() === 'postgres') { it('replaces named parameters with the passed object and ignores casts', function() { return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', null, { raw: true }, { one: 1, two: 2 }).get(0)) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); }); it('supports WITH queries', function() { return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').get(0)) .to.eventually.deep.equal([{ 'sum': '5050' }]); }); } }); describe('set', function() { it("should be configurable with global functions", function() { var defaultClassMethod = sinon.spy() , overrideClassMethod = sinon.spy() , defaultInstanceMethod = sinon.spy() , overrideInstanceMethod = sinon.spy() , defaultSetterMethod = sinon.spy() , overrideSetterMethod = sinon.spy() , defaultGetterMethod = sinon.spy() , overrideGetterMethod = sinon.spy() , customClassMethod = sinon.spy() , customOverrideClassMethod = sinon.spy() , customInstanceMethod = sinon.spy() , customOverrideInstanceMethod = sinon.spy() , customSetterMethod = sinon.spy() , customOverrideSetterMethod = sinon.spy() , customGetterMethod = sinon.spy() , customOverrideGetterMethod = sinon.spy(); this.sequelize.options.define = { 'classMethods': { 'defaultClassMethod': defaultClassMethod, 'overrideClassMethod': overrideClassMethod }, 'instanceMethods': { 'defaultInstanceMethod': defaultInstanceMethod, 'overrideInstanceMethod': overrideInstanceMethod }, 'setterMethods': { 'default': defaultSetterMethod, 'override': overrideSetterMethod }, 'getterMethods': { 'default': defaultGetterMethod, 'override': overrideGetterMethod } }; var testEntity = this.sequelize.define('TestEntity', {}, { 'classMethods': { 'customClassMethod': customClassMethod, 'overrideClassMethod': customOverrideClassMethod }, 'instanceMethods': { 'customInstanceMethod': customInstanceMethod, 'overrideInstanceMethod': customOverrideInstanceMethod }, 'setterMethods': { 'custom': customSetterMethod, 'override': customOverrideSetterMethod }, 'getterMethods': { 'custom': customGetterMethod, 'override': customOverrideGetterMethod } }); // Call all Class Methods testEntity.defaultClassMethod(); testEntity.customClassMethod(); testEntity.overrideClassMethod(); expect(typeof testEntity.defaultClassMethod).to.equal('function'); expect(typeof testEntity.customClassMethod).to.equal('function'); expect(typeof testEntity.overrideClassMethod).to.equal('function'); expect(defaultClassMethod).to.have.been.calledOnce; expect(customClassMethod).to.have.been.calledOnce; expect(overrideClassMethod.callCount).to.be.eql(0); expect(customOverrideClassMethod).to.have.been.calledOnce; // Create Instance to test var instance = testEntity.build(); // Call all Instance Methods instance.defaultInstanceMethod(); instance.customInstanceMethod(); instance.overrideInstanceMethod(); expect(typeof instance.defaultInstanceMethod).to.equal('function'); expect(typeof instance.customInstanceMethod).to.equal('function'); expect(typeof instance.overrideInstanceMethod).to.equal('function'); expect(defaultInstanceMethod).to.have.been.calledOnce; expect(customInstanceMethod).to.have.been.calledOnce; expect(overrideInstanceMethod.callCount).to.be.eql(0); expect(customOverrideInstanceMethod).to.have.been.calledOnce; // Call Getters var defaultVal = instance.default , custom = instance.custom , override = instance.override; expect(defaultGetterMethod).to.have.been.calledOnce; expect(customGetterMethod).to.have.been.calledOnce; expect(overrideGetterMethod.callCount).to.be.eql(0); expect(customOverrideGetterMethod).to.have.been.calledOnce; // Call Setters instance.default = 'test'; instance.custom = 'test'; instance.override = 'test'; expect(defaultSetterMethod).to.have.been.calledOnce; expect(customSetterMethod).to.have.been.calledOnce; expect(overrideSetterMethod.callCount).to.be.eql(0); expect(customOverrideSetterMethod).to.have.been.calledOnce; }); }); if (Support.dialectIsMySQL()) { describe('set', function() { it("should return an promised error if transaction isn't defined", function() { expect(function() { this.sequelize.set({ foo: 'bar' }); }.bind(this)).to.throw(TypeError, 'options.transaction is required'); }); it('one value', function() { return this.sequelize.transaction().bind(this).then(function(t) { this.t = t; return this.sequelize.set({ foo: 'bar' }, { transaction: t }); }).then(function() { return this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t }); }).then(function(data) { expect(data).to.be.ok; expect(data.foo).to.be.equal('bar'); return this.t.commit(); }); }); it('multiple values', function() { return this.sequelize.transaction().bind(this).then(function(t) { this.t = t; return this.sequelize.set({ foo: 'bar', foos: 'bars' }, { transaction: t }); }).then(function() { return this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t }); }).then(function(data) { expect(data).to.be.ok; expect(data.foo).to.be.equal('bar'); expect(data.foos).to.be.equal('bars'); return this.t.commit(); }); }); }); } describe('define', function() { it('adds a new dao to the dao manager', function() { var count = this.sequelize.daoFactoryManager.all.length; this.sequelize.define('foo', { title: DataTypes.STRING }); expect(this.sequelize.daoFactoryManager.all.length).to.equal(count+1); }); it('adds a new dao to sequelize.models', function() { expect(this.sequelize.models.bar).to.equal(undefined); var Bar = this.sequelize.define('bar', { title: DataTypes.STRING }); expect(this.sequelize.models.bar).to.equal(Bar); }); it('overwrites global options', function() { var sequelize = Support.createSequelizeInstance({ define: { collate: 'utf8_general_ci' } }); var DAO = sequelize.define('foo', {bar: DataTypes.STRING}, {collate: 'utf8_bin'}); expect(DAO.options.collate).to.equal('utf8_bin'); }); it('inherits global collate option', function() { var sequelize = Support.createSequelizeInstance({ define: { collate: 'utf8_general_ci' } }); var DAO = sequelize.define('foo', {bar: DataTypes.STRING}); expect(DAO.options.collate).to.equal('utf8_general_ci'); }); it('inherits global classMethods and instanceMethods, and can override global methods with local ones', function() { var globalClassMethod = sinon.spy() , globalInstanceMethod = sinon.spy() , localClassMethod = sinon.spy() , localInstanceMethod = sinon.spy() , sequelize = Support.createSequelizeInstance({ define: { classMethods: { globalClassMethod: function() {}, overrideMe: globalClassMethod }, instanceMethods: { globalInstanceMethod: function() {}, overrideMe: globalInstanceMethod } } }) , DAO; DAO = sequelize.define('foo', {bar: DataTypes.STRING}, { classMethods: { localClassMethod: function() {} } }); expect(typeof DAO.options.classMethods.globalClassMethod).to.equal('function'); expect(typeof DAO.options.classMethods.localClassMethod).to.equal('function'); expect(typeof DAO.options.instanceMethods.globalInstanceMethod).to.equal('function'); // This DAO inherits the global methods DAO.overrideMe(); DAO.build().overrideMe(); DAO = sequelize.define('foo', {bar: DataTypes.STRING}, { classMethods: { overrideMe: localClassMethod }, instanceMethods: { overrideMe: localInstanceMethod } }); // This DAO has its own implementation DAO.overrideMe(); DAO.build().overrideMe(); expect(globalClassMethod).to.have.been.calledOnce; expect(globalInstanceMethod).to.have.been.calledOnce; expect(localClassMethod).to.have.been.calledOnce; expect(localInstanceMethod).to.have.been.calledOnce; }); it('uses the passed tableName', function() { var self = this , Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' }); return Photo.sync({ force: true }).then(function() { return self.sequelize.getQueryInterface().showAllTables().then(function(tableNames) { if (dialect === 'mssql' /* current.dialect.supports.schemas */) { tableNames = _.pluck(tableNames, 'tableName'); } expect(tableNames).to.include('photos'); }); }); }); }); describe('sync', function() { it('synchronizes all daos', function() { var Project = this.sequelize.define('project' + config.rand(), { title: DataTypes.STRING }); var Task = this.sequelize.define('task' + config.rand(), { title: DataTypes.STRING }); return Project.sync({ force: true }).then(function() { return Task.sync({ force: true }).then(function() { return Project.create({title: 'bla'}).then(function() { return Task.create({title: 'bla'}).then(function(task) { expect(task).to.exist; expect(task.title).to.equal('bla'); }); }); }); }); }); it('works with correct database credentials', function() { var User = this.sequelize.define('User', { username: DataTypes.STRING }); return User.sync().then(function() { expect(true).to.be.true; }); }); if (dialect !== 'sqlite') { it('fails with incorrect database credentials (1)', function() { this.sequelizeWithInvalidCredentials = new Sequelize('omg', 'bar', null, _.omit(this.sequelize.options, ['host'])); var User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT }); return User2.sync().catch(function(err) { if (dialect === 'postgres' || dialect === 'postgres-native') { assert([ 'fe_sendauth: no password supplied', 'role "bar" does not exist', 'FATAL: role "bar" does not exist', 'password authentication failed for user "bar"' ].indexOf(err.message.trim()) !== -1); } else if (dialect === 'mssql') { expect(err.message).to.match(/.*ECONNREFUSED.*/); } else { expect(err.message.toString()).to.match(/.*Access\ denied.*/); } }); }); it('fails with incorrect database credentials (2)', function() { var sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect }); var Project = sequelize.define('Project', {title: Sequelize.STRING}); var Task = sequelize.define('Task', {title: Sequelize.STRING}); return sequelize.sync({force: true}).catch(function(err) { expect(err).to.be.ok; }); }); it('fails with incorrect database credentials (3)', function() { var sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999 }); var Project = sequelize.define('Project', {title: Sequelize.STRING}); var Task = sequelize.define('Task', {title: Sequelize.STRING}); return sequelize.sync({force: true}).catch(function(err) { expect(err).to.be.ok; }); }); it('fails with incorrect database credentials (4)', function() { var sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999, pool: {} }); var Project = sequelize.define('Project', {title: Sequelize.STRING}); var Task = sequelize.define('Task', {title: Sequelize.STRING}); return sequelize.sync({force: true}).catch(function(err) { expect(err).to.be.ok; }); }); it('returns an error correctly if unable to sync a foreign key referenced model', function() { var Application = this.sequelize.define('Application', { authorID: { type: Sequelize.BIGINT, allowNull: false, references: 'User', referencesKey: 'id' } }); return this.sequelize.sync().catch(function(error) { assert.ok(error); }); }); it('handles self dependant foreign key constraints', function() { var block = this.sequelize.define('block', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING }, { tableName: 'block', timestamps: false, paranoid: false }); block.hasMany(block, { as: 'childBlocks', foreignKey: 'parent', joinTableName: 'link_block_block', useJunctionTable: true, foreignKeyConstraint: true }); block.belongsTo(block, { as: 'parentBlocks', foreignKey: 'child', joinTableName: 'link_block_block', useJunctionTable: true, foreignKeyConstraint: true }); return this.sequelize.sync(); }); } describe("doesn't emit logging when explicitly saying not to", function() { afterEach(function() { this.sequelize.options.logging = false; }); beforeEach(function() { this.spy = sinon.spy(); var self = this; this.sequelize.options.logging = function() { self.spy(); }; this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING }); }); it('through Sequelize.sync()', function() { var self = this; return this.sequelize.sync({ force: true, logging: false }).then(function() { expect(self.spy.notCalled).to.be.true; }); }); it('through DAOFactory.sync()', function() { var self = this; return this.User.sync({ force: true, logging: false }).then(function() { expect(self.spy.notCalled).to.be.true; }); }); }); describe('match', function() { it('will return an error not matching', function() { return this.sequelize.sync({ force: true, match: /alibabaizshaek/ }).then(function() { throw new Error('I should not have succeeded!'); }).catch(function(err) { assert(true); }); }); }); }); describe('drop should work', function() { it('correctly succeeds', function() { var User = this.sequelize.define('Users', {username: DataTypes.STRING }); return User.sync({ force: true }).then(function() { return User.drop(); }); }); }); describe('import', function() { it('imports a dao definition from a file absolute path', function() { var Project = this.sequelize.import(__dirname + '/assets/project'); expect(Project).to.exist; }); it('imports a dao definition from a function', function() { var Project = this.sequelize.import('Project', function(sequelize, DataTypes) { return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), { name: DataTypes.STRING }); }); expect(Project).to.exist; }); }); describe('define', function() { [ { type: DataTypes.ENUM, values: ['scheduled', 'active', 'finished']}, DataTypes.ENUM('scheduled', 'active', 'finished') ].forEach(function(status) { describe('enum', function() { beforeEach(function() { this.Review = this.sequelize.define('review', { status: status }); return this.Review.sync({ force: true }); }); it('raises an error if no values are defined', function() { var self = this; expect(function() { self.sequelize.define('omnomnom', { bla: { type: DataTypes.ENUM } }); }).to.throw(Error, 'Values for ENUM haven\'t been defined.'); }); it('correctly stores values', function() { return this.Review.create({ status: 'active' }).then(function(review) { expect(review.status).to.equal('active'); }); }); it('correctly loads values', function() { var self = this; return this.Review.create({ status: 'active' }).then(function() { return self.Review.findAll().then(function(reviews) { expect(reviews[0].status).to.equal('active'); }); }); }); it("doesn't save an instance if value is not in the range of enums", function() { return this.Review.create({status: 'fnord'}).catch(function(err) { expect(err).to.be.instanceOf(Error); expect(err.get('status')[0].message).to.equal('Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished'); }); }); }); }); describe('table', function() { [ { id: { type: DataTypes.BIGINT } }, { id: { type: DataTypes.STRING, allowNull: true } }, { id: { type: DataTypes.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } } ].forEach(function(customAttributes) { it('should be able to override options on the default attributes', function() { var Picture = this.sequelize.define('picture', _.cloneDeep(customAttributes)); return Picture.sync({ force: true }).then(function() { Object.keys(customAttributes).forEach(function(attribute) { Object.keys(customAttributes[attribute]).forEach(function(option) { var optionValue = customAttributes[attribute][option]; if (typeof optionValue === "function" && optionValue() instanceof DataTypes.ABSTRACT) { expect(Picture.rawAttributes[attribute][option] instanceof optionValue).to.be.ok; } else { expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue); } }); }); }); }); }); }); if (current.dialect.supports.transactions) { describe('transaction', function() { beforeEach(function() { var self = this; return Support.prepareTransactionTest(this.sequelize).bind({}).then(function(sequelize) { self.sequelizeWithTransaction = sequelize; }); }); it('is a transaction method available', function() { expect(Support.Sequelize).to.respondTo('transaction'); }); it('passes a transaction object to the callback', function() { return this.sequelizeWithTransaction.transaction().then(function(t) { expect(t).to.be.instanceOf(Transaction); }); }); it('allows me to define a callback on the result', function() { return this.sequelizeWithTransaction.transaction().then(function(t) { return t.commit(); }); }); if (dialect === 'sqlite') { it('correctly scopes transaction from other connections', function() { var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }) , self = this; var count = function(transaction) { var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); return self.sequelizeWithTransaction.query(sql, { plain: true, transaction: transaction }).then(function(result) { return result.cnt; }); }; return TransactionTest.sync({ force: true }).bind(this).then(function() { return self.sequelizeWithTransaction.transaction(); }).then(function(t1) { this.t1 = t1; return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', { transaction: t1 }); }).then(function() { return expect(count()).to.eventually.equal(0); }).then(function(cnt) { return expect(count(this.t1)).to.eventually.equal(1); }).then(function () { return this.t1.commit(); }).then(function() { return expect(count()).to.eventually.equal(1); }); }); } else { it('correctly handles multiple transactions', function() { var TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }) , self = this; var count = function(transaction) { var sql = self.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); return self.sequelizeWithTransaction.query(sql, { plain: true, transaction: transaction }).then(function(result) { return parseInt(result.cnt, 10); }); }; return TransactionTest.sync({ force: true }).bind(this).then(function() { return self.sequelizeWithTransaction.transaction(); }).then(function(t1) { this.t1 = t1; return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'foo\');', null, { transaction: t1 }); }).then(function() { return self.sequelizeWithTransaction.transaction(); }).then(function(t2) { this.t2 = t2; return self.sequelizeWithTransaction.query('INSERT INTO ' + qq('TransactionTests') + ' (' + qq('name') + ') VALUES (\'bar\');', null, { transaction: t2 }); }).then(function() { return expect(count()).to.eventually.equal(0); }).then(function() { return expect(count(this.t1)).to.eventually.equal(1); }).then(function() { return expect(count(this.t2)).to.eventually.equal(1); }).then(function() { return this.t2.rollback(); }).then(function() { return expect(count()).to.eventually.equal(0); }).then(function(cnt) { return this.t1.commit(); }).then(function() { return expect(count()).to.eventually.equal(1); }); }); } it('supports nested transactions using savepoints', function() { var self = this; var User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); return User.sync({ force: true }).then(function() { return self.sequelizeWithTransaction.transaction().then(function(t1) { return User.create({ username: '******' }, { transaction: t1 }).then(function(user) { return self.sequelizeWithTransaction.transaction({ transaction: t1 }).then(function(t2) { return user.updateAttributes({ username: '******' }, { transaction: t2 }).then(function() { return t2.commit().then(function() { return user.reload({ transaction: t1 }).then(function(newUser) { expect(newUser.username).to.equal('bar'); return t1.commit(); }); }); }); }); }); }); }); }); describe('supports rolling back to savepoints', function() { beforeEach(function() { this.User = this.sequelizeWithTransaction.define('user', {}); return this.sequelizeWithTransaction.sync({ force: true }); }); it('rolls back to the first savepoint, undoing everything', function() { return this.sequelizeWithTransaction.transaction().bind(this).then(function(transaction) { this.transaction = transaction; return this.sequelizeWithTransaction.transaction({ transaction: transaction }); }).then(function(sp1) { this.sp1 = sp1; return this.User.create({}, { transaction: this.transaction }); }).then(function() { return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); }).then(function(sp2) { this.sp2 = sp2; return this.User.create({}, { transaction: this.transaction }); }).then(function() { return this.User.findAll({}, { transaction: this.transaction }); }).then(function(users) { expect(users).to.have.length(2); return this.sp1.rollback(); }).then(function() { return this.User.findAll({}, { transaction: this.transaction }); }).then(function(users) { expect(users).to.have.length(0); return this.transaction.rollback(); }); }); it('rolls back to the most recent savepoint, only undoing recent changes', function() { return this.sequelizeWithTransaction.transaction().bind(this).then(function(transaction) { this.transaction = transaction; return this.sequelizeWithTransaction.transaction({ transaction: transaction }); }).then(function(sp1) { this.sp1 = sp1; return this.User.create({}, { transaction: this.transaction }); }).then(function() { return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); }).then(function(sp2) { this.sp2 = sp2; return this.User.create({}, { transaction: this.transaction }); }).then(function() { return this.User.findAll({}, { transaction: this.transaction }); }).then(function(users) { expect(users).to.have.length(2); return this.sp2.rollback(); }).then(function() { return this.User.findAll({}, { transaction: this.transaction }); }).then(function(users) { expect(users).to.have.length(1); return this.transaction.rollback(); }); }); }); it('supports rolling back a nested transaction', function() { var self = this; var User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); return User.sync({ force: true }).then(function() { return self.sequelizeWithTransaction.transaction().then(function(t1) { return User.create({ username: '******' }, { transaction: t1 }).then(function(user) { return self.sequelizeWithTransaction.transaction({ transaction: t1 }).then(function(t2) { return user.updateAttributes({ username: '******' }, { transaction: t2 }).then(function() { return t2.rollback().then(function() { return user.reload({ transaction: t1 }).then(function(newUser) { expect(newUser.username).to.equal('foo'); return t1.commit(); }); }); }); }); }); }); }); }); it('supports rolling back outermost transaction', function() { var self = this; var User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); return User.sync({ force: true }).then(function() { return self.sequelizeWithTransaction.transaction().then(function(t1) { return User.create({ username: '******' }, { transaction: t1 }).then(function(user) { return self.sequelizeWithTransaction.transaction({ transaction: t1 }).then(function(t2) { return user.updateAttributes({ username: '******' }, { transaction: t2 }).then(function() { return t1.rollback().then(function() { return User.findAll().then(function(users) { expect(users.length).to.equal(0); }); }); }); }); }); }); }); }); }); } }); describe('databaseVersion', function() { it('should database/dialect version', function() { return this.sequelize.databaseVersion().then(function(version) { expect(typeof version).to.equal('string'); expect(version).to.be.ok; }); }); }); });