不要怂,就是干,撸起袖子干!

Commit ab9a8364 by Sascha Depold

Merge branch 'milestones/2.0.0' of github.com:sequelize/sequelize into milestones/2.0.0

2 parents 5d1a7739 e011a7d2
Showing with 8411 additions and 9773 deletions
{ {
"globals": { "globals": {
"jasmine": false,
"spyOn": false, "spyOn": false,
"it": false, "it": false,
"console": false, "console": false,
......
...@@ -3,8 +3,7 @@ before_script: ...@@ -3,8 +3,7 @@ before_script:
- "psql -c 'create database sequelize_test;' -U postgres" - "psql -c 'create database sequelize_test;' -U postgres"
script: script:
- "npm run test-buster-travis" - "make test"
- "npm run test-jasmine"
notifications: notifications:
email: email:
...@@ -19,4 +18,4 @@ env: ...@@ -19,4 +18,4 @@ env:
language: node_js language: node_js
node_js: node_js:
- 0.8 - 0.8
\ No newline at end of file
REPORTER ?= dot
TESTS = $(shell find ./test/* -name "*.test.js")
DIALECT ?= mysql
# test commands
teaser:
@echo "" && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo '# Running tests for $(DIALECT) #' && \
node -pe "Array(20 + '$(DIALECT)'.length + 3).join('#')" && \
echo ''
test:
@make teaser && \
./node_modules/mocha/bin/mocha \
--colors \
--reporter $(REPORTER) \
$(TESTS)
sqlite:
@DIALECT=sqlite make test
mysql:
@DIALECT=mysql make test
postgres:
@DIALECT=postgres make test
postgres-native:
@DIALECT=postgres-native make test
# test aliases
pgsql: postgres
postgresn: postgres-native
# test all the dialects \o/
all: sqlite mysql postgres postgres-native
.PHONY: sqlite mysql postgres pgsql postgres-native postgresn all test
\ No newline at end of file
# Sequelize # # Sequelize [![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Dependency Status](https://david-dm.org/sequelize/sequelize.png)](https://david-dm.org/sequelize/sequelize) [![Flattr this](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/1259407/Sequelize) #
The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databases by mapping database entries to objects and vice versa. To put it in a nutshell... it's an ORM (Object-Relational-Mapper). The library is written entirely in JavaScript and can be used in the Node.JS environment. MySQL, PostgresSQL, and SQLite Object Relational Mapper (ORM) for [node](http://nodejs.org).
<a href="http://flattr.com/thing/1259407/Sequelize" target="_blank">
<img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
## Important Notes ## ## Important Notes ##
...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/ ...@@ -35,6 +32,7 @@ changelog of the branch: https://github.com/sequelize/sequelize/blob/milestones/
- Asynchronous library - Asynchronous library
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
- Promises
## Documentation and Updates ## ## Documentation and Updates ##
...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -64,7 +62,7 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
- MariaDB support - MariaDB support
- ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude - ~~Support for update and delete calls for whole tables without previous loading of instances~~ Implemented in [#569](https://github.com/sequelize/sequelize/pull/569) thanks to @optiltude
- Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099) - Eager loading of nested associations [#388](https://github.com/sdepold/sequelize/issues/388#issuecomment-12019099)
- Model#delete - ~~Model#delete~~ (renamed to [Model.destroy()](http://sequelizejs.com/documentation#instances-destroy))
- ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango - ~~Validate a model before it gets saved.~~ Implemented in [#601](https://github.com/sequelize/sequelize/pull/601), thanks to @durango
- Move validation of enum attribute value to validate method - Move validation of enum attribute value to validate method
- BLOB [#99](https://github.com/sequelize/sequelize/issues/99) - BLOB [#99](https://github.com/sequelize/sequelize/issues/99)
...@@ -141,32 +139,27 @@ $ npm install ...@@ -141,32 +139,27 @@ $ npm install
### 4. Run the tests ### ### 4. Run the tests ###
Right now, the test base is split into the `spec` folder (which contains the Right now, the test base is split into the `test` folder (which contains the
lovely [BusterJS](http://busterjs.org) tests) and the `spec-jasmine` folder lovely [Mocha](http://visionmedia.github.io/mocha/) tests).
(which contains the ugly and awkward node-jasmine based tests). A main goal
is to get rid of the jasmine tests!
As you might haven't installed all of the supported SQL dialects, here is how As you might haven't installed all of the supported SQL dialects, here is how
to run the test suites for your development environment: to run the test suites for your development environment:
```console ```console
$ # run all tests at once: $ # run all tests at once:
$ npm test $ make all
$ # run only the jasmine tests (for all dialects):
$ npm run test-jasmine
$ # run all of the buster specs (for all dialects):
$ npm run test-buster
$ # run the buster specs for mysql: $ # run the buster specs for mysql:
$ npm run test-buster-mysql $ make mysql
$ # run the buster specs for sqlite: $ # run the buster specs for sqlite:
$ npm run test-buster-sqlite $ make sqlite
$ # run the buster specs for postgresql: $ # run the buster specs for postgresql:
$ npm run test-buster-postgres $ make pgsql
$ # alternatively you can pass database credentials with $variables when testing
$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password make test
``` ```
### 5. That's all ### ### 5. That's all ###
...@@ -235,6 +228,17 @@ for (var key in obj) { ...@@ -235,6 +228,17 @@ for (var key in obj) {
```js ```js
{ {
"globals": {
"spyOn": false,
"it": false,
"console": false,
"describe": false,
"expect": false,
"beforeEach": false,
"waits": false,
"waitsFor": false,
"runs": false
},
"camelcase": true, "camelcase": true,
"curly": true, "curly": true,
"forin": true, "forin": true,
...@@ -246,10 +250,3 @@ for (var key in obj) { ...@@ -246,10 +250,3 @@ for (var key in obj) {
"es5": true "es5": true
} }
``` ```
# Build status
The automated tests we talk about just so much are running on
[Travis public CI](http://travis-ci.org), here is its status:
[![Build Status](https://secure.travis-ci.org/sequelize/sequelize.png)](http://travis-ci.org/sequelize/sequelize)
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango - [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango - [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango - [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango
- [DEPENDENCIES] Upgraded command to 2.0.0 and generic-pool to 2.0.4. thanks to durango
- [DEPENDENCIES] No longer require semver. thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx - [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics - [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango - [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
...@@ -21,8 +23,11 @@ ...@@ -21,8 +23,11 @@
- [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango - [BUG] Fields should be escaped by quoteIdentifier for max/min functions which allows SQL reserved keywords to be used. [#719](https://github.com/sequelize/sequelize/pull/719). thanks to durango
- [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen - [BUG] Fixed bug when trying to save objects with eagerly loaded attributes [#716](https://github.com/sequelize/sequelize/pull/716). thanks to iamjochen
- [BUG] Strings for .find() should be fixed. Also added support for string primary keys to be found easily. [#737](https://github.com/sequelize/sequelize/pull/737). thanks to durango - [BUG] Strings for .find() should be fixed. Also added support for string primary keys to be found easily. [#737](https://github.com/sequelize/sequelize/pull/737). thanks to durango
- [BUG] bulkCreate would have problems with a disparate field list [#738](https://github.com/sequelize/sequelize/pull/738). thanks to durango
- [BUG] Fixed problems with quoteIdentifiers and {raw: false} option on raw queries [#751](https://github.com/sequelize/sequelize/pull/751). thanks to janmeier - [BUG] Fixed problems with quoteIdentifiers and {raw: false} option on raw queries [#751](https://github.com/sequelize/sequelize/pull/751). thanks to janmeier
- [BUG] Fixed SQL escaping with sqlite and unified escaping [#700](https://github.com/sequelize/sequelize/pull/700). thanks to PiPeep - [BUG] Fixed SQL escaping with sqlite and unified escaping [#700](https://github.com/sequelize/sequelize/pull/700). thanks to PiPeep
- [BUG] Fixed Postgres' pools [ff57af63](https://github.com/sequelize/sequelize/commit/ff57af63c2eb395b4828a5984a22984acdc2a5e1)
- [BUG] Fixed BLOB/TEXT columns having a default value declared in MySQL [#793](https://github.com/sequelize/sequelize/pull/793). thanks to durango
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango - [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
...@@ -42,8 +47,12 @@ ...@@ -42,8 +47,12 @@
- [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen - [FEATURE] Added a `findAndCountAll`, useful for pagination. [#533](https://github.com/sequelize/sequelize/pull/533). Thanks to iamjochen
- [FEATURE] Made explicit migrations possible. [#728](https://github.com/sequelize/sequelize/pull/728). Thanks to freezy - [FEATURE] Made explicit migrations possible. [#728](https://github.com/sequelize/sequelize/pull/728). Thanks to freezy
- [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango - [FEATURE] Added support for where clauses containing !=, < etc. and support for date ranges [#727](https://github.com/sequelize/sequelize/pull/727). Thanks to durango
- [FEATURE] Added support for model instances being referenced [#761](https://github.com/sequelize/sequelize/pull/761) thanks to sdepold
- [FEATURE] Added support for specifying the path to load a module for a dialect. [#766](https://github.com/sequelize/sequelize/pull/766) thanks to sonnym.
- [FEATURE] Drop index if exists has been added to sqlite [#766](https://github.com/sequelize/sequelize/pull/776) thanks to coderbuzz
- [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier - [REFACTORING] hasMany now uses a single SQL statement when creating and destroying associations, instead of removing each association seperately [690](https://github.com/sequelize/sequelize/pull/690). Inspired by [#104](https://github.com/sequelize/sequelize/issues/104). janmeier
- [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier - [REFACTORING] Consistent handling of offset across dialects. Offset is now always applied, and limit is set to max table size of not limit is given [#725](https://github.com/sequelize/sequelize/pull/725). janmeier
- [REFACTORING] Moved Jasmine to Buster and then Buster to Mocha + Chai. sdepold and durango
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
...@@ -21,12 +21,26 @@ module.exports = (function() { ...@@ -21,12 +21,26 @@ module.exports = (function() {
where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"} where[self.__factory.connectorDAO.tableName+"."+foreignKey] = {join: self.__factory.target.tableName+".id"}
if (options.where) { if (options.where) {
Utils._.each(options.where, function(value, index) { if (Array.isArray(options.where)) {
delete options.where[index]; smart = Utils.smartWhere([where, options.where], self.__factory.target.daoFactoryManager.sequelize.options.dialect)
options.where[self.__factory.target.tableName+"."+index] = value; smart = Utils.compileSmartWhere.call(self.__factory.target, smart, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
}); if (smart.length > 0) {
options.where = smart
Utils._.extend(options.where, where) }
} else {
Utils._.each(options.where, function(value, index) {
delete options.where[index];
smart = Utils.smartWhere(value, self.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.__factory.target, smart)
if (smart.length > 0) {
value = smart
}
options.where[self.__factory.target.tableName+"."+index] = value;
});
options.where = Utils._.extend(options.where, where)
}
} else { } else {
options.where = where; options.where = where;
} }
...@@ -51,10 +65,10 @@ module.exports = (function() { ...@@ -51,10 +65,10 @@ module.exports = (function() {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) { , unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
...@@ -81,7 +95,7 @@ module.exports = (function() { ...@@ -81,7 +95,7 @@ module.exports = (function() {
}) })
chainer.add(self.__factory.connectorDAO.bulkCreate(bulk)) chainer.add(self.__factory.connectorDAO.bulkCreate(bulk))
} }
chainer chainer
.run() .run()
......
...@@ -12,46 +12,55 @@ module.exports = (function() { ...@@ -12,46 +12,55 @@ module.exports = (function() {
where[this.__factory.identifier] = this.instance.id where[this.__factory.identifier] = this.instance.id
options.where = options.where ? Utils._.extend(options.where, where) : where if (options.where) {
return this.__factory.target.findAll(options) smart = Utils.smartWhere([where, options.where], this.__factory.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(this.__factory.target, smart)
if (smart.length > 0) {
options.where = smart
}
} else {
options.where = where
}
return this.__factory.target.all(options)
} }
HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) { HasManySingleLinked.prototype.injectSetter = function(emitter, oldAssociations, newAssociations) {
var self = this var self = this
, chainer = new Utils.QueryChainer() , chainer = new Utils.QueryChainer()
, obsoleteAssociations = oldAssociations.filter(function (old) { , obsoleteAssociations = oldAssociations.filter(function (old) {
return !Utils._.find(newAssociations, function (obj) { return !Utils._.find(newAssociations, function (obj) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, unassociatedObjects = newAssociations.filter(function (obj) { , unassociatedObjects = newAssociations.filter(function (obj) {
return !Utils._.find(oldAssociations, function (old) { return !Utils._.find(oldAssociations, function (old) {
return obj.id === old.id return obj.id === old.id
}) })
}) })
, update , update
if (obsoleteAssociations.length > 0) { if (obsoleteAssociations.length > 0) {
// clear the old associations // clear the old associations
var obsoleteIds = obsoleteAssociations.map(function(associatedObject) { var obsoleteIds = obsoleteAssociations.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = null associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
return associatedObject.id return associatedObject.id
}) })
update = {} update = {}
update[self.__factory.identifier] = null update[self.__factory.identifier] = null
chainer.add(this.__factory.target.update(update, { id: obsoleteIds })) chainer.add(this.__factory.target.update(update, { id: obsoleteIds }))
} }
if (unassociatedObjects.length > 0) { if (unassociatedObjects.length > 0) {
// set the new associations // set the new associations
var unassociatedIds = unassociatedObjects.map(function(associatedObject) { var unassociatedIds = unassociatedObjects.map(function(associatedObject) {
associatedObject[self.__factory.identifier] = self.instance.id associatedObject[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
return associatedObject.id return associatedObject.id
}) })
update = {} update = {}
update[self.__factory.identifier] = self.instance.id update[self.__factory.identifier] = (newAssociations.length < 1 ? null : self.instance.id)
chainer.add(this.__factory.target.update(update, { id: unassociatedIds })) chainer.add(this.__factory.target.update(update, { id: unassociatedIds }))
} }
......
...@@ -56,6 +56,12 @@ module.exports = (function() { ...@@ -56,6 +56,12 @@ module.exports = (function() {
params = {where: where} params = {where: where}
} }
smart = Utils.smartWhere([where, params.where || []], self.target.daoFactoryManager.sequelize.options.dialect)
smart = Utils.compileSmartWhere.call(self.target, smart)
if (smart.length > 0) {
params.where = smart
}
return self.target.find(params) return self.target.find(params)
} }
...@@ -64,27 +70,35 @@ module.exports = (function() { ...@@ -64,27 +70,35 @@ module.exports = (function() {
HasOne.prototype.injectSetter = function(obj) { HasOne.prototype.injectSetter = function(obj) {
var self = this var self = this
, options = self.options || {}
obj[this.accessors.set] = function(associatedObject) { obj[this.accessors.set] = function(associatedObject) {
var instance = this; var instance = this
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
instance[self.accessors.get]().success(function(oldObj) { instance[self.accessors.get]().success(function(oldObj) {
if (oldObj) { if (oldObj) {
oldObj[self.identifier] = null oldObj[self.identifier] = null
oldObj.save() oldObj.save().success(function() {
} if (associatedObject) {
associatedObject[self.identifier] = instance.id
if (associatedObject) { associatedObject
associatedObject[self.identifier] = instance.id .save()
associatedObject .success(function() { emitter.emit('success', associatedObject) })
.save() .error(function(err) { emitter.emit('error', err) })
.success(function() { emitter.emit('success', associatedObject) }) } else {
.error(function(err) { emitter.emit('error', err) }) emitter.emit('success', null)
}
})
} else { } else {
emitter.emit('success', null) if (associatedObject) {
associatedObject[self.identifier] = instance.id
associatedObject
.save()
.success(function() { emitter.emit('success', associatedObject) })
.error(function(err) { emitter.emit('error', err) })
} else {
emitter.emit('success', null)
}
} }
}) })
}).run() }).run()
} }
......
var Toposort = require('toposort-class') var Toposort = require('toposort-class')
, DaoFactory = require('./dao-factory')
module.exports = (function() { module.exports = (function() {
var DAOFactoryManager = function(sequelize) { var DAOFactoryManager = function(sequelize) {
...@@ -39,16 +40,17 @@ module.exports = (function() { ...@@ -39,16 +40,17 @@ module.exports = (function() {
* before dependents. * before dependents.
*/ */
DAOFactoryManager.prototype.forEachDAO = function(iterator) { DAOFactoryManager.prototype.forEachDAO = function(iterator) {
var daos = {} var daos = {}
, sorter = new Toposort() , sorter = new Toposort()
this.daos.forEach(function(dao) { this.daos.forEach(function(dao) {
daos[dao.tableName] = dao
var deps = [] var deps = []
for(var attrName in dao.rawAttributes) { daos[dao.tableName] = dao
if(dao.rawAttributes.hasOwnProperty(attrName)) {
if(dao.rawAttributes[attrName].references) { for (var attrName in dao.rawAttributes) {
if (dao.rawAttributes.hasOwnProperty(attrName)) {
if (dao.rawAttributes[attrName].references) {
deps.push(dao.rawAttributes[attrName].references) deps.push(dao.rawAttributes[attrName].references)
} }
} }
......
...@@ -5,8 +5,6 @@ var Utils = require("./utils") ...@@ -5,8 +5,6 @@ var Utils = require("./utils")
module.exports = (function() { module.exports = (function() {
var DAOFactory = function(name, attributes, options) { var DAOFactory = function(name, attributes, options) {
var self = this
this.options = Utils._.extend({ this.options = Utils._.extend({
timestamps: true, timestamps: true,
instanceMethods: {}, instanceMethods: {},
...@@ -24,21 +22,28 @@ module.exports = (function() { ...@@ -24,21 +22,28 @@ module.exports = (function() {
// error check options // error check options
Utils._.each(options.validate, function(validator, validatorType) { Utils._.each(options.validate, function(validator, validatorType) {
if (Utils._.contains(Utils._.keys(attributes), validatorType)) if (Utils._.contains(Utils._.keys(attributes), validatorType)) {
throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType) throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType)
if (!Utils._.isFunction(validator)) }
if (!Utils._.isFunction(validator)) {
throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType) throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType)
}
}) })
this.name = name this.name = name
if (!this.options.tableName) { if (!this.options.tableName) {
this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language) this.tableName = this.options.freezeTableName ? name : Utils.pluralize(name, this.options.language)
} else { } else {
this.tableName = this.options.tableName this.tableName = this.options.tableName
} }
this.rawAttributes = attributes
attributes = replaceReferencesWithTableNames(attributes)
this.rawAttributes = attributes
this.daoFactoryManager = null // defined in init function this.daoFactoryManager = null // defined in init function
this.associations = {} this.associations = {}
} }
Object.defineProperty(DAOFactory.prototype, 'attributes', { Object.defineProperty(DAOFactory.prototype, 'attributes', {
...@@ -59,8 +64,8 @@ module.exports = (function() { ...@@ -59,8 +64,8 @@ module.exports = (function() {
var self = this; var self = this;
this.daoFactoryManager = daoFactoryManager this.daoFactoryManager = daoFactoryManager
this.primaryKeys = {};
this.primaryKeys = {};
Utils._.each(this.attributes, function(dataTypeString, attributeName) { Utils._.each(this.attributes, function(dataTypeString, attributeName) {
if ((attributeName !== 'id') && (dataTypeString.indexOf('PRIMARY KEY') !== -1)) { if ((attributeName !== 'id') && (dataTypeString.indexOf('PRIMARY KEY') !== -1)) {
self.primaryKeys[attributeName] = dataTypeString self.primaryKeys[attributeName] = dataTypeString
...@@ -75,9 +80,11 @@ module.exports = (function() { ...@@ -75,9 +80,11 @@ module.exports = (function() {
findAutoIncrementField.call(this) findAutoIncrementField.call(this)
// DAO prototype // DAO prototype
// WTF ... ?
this.DAO = function() { this.DAO = function() {
DAO.apply(this, arguments); DAO.apply(this, arguments);
}; }
Util.inherits(this.DAO, DAO); Util.inherits(this.DAO, DAO);
this.DAO.prototype.rawAttributes = this.rawAttributes; this.DAO.prototype.rawAttributes = this.rawAttributes;
...@@ -89,47 +96,48 @@ module.exports = (function() { ...@@ -89,47 +96,48 @@ module.exports = (function() {
} }
Utils._.each(['Get', 'Set'], function(type) { Utils._.each(['Get', 'Set'], function(type) {
var prop = type.toLowerCase(), var prop = type.toLowerCase()
opt = prop + 'terMethods', , opt = prop + 'terMethods'
meth = '__define' + type + 'ter__', , meth = '__define' + type + 'ter__'
funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {} , funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
;
Utils._.each(self.rawAttributes, function(attr, name) { Utils._.each(self.rawAttributes, function(attr, name) {
if (attr.hasOwnProperty(prop)) if (attr.hasOwnProperty(prop)) {
funcs[name] = attr[prop] funcs[name] = attr[prop]
}); }
})
Utils._.each(funcs, function(fct, name) { Utils._.each(funcs, function(fct, name) {
if (!Utils._.isFunction(fct)) if (!Utils._.isFunction(fct)) {
throw new Error(type + 'ter for "' + name + '" is not a function.') throw new Error(type + 'ter for "' + name + '" is not a function.')
}
self.DAO.prototype[meth](name, fct); self.DAO.prototype[meth](name, fct)
}) })
}) })
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes); this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = []
this.DAO.prototype.defaultValues = {}
this.DAO.prototype.validators = {}
this.DAO.prototype.booleanValues = [];
this.DAO.prototype.defaultValues = {};
this.DAO.prototype.validators = {};
Utils._.each(this.rawAttributes, function (definition, name) { Utils._.each(this.rawAttributes, function (definition, name) {
if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) { if (((definition === DataTypes.BOOLEAN) || (definition.type === DataTypes.BOOLEAN))) {
self.DAO.prototype.booleanValues.push(name); self.DAO.prototype.booleanValues.push(name);
} }
if (definition.hasOwnProperty('defaultValue')) { if (definition.hasOwnProperty('defaultValue')) {
self.DAO.prototype.defaultValues[name] = function() { self.DAO.prototype.defaultValues[name] = function() {
return Utils.toDefaultValue(definition.defaultValue); return Utils.toDefaultValue(definition.defaultValue)
} }
} }
if (definition.hasOwnProperty('validate')) { if (definition.hasOwnProperty('validate')) {
self.DAO.prototype.validators[name] = definition.validate; self.DAO.prototype.validators[name] = definition.validate;
} }
}); })
this.DAO.prototype.__factory = this; this.DAO.prototype.__factory = this
this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues); this.DAO.prototype.hasDefaultValues = !Utils._.isEmpty(this.DAO.prototype.defaultValues)
return this return this
} }
...@@ -432,39 +440,26 @@ module.exports = (function() { ...@@ -432,39 +440,26 @@ module.exports = (function() {
, updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt' , updatedAtAttr = self.options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt' , createdAtAttr = self.options.underscored ? 'created_at' : 'createdAt'
fields = fields || []
// we will re-create from DAOs, which may have set up default attributes // we will re-create from DAOs, which may have set up default attributes
records = [] records = []
var found = false
if (fields) { daos.forEach(function(dao) {
var values = fields.length > 0 ? {} : dao.dataValues
// Always insert updated and created time stamps fields.forEach(function(field) {
if (self.options.timestamps) { values[field] = dao.dataValues[field]
if (fields.indexOf(updatedAtAttr) === -1) { })
fields.push(updatedAtAttr)
}
if (fields.indexOf(createdAtAttr) === -1) { if (self.options.timestamps) {
fields.push(createdAtAttr) values[createdAtAttr] = Utils.now()
} values[updatedAtAttr] = Utils.now()
} }
// Build records for the fields we know about records.push(values);
daos.forEach(function(dao) { })
var values = {};
fields.forEach(function(field) {
values[field] = dao.dataValues[field]
})
if (self.options.timestamps) {
values[updatedAtAttr] = Utils.now()
}
records.push(values);
})
} else {
daos.forEach(function(dao) {
records.push(dao.dataValues)
})
}
// Validate enums // Validate enums
records.forEach(function(values) { records.forEach(function(values) {
...@@ -520,6 +515,10 @@ module.exports = (function() { ...@@ -520,6 +515,10 @@ module.exports = (function() {
return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where) return this.QueryInterface.bulkUpdate(this.tableName, attrValueHash, where)
} }
DAOFactory.prototype.describe = function() {
return this.QueryInterface.describeTable(this.tableName)
}
// private // private
var query = function() { var query = function() {
...@@ -627,6 +626,16 @@ module.exports = (function() { ...@@ -627,6 +626,16 @@ module.exports = (function() {
} }
} }
var replaceReferencesWithTableNames = function(attributes) {
Object.keys(attributes).forEach(function(attrName) {
if (attributes[attrName].references instanceof DAOFactory) {
attributes[attrName].references = attributes[attrName].references.tableName
}
})
return attributes
}
Utils._.extend(DAOFactory.prototype, require("./associations/mixin")) Utils._.extend(DAOFactory.prototype, require("./associations/mixin"))
return DAOFactory return DAOFactory
......
...@@ -399,6 +399,20 @@ module.exports = (function() { ...@@ -399,6 +399,20 @@ module.exports = (function() {
for (key in attrs) { for (key in attrs) {
this.addAttribute(key, attrs[key]) this.addAttribute(key, attrs[key])
} }
// this.addAttributes COMPLETELY destroys the structure of our DAO due to __defineGetter__ resetting the object
// so now we have to rebuild for bulkInserts, bulkUpdates, etc.
var rebuild = {}
// Get the correct map....
Utils._.each(this.attributes, function(key) {
if (this.dataValues.hasOwnProperty(key)) {
rebuild[key] = this.dataValues[key]
}
}.bind(this))
// This allows for aliases, etc.
this.dataValues = Utils._.extend(rebuild, this.dataValues)
} }
return DAO return DAO
......
...@@ -4,11 +4,18 @@ var mysql ...@@ -4,11 +4,18 @@ var mysql
, Utils = require("../../utils") , Utils = require("../../utils")
, without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) } , without = function(arr, elem) { return arr.filter(function(e) { return e != elem }) }
try { mysql = require("mysql") } catch (err) {
console.log("You need to install mysql package manually"); }
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
try {
if (config.dialectModulePath) {
mysql = require(config.dialectModulePath)
} else {
mysql = require('mysql')
}
} catch (err) {
console.log('You need to install mysql package manually')
}
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
...@@ -75,7 +82,9 @@ module.exports = (function() { ...@@ -75,7 +82,9 @@ module.exports = (function() {
read: Pooling.Pool({ read: Pooling.Pool({
name: 'sequelize-read', name: 'sequelize-read',
create: function (done) { create: function (done) {
if (reads >= self.config.replication.read.length) reads = 0; if (reads >= self.config.replication.read.length) {
reads = 0
}
var config = self.config.replication.read[reads++]; var config = self.config.replication.read[reads++];
connect.call(self, function (err, connection) { connect.call(self, function (err, connection) {
...@@ -161,11 +170,12 @@ module.exports = (function() { ...@@ -161,11 +170,12 @@ module.exports = (function() {
query.done(function() { query.done(function() {
self.pendingQueries--; self.pendingQueries--;
if (self.pool) self.pool.release(query.client); if (self.pool) {
else { self.pool.release(query.client);
} else {
if (self.pendingQueries === 0) { if (self.pendingQueries === 0) {
setTimeout(function() { setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self); self.pendingQueries === 0 && self.disconnect.call(self)
}, 100); }, 100);
} }
} }
...@@ -173,16 +183,16 @@ module.exports = (function() { ...@@ -173,16 +183,16 @@ module.exports = (function() {
if (!this.pool) { if (!this.pool) {
query.run(sql); query.run(sql);
} } else {
else {
this.pool.acquire(function(err, client) { this.pool.acquire(function(err, client) {
if (err) return query.emit('error', err); if (err) {
return query.emit('error', err)
}
query.client = client; query.client = client
query.run(sql); query.run(sql)
return; return;
}, undefined, options.type); }, undefined, options.type)
} }
return query; return query;
...@@ -202,8 +212,10 @@ module.exports = (function() { ...@@ -202,8 +212,10 @@ module.exports = (function() {
}; };
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
if (this.client) disconnect.call(this, this.client); if (this.client) {
return; disconnect.call(this, this.client)
}
return
}; };
...@@ -258,12 +270,12 @@ module.exports = (function() { ...@@ -258,12 +270,12 @@ module.exports = (function() {
switch(err.code) { switch(err.code) {
case 'ECONNREFUSED': case 'ECONNREFUSED':
case 'ER_ACCESS_DENIED_ERROR': case 'ER_ACCESS_DENIED_ERROR':
emitter.emit('error', new Error("Failed to authenticate for MySQL. Please double check your settings.")) emitter.emit('error', 'Failed to authenticate for MySQL. Please double check your settings.')
break break
case 'ENOTFOUND': case 'ENOTFOUND':
case 'EHOSTUNREACH': case 'EHOSTUNREACH':
case 'EINVAL': case 'EINVAL':
emitter.emit('error', new Error("Failed to find MySQL server. Please double check your settings.")) emitter.emit('error', 'Failed to find MySQL server. Please double check your settings.')
break break
} }
} }
...@@ -288,7 +300,7 @@ module.exports = (function() { ...@@ -288,7 +300,7 @@ module.exports = (function() {
} }
var validateConnection = function(client) { var validateConnection = function(client) {
return client && client.state != 'disconnected' return client && client.state !== 'disconnected'
} }
var enqueue = function(queueItem, options) { var enqueue = function(queueItem, options) {
...@@ -335,8 +347,6 @@ module.exports = (function() { ...@@ -335,8 +347,6 @@ module.exports = (function() {
} }
var afterQuery = function(queueItem) { var afterQuery = function(queueItem) {
var self = this
dequeue.call(this, queueItem) dequeue.call(this, queueItem)
transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length) transferQueuedItems.call(this, this.maxConcurrentQueries - this.activeQueue.length)
disconnectIfNoConnections.call(this) disconnectIfNoConnections.call(this)
...@@ -376,4 +386,4 @@ module.exports = (function() { ...@@ -376,4 +386,4 @@ module.exports = (function() {
} }
return ConnectorManager return ConnectorManager
})() })()
\ No newline at end of file
...@@ -29,9 +29,8 @@ module.exports = (function() { ...@@ -29,9 +29,8 @@ module.exports = (function() {
return Utils._.template(query)({}) return Utils._.template(query)({})
}, },
dropSchema: function() { dropSchema: function(tableName, options) {
var query = "SHOW TABLES" return QueryGenerator.dropTableQuery(tableName, options)
return Utils._.template(query)({})
}, },
showSchemasQuery: function() { showSchemasQuery: function() {
...@@ -221,7 +220,7 @@ module.exports = (function() { ...@@ -221,7 +220,7 @@ module.exports = (function() {
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
/* /*
* If no limit is defined, our best bet is to use the max number of rows in a table. From the MySQL docs: * If no limit is defined, our best bet is to use the max number of rows in a table. From the MySQL docs:
* There is a limit of 2^32 (~4.295E+09) rows in a MyISAM table. If you build MySQL with the --with-big-tables option, * There is a limit of 2^32 (~4.295E+09) rows in a MyISAM table. If you build MySQL with the --with-big-tables option,
* the row limitation is increased to (2^32)^2 (1.844E+19) rows. * the row limitation is increased to (2^32)^2 (1.844E+19) rows.
*/ */
query += " LIMIT " + options.offset + ", " + 18440000000000000000; query += " LIMIT " + options.offset + ", " + 18440000000000000000;
...@@ -521,7 +520,8 @@ module.exports = (function() { ...@@ -521,7 +520,8 @@ module.exports = (function() {
template += " auto_increment" template += " auto_increment"
} }
if ((dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) { // Blobs/texts cannot have a defaultValue
if (dataType.type !== "TEXT" && dataType.type._binary !== true && (dataType.defaultValue !== undefined) && (dataType.defaultValue != DataTypes.NOW)) {
template += " DEFAULT " + this.escape(dataType.defaultValue) template += " DEFAULT " + this.escape(dataType.defaultValue)
} }
...@@ -536,7 +536,6 @@ module.exports = (function() { ...@@ -536,7 +536,6 @@ module.exports = (function() {
if(dataType.references) { if(dataType.references) {
template += " REFERENCES " + this.quoteIdentifier(dataType.references) template += " REFERENCES " + this.quoteIdentifier(dataType.references)
if(dataType.referencesKey) { if(dataType.referencesKey) {
template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")" template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")"
} else { } else {
......
...@@ -3,12 +3,15 @@ var Query = require("./query") ...@@ -3,12 +3,15 @@ var Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize, config) { var ConnectorManager = function(sequelize, config) {
var pgModule = config.dialectModulePath || 'pg'
this.sequelize = sequelize this.sequelize = sequelize
this.client = null this.client = null
this.config = config || {} this.config = config || {}
this.config.port = this.config.port || 5432 this.config.port = this.config.port || 5432
this.pooling = (!!this.config.poolCfg && (this.config.poolCfg.maxConnections > 0)) this.pooling = (!!this.config.pool && (this.config.pool.maxConnections > 0))
this.pg = this.config.native ? require('pg').native : require('pg') this.pg = this.config.native ? require(pgModule).native : require(pgModule)
this.poolIdentifier = null
// Better support for BigInts // Better support for BigInts
// https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935 // https://github.com/brianc/node-postgres/issues/166#issuecomment-9514935
...@@ -16,52 +19,60 @@ module.exports = (function() { ...@@ -16,52 +19,60 @@ module.exports = (function() {
// set pooling parameters if specified // set pooling parameters if specified
if (this.pooling) { if (this.pooling) {
this.pg.defaults.poolSize = this.config.poolCfg.maxConnections this.pg.defaults.poolSize = this.config.pool.maxConnections
this.pg.defaults.poolIdleTimeout = this.config.poolCfg.maxIdleTime this.pg.defaults.poolIdleTimeout = this.config.pool.maxIdleTime
} }
this.disconnectTimeoutId = null this.disconnectTimeoutId = null
this.pendingQueries = 0 this.pendingQueries = 0
this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50) this.maxConcurrentQueries = (this.config.maxConcurrentQueries || 50)
process.on('exit', function() {
this.disconnect()
}.bind(this))
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
var isConnecting = false ConnectorManager.prototype.endQuery = function() {
var isConnected = false
ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
if (this.client === null) { if (!self.pooling && self.pendingQueries === 0) {
this.connect() setTimeout(function() {
self.pendingQueries === 0 && self.disconnect.call(self)
}, 100)
} }
var query = new Query(this.client, this.sequelize, callee, options || {})
self.pendingQueries += 1
return query.run(sql)
.success(function() { self.endQuery.call(self) })
.error(function() { self.endQuery.call(self) })
} }
ConnectorManager.prototype.endQuery = function() { ConnectorManager.prototype.query = function(sql, callee, options) {
var self = this var self = this
self.pendingQueries -= 1
if (self.pendingQueries == 0) { self.pendingQueries++
setTimeout(function() {
self.pendingQueries == 0 && self.disconnect.call(self) return new Utils.CustomEventEmitter(function(emitter) {
}, 100) self.connect()
} .on('error', function(err) {
emitter.emit('error', err)
})
.on('success', function(done) {
var query = new Query(self.client, self.sequelize, callee, options || {})
done = done || null
query.run(sql, done)
.success(function(results) { emitter.emit('success', results); self.endQuery.call(self) })
.error(function(err) { emitter.emit('error', err); self.endQuery.call(self) })
.on('sql', function(sql) { emitter.emit('sql', sql) })
})
}).run().complete(function() { self.pendingQueries-- })
} }
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function(callback) {
var self = this var self = this
var emitter = new (require('events').EventEmitter)() var emitter = new (require('events').EventEmitter)()
// in case database is slow to connect, prevent orphaning the client // in case database is slow to connect, prevent orphaning the client
if (this.isConnecting) { if (this.isConnecting) {
return emitter.emit('success')
return emitter
} }
this.isConnecting = true this.isConnecting = true
...@@ -69,36 +80,44 @@ module.exports = (function() { ...@@ -69,36 +80,44 @@ module.exports = (function() {
var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config) var uri = this.sequelize.getQueryInterface().QueryGenerator.databaseConnectionUri(this.config)
var connectCallback = function(err, client) { var connectCallback = function(err, client, done) {
self.isConnecting = false self.isConnecting = false
if (!!err) { if (!!err) {
switch(err.code) { // release the pool immediately, very important.
case 'ECONNREFUSED': done && done(err)
emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
break if (err.code) {
case 'ENOTFOUND': switch(err.code) {
case 'EHOSTUNREACH': case 'ECONNREFUSED':
case 'EINVAL': emitter.emit('error', new Error("Failed to authenticate for PostgresSQL. Please double check your settings."))
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings.")) break
break case 'ENOTFOUND':
default: case 'EHOSTUNREACH':
emitter.emit('error', err) case 'EINVAL':
emitter.emit('error', new Error("Failed to find PostgresSQL server. Please double check your settings."))
break
default:
emitter.emit('error', err)
break
}
} }
} else if (client) { } else if (client) {
client.query("SET TIME ZONE 'UTC'") client.query("SET TIME ZONE 'UTC'").on('end', function() {
.on('end', function() {
self.isConnected = true self.isConnected = true
this.client = client self.client = client
emitter.emit('success', done)
}); });
} else { } else {
this.client = null self.client = null
emitter.emit('success', done)
} }
} }
if (this.pooling) { if (this.pooling) {
// acquire client from pool // acquire client from pool
this.pg.connect(uri, connectCallback) this.poolIdentifier = this.pg.pools.getOrCreate(this.sequelize.config)
this.poolIdentifier.connect(connectCallback)
} else { } else {
//create one-off client //create one-off client
this.client = new this.pg.Client(uri) this.client = new this.pg.Client(uri)
...@@ -109,8 +128,14 @@ module.exports = (function() { ...@@ -109,8 +128,14 @@ module.exports = (function() {
} }
ConnectorManager.prototype.disconnect = function() { ConnectorManager.prototype.disconnect = function() {
var self = this if (this.poolIdentifier) {
if (this.client) this.client.end() this.poolIdentifier.destroyAllNow()
}
if (this.client) {
this.client.end()
}
this.client = null this.client = null
this.isConnecting = false this.isConnecting = false
this.isConnected = false this.isConnected = false
......
...@@ -112,7 +112,7 @@ module.exports = (function() { ...@@ -112,7 +112,7 @@ module.exports = (function() {
}, },
describeTableQuery: function(tableName) { describeTableQuery: function(tableName) {
var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", c.data_type as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS special FROM information_schema.columns c WHERE table_name = <%= table %>;' var query = 'SELECT c.column_name as "Field", c.column_default as "Default", c.is_nullable as "Null", c.data_type as "Type", (SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special" FROM information_schema.columns c WHERE table_name = <%= table %>;'
return Utils._.template(query)({ return Utils._.template(query)({
table: this.escape(tableName) table: this.escape(tableName)
}) })
......
...@@ -18,16 +18,17 @@ module.exports = (function() { ...@@ -18,16 +18,17 @@ module.exports = (function() {
} }
Utils.inherit(Query, AbstractQuery) Utils.inherit(Query, AbstractQuery)
Query.prototype.run = function(sql) { Query.prototype.run = function(sql, done) {
this.sql = sql this.sql = sql
var self = this
if (this.options.logging !== false) { if (this.options.logging !== false) {
this.options.logging('Executing: ' + this.sql) this.options.logging('Executing: ' + this.sql)
} }
var receivedError = false var receivedError = false
, query = this.client.query(sql) , query = this.client.query(sql)
, rows = [] , rows = []
query.on('row', function(row) { query.on('row', function(row) {
rows.push(row) rows.push(row)
...@@ -39,13 +40,14 @@ module.exports = (function() { ...@@ -39,13 +40,14 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
query.on('end', function() { query.on('end', function() {
done && done()
this.emit('sql', this.sql) this.emit('sql', this.sql)
if (receivedError) { if (receivedError) {
return return
} }
onSuccess.call(this, rows) onSuccess.call(this, rows, sql)
}.bind(this)) }.bind(this))
return this return this
...@@ -55,11 +57,11 @@ module.exports = (function() { ...@@ -55,11 +57,11 @@ module.exports = (function() {
return 'id' return 'id'
} }
var onSuccess = function(rows) { var onSuccess = function(rows, sql) {
var results = [] var results = []
, self = this , self = this
, isTableNameQuery = (this.sql.indexOf('SELECT table_name FROM information_schema.tables') === 0) , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
, isRelNameQuery = (this.sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0) , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0)
if (isTableNameQuery || isRelNameQuery) { if (isTableNameQuery || isRelNameQuery) {
if (isRelNameQuery) { if (isRelNameQuery) {
...@@ -70,7 +72,7 @@ module.exports = (function() { ...@@ -70,7 +72,7 @@ module.exports = (function() {
} }
}) })
} else { } else {
results = rows.map(function(row) { return Utils._.values(row) }) results = rows.map(function(row) { return Utils._.values(row) })
} }
return this.emit('success', results) return this.emit('success', results)
} }
......
...@@ -289,7 +289,7 @@ module.exports = (function() { ...@@ -289,7 +289,7 @@ module.exports = (function() {
Escape a value (e.g. a string, number or date) Escape a value (e.g. a string, number or date)
*/ */
escape: function(value) { escape: function(value) {
throwMethodUndefined('quoteIdentifier') throwMethodUndefined('escape')
} }
} }
......
var Utils = require("../../utils") var sqlite3
, sqlite3 = require('sqlite3').verbose() , Utils = require("../../utils")
, Query = require("./query") , Query = require("./query")
module.exports = (function() { module.exports = (function() {
var ConnectorManager = function(sequelize) { var ConnectorManager = function(sequelize, config) {
this.sequelize = sequelize this.sequelize = sequelize
if (config.dialectModulePath) {
sqlite3 = require(config.dialectModulePath).verbose()
} else {
sqlite3 = require('sqlite3').verbose()
}
} }
Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype) Utils._.extend(ConnectorManager.prototype, require("../connector-manager").prototype)
...@@ -16,7 +22,7 @@ module.exports = (function() { ...@@ -16,7 +22,7 @@ module.exports = (function() {
this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) { this.database = db = new sqlite3.Database(self.sequelize.options.storage || ':memory:', function(err) {
if (err) { if (err) {
if (err.code === "SQLITE_CANTOPEN") { if (err.code === "SQLITE_CANTOPEN") {
emitter.emit('error', new Error("Failed to find SQL server. Please double check your settings.")) emitter.emit('error', 'Failed to find SQL server. Please double check your settings.')
} }
} }
......
...@@ -37,9 +37,8 @@ module.exports = (function() { ...@@ -37,9 +37,8 @@ module.exports = (function() {
return Utils._.template(query)({}) return Utils._.template(query)({})
}, },
dropSchema: function() { dropSchema: function(tableName, options) {
var query = "SELECT name FROM sqlite_master WHERE type='table' and name!='sqlite_sequence';" return this.dropTableQuery(tableName, options)
return Utils._.template(query)({})
}, },
showSchemasQuery: function() { showSchemasQuery: function() {
...@@ -89,6 +88,16 @@ module.exports = (function() { ...@@ -89,6 +88,16 @@ module.exports = (function() {
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
}, },
dropTableQuery: function(tableName, options) {
options = options || {}
var query = "DROP TABLE IF EXISTS <%= table %>;"
return Utils._.template(query)({
table: this.quoteIdentifier(tableName)
})
},
addColumnQuery: function() { addColumnQuery: function() {
var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments) var sql = MySqlQueryGenerator.addColumnQuery.apply(this, arguments)
return this.replaceBooleanDefaults(sql) return this.replaceBooleanDefaults(sql)
...@@ -134,6 +143,7 @@ module.exports = (function() { ...@@ -134,6 +143,7 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
selectQuery: function(tableName, options) { selectQuery: function(tableName, options) {
var table = null, var table = null,
joinQuery = "" joinQuery = ""
...@@ -378,7 +388,7 @@ module.exports = (function() { ...@@ -378,7 +388,7 @@ module.exports = (function() {
}, },
removeIndexQuery: function(tableName, indexNameOrAttributes) { removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = "DROP INDEX <%= indexName %>" var sql = "DROP INDEX IF EXISTS <%= indexName %>"
, indexName = indexNameOrAttributes , indexName = indexNameOrAttributes
if (typeof indexName !== 'string') { if (typeof indexName !== 'string') {
......
...@@ -54,6 +54,12 @@ module.exports = (function() { ...@@ -54,6 +54,12 @@ module.exports = (function() {
return this return this
} }
CustomEventEmitter.prototype.sql =
function(fct) {
this.on('sql', bindToProcess(fct))
return this;
}
// emit the events on the foreign emitter once events got triggered for me // emit the events on the foreign emitter once events got triggered for me
CustomEventEmitter.prototype.proxy = function(emitter) { CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) { proxyEventKeys.forEach(function (eventKey) {
......
const fs = require("fs") const fs = require("fs")
, moment = require("moment") , moment = require("moment")
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
, Migration = require("./migration") , Migration = require(__dirname + "/migration")
, DataTypes = require("./data-types") , DataTypes = require(__dirname + "/data-types")
module.exports = (function() { module.exports = (function() {
var Migrator = function(sequelize, options) { var Migrator = function(sequelize, options) {
...@@ -45,7 +45,7 @@ module.exports = (function() { ...@@ -45,7 +45,7 @@ module.exports = (function() {
if (err) { if (err) {
emitter.emit('error', err) emitter.emit('error', err)
} else { } else {
var chainer = new Utils.QueryChainer var chainer = new Utils.QueryChainer()
, from = migrations[0] , from = migrations[0]
if (options.method === 'down') { if (options.method === 'down') {
...@@ -188,7 +188,7 @@ module.exports = (function() { ...@@ -188,7 +188,7 @@ module.exports = (function() {
var self = this; var self = this;
return new Utils.CustomEventEmitter(function(emitter) { return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer; var chainer = new Utils.QueryChainer()
var addMigration = function(filename) { var addMigration = function(filename) {
self.options.logging('Adding migration script at ' + filename) self.options.logging('Adding migration script at ' + filename)
var migration = new Migration(self, filename) var migration = new Migration(self, filename)
......
var Utils = require("./utils") var Utils = require(__dirname + "/utils")
module.exports = (function() { module.exports = (function() {
var QueryChainer = function(emitters) { var QueryChainer = function(emitters) {
......
var Utils = require('./utils') var Utils = require(__dirname + '/utils')
, DataTypes = require('./data-types') , DataTypes = require(__dirname + '/data-types')
, SQLiteQueryInterface = require('./dialects/sqlite/query-interface') , SQLiteQueryInterface = require(__dirname + '/dialects/sqlite/query-interface')
module.exports = (function() { module.exports = (function() {
var QueryInterface = function(sequelize) { var QueryInterface = function(sequelize) {
...@@ -54,7 +54,7 @@ module.exports = (function() { ...@@ -54,7 +54,7 @@ module.exports = (function() {
var showSchemasSql = self.QueryGenerator.showSchemasQuery() var showSchemasSql = self.QueryGenerator.showSchemasQuery()
self.sequelize.query(showSchemasSql, null, { raw: true }).success(function(schemaNames) { self.sequelize.query(showSchemasSql, null, { raw: true }).success(function(schemaNames) {
self.emit('showAllSchemas', null) self.emit('showAllSchemas', null)
emitter.emit('success', Utils._.flatten(Utils._.map(schemaNames, function(value){ return value.schema_name }))) emitter.emit('success', Utils._.flatten(Utils._.map(schemaNames, function(value){ return (!!value.schema_name ? value.schema_name : value) })))
}).error(function(err) { }).error(function(err) {
self.emit('showAllSchemas', err) self.emit('showAllSchemas', err)
emitter.emit('error', err) emitter.emit('error', err)
...@@ -266,23 +266,128 @@ module.exports = (function() { ...@@ -266,23 +266,128 @@ module.exports = (function() {
} }
QueryInterface.prototype.update = function(dao, tableName, values, identifier) { QueryInterface.prototype.update = function(dao, tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var self = this
return queryAndEmit.call(this, [sql, dao], 'update') , restrict = false
, sql = self.QueryGenerator.updateQuery(tableName, values, identifier)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
var keys = Object.keys(dao.daoFactory.associations)
, length = keys.length
for (var i = 0; i < length; i++) {
if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onUpdate && dao.daoFactory.associations[keys[i]].options.onUpdate === "restrict") {
restrict = true
}
}
}
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('success', results[1])
emitter.emit('sql', sql)
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('error', err)
emitter.emit('sql', sql)
})
.on('sql', function(sql) {
emitter.emit('sql', sql)
})
}).run()
} }
QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) { QueryInterface.prototype.bulkUpdate = function(tableName, values, identifier) {
var sql = this.QueryGenerator.updateQuery(tableName, values, identifier) var self = this
return queryAndEmit.call(this, sql, 'bulkUpdate') , sql = self.QueryGenerator.updateQuery(tableName, values, identifier)
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkUpdate'])
return chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.delete = function(dao, tableName, identifier) { QueryInterface.prototype.delete = function(dao, tableName, identifier) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier) var self = this
return queryAndEmit.call(this, [sql, dao], 'delete') , restrict = false
, sql = self.QueryGenerator.deleteQuery(tableName, identifier)
// Check for a restrict field
if (!!dao.daoFactory && !!dao.daoFactory.associations) {
var keys = Object.keys(dao.daoFactory.associations)
, length = keys.length
for (var i = 0; i < length; i++) {
if (dao.daoFactory.associations[keys[i]].options && dao.daoFactory.associations[keys[i]].options.onDelete && dao.daoFactory.associations[keys[i]].options.onDelete === "restrict") {
restrict = true
}
}
}
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [[sql, dao], 'delete'])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) { QueryInterface.prototype.bulkDelete = function(tableName, identifier, options) {
var sql = this.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null})) var self = this
return queryAndEmit.call(this, sql, 'bulkDelete') var sql = self.QueryGenerator.deleteQuery(tableName, identifier, Utils._.defaults(options || {}, {limit: null}))
return new Utils.CustomEventEmitter(function(emitter) {
var chainer = new Utils.QueryChainer()
chainer.add(self, 'enableForeignKeyConstraints', [])
chainer.add(self, 'queryAndEmit', [sql, 'bulkDelete', options])
chainer.runSerially()
.success(function(results){
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('success', results[1])
})
.error(function(err) {
emitter.query = { sql: sql }
emitter.emit('sql', sql)
emitter.emit('error', err)
})
}).run()
} }
QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) { QueryInterface.prototype.select = function(factory, tableName, options, queryOptions) {
...@@ -385,9 +490,7 @@ module.exports = (function() { ...@@ -385,9 +490,7 @@ module.exports = (function() {
return this.QueryGenerator.escape(value) return this.QueryGenerator.escape(value)
} }
// private var queryAndEmit = QueryInterface.prototype.queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
var queryAndEmit = function(sqlOrQueryParams, methodName, options, emitter) {
options = Utils._.extend({ options = Utils._.extend({
success: function(){}, success: function(){},
error: function(){} error: function(){}
......
...@@ -14,6 +14,7 @@ module.exports = (function() { ...@@ -14,6 +14,7 @@ module.exports = (function() {
@param {String} [password=null] The password which is used to authenticate against the database. @param {String} [password=null] The password which is used to authenticate against the database.
@param {Object} [options={}] An object with options. @param {Object} [options={}] An object with options.
@param {String} [options.dialect='mysql'] The dialect of the relational database. @param {String} [options.dialect='mysql'] The dialect of the relational database.
@param {String} [options.dialectModulePath=null] If specified, load the dialect library from this path.
@param {String} [options.host='localhost'] The host of the relational database. @param {String} [options.host='localhost'] The host of the relational database.
@param {Integer} [options.port=3306] The port of the relational database. @param {Integer} [options.port=3306] The port of the relational database.
@param {String} [options.protocol='tcp'] The protocol of the relational database. @param {String} [options.protocol='tcp'] The protocol of the relational database.
...@@ -68,6 +69,7 @@ module.exports = (function() { ...@@ -68,6 +69,7 @@ module.exports = (function() {
this.options = Utils._.extend({ this.options = Utils._.extend({
dialect: 'mysql', dialect: 'mysql',
dialectModulePath: null,
host: 'localhost', host: 'localhost',
protocol: 'tcp', protocol: 'tcp',
define: {}, define: {},
...@@ -99,6 +101,7 @@ module.exports = (function() { ...@@ -99,6 +101,7 @@ module.exports = (function() {
queue : this.options.queue, queue : this.options.queue,
native : this.options.native, native : this.options.native,
replication: this.options.replication, replication: this.options.replication,
dialectModulePath: this.options.dialectModulePath,
maxConcurrentQueries: this.options.maxConcurrentQueries maxConcurrentQueries: this.options.maxConcurrentQueries
} }
...@@ -183,11 +186,12 @@ module.exports = (function() { ...@@ -183,11 +186,12 @@ module.exports = (function() {
// if you call "define" multiple times for the same daoName, do not clutter the factory // if you call "define" multiple times for the same daoName, do not clutter the factory
if(this.isDefined(daoName)) { if(this.isDefined(daoName)) {
this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName)) this.daoFactoryManager.removeDAO(this.daoFactoryManager.getDAO(daoName))
} }
var factory = new DAOFactory(daoName, attributes, options) var factory = new DAOFactory(daoName, attributes, options)
this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager)) this.daoFactoryManager.addDAO(factory.init(this.daoFactoryManager))
return factory return factory
} }
......
...@@ -9,17 +9,31 @@ SqlString.escapeId = function (val, forbidQualified) { ...@@ -9,17 +9,31 @@ SqlString.escapeId = function (val, forbidQualified) {
}; };
SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) { SqlString.escape = function(val, stringifyObjects, timeZone, dialect, field) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
val = val.val || val.value || null
stringifyObjects = val.stringifyObjects || val.objects || undefined
timeZone = val.timeZone || val.zone || null
dialect = val.dialect || null
field = val.field || null
}
else if (arguments.length < 3 && typeof arguments[1] === "object") {
timeZone = stringifyObjects.timeZone || stringifyObjects.zone || null
dialect = stringifyObjects.dialect || null
field = stringifyObjects.field || null
}
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 'NULL'; return 'NULL';
} }
switch (typeof val) { switch (typeof val) {
case 'boolean': case 'boolean':
// SQLite doesn't have true/false support. MySQL aliases true/false to 1/0 // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
// for us. Postgres actually has a boolean type with true/false literals, // for us. Postgres actually has a boolean type with true/false literals,
// but sequelize doesn't use it yet. // but sequelize doesn't use it yet.
return dialect === 'sqlite' ? +!!val : ('' + !!val); return dialect === 'sqlite' ? +!!val : ('' + !!val);
case 'number': return val+''; case 'number':
return val+'';
} }
if (val instanceof Date) { if (val instanceof Date) {
...@@ -97,7 +111,7 @@ SqlString.dateToString = function(date, timeZone, dialect) { ...@@ -97,7 +111,7 @@ SqlString.dateToString = function(date, timeZone, dialect) {
// TODO: Ideally all dialects would work a bit more like this // TODO: Ideally all dialects would work a bit more like this
if (dialect === "postgres") { if (dialect === "postgres") {
return moment(dt).format("YYYY-MM-DD HH:mm:ss.SSS Z"); return moment(dt).zone('+00:00').format("YYYY-MM-DD HH:mm:ss.SSS Z");
} }
if (timeZone !== 'local') { if (timeZone !== 'local') {
......
var util = require("util") var util = require("util")
, DataTypes = require("./data-types") , DataTypes = require("./data-types")
, SqlString = require("./sql-string") , SqlString = require("./sql-string")
, lodash = require("lodash")
, _string = require('underscore.string')
var Utils = module.exports = { var Utils = module.exports = {
_: (function() { _: (function() {
var _ = require("lodash") var _ = lodash
, _s = require('underscore.string') , _s = _string
_.mixin(_s.exports()) _.mixin(_s.exports())
_.mixin({ _.mixin({
...@@ -39,6 +41,214 @@ var Utils = module.exports = { ...@@ -39,6 +41,214 @@ var Utils = module.exports = {
var timeZone = null; var timeZone = null;
return SqlString.format(arr.shift(), arr, timeZone, dialect) return SqlString.format(arr.shift(), arr, timeZone, dialect)
}, },
// smartWhere can accept an array of {where} objects, or a single {where} object.
// The smartWhere function breaks down the collection of where objects into a more
// centralized object for each column so we can avoid duplicates
// e.g. WHERE username='dan' AND username='dan' becomes WHERE username='dan'
// All of the INs, NOT INs, BETWEENS, etc. are compressed into one key for each column
// This function will hopefully provide more functionality to sequelize in the future.
// tl;dr It's a nice way to dissect a collection of where objects and compress them into one object
smartWhere: function(whereArg, dialect) {
var self = this
, _where = {}
, logic
, type
(Array.isArray(whereArg) ? whereArg : [whereArg]).forEach(function(where) {
// If it's an array we're already good... / it's in a format that can't be broken down further
// e.g. Util.format['SELECT * FROM world WHERE status=?', 'hello']
if (Array.isArray(where)) {
_where._ = where._ || {queries: [], bindings: []}
_where._.queries[_where._.queries.length] = where[0]
if (where.length > 1) {
var values = where.splice(1)
if (dialect === "sqlite") {
values.forEach(function(v, i) {
if (typeof v === "boolean") {
values[i] = (v === true ? 1 : 0)
}
})
}
_where._.bindings = _where._.bindings.concat(values)
}
}
else if (typeof where === "object") {
// First iteration is trying to compress IN and NOT IN as much as possible...
// .. reason being is that WHERE username IN (?) AND username IN (?) != WHERE username IN (?,?)
Object.keys(where).forEach(function(i) {
if (Array.isArray(where[i])) {
where[i] = {
in: where[i]
}
}
})
// Build our smart object
Object.keys(where).forEach(function(i) {
type = typeof where[i]
_where[i] = _where[i] || {}
if (where[i] === null) {
// skip nulls
}
else if (Array.isArray(where[i])) {
_where[i].in = _where[i].in || []
_where[i].in.concat(where[i])
}
else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) {
logic = self.getWhereLogic(ii)
switch(logic) {
case 'IN':
_where[i].in = _where[i].in || []
_where[i].in = _where[i].in.concat(where[i][ii]);
break
case 'NOT':
_where[i].not = _where[i].not || []
_where[i].not = _where[i].not.concat(where[i][ii]);
break
case 'BETWEEN':
_where[i].between = _where[i].between || []
_where[i].between[_where[i].between.length] = [where[i][ii][0], where[i][ii][1]]
break
case 'NOT BETWEEN':
_where[i].nbetween = _where[i].nbetween || []
_where[i].nbetween[_where[i].nbetween.length] = [where[i][ii][0], where[i][ii][1]]
break
case 'JOIN':
_where[i].joined = _where[i].joined || []
_where[i].joined[_where[i].joined.length] = where[i][ii]
break
default:
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = logic + ' ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i][ii])
}
})
}
else if (type === "string" || type === "number" || type === "boolean") {
_where[i].lazy = _where[i].lazy || {conditions: [], bindings: []}
if (type === "boolean") {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ' + SqlString.escape(where[i], false, null, dialect) // sqlite is special
} else {
_where[i].lazy.conditions[_where[i].lazy.conditions.length] = '= ?'
_where[i].lazy.bindings = _where[i].lazy.bindings.concat(where[i])
}
}
})
}
})
return _where
},
// Converts {smart where} object(s) into an array that's friendly for Utils.format()
// NOTE: Must be applied/called from the QueryInterface
compileSmartWhere: function(obj, dialect) {
var self = this
, whereArgs = []
, text = []
, columnName
if (typeof obj !== "object") {
return obj
}
for (var column in obj) {
if (column === "_") {
text[text.length] = obj[column].queries.join(' AND ')
if (obj[column].bindings.length > 0) {
whereArgs = whereArgs.concat(obj[column].bindings)
}
} else {
Object.keys(obj[column]).forEach(function(condition) {
columnName = self.QueryInterface.quoteIdentifiers(column)
switch(condition) {
case 'in':
text[text.length] = columnName + ' IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition])
break
case 'not':
text[text.length] = columnName + ' NOT IN (' + obj[column][condition].map(function(){ return '?' }) + ')'
whereArgs = whereArgs.concat(obj[column][condition])
break
case 'between':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
})
break
case 'nbetween':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' BETWEEN ? AND ?'
whereArgs = whereArgs.concat(obj[column][condition][row][0], obj[column][condition][row][1])
})
break
case 'joined':
Object.keys(obj[column][condition]).forEach(function(row) {
text[text.length] = columnName + ' = ' + self.QueryInterface.quoteIdentifiers(obj[column][condition][row])
})
break
default: // lazy
text = text.concat(obj[column].lazy.conditions.map(function(val){ return columnName + ' ' + val }))
obj[column].lazy.bindings = obj[column].lazy.bindings.map(function(v) { return SqlString.escape(v, false, null, dialect) })
whereArgs = whereArgs.concat(obj[column].lazy.bindings)
}
})
}
}
return lodash.compact([text.join(' AND ')].concat(whereArgs))
},
getWhereLogic: function(logic) {
switch (logic) {
case 'join':
return 'JOIN'
case 'gte':
return '>='
break
case 'gt':
return '>'
break
case 'lte':
return '<='
break
case 'lt':
return '<'
break
case 'eq':
case 'join':
return '='
break
case 'ne':
return '!='
break
case 'between':
case '..':
return 'BETWEEN'
break
case 'nbetween':
case 'notbetween':
case '!..':
return 'NOT BETWEEN'
break
case 'in':
return 'IN'
break
case 'not':
return 'NOT IN'
break
case 'like':
return 'LIKE'
break
case 'nlike':
case 'notlike':
return 'NOT LIKE'
break
default:
return ''
}
},
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
}, },
...@@ -173,6 +383,6 @@ var Utils = module.exports = { ...@@ -173,6 +383,6 @@ var Utils = module.exports = {
} }
} }
Utils.CustomEventEmitter = require("./emitters/custom-event-emitter") Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
Utils.QueryChainer = require("./query-chainer") Utils.QueryChainer = require(__dirname + "/query-chainer")
Utils.Lingo = require("lingo") Utils.Lingo = require("lingo")
...@@ -22,6 +22,10 @@ ...@@ -22,6 +22,10 @@
"janzeh@gmail.com", "janzeh@gmail.com",
"jmei@itu.dk" "jmei@itu.dk"
] ]
},
{
"name": "Daniel Durante",
"email": "me@danieldurante.com"
} }
], ],
"repository": { "repository": {
...@@ -32,26 +36,28 @@ ...@@ -32,26 +36,28 @@
"url": "https://github.com/sequelize/sequelize/issues" "url": "https://github.com/sequelize/sequelize/issues"
}, },
"dependencies": { "dependencies": {
"lodash": "~1.2.1", "lodash": "~1.3.1",
"underscore.string": "~2.3.0", "underscore.string": "~2.3.0",
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "1.1.1", "validator": "~1.3.0",
"moment": "~1.7.0", "moment": "~2.1.0",
"commander": "~0.6.0", "commander": "~2.0.0",
"dottie": "0.0.6-1", "dottie": "0.0.8-0",
"toposort-class": "0.1.4", "toposort-class": "~0.2.0",
"generic-pool": "2.0.3", "generic-pool": "2.0.4",
"promise": "~3.0.0" "promise": "~3.2.0"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.5.0", "sqlite3": "~2.1.12",
"sqlite3": "~2.1.5", "mysql": "~2.0.0-alpha8",
"mysql": "~2.0.0-alpha7", "pg": "~2.2.0",
"pg": "~2.0.0",
"buster": "~0.6.3", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.4.3",
"yuidocjs": "~0.3.36", "yuidocjs": "~0.3.36",
"semver": "~2.0.8" "chai": "~1.7.2",
"mocha": "~1.12.0",
"chai-datetime": "~1.0.0",
"sinon": "~1.7.3"
}, },
"keywords": [ "keywords": [
"mysql", "mysql",
...@@ -61,14 +67,7 @@ ...@@ -61,14 +67,7 @@
], ],
"main": "index", "main": "index",
"scripts": { "scripts": {
"test": "npm run test-jasmine && npm run test-buster", "test": "make all",
"test-jasmine": "jasmine-node spec-jasmine/",
"test-buster": "npm run test-buster-mysql && npm run test-buster-postgres && npm run test-buster-postgres-native && npm run test-buster-sqlite",
"test-buster-travis": "buster-test",
"test-buster-mysql": "DIALECT=mysql buster-test",
"test-buster-postgres": "DIALECT=postgres buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native buster-test",
"test-buster-sqlite": "DIALECT=sqlite buster-test",
"docs": "node_modules/.bin/yuidoc . -o docs" "docs": "node_modules/.bin/yuidoc . -o docs"
}, },
"bin": { "bin": {
......
module.exports = {
up: function(migration, DataTypes) {
migration.createTable('Person', {
name: DataTypes.STRING,
isBetaMember: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
}
})
},
down: function(migration) {
migration.dropTable('Person')
}
}
module.exports = {
up: function() {},
down: function() {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameTable('Person', 'User')
},
down: function(migration, DataTypes) {
migration.renameTable('User', 'Person')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.addColumn('User', 'signature', DataTypes.TEXT)
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
migration.addColumn('User', 'isAdmin', { type: DataTypes.BOOLEAN, defaultValue: false, allowNull: false })
},
down: function(migration, DataTypes) {
migration.removeColumn('User', 'signature')
migration.removeColumn('User', 'shopId')
migration.removeColumn('User', 'isAdmin')
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.removeColumn('User', 'shopId')
},
down: function(migration, DataTypes) {
migration.addColumn('User', 'shopId', { type: DataTypes.INTEGER, allowNull: true })
}
}
module.exports = {
up: function(migration, DataTypes) {
migration.changeColumn('User', 'signature', {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'Signature'
})
},
down: function(migration, DataTypes) {}
}
module.exports = {
up: function(migration, DataTypes) {
migration.renameColumn('User', 'signature', 'sig')
},
down: function(migration, DataTypes) {
migration.renameColumn('User', 'sig', 'signature')
}
}
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('BelongsTo', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING, enabled: {
type: Sequelize.BOOLEAN,
defaultValue: true
}})
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it('adds the foreign key', function() {
Task.belongsTo(User)
expect(Task.attributes['UserId']).toEqual("INTEGER")
})
it("underscores the foreign key", function() {
Task = sequelize.define('Task', { title: Sequelize.STRING }, {underscored: true})
Task.belongsTo(User)
expect(Task.attributes['user_id']).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
Task.belongsTo(User, {foreignKey: 'person_id'})
expect(Task.attributes['person_id']).toEqual("INTEGER")
})
it("defines getters and setters", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task.setUser).toBeDefined()
expect(task.getUser).toBeDefined()
})
it("aliases the getters and setters according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Person'})
var task = Task.build({title: 'asd'})
expect(task.setPerson).toBeDefined()
expect(task.getPerson).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
Task.belongsTo(User, {as: 'Poster'})
Task.belongsTo(User, {as: 'Owner'})
var task = Task.build({title: 'asd'})
expect(task.getPoster).toBeDefined()
expect(task.setPoster).toBeDefined()
expect(task.getOwner).toBeDefined()
expect(task.setOwner).toBeDefined()
})
it("intializes the foreign key with null", function() {
Task.belongsTo(User)
var task = Task.build({title: 'asd'})
expect(task['UserId']).not.toBeDefined();
})
it("sets and gets the correct objects", function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd'}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser().success(function(user) {
expect(user.username).toEqual('asd')
done()
})
})
})
})
})
})
it('extends the id where param with the supplied where params', function() {
Task.belongsTo(User, {as: 'User'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'asd', enabled: false}).success(function(u) {
Task.create({title: 'a task'}).success(function(t) {
t.setUser(u).success(function() {
t.getUser({where: {enabled: true}}).success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
it("handles self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.belongsTo(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("sets the foreign key in self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.belongsTo(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
var User = null
, Task = null
, sequelize = null
, Helpers = null
var setup = function() {
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
Helpers = new (require("../config/helpers"))(sequelize)
Helpers.dropAllTables()
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('mono-directional', function() {
it("adds the foreign key", function() {
User.hasMany(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it('adds the foreign key with underscore', function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING }, { underscored: true })
Task.hasMany(User)
expect(User.attributes.task_id).toBeDefined()
})
it('uses the passed foreign key', function() {
User.hasMany(Task, { foreignKey: 'person_id' })
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it('defines getters and setters', function() {
User.hasMany(Task)
var u = User.build({username: 'asd'})
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
})
it("defines getters and setters according to the 'as' option", function() {
User.hasMany(Task, {as: 'Tasks'})
var u = User.build({username: 'asd'})
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
})
it("sets and gets associated objects", function() {
var user, task1, task2;
User.hasMany(Task, { as: 'Tasks' })
Helpers.async(function(done) {
User.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'task1'}).success(function(_task1) {
Task.create({title: 'task2'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
Helpers.async(function(done) {
user.setTasks([task1, task2]).success(function() {
user.getTasks().success(function(tasks) {
expect(tasks.length).toEqual(2)
user.getTasks({attributes: ['title']}).success(function(tasks) {
expect(tasks[0].selectedValues.title).toEqual('task1')
expect(tasks[0].selectedValues.id).toEqual(null)
done()
})
})
})
})
})
it("should allow selfAssociation to be single linked (only one DAO is created)", function() {
var oldLength = sequelize.daoFactoryManager.daos.length;
var Comment = sequelize.define('Comment', { content: Sequelize.STRING })
Comment.belongsTo(Comment, {as: "Parent"});
Comment.hasMany(Comment, {as: 'Children', foreignKey: "ParentId", useJunctionTable: false})
expect(sequelize.daoFactoryManager.daos.length).toEqual(oldLength + 1)
Helpers.async(function(done) {
Comment.sync({force: true}).success(function() {
done()
})
})
var parent
Helpers.async(function(done) {
Comment.create({ content: 'parentComment' }).success(function(p) {
parent = p
done()
})
})
Helpers.async(function(done) {
Comment.create({ content: 'child1' }).success(function(child1) {
child1.setParent(parent).success(function() {
done()
})
})
})
Helpers.async(function(done) {
Comment.create({ content: 'child2' }).success(function(child2) {
child2.setParent(parent).success(function() {
done()
})
})
})
Helpers.async(function(done) {
Comment.find({where: { content: 'parentComment' }}).success(function(parent) {
parent.getChildren().success(function(children) {
expect(children.length).toEqual(2)
done()
})
})
})
})
it("should still use many to many for selfAssociation by default (two DAOs are created)", function() {
Helpers.async(function(done) {
var oldLength = sequelize.daoFactoryManager.daos.length;
var Comment = sequelize.define('Comment', { content: Sequelize.STRING })
Comment.belongsTo(Comment, {as: "Parent"})
Comment.hasMany(Comment, {as: 'Children'})
expect(sequelize.daoFactoryManager.daos.length).toEqual(oldLength + 2)
done();
})
})
})
describe('bi-directional', function() {
it('adds the foreign key', function() {
Task.hasMany(User)
User.hasMany(Task)
expect(Task.attributes.UserId).toBeUndefined()
expect(User.attributes.UserId).toBeUndefined()
var daos = sequelize.daoFactoryManager.daos.filter(function(dao) {
return (dao.tableName == (Task.tableName + User.tableName))
})
daos.forEach(function(dao) {
expect(dao.attributes.UserId).toBeDefined()
expect(dao.attributes.TaskId).toBeDefined()
})
})
it("adds the foreign key with underscores", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, { underscored: true })
Task = sequelize.define('Task', { title: Sequelize.STRING })
Task.hasMany(User)
User.hasMany(Task)
expect(Task.attributes.user_id).toBeUndefined()
expect(User.attributes.user_id).toBeUndefined()
var daos = sequelize.daoFactoryManager.daos.filter(function(dao) {
return (dao.tableName == (Task.tableName + User.tableName))
})
daos.forEach(function(dao) {
expect(dao.attributes.user_id).toBeDefined()
expect(dao.attributes.TaskId).toBeDefined()
})
})
it("uses the passed foreign keys", function() {
User.hasMany(Task, { foreignKey: 'person_id' })
Task.hasMany(User, { foreignKey: 'work_item_id' })
var daos = sequelize.daoFactoryManager.daos.filter(function(dao) {
return (dao.tableName == (Task.tableName + User.tableName))
})
daos.forEach(function(dao) {
expect(dao.attributes.person_id).toBeDefined()
expect(dao.attributes.work_item_id).toBeDefined()
})
})
it("defines getters and setters", function() {
User.hasMany(Task)
Task.hasMany(User)
var u = User.build({ username: 'asd' })
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
var t = Task.build({ title: 'foobar' })
expect(t.setUsers).toBeDefined()
expect(t.getUsers).toBeDefined()
})
it("defines getters and setters according to the 'as' option", function() {
User.hasMany(Task, { as: 'Tasks' })
Task.hasMany(User, { as: 'Users' })
var u = User.build({ username: 'asd' })
expect(u.setTasks).toBeDefined()
expect(u.getTasks).toBeDefined()
var t = Task.build({ title: 'asd' })
expect(t.setUsers).toBeDefined()
expect(t.getUsers).toBeDefined()
})
it("sets and gets the corrected associated objects", function() {
var users = []
, tasks = []
User.hasMany(Task, {as: 'Tasks'})
Task.hasMany(User, {as: 'Users'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(done)
})
})
Helpers.async(function(done) {
User.create({username: 'name'}).success(function(user1) {
User.create({username: 'name2'}).success(function(user2) {
Task.create({title: 'task1'}).success(function(task1) {
Task.create({title: 'task2'}).success(function(task2) {
users.push(user1)
users.push(user2)
tasks.push(task1)
tasks.push(task2)
done()
})
})
})
})
})
Helpers.async(function(done) {
users[0].setTasks(tasks).success(function() {
users[0].getTasks().success(function(_tasks) {
expect(_tasks.length).toEqual(2)
tasks[1].setUsers(users).success(function() {
tasks[1].getUsers().success(function(_users) {
expect(users.length).toEqual(2)
done()
})
})
})
})
})
})
})
it("build the connector daos name", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).success(function() {
var daoNames = sequelize.daoFactoryManager.daos.map(function(dao) { return dao.tableName })
, expectation = ["Persons", "ChildrenPersons", "CoWorkersPersons", "FriendsPersons"]
expectation.forEach(function(ex) {
expect(daoNames.indexOf(ex) > -1).toBeTruthy()
})
done()
})
})
})
it("allows join table to be specified", function() {
Helpers.async(function(done) {
var Child = sequelize.define('Child', { name: Sequelize.STRING }, {underscore: true, freezeTableName: true})
var Parent = sequelize.define('Parent', { name: Sequelize.STRING }, {underscore: true, freezeTableName: true})
var ParentJoin = sequelize.define('ParentRelationship', { parent_id: Sequelize.INTEGER, child_id: Sequelize.INTEGER }, {underscore: true, freezeTableName: true})
Parent.hasMany(Child, {as: 'Children', foreignKey: 'child_id', joinTableName: 'ParentRelationship'})
Child.hasMany(Parent, {as: 'Parents', foreignKey: 'parent_id', joinTableName: 'ParentRelationship'})
var parents = []
ParentJoin.sync({force: true}).success(function() {
Parent.sync({force: true}).success(function() {
Child.sync({force: true}).success(function() {
Parent.create({name: 'mom'}).success(function(mom) {
parents.push(mom)
Parent.create({name: 'dad'}).success(function(dad) {
parents.push(dad)
Child.create({name: 'baby'}).success(function(baby) {
baby.setParents(parents).success(function(){
parents[0].getChildren().success(function(children){
expect(children).not.toBe(null)
expect(children.length).toBeDefined()
expect(children.length).toEqual(1)
expect(children[0]).toBeDefined()
expect(children[0].name).toEqual('baby')
done()
})
})
})
})
})
})
})
})
})
})
it("allows join table to be mapped and specified", function() {
var User = sequelize.define('User', { name: Sequelize.STRING }, {underscore: true, freezeTableName: true})
var Company = sequelize.define('Company', { name: Sequelize.STRING }, {underscore: true, freezeTableName: true})
var CompanyAccess = sequelize.define('CompanyAccess', { company_id: Sequelize.INTEGER, user_id: Sequelize.INTEGER, permission: Sequelize.STRING }, {underscore: true, freezeTableName: true})
CompanyAccess.belongsTo(User, {as: 'User', foreignKey: 'user_id'})
CompanyAccess.belongsTo(Company, {as: 'Company', foreignKey: 'company_id'})
User.hasMany(Company, {as: 'Companies', foreignKey: 'user_id', joinTableName: 'CompanyAccess'})
Company.hasMany(User, {as: 'Users', foreignKey: 'company_id', joinTableName: 'CompanyAccess'})
Helpers.async(function(done) {
var companies = []
CompanyAccess.sync({force: true}).success(function() {
User.sync({force: true}).success(function() {
Company.sync({force: true}).success(function() {
Company.create({name: 'IBM'}).success(function(ibm) {
companies.push(ibm)
Company.create({name: 'EA'}).success(function(ea) {
companies.push(ea)
User.create({name: 'joe@ibm.com'}).success(function(joe) {
joe.setCompanies(companies).success(function(){
User.find({where: {name: 'joe@ibm.com'}}).success(function(joe) {
expect(joe).not.toEqual(null)
joe.getCompanies().success(function(comps) {
expect(comps).not.toEqual(null)
expect(comps.length).toEqual(2)
done()
})
})
})
})
})
})
})
})
})
})
})
it("gets and sets the connector daos", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasMany(Person, {as: 'Children'})
Person.hasMany(Person, {as: 'Friends'})
Person.hasMany(Person, {as: 'CoWorkers'})
Person.sync({force: true}).success(function() {
Person.create({name: 'foobar'}).success(function(person) {
Person.create({name: 'friend'}).success(function(friend) {
person.setFriends([friend]).success(function() {
person.getFriends().success(function(friends) {
expect(friends.length).toEqual(1)
expect(friends[0].name).toEqual('friend')
done()
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasOne', function() {
var User = null
, Task = null
var setup = function() {
User = sequelize.define('User', { username: Sequelize.STRING })
Task = sequelize.define('Task', { title: Sequelize.STRING })
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
it("adds the foreign key", function() {
User.hasOne(Task)
expect(Task.attributes.UserId).toEqual("INTEGER")
})
it("adds an underscored foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task)
expect(Task.attributes.user_id).toEqual("INTEGER")
})
it("uses the passed foreign key", function() {
User = sequelize.define('User', { username: Sequelize.STRING }, {underscored: true})
Task = sequelize.define('Task', { title: Sequelize.STRING })
User.hasOne(Task, {foreignKey: 'person_id'})
expect(Task.attributes.person_id).toEqual("INTEGER")
})
it("defines the getter and the setter", function() {
User.hasOne(Task)
var u = User.build({username: 'asd'})
expect(u.setTask).toBeDefined()
expect(u.getTask).toBeDefined()
})
it("defined the getter and the setter according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'})
var u = User.build({username: 'asd'})
expect(u.setWork).toBeDefined()
expect(u.getWork).toBeDefined()
})
it("aliases associations to the same table according to the passed 'as' option", function() {
User.hasOne(Task, {as: 'Work'});
User.hasOne(Task, {as: 'Play'});
var u = User.build({username: 'asd'})
expect(u.getWork).toBeDefined()
expect(u.setWork).toBeDefined()
expect(u.getPlay).toBeDefined()
expect(u.setPlay).toBeDefined()
})
it("gets and sets the correct objects", function() {
var user, task;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task) {
user = _user
task = _task
done()
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task).on('success', function() {
user.getTask().on('success', function(task2) {
expect(task.title).toEqual(task2.title)
user.getTask({attributes: ['title']}).on('success', function(task2) {
expect(task2.selectedValues.title).toEqual('snafu')
expect(task2.selectedValues.id).toEqual(null)
done()
})
})
})
})
})
it("unsets unassociated objects", function() {
var user, task1, task2;
User.hasOne(Task, {as: 'Task'})
Helpers.async(function(done) {
User.sync({force: true}).success(function() {
Task.sync({force: true}).success(function() {
User.create({username: 'name'}).success(function(_user) {
Task.create({title: 'snafu'}).success(function(_task1) {
Task.create({title: 'another task'}).success(function(_task2) {
user = _user
task1 = _task1
task2 = _task2
done()
})
})
})
})
})
})
Helpers.async(function(done) {
user.setTask(task1).success(function() {
user.getTask().success(function(_task) {
expect(task1.title).toEqual(_task.title)
user.setTask(task2).success(function() {
user.getTask().success(function(_task2) {
expect(task2.title).toEqual(task2.title)
done()
})
})
})
})
})
})
it("sets self associations", function() {
Helpers.async(function(done) {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother', foreignKey: 'MotherId'})
Person.hasOne(Person, {as: 'Father', foreignKey: 'FatherId'})
Person.sync({force: true}).success(function() {
var p = Person.build()
expect(p.setFather).toBeDefined()
expect(p.setMother).toBeDefined()
done()
})
})
})
it("automatically sets the foreign key on self associations", function() {
var Person = sequelize.define('Person', { name: Sequelize.STRING })
Person.hasOne(Person, {as: 'Mother'})
expect(Person.associations.MotherPersons.options.foreignKey).toEqual('MotherId')
})
})
module.exports = {
rand: function() {
return parseInt(Math.random() * 999)
},
//make maxIdleTime small so that tests exit promptly
mysql: {
username: "root",
password: null,
database: 'sequelize_test',
host: '127.0.0.1',
port: 3306,
pool: { maxConnections: 5, maxIdleTime: 30}
},
sqlite: {
},
postgres: {
database: 'sequelize_test',
username: "postgres",
port: 5432,
pool: { maxConnections: 5, maxIdleTime: 30}
}
}
var Factories = module.exports = function(helpers) {
this.helpers = helpers
this.sequelize = this.helpers.sequelize
}
Factories.prototype.DAO = function(daoName, options, callback, count) {
count = count || 1
var self = this
, daos = []
this.helpers.async(function(done) {
var DAO = self.sequelize.daoFactoryManager.getDAO(daoName)
var create = function(cb) {
DAO.create(options).on('success', function(dao) {
daos.push(dao)
cb && cb()
}).on('error', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(daos)
}
}
create(cb)
})
}
Factories.prototype.User = function(options, callback, count) {
this.DAO('User', options, callback, count)
}
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize
this.Factories = new (require("./factories"))(this)
}
Helpers.prototype.sync = function() {
var self = this
this.async(function(done) {
self.sequelize
.sync({force: true})
.success(done)
.failure(function(err) { console.log(err) })
})
}
Helpers.prototype.drop = function() {
var self = this
this.async(function(done) {
self.sequelize
.drop()
.on('success', done)
.on('error', function(err) { console.log(err) })
})
}
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) {
var done = false
runs(function() {
fct(function() { return done = true })
})
waitsFor(function(){ return done })
}
var config = require("./config/config")
, Sequelize = require("../index")
, dialects = ['sqlite', 'mysql', 'postgres']
describe('DAOFactory', function() {
dialects.forEach(function(dialect) {
describe('with dialect "' + dialect + '"', function() {
var User = null
, sequelize = new Sequelize(
config[dialect].database,
config[dialect].username,
config[dialect].password,
{
logging: false,
dialect: dialect,
port: config[dialect].port
}
)
, Helpers = new (require("./config/helpers"))(sequelize)
var setup = function(options) {
User = sequelize.define('User', options || {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.dropAllTables()
Helpers.async(function(done) {
User.sync({force: true}).success(done).error(function(err) { console.log(err) })
})
}
var checkMatchForDialects = function(value, expectations) {
if(!!expectations[dialect])
expect(value).toMatch(expectations[dialect])
else
throw new Error('Undefined expectation for "' + dialect + '"!')
}
beforeEach(function() { setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('destroy', function() {
it('deletes a record from the database if dao is not paranoid', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
User.sync({force: true}).success(done)
})
Helpers.async(function(done) {
User.create({name: 'hallo', bio: 'welt'}).success(function(u) {
User.all().success(function(users) {
expect(users.length).toEqual(1)
u.destroy().success(function() {
User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
}).error(function(err) { console.log(err) })
}).error(function(err) { console.log(err) })
}).error(function(err) { console.log(err) })
})
})
})
it('allows sql logging of delete statements', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
User.sync({force: true}).success(done)
})
Helpers.async(function(done) {
User.create({name: 'hallo', bio: 'welt'}).success(function(u) {
User.all().success(function(users) {
expect(users.length).toEqual(1)
u.destroy().on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("DELETE")).toBeGreaterThan(-1)
done()
}).error(function(err) { console.log(err) })
}).error(function(err) { console.log(err) })
})
})
})
it('allows sql logging of update statements', function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
name: Sequelize.STRING, bio: Sequelize.TEXT
}, { paranoid:true })
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
User.create({ name: 'meg', bio: 'none' }).success(function(u) {
expect(u).toBeDefined()
expect(u).not.toBe(null)
u.destroy().on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("UPDATE")).toBeGreaterThan(-1)
done()
})
})
})
})
})
describe('findAll', function() {
var users = []
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, 2)
})
it("finds all entries", function() {
Helpers.async(function(done) {
User.findAll().on('success', function(_users) {
expect(_users.length).toEqual(2)
done()
})
})
})
it("finds all users matching the passed conditions", function() {
Helpers.async(function(done) {
User.findAll({where: "id != " + users[1].id}).success(function(_users) {
expect(_users.length).toEqual(1)
done()
})
})
})
it("can also handle array notation", function() {
Helpers.async(function(done){
User.findAll({where: ['id = ?', users[1].id]}).success(function(_users) {
expect(_users.length).toEqual(1)
expect(_users[0].id).toEqual(users[1].id)
done()
})
})
})
it("sorts the results via id", function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, 2)
Helpers.async(function(done) {
setTimeout(function() {
User.create({name: 'user', bio: 'foobar'}).success(function(user) {
users.push(user)
done()
})
}, 2000)
})
Helpers.async(function(done) {
User.findAll({ order: "id DESC" }).success(function(users) {
expect(users[0].id).toBeGreaterThan(users[2].id)
done()
})
})
})
it("sorts the results via createdAt", function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, 2)
Helpers.async(function(done) {
setTimeout(function() {
User.create({name: 'user', bio: 'foobar'}).success(function(user) {
users.push(user)
done()
})
}, 2000)
})
Helpers.async(function(done) {
User.findAll({ order: 'createdAt DESC' }).success(function(users) {
expect(users[0].id).toBeGreaterThan(users[2].id)
done()
})
})
})
it("handles offset and limit", function() {
setup()
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 10)
Helpers.async(function(done) {
User.findAll({ limit: 2, offset: 2 }).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].id).toEqual(3)
done()
})
})
})
})
describe('findAndCountAll', function() {
var users = [], fullcount = 3
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, function(_users) {
users = _users
}, fullcount)
})
it("handles where clause [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
it("handles where clause with ordering [only]", function() {
Helpers.async(function(done) {
User.findAndCountAll({where: "id != " + users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).toEqual(fullcount - 1)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
it("handles offset", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(fullcount - 1)
done()
})
})
})
it("handles limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
it("handles offset and limit", function() {
Helpers.async(function(done) {
User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).toEqual(fullcount)
expect(Array.isArray(info.rows)).toBeTruthy()
expect(info.rows.length).toEqual(1)
done()
})
})
})
})
describe('all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('error', function(err) { console.log(err) })
})
})
})
describe('count', function() {
it('counts all created objects', function() {
Helpers.async(function(done) {
User.create({name: 'user1'}).success(function() {
User.create({name: 'user2'}).success(done)
})
})
Helpers.async(function(done) {
User.count().success(function(count) {
expect(count).toEqual(2)
done()
})
})
})
it('allows sql logging', function() {
Helpers.async(function(done) {
User.count()
.on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("SELECT")).toBeGreaterThan(-1)
done()
})
})
})
it('filters object', function() {
Helpers.async(function(done) {
User.create({name: 'user1'}).success(function() {
User.create({name: 'foo'}).success(done)
})
})
Helpers.async(function(done) {
User.count({where: "name LIKE '%us%'"}).success(function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
describe('equals', function() {
it("correctly determines equality of objects", function() {
setup({ name: Sequelize.STRING, bio: Sequelize.TEXT })
Helpers.async(function(done) {
User.create({name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equals(u)).toBeTruthy()
done()
})
})
})
// sqlite can't handle multiple primary keys
if(dialect != 'sqlite') {
it("correctly determines equality with multiple primary keys", function() {
setup({
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equals(u)).toBeTruthy()
done()
}).error(function(err) { console.log(err) })
})
})
}
})
describe('equalsOneOf', function() {
// sqlite can't handle multiple primary keys
if(dialect != 'sqlite') {
beforeEach(function() {
setup({
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING, bio: Sequelize.TEXT
})
})
it('determines equality if one is matching', function() {
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([u, {a:1}])).toBeTruthy()
done()
})
})
})
it("doesn't determine equality if none is matching", function() {
Helpers.async(function(done) {
User.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([{b:2}, {a:1}])).toBeFalsy()
done()
})
})
})
}
})
describe('Mixin', function() {
var DAOFactory = require("../lib/dao-factory")
it("adds the mixed-in functions to the dao", function() {
expect(DAOFactory.prototype.hasOne).toBeDefined()
expect(DAOFactory.prototype.hasMany).toBeDefined()
expect(DAOFactory.prototype.belongsTo).toBeDefined()
})
})
describe('sync', function() {
it('works with correct database credentials', function() {
Helpers.async(function(done) {
User.sync().success(done)
})
})
it("fails with incorrect database credentials", function() {
Helpers.async(function(done) {
var sequelize2 = new Sequelize('foo', 'bar', null, { logging: false })
, User2 = sequelize2.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT })
User2.sync().error(function(err) {
expect(err.message).toMatch(/.*Access\ denied.*/)
done()
})
})
})
})
describe('drop should work', function() {
it('correctly succeeds', function() {
Helpers.async(function(done) {
User.drop().success(done)
})
})
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, dialects = ['sqlite', 'mysql', 'postgres']
describe('DAO', function() {
dialects.forEach(function(dialect) {
describe('with dialect "' + dialect + '"', function() {
var User = null
, sequelize = new Sequelize(
config[dialect].database,
config[dialect].username,
config[dialect].password,
{
logging: false,
dialect: dialect,
port: config[dialect].port,
host: config[dialect].host
}
)
, Helpers = new (require("./config/helpers"))(sequelize)
var setup = function() {
Helpers.async(function(done) {
User = sequelize.define('User', {
username: Sequelize.STRING,
birthDate: Sequelize.DATE
})
User.sync({ force: true }).success(done)
})
}
beforeEach(function() { Helpers.dropAllTables(); setup() })
afterEach(function() { Helpers.dropAllTables() })
describe('Escaping', function() {
it('is done properly for special characters', function() {
var User = sequelize.define('User', {
bio: Sequelize.TEXT
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
// Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a"
// But this causes sqlite to fail and exits the entire test suite immediately
var bio = dialect + "'\"\n"; // Need to add the dialect here so in case of failure I know what DB it failed for
User.create({ bio: bio }).success(function(u1) {
User.find(u1.id).success(function(u2) {
expect(u2.bio).toEqual(bio)
done()
})
})
})
})
})
describe('isNewRecord', function() {
it('returns true for non-saved objects', function() {
var user = User.build({ username: 'user' })
expect(user.id).toBeNull()
expect(user.isNewRecord).toBeTruthy()
})
it("returns false for saved objects", function() {
Helpers.async(function(done) {
User.build({ username: 'user' }).save().success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
it("returns false for created objects", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
it("returns false for objects found by find method", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
User.create({ username: 'user' }).success(function(user) {
User.find(user.id).success(function(user) {
expect(user.isNewRecord).toBeFalsy()
done()
})
})
})
})
})
it("returns false for objects found by findAll method", function() {
var chainer = new Sequelize.Utils.QueryChainer
for(var i = 0; i < 10; i++)
chainer.add(User.create({ username: 'user' }))
Helpers.async(function(done) {
chainer.run().success(function() {
User.findAll().success(function(users) {
users.forEach(function(u) {
expect(u.isNewRecord).toBeFalsy()
})
done()
})
})
})
})
})
describe('save', function() {
it('only updates fields in passed array', function() {
var user = null
, user2 = null
, userId = null
, date = new Date(1990, 01, 01)
Helpers.async(function(done) {
User.create({
username: 'foo',
birthDate: new Date()
}).success(function(_user) {
user = _user
done()
}).error(function(err) {
console.log(err)
})
})
Helpers.async(function(done) {
user.username = 'fizz'
user.birthDate = date
done()
})
Helpers.async(function(done) {
user.save(['username']).success(function(){
// re-select user
User.find(user.id).success(function(_user2) {
user2 = _user2
done()
})
})
})
Helpers.async(function(done) {
// name should have changed
expect(user2.username).toEqual('fizz')
// bio should be unchanged
expect(user2.birthDate).toNotEqual(date)
done()
})
})
it("stores an entry in the database", function() {
var username = 'user'
, user = User.build({
username: username,
birthDate: new Date(1984, 8, 23)
})
Helpers.async(function(done) {
User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
})
})
Helpers.async(function(done) {
user.save().success(done)
})
Helpers.async(function(done) {
User.all().success(function(users) {
expect(users.length).toEqual(1)
expect(users[0].username).toEqual(username)
expect(users[0].birthDate instanceof Date).toBe(true)
expect(users[0].birthDate).toEqual(new Date(1984, 8, 23))
done()
})
})
})
it("updates the timestamps", function() {
var now = Date.now()
, user = null
, updatedAt = null
Helpers.async(function(done) {
// timeout is needed, in order to check the update of the timestamp
setTimeout(function() {
user = User.build({ username: 'user' })
updatedAt = user.updatedAt
expect(updatedAt.getTime()).toBeGreaterThan(now)
done()
}, 1000)
})
Helpers.async(function(done) {
setTimeout(function() {
user.save().success(function() {
expect(updatedAt.getTime()).toBeLessThan(user.updatedAt.getTime())
done()
})
}, 1000)
})
})
describe('without timestamps option', function() {
var User2 = sequelize.define('User2', {
username: Sequelize.STRING,
updatedAt: Sequelize.DATE
}, {
timestamps: false
})
beforeEach(function() {
Helpers.async(function(done) {
User2.sync({ force: true }).success(done)
})
})
it("doesn't update the updatedAt column", function() {
Helpers.async(function(done) {
User2.create({ username: 'john doe' }).success(function(johnDoe) {
// sqlite and mysql return undefined, whereas postgres returns null
expect([undefined, null].indexOf(johnDoe.updatedAt)).not.toBe(-1);
done()
})
})
})
})
})
describe('updateAttributes', function() {
it("updates attributes in the database", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
expect(user.username).toEqual('user')
user.updateAttributes({ username: 'person' }).success(function(user) {
expect(user.username).toEqual('person')
done()
})
})
})
})
it("ignores unknown attributes", function() {
Helpers.async(function(done) {
User.create({ username: 'user' }).success(function(user) {
user.updateAttributes({ username: 'person', foo: 'bar'}).success(function(user) {
expect(user.username).toEqual('person')
expect(user.foo).toBeUndefined()
done()
})
})
})
})
it("doesn't update primary keys or timestamps", function() {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
var oldCreatedAt = user.createdAt
, oldIdentifier = user.identifier
user.updateAttributes({
name: 'foobar',
createdAt: new Date(2000, 1, 1),
identifier: 'another identifier'
}).success(function(user) {
expect(user.createdAt).toEqual(oldCreatedAt)
expect(user.identifier).toEqual(oldIdentifier)
done()
})
})
})
})
it("uses primary keys in where clause", function() {
var User = sequelize.define('User' + config.rand(), {
name: Sequelize.STRING, bio: Sequelize.TEXT, identifier: {type: Sequelize.STRING, primaryKey: true}
})
Helpers.async(function(done) {
User.sync({ force:true }).success(done)
})
Helpers.async(function(done) {
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
user
.updateAttributes({name: 'foobar'})
.on('sql', function(sql) {
expect(sql).toMatch(/WHERE [`"]identifier[`"]..identifier./)
done()
})
})
})
})
})
describe('values', function() {
it('returns all values', function() {
var User = sequelize.define('User', {
username: Sequelize.STRING
}, { timestamps: false, logging: false })
Helpers.async(function(done) {
User.sync({ force: true }).success(done)
})
Helpers.async(function(done) {
var user = User.build({ username: 'foo' })
expect(user.values).toEqual({ username: "foo", id: null })
done()
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('ConnectorManager', function() {
beforeEach(function() {
Helpers.dropAllTables()
})
afterEach(function() {
Helpers.dropAllTables()
})
it('works correctly after being idle', function() {
var User = sequelize.define('User', { username: Sequelize.STRING })
Helpers.async(function(done) {
User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
})
})
})
Helpers.async(function(done) {
setTimeout(function() {
User.count().on('success', function(count) {
expect(count).toEqual(1)
done()
})
}, 1000)
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port })
, Helpers = new (require("../config/helpers"))(sequelize)
describe('DAOFactory', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var User = sequelize.define('User', { age: Sequelize.INTEGER, name: Sequelize.STRING, bio: Sequelize.TEXT })
describe('constructor', function() {
it("handles extended attributes (unique)", function() {
var User = sequelize.define('User' + config.rand(), {
username: { type: Sequelize.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) UNIQUE",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (default)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) DEFAULT 'foo'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (null)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (comment)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, comment: 'This be\'s a comment'}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) COMMENT 'This be\\'s a comment'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
})
it("handles extended attributes (primaryKey)", function() {
var User = sequelize.define('User' + config.rand(), {
username: {type: Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).toEqual({username:"VARCHAR(255) PRIMARY KEY"})
})
it("adds timestamps", function() {
var User1 = sequelize.define('User' + config.rand(), {})
var User2 = sequelize.define('User' + config.rand(), {}, { timestamps: true })
expect(User1.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
expect(User2.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("adds deletedAt if paranoid", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
})
it("underscores timestamps if underscored", function() {
var User = sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
expect(User.attributes).toEqual({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function() {
var User = sequelize.define('User' + config.rand(), {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: Sequelize.STRING
})
expect(User.primaryKeys).toEqual({"foo":"VARCHAR(255) PRIMARY KEY"})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('HasMany', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
//prevent periods from occurring in the table name since they are used to delimit (table.column)
var User = sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, Task = sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Sequelize.STRING })
, users = null
, tasks = null
User.hasMany(Task, {as:'Tasks'})
Task.hasMany(User, {as:'Users'})
beforeEach(function() {
Helpers.async(function(_done) {
Helpers.Factories.DAO(User.name, {name: 'User' + Math.random()}, function(_users) {
users = _users; _done()
}, 5)
})
Helpers.async(function(_done) {
Helpers.Factories.DAO(Task.name, {name: 'Task' + Math.random()}, function(_tasks) {
tasks = _tasks; _done()
}, 2)
})
})
describe('addDAO / getDAO', function() {
var user = null
, task = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(_users) {
Task.all().on('success', function(_tasks) {
user = _users[0]
task = _tasks[0]
done()
})
})
})
})
it('should correctly add an association to the dao', function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0)
user.addTask(task).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1)
done()
})
})
})
})
})
})
describe('removeDAO', function() {
var user = null
, tasks = null
beforeEach(function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
Task.all().on('success', function(_tasks) {
user = users[0]
tasks = _tasks
done()
})
})
})
})
it("should correctly remove associated objects", function() {
Helpers.async(function(done) {
user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0)
user.setTasks(tasks).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length)
user.removeTask(tasks[0]).on('success', function() {
user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
logging: false,
port: config.postgres.port,
dialect: 'postgres'
})
, Helpers = new (require("../config/helpers"))(sequelize)
describe('Associations', function() {
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
/////////// many-to-many with same prefix ////////////
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
var Table2 = sequelize.define('wp_table2', {foo: Sequelize.STRING})
, Table1 = sequelize.define('wp_table1', {foo: Sequelize.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
it("should create a table wp_table1wp_table2s", function() {
Helpers.async(function(done) {
expect(sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined()
done()
})
})
})
describe('when join table name is specified', function() {
var Table2 = sequelize.define('ms_table1', {foo: Sequelize.STRING})
, Table1 = sequelize.define('ms_table2', {foo: Sequelize.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
it("should not use a combined name", function() {
expect(sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).toBeUndefined()
})
it("should use the specified name", function() {
expect(sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined()
})
})
})
})
var config = require("./config/config")
, Sequelize = require("../index")
, QueryInterface = require("../lib/query-interface")
describe('Sequelize', function() {
var sequelize = null
, Helpers = null
var setup = function(options) {
options = options || {}
if (!options.hasOwnProperty('pool'))
options.pool = config.mysql.pool
if (!options.hasOwnProperty('logging'))
options.logging = false
if (!options.hasOwnProperty('host'))
options.host = config.mysql.host
if (!options.hasOwnProperty('port'))
options.port = config.mysql.port
sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, options)
Helpers = new (require("./config/helpers"))(sequelize)
return options
}
beforeEach(function() { setup() })
afterEach(function() { sequelize = null })
describe('constructor', function() {
it('should pass the global options correctly', function() {
setup({ logging: false, define: { underscored:true } })
var DAO = sequelize.define('dao', {name: Sequelize.STRING})
expect(DAO.options.underscored).toBeTruthy()
})
it('should correctly set the host and the port', function() {
var options = setup({ host: '127.0.0.1', port: 1234 })
expect(sequelize.config.host).toEqual(options.host)
expect(sequelize.config.port).toEqual(options.port)
})
})
describe('define', function() {
it("adds a new dao to the dao manager", function() {
expect(sequelize.daoFactoryManager.all.length).toEqual(0)
sequelize.define('foo', { title: Sequelize.STRING })
expect(sequelize.daoFactoryManager.all.length).toEqual(1)
})
it("overwrites global options", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {collate: 'utf8_bin'})
expect(DAO.options.collate).toEqual('utf8_bin')
})
it("inherits global collate option", function() {
setup({ define: { collate: 'utf8_general_ci' } })
var DAO = sequelize.define('foo', {bar: Sequelize.STRING})
expect(DAO.options.collate).toEqual('utf8_general_ci')
})
it("inherits global classMethods and instanceMethods", function() {
setup({
define: {
classMethods : { globalClassMethod : function() {} },
instanceMethods : { globalInstanceMethod : function() {} }
}
})
var DAO = sequelize.define('foo', {bar: Sequelize.STRING}, {
classMethods : { localClassMethod : function() {} }
})
expect(typeof DAO.options.classMethods.globalClassMethod).toEqual('function')
expect(typeof DAO.options.classMethods.localClassMethod).toEqual('function')
expect(typeof DAO.options.instanceMethods.globalInstanceMethod).toEqual('function')
})
it("uses the passed tableName", function(done) {
var Photo = sequelize.define('Foto', { name: Sequelize.STRING }, { tableName: 'photos' })
Photo.sync({ force: true }).success(function() {
sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
expect(tableNames).toContain('photos')
done()
})
})
})
})
describe('sync', function() {
it("synchronizes all daos", function() {
var Project = sequelize.define('project' + config.rand(), { title: Sequelize.STRING })
var Task = sequelize.define('task' + config.rand(), { title: Sequelize.STRING })
Helpers.async(function(done) {
sequelize.sync().success(function() {
Project.create({title: 'bla'}).success(function() {
Task.create({title: 'bla'}).success(done)
})
})
})
})
})
describe('import', function() {
it("imports a dao definition from a file", function() {
var Project = sequelize.import(__dirname + "/assets/project")
expect(Project).toBeDefined()
})
})
})
var config = require("../config/config")
, Sequelize = require("../../index")
, dbFile = __dirname + '/test.sqlite'
, storages = [':memory:', dbFile]
describe('DAOFactory', function() {
storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() {
var User = null
, sequelize = null
, Helpers = null
beforeEach(function() {
sequelize = new Sequelize(config.database, config.username, config.password, {
logging: false,
dialect: 'sqlite',
storage: storage
})
Helpers = new (require("../config/helpers"))(sequelize)
User = sequelize.define('User', {
age: Sequelize.INTEGER,
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
Helpers.sync()
})
afterEach(function() {
Helpers.dropAllTables()
if(storage == dbFile) {
Helpers.async(function(done) {
require("fs").unlink(__dirname + '/test.sqlite', done)
})
}
})
describe('create', function() {
it('creates a table entry', function() {
Helpers.async(function(done) {
User
.create({ age: 21, name: 'John Wayne', bio: 'noot noot' })
.success(done)
.error(function(err) { console.log(err) })
})
Helpers.async(function(done) {
User.all().success(function(users) {
var usernames = users.map(function(user) {
return user.name
})
expect(usernames).toEqual(['John Wayne'])
done()
}).error(function(err){ console.log(err) })
})
})
it('should allow the creation of an object with options as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Helpers.Factories.DAO('Person', {
name: 'John Doe',
options: options
}, function(people) {
expect(people[0].options).toEqual(options)
done()
})
})
})
it('should allow the creation of an object with a boolean (true) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: true
}, function(people) {
expect(people[0].has_swag).toBeTruthy();
done()
})
})
})
it('should allow the creation of an object with a boolean (false) as attribute', function() {
var Person = sequelize.define('Person', {
name: Sequelize.STRING,
has_swag: Sequelize.BOOLEAN
})
Helpers.async(function(done) {
Person.sync({force: true}).success(done)
})
Helpers.async(function(done) {
Helpers.Factories.DAO('Person', {
name: 'John Doe',
has_swag: false
}, function(people) {
expect(people[0].has_swag).toBeFalsy();
done()
})
})
})
})
////////// find //////////////
describe('.find', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("finds normal lookups", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).toEqual('user')
done()
})
})
})
it("should make aliased attributes available", function() {
Helpers.async(function(done) {
User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user')
done()
})
})
})
})
////////// all //////////////
describe('.all', function() {
beforeEach(function() {
Helpers.Factories.User({name: 'user', bio: 'foobar'}, null, 2)
})
it("should return all users", function() {
Helpers.async(function(done) {
User.all().on('success', function(users) {
done()
expect(users.length).toEqual(2)
}).on('error', function(err) { console.log(err) })
})
})
})
////////// min //////////////
describe('.min', function() {
it("should return the min value", function() {
for(var i = 2; i < 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.min('age').on('success', function(min) {
expect(min).toEqual(2); done()
})
})
})
})
////////// max //////////////
describe('.max', function() {
it("should return the max value", function() {
for(var i = 2; i <= 5; i++) Helpers.Factories.User({ age: i })
Helpers.async(function(done) {
User.max('age').on('success', function(min) {
expect(min).toEqual(5); done()
})
})
})
})
})
})
})
// var config = require("./config/config")
// , Sequelize = require("../index")
// , User = null
// , sequelize = new Sequelize(config.database, config.username, config.password, {
// logging: false,
// dialect: dialect
// })
// , Helpers = new (require("./config/helpers"))(sequelize)
// describe('DAO', function() {
// var setup = function() {
// Helpers.async(function(done) {
// User = sequelize.define('User', { username: Sequelize.STRING })
// User.sync({ force: true }).success(done)
// })
// }
// beforeEach(function() { Helpers.dropAllTables(); setup() })
// afterEach(function() { Helpers.dropAllTables() })
// describe('findAll', function() {
// it("can handle dates correctly", function() {
// })
// })
// })
var Utils = require('../lib/utils')
describe('Utils', function() {
describe('removeCommentsFromFunctionString', function() {
it("removes line comments at the start of a line", function() {
var functionWithLineComments = function() {
// noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes lines comments in the middle of a line", function() {
var functionWithLineComments = function() {
alert(1) // noot noot
}
var result = Utils.removeCommentsFromFunctionString(functionWithLineComments.toString())
expect(result).toNotMatch(/.*noot.*/)
})
it("removes range comments", function() {
var s = function() {
alert(1) /*
noot noot
*/
alert(2) /*
foo
*/
}.toString()
var result = Utils.removeCommentsFromFunctionString(s)
expect(result).toNotMatch(/.*noot.*/)
expect(result).toNotMatch(/.*foo.*/)
expect(result).toMatch(/.*alert\(2\).*/)
})
})
describe('argsArePrimaryKeys', function() {
it("doesn't detect primary keys if primareyKeys and values have different lengths", function() {
expect(Utils.argsArePrimaryKeys([1,2,3], [1])).toBeFalsy()
})
it("doesn't detect primary keys if primary keys are hashes or arrays", function() {
expect(Utils.argsArePrimaryKeys([[]], [1])).toBeFalsy()
})
it('detects primary keys if length is correct and data types are matching', function() {
expect(Utils.argsArePrimaryKeys([1,2,3], ["INTEGER", "INTEGER", "INTEGER"])).toBeTruthy()
})
it("detects primary keys if primary keys are dates and lengths are matching", function() {
expect(Utils.argsArePrimaryKeys([new Date()], ['foo'])).toBeTruthy()
})
})
describe('underscore', function() {
describe('underscoredIf', function() {
it('is defined', function() {
expect(Utils._.underscoredIf).toBeDefined()
})
it('underscores if second param is true', function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
it("doesn't underscore if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', false)).toEqual('fooBar')
})
})
describe('camelizeIf', function() {
it('is defined', function() {
expect(Utils._.camelizeIf).toBeDefined()
})
it('camelizes if second param is true', function() {
expect(Utils._.camelizeIf('foo_bar', true)).toEqual('fooBar')
})
it("doesn't camelize if second param is false", function() {
expect(Utils._.underscoredIf('fooBar', true)).toEqual('foo_bar')
})
})
})
describe('isHash', function() {
it('doesn\'t match arrays', function() {
expect(Utils.isHash([])).toBeFalsy();
});
it('doesn\'t match null', function() {
expect(Utils.isHash(null)).toBeFalsy();
});
it('matches plain objects', function() {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
}
};
expect(Utils.isHash(values)).toBeTruthy();
});
it('matches plain objects with length property/key', function() {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
},
'length': 1
};
expect(Utils.isHash(values)).toBeTruthy();
});
});
describe('format', function() {
it('should format where clause correctly when the value is truthy', function() {
var where = ['foo = ?', 1];
expect(Utils.format(where)).toEqual('foo = 1');
});
it('should format where clause correctly when the value is falsy', function() {
var where = ['foo = ?', 0];
expect(Utils.format(where)).toEqual('foo = 0');
});
});
})
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Project' + parseInt(Math.random() * 9999999999999999), {
name: DataTypes.STRING
})
}
\ No newline at end of file
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("BelongsTo"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: done
})
})
describe('setAssociation', function() {
it('clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUserXYZ(user).success(function() {
task.getUserXYZ().success(function(user) {
expect(user).not.toEqual(null)
task.setUserXYZ(null).success(function() {
task.getUserXYZ().success(function(user) {
expect(user).toEqual(null)
done()
})
})
})
})
})
})
})
})
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
Task.belongsTo(User, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUser(user).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
Task.belongsTo(User, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
buster.testRunner.timeout = 500
describe(Helpers.getTestDialectTeaser("HasMany"), function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { self.sequelize = sequelize },
onComplete: done
})
})
describe('(1:N)', function() {
describe('hasSingle', function() {
before(function(done) {
this.Article = this.sequelize.define('Article', { 'title': Sequelize.STRING })
this.Label = this.sequelize.define('Label', { 'text': Sequelize.STRING })
this.Article.hasMany(this.Label)
this.sequelize.sync({ force: true }).success(done)
})
it('does not have any labels assigned to it initially', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
this.Label.create({ text: 'Epicness' })
])
chainer.run().success(function(results, article, label1, label2) {
var chainer = new Sequelize.Utils.QueryChainer([
article.hasLabel(label1),
article.hasLabel(label2)
])
chainer.run().success(function(_, hasLabel1, hasLabel2) {
expect(hasLabel1).toBeFalse()
expect(hasLabel2).toBeFalse()
done()
})
})
})
it('answers true if the label has been assigned', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
this.Label.create({ text: 'Epicness' })
])
chainer.run().success(function(results, article, label1, label2) {
var chainer = new Sequelize.Utils.QueryChainer([
[ article, 'addLabel', [ label1 ]],
[ article, 'hasLabel', [ label1 ]],
[ article, 'hasLabel', [ label2 ]]
])
chainer.runSerially().success(function(_, label1, hasLabel1, hasLabel2) {
expect(hasLabel1).toBeTrue()
expect(hasLabel2).toBeFalse()
done()
})
})
})
})
describe('hasAll', function() {
before(function(done) {
this.Article = this.sequelize.define('Article', { 'title': Sequelize.STRING })
this.Label = this.sequelize.define('Label', { 'text': Sequelize.STRING })
this.Article.hasMany(this.Label)
this.sequelize.sync({ force: true }).success(done)
})
it('answers false if only some labels have been assigned', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
this.Label.create({ text: 'Epicness' })
])
chainer.run().success(function(results, article, label1, label2) {
article.addLabel(label1).success(function() {
article.hasLabels([label1, label2]).success(function(result) {
expect(result).toBeFalse()
done()
})
})
})
})
it('answers true if all label have been assigned', function(done) {
var chainer = new Sequelize.Utils.QueryChainer([
this.Article.create({ title: 'Article' }),
this.Label.create({ text: 'Awesomeness' }),
this.Label.create({ text: 'Epicness' })
])
chainer.run().success(function(results, article, label1, label2) {
article.setLabels([label1, label2]).success(function() {
article.hasLabels([label1, label2]).success(function(result) {
expect(result).toBeTrue()
done()
})
})
})
})
})
describe('setAssociations', function() {
it("clears associations when passing null to the set-method", function(done) {
var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING })
Task.hasMany(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(1)
task.setUsers(null).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(0)
done()
})
})
})
})
})
})
})
})
})
it("clears associations when passing null to the set-method with omitNull set to true", function(done) {
this.sequelize.options.omitNull = true
var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING })
Task.hasMany(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(1)
task.setUsers(null).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(0)
done()
})
})
})
})
})
})
})
})
describe("getting assocations with options", function() {
before(function(done) {
var self = this
this.User = this.sequelize.define('User', { username: Sequelize.STRING })
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING, active: Sequelize.BOOLEAN })
this.User.hasMany(self.Task)
this.sequelize.sync({ force: true }).done(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
self.Task.create({ title: 'Die trying', active: false})
])
chainer.run().success(function (results, john, task1, task2) {
john.setTasks([task1, task2]).success(done)
})
})
})
it("gets all associated objects when no options are passed", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) {
expect(tasks.length).toEqual(2)
done()
})
})
})
it("only get objects that fulfill the options", function(done) {
this.User.find({ where: { username: 'John' } }).success(function (john) {
john.getTasks({ where: { active: true }, limit: 10, order: 'id DESC' }).success(function (tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
describe('optimizations using bulk create, destroy and update', function () {
before(function (done) {
this.User = this.sequelize.define('User', { username: Sequelize.STRING }, {timestamps: false})
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false})
this.User.hasMany(this.Task)
this.sequelize.sync({force: true}).success(done)
})
it('uses one UPDATE statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function(user) {
this.Task.create({ title: 'task1' }).success(function(task1) {
this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) { // We don't care about SELECt, only UPDAET
expect(sql).toMatch("UPDATE")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for UPDATE
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
it('uses one UPDATE statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function (user) {
this.Task.create({ title: 'task1' }).success(function (task1) {
this.Task.create({ title: 'task2' }).success(function (task2) {
user.setTasks([task1, task2]).success(function () {
user.setTasks(null).on('sql', spy).on('sql', _.after(2, function (sql) { // We don't care about SELECT, only UPDATE
expect(sql).toMatch("UPDATE")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for UPDATE
done()
})
})
}.bind(this))
}.bind(this))
}.bind(this))
})
}) // end optimization using bulk create, destroy and update
})
describe('(N:M)', function() {
describe("getting assocations with options", function() {
before(function(done) {
var self = this
this.User = this.sequelize.define('User', { username: Sequelize.STRING })
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING, active: Sequelize.BOOLEAN })
self.User.hasMany(self.Task)
self.Task.hasMany(self.User)
this.sequelize.sync({ force: true }).done(function() {
var chainer = new Sequelize.Utils.QueryChainer([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
self.Task.create({ title: 'Die trying', active: false})
])
chainer.run().success(function (results, john, task1, task2) {
john.setTasks([task1, task2]).success(done)
})
})
})
it("gets all associated objects when no options are passed", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks().success(function (tasks) {
expect(tasks.length).toEqual(2)
done()
})
})
})
it("only get objects that fulfill the options", function(done) {
this.User.find({where: {username: 'John'}}).success(function (john) {
john.getTasks({where: {active: true}}).success(function (tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
it("removes the reference id, which was added in the first place", function() {
var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING })
User.hasMany(Task)
expect(Task.attributes.UserId).toBeDefined()
Task.hasMany(User)
expect(Task.attributes.UserId).not.toBeDefined()
})
it("adds three items to the query chainer when calling sync", function() {
var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING })
User.hasMany(Task)
Task.hasMany(User)
var add = this.spy()
this.stub(Sequelize.Utils, 'QueryChainer').returns({ add: add, runSerially: function(){} })
this.sequelize.sync({ force: true })
expect(add).toHaveBeenCalledThrice()
})
describe('setAssociations', function() {
it("clears associations when passing null to the set-method", function(done) {
var User = this.sequelize.define('User', { username: Sequelize.STRING })
, Task = this.sequelize.define('Task', { title: Sequelize.STRING })
User.hasMany(Task)
Task.hasMany(User)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
task.setUsers([ user ]).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(1)
task.setUsers(null).success(function() {
task.getUsers().success(function(_users) {
expect(_users.length).toEqual(0)
done()
})
})
})
})
})
})
})
})
})
describe('optimizations using bulk create, destroy and update', function () {
before(function (done) {
this.User = this.sequelize.define('User', { username: Sequelize.STRING }, {timestamps: false})
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }, {timestamps: false})
this.User.hasMany(this.Task)
this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(done)
})
it('uses one insert into statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function(user) {
this.Task.create({ title: 'task1' }).success(function(task1) {
this.Task.create({ title: 'task2' }).success(function(task2) {
user.setTasks([task1, task2]).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("INSERT INTO")
expect(sql).toMatch("VALUES (1,1),(1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for INSERT into
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
it('uses one delete from statement', function (done) {
var spy = this.spy()
this.User.create({ username: 'foo' }).success(function (user) {
this.Task.create({ title: 'task1' }).success(function (task1) {
this.Task.create({ title: 'task2' }).success(function (task2) {
user.setTasks([task1, task2]).success(function () {
user.setTasks(null).on('sql', spy).on('sql', _.after(2, function (sql) {
expect(sql).toMatch("DELETE FROM")
expect(sql).toMatch("IN (1,2)")
})).success(function () {
expect(spy).toHaveBeenCalledTwice() // Once for SELECT, once for DELETE
done()
})
})
}.bind(this))
}.bind(this))
}.bind(this))
})
}) // end optimization using bulk create, destroy and update
describe('join table creation', function () {
before(function (done) {
this.User = this.sequelize.define('User',
{ username: Sequelize.STRING },
{ tableName: 'users'}
)
this.Task = this.sequelize.define('Task',
{ title: Sequelize.STRING },
{ tableName: 'tasks' }
)
this.User.hasMany(this.Task,
{ joinTableName: 'user_has_tasks' }
)
this.Task.hasMany(this.User)
this.sequelize.sync({force: true}).success(done)
})
it('uses the specified joinTableName or a reasonable default', function(done) {
for (var associationName in this.User.associations) {
expect(associationName).not.toEqual(this.User.tableName)
expect(associationName).not.toEqual(this.Task.tableName)
var joinTableName = this.User.associations[associationName].options.joinTableName
if (typeof joinTableName !== 'undefined') {
expect(joinTableName).toEqual(associationName)
}
var tableName = this.User.associations[associationName].options.tableName
if (typeof tableName !== 'undefined') {
expect(tableName).toEqual(associationName)
}
}
done()
})
})
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTasks([task]).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasMany(Task, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../../index")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1500
describe(Helpers.getTestDialectTeaser("HasOne"), function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { self.sequelize = sequelize },
onComplete: done
})
})
describe('setAssociation', function() {
it('//clears the association if null is passed', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTaskXYZ(task).success(function() {
user.getTaskXYZ().success(function(task) {
expect(task).not.toEqual(null)
user.setTaskXYZ(null).success(function() {
user.getTaskXYZ().success(function(task) {
expect(task).toEqual(null)
done()
})
})
})
})
})
})
})
})
})
describe("Foreign key constraints", function() {
it("are not enabled by default", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task)
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(0)
done()
})
})
})
})
})
})
})
it("can restrict deletes", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onDelete: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
user.destroy().error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
it("can cascade updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'cascade'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.success(function() {
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
expect(tasks[0].UserId).toEqual(999)
done()
})
})
})
})
})
})
})
it("can restrict updates", function(done) {
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasOne(Task, {onUpdate: 'restrict'})
this.sequelize.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(user) {
Task.create({ title: 'task' }).success(function(task) {
user.setTask(task).success(function() {
// Changing the id of a DAO requires a little dance since
// the `UPDATE` query generated by `save()` uses `id` in the
// `WHERE` clause
var tableName = user.QueryInterface.QueryGenerator.addSchema(user.__factory)
user.QueryInterface.update(user, tableName, {id: 999}, user.id)
.error(function() {
// Should fail due to FK restriction
Task.findAll().success(function(tasks) {
expect(tasks.length).toEqual(1)
done()
})
})
})
})
})
})
})
})
describe("Association options", function() {
it('can specify data type for autogenerated relational keys', function(done) {
var User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING })
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING]
, self = this
dataTypes.forEach(function(dataType) {
var tableName = 'TaskXYZ_' + dataType.toString()
, Task = self.sequelize.define(tableName, { title: Sequelize.STRING })
User.hasOne(Task, { foreignKey: 'userId', keyType: dataType })
self.sequelize.sync({ force: true }).success(function() {
expect(Task.rawAttributes.userId.type.toString())
.toEqual(dataType.toString())
dataTypes.splice(dataTypes.indexOf(dataType), 1)
if (!dataTypes.length) {
done()
}
})
})
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, Sequelize = require('../../index')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Mixin"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: done
})
})
describe('getAssociation', function() {
it('returns the respective part of the association for 1:1 associations', function() {
var User = this.sequelize.define('User', {})
var Task = this.sequelize.define('Task', {})
User.hasOne(Task)
Task.belongsTo(User)
expect(User.getAssociation(Task).target).toEqual(Task)
})
})
})
var config = module.exports
config["node tests"] = {
environment: "node",
rootPath: "../",
tests: [
"spec/**/*.spec.js"
]
}
var Factories = module.exports = function(helpers) {
this.helpers = helpers
this.sequelize = this.helpers.sequelize
}
Factories.prototype.DAO = function(daoName, options, callback, count) {
count = count || 1
var self = this
, daos = []
this.helpers.async(function(done) {
var DAO = self.sequelize.daoFactoryManager.getDAO(daoName)
var create = function(cb) {
DAO.create(options).on('success', function(dao) {
daos.push(dao)
cb && cb()
}).on('error', function(err) {
console.log(err)
done()
})
}
var cb = function() {
if(--count) {
create(cb)
} else {
done()
callback && callback(daos)
}
}
create(cb)
})
}
Factories.prototype.User = function(options, callback, count) {
this.DAO('User', options, callback, count)
}
Sequelize = require("../../index")
var Helpers = module.exports = function(sequelize) {
this.sequelize = sequelize
this.Factories = new (require("./factories"))(this)
}
Helpers.prototype.sync = function() {
var self = this
this.async(function(done) {
self.sequelize
.sync({force: true})
.success(done)
.failure(function(err) { console.log(err) })
})
}
Helpers.prototype.drop = function() {
var self = this
this.async(function(done) {
self.sequelize
.drop()
.on('success', done)
.on('error', function(err) { console.log(err) })
})
}
Helpers.prototype.dropAllTables = function() {
var self = this
this.async(function(done) {
self.sequelize
.getQueryInterface()
.dropAllTables()
.success(done)
.error(function(err) { console.log(err) })
})
}
Helpers.prototype.async = function(fct) {
var done = false
runs(function() {
fct(function() { return done = true })
})
waitsFor(function(){ return done })
}
if(typeof require === 'function') {
const buster = require("buster")
, semver = require("semver")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, config = require(__dirname + "/config/config")
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
var Sequelize = require(__dirname + '/../index')
, noDomains = semver.lt(process.version, '0.8.0')
describe(Helpers.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it('when we don\'t have the correct server details', function(done) {
if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
expect('').toEqual('') // Silence Buster!
return done()
}
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
, domain = require('domain')
, d = domain.create()
d.on('error', function(err){
var msg = 'Failed to find SQL server. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to find PostgresSQL server. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to find MySQL server. Please double check your settings.'
}
expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done()
})
d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello')
.success(function(){})
})
})
it('when we don\'t have the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native" && dialect !== "mysql") {
console.log('This dialect doesn\'t support me :(')
expect('').toEqual('') // Silence Buster
return done()
}
if (noDomains === true) {
console.log('WARNING: Configuration specs requires NodeJS version >= 0.8 for full compatibility')
expect('').toEqual('') // Silence Buster!
return done()
}
var sequelize = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
, domain = require('domain')
, d = domain.create()
d.on('error', function(err){
var msg = 'Failed to authenticate for SQL. Please double check your settings.'
if (dialect === "postgres" || dialect === "postgres-native") {
msg = 'Failed to authenticate for PostgresSQL. Please double check your settings.'
}
else if (dialect === "mysql") {
msg = 'Failed to authenticate for MySQL. Please double check your settings.'
}
expect(err.message).toEqual(msg)
d.remove(sequelize.query)
done()
})
d.run(function(){
d.add(sequelize.query)
sequelize.query('select 1 as hello')
.success(function(){})
})
})
it('when we don\'t have a valid dialect.', function() {
Helpers.assertException(function() {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {host: '0.0.0.1', port: config[dialect].port, dialect: undefined})
}.bind(this), 'The dialect undefined is not supported.')
})
})
describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
var config = sequelize.config
var options = sequelize.options
expect(options.dialect).toEqual('mysql')
expect(config.database).toEqual('dbname')
expect(config.host).toEqual('example.com')
expect(config.username).toEqual('user')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(9821)
})
it('should work with no authentication options', function() {
var sequelize = new Sequelize('mysql://example.com:9821/dbname')
var config = sequelize.config
expect(config.username).toEqual(undefined)
expect(config.password).toEqual(null)
})
it('should use the default port when no other is specified', function() {
var sequelize = new Sequelize('mysql://example.com/dbname')
var config = sequelize.config
// The default port should be set
expect(config.port).toEqual(3306)
})
})
describe('Intantiation with arguments', function() {
it('should accept two parameters (database, username)', function() {
var sequelize = new Sequelize('dbname', 'root')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
})
it('should accept three parameters (database, username, password)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass')
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
})
it('should accept four parameters (database, username, password, options)', function() {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 })
var config = sequelize.config
expect(config.database).toEqual('dbname')
expect(config.username).toEqual('root')
expect(config.password).toEqual('pass')
expect(config.port).toEqual(999)
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../index")
, Helpers = require('./buster-helpers')
, _ = require('lodash')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
this.DataTypes = DataTypes
this.sequelize = sequelize
this.User = sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE
})
}.bind(this),
onComplete: function() {
this.User.sync({ force: true }).success(done)
}.bind(this)
})
})
describe('constructor', function() {
it("uses the passed dao name as tablename if freezeTableName", function() {
var User = this.sequelize.define('FrozenUser', {}, { freezeTableName: true })
expect(User.tableName).toEqual('FrozenUser')
})
it("uses the pluralized dao name as tablename unless freezeTableName", function() {
var User = this.sequelize.define('SuperUser', {}, { freezeTableName: false })
expect(User.tableName).toEqual('SuperUsers')
})
it("uses checks to make sure dao factory isnt leaking on multiple define", function() {
var User = this.sequelize.define('SuperUser', {}, { freezeTableName: false })
var factorySize = this.sequelize.daoFactoryManager.all.length
var User2 = this.sequelize.define('SuperUser', {}, { freezeTableName: false })
var factorySize2 = this.sequelize.daoFactoryManager.all.length
expect(factorySize).toEqual(factorySize2)
})
it("attaches class and instance methods", function() {
var User = this.sequelize.define('UserWithClassAndInstanceMethods', {}, {
classMethods: { doSmth: function(){ return 1 } },
instanceMethods: { makeItSo: function(){ return 2}}
})
expect(User.doSmth).toBeDefined()
expect(User.doSmth()).toEqual(1)
expect(User.makeItSo).not.toBeDefined()
expect(User.build().doSmth).not.toBeDefined()
expect(User.build().makeItSo).toBeDefined()
expect(User.build().makeItSo()).toEqual(2)
})
it("throws an error if 2 autoIncrements are passed", function() {
Helpers.assertException(function() {
this.sequelize.define('UserWithTwoAutoIncrements', {
userid: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
userscore: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
})
}.bind(this), 'Invalid DAO definition. Only one autoincrement field allowed.')
})
it('throws an error if a custom model-wide validation is not a function', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
notFunction: 33
}
})
}.bind(this), 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction')
})
it('throws an error if a custom model-wide validation has the same name as a field', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
field: function() {}
}
})
}.bind(this), 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field')
})
})
describe('build', function() {
it("doesn't create database entries", function(done) {
this.User.build({ username: 'John Wayne' })
this.User.all().success(function(users) {
expect(users.length).toEqual(0)
done()
})
})
it("fills the objects with default values", function() {
var Task = this.sequelize.define('Task', {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
})
expect(Task.build().title).toEqual('a task!')
expect(Task.build().foo).toEqual(2)
expect(Task.build().bar).toEqual(undefined)
expect(Task.build().foobar).toEqual('asd')
expect(Task.build().flag).toEqual(false)
})
it("fills the objects with default values", function() {
var Task = this.sequelize.define('Task', {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
}, { timestamps: false })
expect(Task.build().title).toEqual('a task!')
expect(Task.build().foo).toEqual(2)
expect(Task.build().bar).toEqual(undefined)
expect(Task.build().foobar).toEqual('asd')
expect(Task.build().flag).toEqual(false)
})
it("stores the the passed values in a special variable", function() {
var user = this.User.build({ username: 'John Wayne' })
expect(user.selectedValues).toEqual({ username: 'John Wayne' })
})
it("attaches getter and setter methods from attribute definition", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters1', {
price: {
type: Sequelize.INTEGER,
get : function() {
return 'answer = ' + this.getDataValue('price');
},
set : function(v) {
return this.setDataValue('price', v + 42);
}
}
},{
});
expect(Product.build({price: 42}).price).toEqual('answer = 84');
var p = Product.build({price: 1});
expect(p.price).toEqual('answer = 43');
p.price = 0;
expect(p.price).toEqual('answer = 42'); // ah finally the right answer :-)
})
it("attaches getter and setter methods from options", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters2', {
priceInCents: {
type: Sequelize.INTEGER
}
},{
setterMethods: {
price: function(value) {
this.dataValues.priceInCents = value * 100;
}
},
getterMethods: {
price: function() {
return '$' + (this.getDataValue('priceInCents') / 100);
},
priceInCents: function() {
return this.dataValues.priceInCents;
}
}
});
expect(Product.build({price: 20}).priceInCents).toEqual(20 * 100);
expect(Product.build({priceInCents: 30 * 100}).price).toEqual('$' + 30);
})
it("attaches getter and setter methods from options only if not defined in attribute", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters3', {
price1: {
type: Sequelize.INTEGER,
set : function(v) { this.setDataValue('price1', v * 10); }
},
price2: {
type: Sequelize.INTEGER,
get : function(v) { return this.getDataValue('price2') * 10; }
}
},{
setterMethods: {
price1: function(v) { this.setDataValue('price1', v * 100); }
},
getterMethods: {
price2: function() { return '$' + this.getDataValue('price2'); }
}
});
var p = Product.build({ price1: 1, price2: 2 });
expect(p.price1).toEqual(10);
expect(p.price2).toEqual(20);
})
})
describe('findOrCreate', function () {
it("Returns instace if already existent. Single find field.", function (done) {
var self = this,
data = {
username: 'Username'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate({
username: user.username
}).success(function (_user, created) {
expect(_user.id).toEqual(user.id)
expect(_user.username).toEqual('Username')
expect(created).toBeFalse()
done()
})
})
})
it("Returns instace if already existent. Multiple find fields.", function (done) {
var self = this,
data = {
username: 'Username',
data: 'ThisIsData'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate(data).success(function (_user, created) {
expect(_user.id).toEqual(user.id)
expect(_user.username).toEqual('Username')
expect(_user.data).toEqual('ThisIsData')
expect(created).toBeFalse()
done()
})
})
})
it("creates new instance with default value.", function (done) {
var self = this,
data = {
username: 'Username'
},
default_values = {
data: 'ThisIsData'
};
this.User.findOrCreate(data, default_values).success(function (user, created) {
expect(user.username).toEqual('Username')
expect(user.data).toEqual('ThisIsData')
expect(created).toBeTrue()
done()
})
})
})
describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').toEqual('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).toBeGreaterThan(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).toBeGreaterThan(-1)
done()
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true }
})
User.sync({ force: true }).success(function() {
User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) {
expect(err).toBeDefined()
Helpers.checkMatchForDialects(dialect, err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: /.*Duplicate\ entry.*/,
postgres: /.*duplicate\ key\ value.*/
})
done()
})
})
})
})
it("raises an error if created object breaks definition contraints", function(done) {
var User = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false }
})
User.sync({ force: true }).success(function() {
User.create({ username: 'foo', smth: null }).error(function(err) {
expect(err).toBeDefined()
Helpers.checkMatchForDialects(dialect, err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: "Column 'smth' cannot be null",
postgres: /.*column "smth" violates not-null.*/
})
User.create({ username: 'foo', smth: 'foo' }).success(function() {
User.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).toBeDefined()
Helpers.checkMatchForDialects(dialect, err.message, {
sqlite: /.*SQLITE_CONSTRAINT.*/,
mysql: "Duplicate entry 'foo' for key 'username'",
postgres: /.*duplicate key value violates unique constraint.*/
})
done()
})
})
})
})
})
it('raises an error if you mess up the datatype', function() {
Helpers.assertException(function() {
this.sequelize.define('UserBadDataType', {
activity_date: Sequelize.DATe
})
}.bind(this), 'Unrecognized data type for field activity_date')
Helpers.assertException(function() {
this.sequelize.define('UserBadDataType', {
activity_date: {type: Sequelize.DATe}
})
}.bind(this), 'Unrecognized data type for field activity_date')
})
it('sets a 64 bit int in bigint', function(done) {
var User = this.sequelize.define('UserWithBigIntFields', {
big: Sequelize.BIGINT
})
User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).toBe( '9223372036854775807' )
done()
})
})
})
it('sets auto increment fields', function(done) {
var User = this.sequelize.define('UserWithAutoIncrementField', {
userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false }
})
User.sync({ force: true }).success(function() {
User.create({}).on('success', function(user) {
expect(user.userid).toEqual(1)
User.create({}).on('success', function(user) {
expect(user.userid).toEqual(2)
done()
})
})
})
})
it('allows the usage of options as attribute', function(done) {
var User = this.sequelize.define('UserWithNameAndOptions', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
User.sync({ force: true }).success(function() {
User
.create({ name: 'John Doe', options: options })
.success(function(user) {
expect(user.options).toEqual(options)
done()
})
})
})
it('allows sql logging', function(done) {
var User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', {
name: {type: Sequelize.STRING, unique: true},
smth: {type: Sequelize.STRING, allowNull: false}
})
User.sync({ force: true }).success(function() {
User
.create({ name: 'Fluffy Bunny', smth: 'else' })
.on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("INSERT")).toBeGreaterThan(-1)
done()
})
})
})
it('should only store the values passed in the whitelist', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data, ['username']).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).toEqual(data.username)
expect(_user.secretValue).not.toEqual(data.secretValue)
expect(_user.secretValue).toEqual(null)
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).toEqual(data.username)
expect(_user.secretValue).toEqual(data.secretValue)
done()
})
})
})
it('can omitt autoincremental columns', function(done) {
var self = this
, data = { title: 'Iliad' }
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT]
dataTypes.forEach(function(dataType, index) {
var Book = self.sequelize.define('Book'+index, {
id: { type: dataType, primaryKey: true, autoIncrement: true },
title: Sequelize.TEXT
})
Book.sync({ force: true }).success(function() {
Book
.create(data)
.success(function(book) {
expect(book.title).toEqual(data.title)
expect(book.author).toEqual(data.author)
expect(Book.rawAttributes.id.type.toString())
.toEqual(dataTypes[index].toString())
Book.drop()
if (index >= dataTypes.length - 1) {
done()
}
})
})
})
})
it('saves data with single quote', function(done) {
var quote = "single'quote"
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).toEqual(quote, 'memory single quote')
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).toEqual(quote, 'SQL single quote')
done()
})
})
})
it('saves data with double quote', function(done) {
var quote = 'double"quote'
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).toEqual(quote, 'memory double quote')
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).toEqual(quote, 'SQL double quote')
done()
})
})
})
it('saves stringified JSON data', function(done) {
var json = JSON.stringify({ key: 'value' })
, self = this
this.User.create({ data: json }).success(function(user) {
expect(user.data).toEqual(json, 'memory data')
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).toEqual(json, 'SQL data')
done()
})
})
})
it('stores the current date in createdAt', function(done) {
this.User.create({ username: 'foo' }).success(function(user) {
expect(parseInt(+user.createdAt/5000)).toEqual(parseInt(+new Date()/5000))
done()
})
})
it('allows setting custom IDs', function (done) {
this.User.create({ id: 42 }).success(function (user) {
expect(user.id).toEqual(42)
this.User.find(42).success(function (user) {
expect(user).toBeDefined()
done()
})
}.bind(this))
})
describe('enums', function() {
before(function(done) {
this.Item = this.sequelize.define('Item', {
state: { type: Helpers.Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }
})
this.sequelize.sync({ force: true }).success(function() {
this.Item.create({ state: 'available' }).success(function(item) {
this.item = item
done()
}.bind(this))
}.bind(this))
})
it('correctly restores enum values', function(done) {
this.Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.id).toEqual(this.item.id)
done()
}.bind(this))
})
})
})
describe('bulkCreate', function() {
it('inserts multiple values respecting the white list', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data, ['username']).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(users[0].secretValue).toBeNull();
expect(users[1].username).toEqual("Paul")
expect(users[1].secretValue).toBeNull();
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(users[0].secretValue).toEqual('42')
expect(users[1].username).toEqual("Paul")
expect(users[1].secretValue).toEqual('23')
done()
})
})
})
it('saves data with single quote', function(done) {
var self = this
, quote = "Single'Quote"
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(users[0].data).toEqual(quote)
expect(users[1].username).toEqual("Paul")
expect(users[1].data).toEqual(quote)
done()
})
})
})
it('saves data with double quote', function(done) {
var self = this
, quote = 'Double"Quote'
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(users[0].data).toEqual(quote)
expect(users[1].username).toEqual("Paul")
expect(users[1].data).toEqual(quote)
done()
})
})
})
it('saves stringified JSON data', function(done) {
var self = this
, json = JSON.stringify({ key: 'value' })
, data = [{ username: 'Peter', data: json},
{ username: 'Paul', data: json}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(users[0].data).toEqual(json)
expect(users[1].username).toEqual("Paul")
expect(users[1].data).toEqual(json)
done()
})
})
})
it('stores the current date in createdAt', function(done) {
var self = this
, data = [{ username: 'Peter'},
{ username: 'Paul'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(2)
expect(users[0].username).toEqual("Peter")
expect(parseInt(+users[0].createdAt/5000)).toEqual(parseInt(+new Date()/5000))
expect(users[1].username).toEqual("Paul")
expect(parseInt(+users[1].createdAt/5000)).toEqual(parseInt(+new Date()/5000))
done()
})
})
})
describe('enums', function() {
before(function(done) {
this.Item = this.sequelize.define('Item', {
state: { type: Helpers.Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] },
name: Sequelize.STRING
})
this.sequelize.sync({ force: true }).success(function() {
this.Item.bulkCreate([{state: 'in_cart', name: 'A'}, { state: 'available', name: 'B'}]).success(function() {
done()
}.bind(this))
}.bind(this))
})
it('correctly restores enum values', function(done) {
this.Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.name).toEqual('B')
done()
}.bind(this))
})
})
}) // - bulkCreate
describe('update', function() {
it('updates only values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.update({username: 'Bill'}, {secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(3)
users.forEach(function (user) {
if (user.secretValue == '42') {
expect(user.username).toEqual("Bill")
} else {
expect(user.username).toEqual("Bob")
}
})
done()
})
})
})
})
it('sets updatedAt to the current timestamp', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.update({username: 'Bill'}, {secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(3)
expect(users[0].username).toEqual("Bill")
expect(users[1].username).toEqual("Bill")
expect(users[2].username).toEqual("Bob")
expect(parseInt(+users[0].updatedAt/5000)).toEqual(parseInt(+new Date()/5000))
expect(parseInt(+users[1].updatedAt/5000)).toEqual(parseInt(+new Date()/5000))
done()
})
})
})
})
}) // - update
describe('destroy', function() {
it('deletes values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.destroy({secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(1)
expect(users[0].username).toEqual("Bob")
done()
})
})
})
})
it('sets deletedAt to the current timestamp if paranoid is true', function(done) {
var self = this
, User = this.sequelize.define('ParanoidUser', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING,
data: Sequelize.STRING,
intVal: { type: Sequelize.INTEGER, defaultValue: 1}
}, {
paranoid: true
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
User.sync({ force: true }).success(function() {
User.bulkCreate(data).success(function() {
User.destroy({secretValue: '42'})
.success(function() {
User.findAll({order: 'id'}).success(function(users) {
expect(users.length).toEqual(3)
expect(users[0].username).toEqual("Peter")
expect(users[1].username).toEqual("Paul")
expect(users[2].username).toEqual("Bob")
expect(parseInt(+users[0].deletedAt/5000)).toEqual(parseInt(+new Date()/5000))
expect(parseInt(+users[1].deletedAt/5000)).toEqual(parseInt(+new Date()/5000))
done()
})
})
})
})
})
}) // - destroy
describe('special where conditions', function() {
before(function(done) {
var self = this
this.User.create({
username: 'boo',
intVal: 5,
theDate: '2013-01-01 12:00'
}).success(function(user){
self.user = user
self.User.create({
username: 'boo2',
intVal: 10,
theDate: '2013-01-10 12:00'
}).success(function(user2){
self.user2 = user2
done()
})
})
})
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo2')
expect(users[0].intVal).toEqual(10)
done()
})
})
it('should be able to find a row between a certain date and an additional where clause', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
},
intVal: 10
}
}).success(function(users) {
expect(users[0].username).toEqual('boo2')
expect(users[0].intVal).toEqual(10)
done()
})
})
it('should be able to find a row not between a certain integer', function(done) {
this.User.findAll({
where: {
intVal: {
nbetween: [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo')
expect(users[0].intVal).toEqual(5)
done()
})
})
it('should be able to find a row using not between and between logic', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2012-12-10', '2013-01-02'],
nbetween: ['2013-01-04', '2013-01-20']
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo')
expect(users[0].intVal).toEqual(5)
done()
})
})
it('should be able to find a row using not between and between logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
between: [new Date('2012-12-10'), new Date('2013-01-02')],
nbetween: [new Date('2013-01-04'), new Date('2013-01-20')]
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo')
expect(users[0].intVal).toEqual(5)
done()
})
})
it('should be able to find a row using greater than or equal to logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
gte: new Date('2013-01-09')
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo2')
expect(users[0].intVal).toEqual(10)
done()
})
})
it('should be able to find a row using greater than or equal to', function(done) {
this.User.find({
where: {
intVal: {
gte: 6
}
}
}).success(function(user) {
expect(user.username).toEqual('boo2')
expect(user.intVal).toEqual(10)
done()
})
})
it('should be able to find a row using greater than', function(done) {
this.User.find({
where: {
intVal: {
gt: 5
}
}
}).success(function(user) {
expect(user.username).toEqual('boo2')
expect(user.intVal).toEqual(10)
done()
})
})
it('should be able to find a row using lesser than or equal to', function(done) {
this.User.find({
where: {
intVal: {
lte: 5
}
}
}).success(function(user) {
expect(user.username).toEqual('boo')
expect(user.intVal).toEqual(5)
done()
})
})
it('should be able to find a row using lesser than', function(done) {
this.User.find({
where: {
intVal: {
lt: 6
}
}
}).success(function(user) {
expect(user.username).toEqual('boo')
expect(user.intVal).toEqual(5)
done()
})
})
it('should have no problem finding a row using lesser and greater than', function(done) {
this.User.findAll({
where: {
intVal: {
lt: 6,
gt: 4
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo')
expect(users[0].intVal).toEqual(5)
done()
})
})
it('should be able to find a row using not equal to logic', function(done) {
this.User.find({
where: {
intVal: {
ne: 10
}
}
}).success(function(user) {
expect(user.username).toEqual('boo')
expect(user.intVal).toEqual(5)
done()
})
})
it('should be able to find multiple users with any of the special where logic properties', function(done) {
this.User.findAll({
where: {
intVal: {
lte: 10
}
}
}).success(function(users) {
expect(users[0].username).toEqual('boo')
expect(users[0].intVal).toEqual(5)
expect(users[1].username).toEqual('boo2')
expect(users[1].intVal).toEqual(10)
done()
})
})
})
describe('find', function find() {
before(function(done) {
this.User.create({
username: 'barfooz'
}).success(function(user) {
this.UserPrimary = this.sequelize.define('UserPrimary', {
specialKey: {
type: this.DataTypes.STRING,
primaryKey: true
}
})
this.UserPrimary.sync({force: true}).success(function(primary){
this.UserPrimary.create({specialKey: 'a string'}).success(function(){
this.user = user
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) {
this.UserPrimary.find('a string').success(function(user) {
expect(user.specialKey).toEqual('a string')
done()
})
})
it('doesn\'t throw an error when entering in a non integer value', function(done) {
this.User.find('a string value').success(function(user) {
expect(user).toBeNull()
done()
})
})
it('returns a single dao', function(done) {
this.User.find(this.user.id).success(function(user) {
expect(Array.isArray(user)).toBeFalsy()
expect(user.id).toEqual(this.user.id)
expect(user.id).toEqual(1)
done()
}.bind(this))
})
it('returns a single dao given a string id', function(done) {
this.User.find(this.user.id + '').success(function(user) {
expect(Array.isArray(user)).toBeFalsy()
expect(user.id).toEqual(this.user.id)
expect(user.id).toEqual(1)
done()
}.bind(this))
})
it("should make aliased attributes available", function(done) {
this.User.find({
where: { id: 1 },
attributes: ['id', ['username', 'name']]
}).success(function(user) {
expect(user.name).toEqual('barfooz')
done()
})
})
it("should not try to convert boolean values if they are not selected", function (done) {
var UserWithBoolean = this.sequelize.define('user', {
active: Sequelize.BOOLEAN
})
this.sequelize.sync({force: true}).success(function () {
UserWithBoolean.create({ active: true }).success(function (user) {
UserWithBoolean.find({ where: { id: user.id }, attributes: [ 'id' ] }).success(function (user) {
expect(user.active).not.toBeDefined()
done()
})
})
})
})
it('finds a specific user via where option', function(done) {
this.User.find({ where: { username: 'barfooz' } }).success(function(user) {
expect(user.username).toEqual('barfooz')
done()
})
})
it("doesn't find a user if conditions are not matching", function(done) {
this.User.find({ where: { username: 'foo' } }).success(function(user) {
expect(user).toBeNull()
done()
})
})
it('allows sql logging', function(done) {
this.User.find({ where: { username: 'foo' } })
.on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("SELECT")).toBeGreaterThan(-1)
done()
})
})
it('ignores passed limit option', function(done) {
this.User.find({ limit: 10 }).success(function(user) {
// it returns an object instead of an array
expect(Array.isArray(user)).toBeFalsy()
expect(user.hasOwnProperty('username')).toBeTruthy()
done()
})
})
it('finds entries via primary keys', function(done) {
var User = this.sequelize.define('UserWithPrimaryKey', {
identifier: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
User.sync({ force: true }).success(function() {
User.create({
identifier: 'an identifier',
name: 'John'
}).success(function(u) {
expect(u.id).not.toBeDefined()
User.find('an identifier').success(function(u2) {
expect(u2.identifier).toEqual('an identifier')
expect(u2.name).toEqual('John')
done()
})
})
})
})
it('returns the selected fields as instance.selectedValues', function(done) {
this.User.create({
username: 'JohnXOXOXO'
}).success(function() {
this.User.find({
where: { username: 'JohnXOXOXO' },
attributes: ['username']
}).success(function(user) {
expect(user.selectedValues).toEqual({ username: 'JohnXOXOXO' })
done()
})
}.bind(this))
})
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) {
this.Mission = this.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
})
this.Mission.belongsTo(this.User)
this.User.hasMany(this.Mission)
this.sequelize.sync({ force: true }).complete(function() {
this.Mission.create()
.success(function(mission) {
this.User.create({
username: 'John DOE'
}).success(function(user) {
mission.setUser(user)
.success(function() {
this.User.find({
where: { username: 'John DOE' },
attributes: ['username'],
include: [this.Mission]
}).success(function(user) {
expect(user.selectedValues).toEqual({ username: 'John DOE' })
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('always honors ZERO as primary key', function(_done) {
var permutations = [
0,
'0',
{where: {id: 0}},
{where: {id: '0'}}
]
, done = _.after(2 * permutations.length, _done);
this.User.create({username: 'jack'}).success(function (jack) {
this.User.create({username: 'jill'}).success(function (jill) {
permutations.forEach(function(perm) {
this.User.find(perm).done(function(err, user) {
expect(err).toBeNull();
expect(user).toBeNull();
done();
}).on('sql', function(s) {
expect(s.indexOf(0)).not.toEqual(-1);
done();
})
}.bind(this))
}.bind(this))
}.bind(this))
})
describe('eager loading', function() {
before(function() {
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING })
this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING })
this.init = function(callback) {
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
callback()
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this)
})
describe('belongsTo', function() {
before(function(done) {
this.Task.belongsTo(this.Worker)
this.init(function() {
this.task.setWorker(this.worker).success(done)
}.bind(this))
})
it('throws an error about unexpected input if include contains a non-object', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ 1 ] })
}.bind(this), 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
})
it('throws an error about missing attributes if include contains an object with daoFactory', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ { daoFactory: this.Worker } ] })
}.bind(this), 'Include malformed. Expected attributes: daoFactory, as!')
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('returns the associated worker via task.worker', function(done) {
this.Task.find({
where: { title: 'homework' },
include: [ this.Worker ]
}).complete(function(err, task) {
expect(err).toBeNull()
expect(task).toBeDefined()
expect(task.worker).toBeDefined()
expect(task.worker.name).toEqual('worker')
done()
}.bind(this))
})
it('returns the private and public ip', function(done) {
var Domain = this.sequelize.define('Domain', { ip: Sequelize.STRING })
var Environment = this.sequelize.define('Environment', { name: Sequelize.STRING })
Environment
.belongsTo(Domain, { as: 'PrivateDomain', foreignKey: 'privateDomainId' })
.belongsTo(Domain, { as: 'PublicDomain', foreignKey: 'publicDomainId' })
this.sequelize.sync({ force: true }).complete(function() {
Domain.create({ ip: '192.168.0.1' }).success(function(privateIp) {
Domain.create({ ip: '91.65.189.19' }).success(function(publicIp) {
Environment.create({ name: 'environment' }).success(function(env) {
env.setPrivateDomain(privateIp).success(function() {
env.setPublicDomain(publicIp).success(function() {
Environment.find({
where: { name: 'environment' },
include: [
{ daoFactory: Domain, as: 'PrivateDomain' },
{ daoFactory: Domain, as: 'PublicDomain' }
]
}).complete(function(err, environment) {
expect(err).toBeNull()
expect(environment).toBeDefined()
expect(environment.privateDomain).toBeDefined()
expect(environment.privateDomain.ip).toEqual('192.168.0.1')
expect(environment.publicDomain).toBeDefined()
expect(environment.publicDomain.ip).toEqual('91.65.189.19')
done()
})
})
})
})
})
})
})
})
})
describe('hasOne', function() {
before(function(done) {
this.Worker.hasOne(this.Task)
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setTask(this.task).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Task.find({ include: [ this.Worker ] })
}.bind(this), 'Worker is not associated to Task!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).toBeNull()
expect(worker).toBeDefined()
expect(worker.task).toBeDefined()
expect(worker.task.title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasOne with alias', function() {
before(function(done) {
this.Worker.hasOne(this.Task, { as: 'ToDo' })
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setToDo(this.task).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not referenced by alias', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('throws an error if alias is not associated', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ { daoFactory: this.Task, as: 'Work' } ] })
}.bind(this), 'Task (Work) is not associated to Worker!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(err).toBeNull()
expect(worker).toBeDefined()
expect(worker.toDo).toBeDefined()
expect(worker.toDo.title).toEqual('homework')
done()
}.bind(this))
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(worker.toDo.title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasMany', function() {
before(function(done) {
this.Worker.hasMany(this.Task)
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setTasks([ this.task ]).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Task.find({ include: [ this.Worker ] })
}.bind(this), 'Worker is not associated to Task!')
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).toBeNull()
expect(worker).toBeDefined()
expect(worker.tasks).toBeDefined()
expect(worker.tasks[0].title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasMany with alias', function() {
before(function(done) {
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setToDos([ this.task ]).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not referenced by alias', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('throws an error if alias is not associated', function() {
Helpers.assertException(function() {
this.Worker.find({ include: [ { daoFactory: this.Task, as: 'Work' } ] })
}.bind(this), 'Task (Work) is not associated to Worker!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(err).toBeNull()
expect(worker).toBeDefined()
expect(worker.toDos).toBeDefined()
expect(worker.toDos[0].title).toEqual('homework')
done()
}.bind(this))
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(worker.toDos[0].title).toEqual('homework')
done()
}.bind(this))
})
})
})
describe('queryOptions', function() {
before(function(done) {
this.User.create({
username: 'barfooz'
}).success(function(user) {
this.user = user
done()
}.bind(this))
})
it("should return a DAO when queryOptions are not set", function (done) {
this.User.find({ where: { username: 'barfooz'}}).done(function (err, user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
done();
}.bind(this))
})
it("should return a DAO when raw is false", function (done) {
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
done();
}.bind(this))
})
it("should return raw data when raw is true", function (done) {
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, user) {
expect(user).not.toHavePrototype(this.User.DAO.prototype)
expect(user).toBeObject()
done();
}.bind(this))
})
}) // - describe: queryOptions
}) //- describe: find
describe('findAll', function findAll() {
describe('eager loading', function() {
before(function() {
this.Task = this.sequelize.define('Task', { title: Sequelize.STRING })
this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING })
})
describe('belongsTo', function() {
before(function(done) {
this.Task.belongsTo(this.Worker)
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.task.setWorker(this.worker).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error about unexpected input if include contains a non-object', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ 1 ] })
}.bind(this), 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
})
it('throws an error about missing attributes if include contains an object with daoFactory', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ { daoFactory: this.Worker } ] })
}.bind(this), 'Include malformed. Expected attributes: daoFactory, as!')
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('returns the associated worker via task.worker', function(done) {
this.Task.findAll({
where: { title: 'homework' },
include: [ this.Worker ]
}).complete(function(err, tasks) {
expect(err).toBeNull()
expect(tasks).toBeDefined()
expect(tasks[0].worker).toBeDefined()
expect(tasks[0].worker.name).toEqual('worker')
done()
}.bind(this))
})
})
describe('hasOne', function() {
before(function(done) {
this.Worker.hasOne(this.Task)
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setTask(this.task).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Task.findAll({ include: [ this.Worker ] })
}.bind(this), 'Worker is not associated to Task!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).toBeNull()
expect(workers).toBeDefined()
expect(workers[0].task).toBeDefined()
expect(workers[0].task.title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasOne with alias', function() {
before(function(done) {
this.Worker.hasOne(this.Task, { as: 'ToDo' })
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setToDo(this.task).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not referenced by alias', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('throws an error if alias is not associated', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ { daoFactory: this.Task, as: 'Work' } ] })
}.bind(this), 'Task (Work) is not associated to Worker!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(err).toBeNull()
expect(workers).toBeDefined()
expect(workers[0].toDo).toBeDefined()
expect(workers[0].toDo.title).toEqual('homework')
done()
}.bind(this))
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(workers[0].toDo.title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasMany', function() {
before(function(done) {
this.Worker.hasMany(this.Task)
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setTasks([ this.task ]).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not associated', function() {
Helpers.assertException(function() {
this.Task.findAll({ include: [ this.Worker ] })
}.bind(this), 'Worker is not associated to Task!')
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).toBeNull()
expect(workers).toBeDefined()
expect(workers[0].tasks).toBeDefined()
expect(workers[0].tasks[0].title).toEqual('homework')
done()
}.bind(this))
})
})
describe('hasMany with alias', function() {
before(function(done) {
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.sequelize.sync({ force: true }).complete(function() {
this.Worker.create({ name: 'worker' }).success(function(worker) {
this.Task.create({ title: 'homework' }).success(function(task) {
this.worker = worker
this.task = task
this.worker.setToDos([ this.task ]).success(done)
}.bind(this))
}.bind(this))
}.bind(this))
})
it('throws an error if included DaoFactory is not referenced by alias', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ this.Task ] })
}.bind(this), 'Task is not associated to Worker!')
})
it('throws an error if alias is not associated', function() {
Helpers.assertException(function() {
this.Worker.findAll({ include: [ { daoFactory: this.Task, as: 'Work' } ] })
}.bind(this), 'Task (Work) is not associated to Worker!')
})
it('returns the associated task via worker.task', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(err).toBeNull()
expect(workers).toBeDefined()
expect(workers[0].toDos).toBeDefined()
expect(workers[0].toDos[0].title).toEqual('homework')
done()
}.bind(this))
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(workers[0].toDos[0].title).toEqual('homework')
done()
}.bind(this))
})
})
describe('queryOptions', function() {
before(function(done) {
this.User.create({
username: 'barfooz'
}).success(function(user) {
this.user = user
done()
}.bind(this))
})
it("should return a DAO when queryOptions are not set", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}).done(function (err, users) {
users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
}, this)
done();
}.bind(this))
})
it("should return a DAO when raw is false", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, users) {
users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype)
}, this)
done();
}.bind(this))
})
it("should return raw data when raw is true", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, users) {
users.forEach(function (user) {
expect(user).not.toHavePrototype(this.User.DAO.prototype)
expect(users[0]).toBeObject()
}, this)
done();
}.bind(this))
})
}) // - describe: queryOptions
})
}) //- describe: findAll
describe('min', function() {
before(function(done) {
this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER
})
this.UserWithDec = this.sequelize.define('UserWithDec', {
value: Sequelize.DECIMAL(10, 3)
})
this.UserWithAge.sync({ force: true }).success(function(){
this.UserWithDec.sync({ force: true }).success(done)
}.bind(this))
})
it("should return the min value", function(done) {
this.UserWithAge.create({ age: 2 }).success(function() {
this.UserWithAge.create({ age: 3 }).success(function() {
this.UserWithAge.min('age').success(function(min) {
expect(min).toEqual(2)
done()
})
}.bind(this))
}.bind(this))
})
it('allows sql logging', function(done) {
this.UserWithAge.min('age').on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("SELECT")).toBeGreaterThan(-1)
done()
})
})
it("should allow decimals in min", function(done){
this.UserWithDec.create({value: 3.5}).success(function(){
this.UserWithDec.create({ value: 5.5 }).success(function(){
this.UserWithDec.min('value').success(function(min){
expect(min).toEqual(3.5)
done()
})
}.bind(this))
}.bind(this))
})
}) //- describe: min
describe('max', function() {
before(function(done) {
this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER,
order: Sequelize.INTEGER
})
this.UserWithDec = this.sequelize.define('UserWithDec', {
value: Sequelize.DECIMAL(10, 3)
})
this.UserWithAge.sync({ force: true }).success(function(){
this.UserWithDec.sync({ force: true }).success(done)
}.bind(this))
})
it("should return the max value for a field named the same as an SQL reserved keyword", function(done) {
this.UserWithAge.create({age: 3, order: 5}).success(function(){
this.UserWithAge.max('order').success(function(max) {
expect(max).toEqual(5)
done()
})
}.bind(this))
})
it("should return the max value", function(done) {
this.UserWithAge.create({ age: 2 }).success(function() {
this.UserWithAge.create({ age: 3 }).success(function() {
this.UserWithAge.max('age').success(function(max) {
expect(max).toEqual(3)
done()
})
}.bind(this))
}.bind(this))
})
it("should allow decimals in max", function(done){
this.UserWithDec.create({value: 3.5}).success(function(){
this.UserWithDec.create({ value: 5.5 }).success(function(){
this.UserWithDec.max('value').success(function(max){
expect(max).toEqual(5.5)
done()
})
}.bind(this))
}.bind(this))
})
it('allows sql logging', function(done) {
this.UserWithAge.max('age').on('sql', function(sql) {
expect(sql).toBeDefined()
expect(sql.toUpperCase().indexOf("SELECT")).toBeGreaterThan(-1)
done()
})
})
}) //- describe: max
describe('schematic support', function() {
before(function(done){
var self = this;
this.UserPublic = this.sequelize.define('UserPublic', {
age: Sequelize.INTEGER
})
this.UserSpecial = this.sequelize.define('UserSpecial', {
age: Sequelize.INTEGER
})
self.sequelize.dropAllSchemas().success(function(){
self.sequelize.createSchema('schema_test').success(function(){
self.sequelize.createSchema('special').success(function(){
self.UserSpecial.schema('special').sync({force: true}).success(function(UserSpecialSync){
self.UserSpecialSync = UserSpecialSync;
done()
})
})
})
})
})
it("should be able to list schemas", function(done){
this.sequelize.showAllSchemas().success(function(schemas){
expect(schemas).toBeDefined()
expect(schemas[0]).toBeArray()
expect(schemas[0].length).toEqual(2)
done()
})
})
if (dialect === "mysql") {
it("should take schemaDelimiter into account if applicable", function(done){
var UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', {age: Sequelize.INTEGER}, {schema: 'hello', schemaDelimiter: '_'})
var UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', {age: Sequelize.INTEGER})
UserSpecialUnderscore.sync({force: true}).success(function(User){
UserSpecialDblUnderscore.schema('hello', '__').sync({force: true}).success(function(DblUser){
DblUser.create({age: 3}).on('sql', function(dblSql){
User.create({age: 3}).on('sql', function(sql){
expect(dblSql).toBeDefined()
expect(dblSql.indexOf('INSERT INTO `hello__UserSpecialDblUnderscores`')).toBeGreaterThan(-1)
expect(sql).toBeDefined()
expect(sql.indexOf('INSERT INTO `hello_UserSpecialUnderscores`')).toBeGreaterThan(-1)
done()
})
})
})
})
})
}
it("should be able to create and update records under any valid schematic", function(done){
var self = this
self.UserPublic.sync({ force: true }).success(function(UserPublicSync){
UserPublicSync.create({age: 3}).on('sql', function(UserPublic){
self.UserSpecialSync.schema('special').create({age: 3})
.on('sql', function(UserSpecial){
expect(UserSpecial).toBeDefined()
expect(UserPublic).toBeDefined()
if (dialect === "postgres") {
expect(self.UserSpecialSync.getTableName()).toEqual('"special"."UserSpecials"');
expect(UserSpecial.indexOf('INSERT INTO "special"."UserSpecials"')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).toBeGreaterThan(-1)
} else if (dialect === "sqlite") {
expect(self.UserSpecialSync.getTableName()).toEqual('`special`.`UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).toBeGreaterThan(-1)
} else {
expect(self.UserSpecialSync.getTableName()).toEqual('`special.UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).toBeGreaterThan(-1)
}
})
.success(function(UserSpecial){
UserSpecial.updateAttributes({age: 5})
.on('sql', function(user){
expect(user).toBeDefined()
if (dialect === "postgres") {
expect(user.indexOf('UPDATE "special"."UserSpecials"')).toBeGreaterThan(-1)
} else {
expect(user.indexOf('UPDATE `special.UserSpecials`')).toBeGreaterThan(-1)
}
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
})
})
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("DAO"), function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1,20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne( self.ParanoidUser )
},
onComplete: function() {
self.User.sync({ force: true }).success(function(){
self.HistoryLog.sync({ force: true }).success(function(){
self.ParanoidUser.sync({force: true }).success(done)
})
})
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.increment(['aNumber'], 2).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(2);
done();
});
});
});
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.increment('aNumber', 2).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(2);
done();
});
});
});
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
// Select the user again (simulating a concurrent query)
self.User.find(1).done(function (err, user2) {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).done(function (err, user3) {
user1.increment(['aNumber'], 2).done(function (err, user4) {
self.User.find(1).done(function (err, user5) {
expect(user5.aNumber).toBe(3);
done();
});
});
});
});
});
});
it('should still work right with other concurrent increments', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
var _done = _.after(3, function () {
self.User.find(1).done(function (err, user2) {
expect(user2.aNumber).toEqual(6);
done();
})
});
user1.increment(['aNumber'], 2).done(_done);
user1.increment(['aNumber'], 2).done(_done);
user1.increment(['aNumber'], 2).done(_done);
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.increment({ 'aNumber': 1, 'bNumber': 2}).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
});
});
});
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.decrement(['aNumber'], 2).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(-2);
done();
});
});
});
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.decrement('aNumber', 2).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(-2);
done();
});
});
});
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
// Select the user again (simulating a concurrent query)
self.User.find(1).done(function (err, user2) {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).done(function (err, user3) {
user1.decrement(['aNumber'], 2).done(function (err, user4) {
self.User.find(1).done(function (err, user5) {
expect(user5.aNumber).toBe(-1);
done();
});
});
});
});
});
});
it('should still work right with other concurrent increments', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
var _done = _.after(3, function () {
self.User.find(1).done(function (err, user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).done(function (err, user1) {
user1.decrement({ 'aNumber': 1, 'bNumber': 2}).done(function (err, user2) {
self.User.find(1).done(function (err, user3) {
expect(user3.aNumber).toBe(-1);
expect(user3.bNumber).toBe(-2);
done();
});
});
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).done(function (err, originalUser) {
originalUser.updateAttributes({ username: 'Doe John' }).done(function () {
originalUser.reload().done(function (err, updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
})
})
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).done(function (err, originalUser) {
self.User.find(originalUser.id).done(function (err, updater) {
updater.updateAttributes({ username: 'Doe John' }).done(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
originalUser.reload().done(function (err, updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
})
})
})
})
})
it("should update read only attributes as well (updatedAt)", function (done) {
var self = this
this.timeout = 2000;
this.User.create({ username: 'John Doe' }).done(function (err, originalUser) {
var originallyUpdatedAt = originalUser.updatedAt
// Wait for a second, so updatedAt will actually be different
setTimeout(function () {
self.User.find(originalUser.id).done(function (err, updater) {
updater.updateAttributes({ username: 'Doe John' }).done(function () {
originalUser.reload().done(function (err, updatedUser) {
expect(originalUser.updatedAt).toBeGreaterThan(originallyUpdatedAt)
expect(updatedUser.updatedAt).toBeGreaterThan(originallyUpdatedAt)
done();
})
})
})
}, 1000)
})
})
it("should update the associations as well", function(done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).success(function() {
Book.create({ title: 'A very old book' }).success(function(book) {
Page.create({ content: 'om nom nom' }).success(function(page) {
book.setPages([ page ]).success(function() {
Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).success(function(leBook) {
page.updateAttributes({ content: 'something totally different' }).success(function(page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
leBook.reload().success(function(leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}.bind(this))
}.bind(this))
})
});
describe('default values', function() {
describe('current date', function() {
it('should store a date in touchedAt', function() {
var user = this.User.build({ username: 'a user'})
expect(user.touchedAt instanceof Date).toBeTrue()
})
it("should store the current date in touchedAt", function() {
this.useFakeTimers().tick(5000)
var user = this.User.build({ username: 'a user'})
expect(+user.touchedAt).toBe(5000)
})
})
describe('allowNull date', function() {
it('should be just "null" and not Date with Invalid Date', function(done) {
var self = this;
this.User.build({ username: 'a user'}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue).toBe(null)
done()
})
})
})
it('should be the same valid date when saving the date', function(done) {
var self = this;
var date = new Date();
this.User.build({ username: 'a user', dateAllowNullTrue: date}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue.toString()).toEqual(date.toString())
done()
})
})
})
})
})
describe('complete', function() {
it("gets triggered if an error occurs", function(done) {
this.User.find({ where: "asdasdasd" }).complete(function(err, result) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function(done) {
this.User.count().complete(function(err, result) {
expect(err).toBeNull()
expect(result).toBeDefined()
done()
})
})
})
describe('save', function() {
it('should fail a validation upon creating', function(done){
this.User.create({aNumber: 0, validateTest: 'hello'}).error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function(done){
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function(done){
this.User.create({aNumber: 0}).success(function(user){
user.updateAttributes({validateTest: 'hello'}).error(function(err){
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1)
done()
})
})
})
it('takes zero into account', function(done) {
this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) {
expect(user.aNumber).toEqual(0)
done()
})
})
it('saves a record with no primary key', function(done){
this.HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }).success(function(log) {
log.updateAttributes({ aNumber: 5 }).success(function(newLog){
expect(newLog.aNumber).toEqual(5)
done()
})
})
})
before(function(done) {
this.UserEager = this.sequelize.define('UserEagerLoadingSaves', {
username : Helpers.Sequelize.STRING,
age : Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.ProjectEager = this.sequelize.define('ProjectEagerLoadingSaves', {
title : Helpers.Sequelize.STRING,
overdue_days: Helpers.Sequelize.INTEGER
}, { timestamps: false })
this.UserEager.hasMany(this.ProjectEager, { as: 'Projects' })
this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah' })
this.sequelize.sync({ force: true }).success(done)
})
it('saves one object that has a collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'joe', age: 1 }).success(function(user) {
this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).success(function(project1) {
this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).success(function(project2) {
user.setProjects([project1, project2]).success(function() {
this.UserEager.find({where: {age: 1}, include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(user) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(1)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
user.age = user.age + 1; // happy birthday joe
user.save().success(function(saveduser) {
expect(user.username).toEqual('joe')
expect(user.age).toEqual(2)
expect(user.projects).toBeDefined()
expect(user.projects.length).toEqual(2)
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each a have collection of eagerly loaded objects', function(done) {
this.UserEager.create({ username: 'bart', age: 20 }).success(function(bart) {
this.UserEager.create({ username: 'lisa', age: 20 }).success(function(lisa) {
this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).success(function(detention1) {
this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).success(function(detention2) {
this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).success(function(exam1) {
this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).success(function(exam2) {
bart.setProjects([detention1, detention2]).success(function() {
lisa.setProjects([exam1, exam2]).success(function() {
this.UserEager.findAll({where: {age: 20}, order: 'username ASC', include: [{model: this.ProjectEager, as: 'Projects'}]}).success(function(simpsons) {
var _bart, _lisa;
expect(simpsons.length).toEqual(2)
_bart = simpsons[0];
_lisa = simpsons[1];
expect(_bart.projects).toBeDefined()
expect(_lisa.projects).toBeDefined()
expect(_bart.projects.length).toEqual(2)
expect(_lisa.projects.length).toEqual(2)
_bart.age = _bart.age + 1; // happy birthday bart - off to Moe's
_bart.save().success(function(savedbart) {
expect(savedbart.username).toEqual('bart')
expect(savedbart.age).toEqual(21)
_lisa.username = 'lsimpson';
_lisa.save().success(function(savedlisa) {
expect(savedlisa.username).toEqual('lsimpson')
expect(savedlisa.age).toEqual(20)
done()
})
})
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('saves many objects that each has one eagerly loaded object (to which they belong)', function(done) {
this.UserEager.create({ username: 'poobah', age: 18 }).success(function(user) {
this.ProjectEager.create({ title: 'homework', overdue_days: 10 }).success(function(homework) {
this.ProjectEager.create({ title: 'party', overdue_days: 2 }).success(function(party) {
user.setProjects([homework, party]).success(function() {
this.ProjectEager.findAll({include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(projects) {
expect(projects.length).toEqual(2)
expect(projects[0].poobah).toBeDefined()
expect(projects[1].poobah).toBeDefined()
expect(projects[0].poobah.username).toEqual('poobah')
expect(projects[1].poobah.username).toEqual('poobah')
projects[0].title = 'partymore';
projects[1].title = 'partymore';
projects[0].overdue_days = 0;
projects[1].overdue_days = 0;
projects[0].save().success(function() {
projects[1].save().success(function() {
this.ProjectEager.findAll({where: {title: 'partymore', overdue_days: 0}, include: [{model: this.UserEager, as: 'Poobah'}]}).success(function(savedprojects) {
expect(savedprojects.length).toEqual(2)
expect(savedprojects[0].poobah).toBeDefined()
expect(savedprojects[1].poobah).toBeDefined()
expect(savedprojects[0].poobah.username).toEqual('poobah')
expect(savedprojects[1].poobah.username).toEqual('poobah')
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('toJSON', function toJSON() {
before(function(done) {
this.User = this.sequelize.define('UserWithUsernameAndAgeAndIsAdmin', {
username: Helpers.Sequelize.STRING,
age: Helpers.Sequelize.INTEGER,
isAdmin: Helpers.Sequelize.BOOLEAN
}, { timestamps: false })
this.Project = this.sequelize.define('NiceProject', { title: Helpers.Sequelize.STRING }, { timestamps: false })
this.User.hasMany(this.Project, { as: 'Projects' })
this.Project.belongsTo(this.User, { as: 'LovelyUser' })
this.sequelize.sync({ force: true }).success(done)
})
it('returns an object containing all values', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(user.toJSON()).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
})
it('returns a response that can be stringified', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.stringify(user)).toEqual('{"username":"test.user","age":99,"isAdmin":true,"id":null}')
})
it('returns a response that can be stringified and then parsed', function() {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.parse(JSON.stringify(user))).toEqual({ username: 'test.user', age: 99, isAdmin: true, id: null })
})
it('includes the eagerly loaded associations', function(done) {
this.User.create({ username: 'fnord', age: 1, isAdmin: true }).success(function(user) {
this.Project.create({ title: 'fnord' }).success(function(project) {
user.setProjects([ project ]).success(function() {
this.User.findAll({include: [ { model: this.Project, as: 'Projects' } ]}).success(function(users) {
var _user = users[0]
expect(_user.projects).toBeDefined()
expect(JSON.parse(JSON.stringify(_user)).projects).toBeDefined()
this.Project.findAll({include: [ { model: this.User, as: 'LovelyUser' } ]}).success(function(projects) {
var _project = projects[0]
expect(_project.lovelyUser).toBeDefined()
expect(JSON.parse(JSON.stringify(_project)).lovelyUser).toBeDefined()
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('findAll', function findAll() {
it("escapes a single single quotes properly in where clauses", function(done) {
var self = this
this.User
.create({ username: "user'name" })
.success(function() {
self.User.findAll({
where: { username: "user'name" }
}).success(function(users) {
expect(users.length).toEqual(1)
expect(users[0].username).toEqual("user'name")
done()
})
})
})
it("escapes two single quotes properly in where clauses", function(done) {
var self = this
this.User
.create({ username: "user''name" })
.success(function() {
self.User.findAll({
where: { username: "user''name" }
}).success(function(users) {
expect(users.length).toEqual(1)
expect(users[0].username).toEqual("user''name")
done()
})
})
})
it("returns the timestamps if no attributes have been specified", function(done) {
this.User.create({ username: 'fnord' }).success(function() {
this.User.findAll().success(function(users) {
expect(users[0].createdAt).toBeDefined()
done()
}.bind(this))
}.bind(this))
})
it("does not return the timestamps if the username attribute has been specified", function(done) {
this.User.create({ username: 'fnord' }).success(function() {
this.User.findAll({ attributes: ['username'] }).success(function(users) {
expect(users[0].createdAt).not.toBeDefined()
expect(users[0].username).toBeDefined()
done()
}.bind(this))
}.bind(this))
})
it("creates the deletedAt property, when defining paranoid as true", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
expect(users[0].deletedAt).toBeDefined()
expect(users[0].deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
users[0].destroy().success(function(user) {
expect(user.deletedAt.getMonth).toBeDefined()
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it("keeps the deletedAt-attribute with value null, when running updateAttributes", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
users[0].updateAttributes({username: 'newFnord'}).success(function(user) {
expect(user.deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it("keeps the deletedAt-attribute with value null, when updating associations", function(done) {
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
this.ParanoidUser.findAll().success(function(users) {
this.ParanoidUser.create({ username: 'linkedFnord' }).success(function( linkedUser ) {
users[0].setParanoidUser( linkedUser ).success(function(user) {
expect(user.deletedAt).toBe(null)
done()
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it("can reuse query option objects", function(done) {
this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }}
this.User.findAll(query).success(function(users) {
expect(users[0].username).toEqual('fnord')
this.User.findAll(query).success(function(users) {
expect(users[0].username).toEqual('fnord')
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('find', function find() {
it("can reuse query option objects", function(done) {
this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }}
this.User.find(query).success(function(user) {
expect(user.username).toEqual('fnord')
this.User.find(query).success(function(user) {
expect(user.username).toEqual('fnord')
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('equals', function find() {
it("can compare records with Date field", function(done) {
this.User.create({ username: 'fnord' }).success(function(user1) {
var query = { where: { username: 'fnord' }}
this.User.find(query).success(function(user2) {
expect(user1.equals(user2)).toBeTrue()
done()
}.bind(this))
}.bind(this))
})
})
describe('updateAttributes', function() {
it('stores and restores null values', function(done) {
var Download = this.sequelize.define('download', {
startedAt: Helpers.Sequelize.DATE,
canceledAt: Helpers.Sequelize.DATE,
finishedAt: Helpers.Sequelize.DATE
})
Download.sync({ force: true }).success(function() {
Download.create({
startedAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt).toBeFalsy()
expect(download.finishedAt).toBeFalsy()
download.updateAttributes({
canceledAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt instanceof Date).toBeTrue()
expect(download.finishedAt).toBeFalsy()
Download.all({
where: (dialect === 'postgres' ? '"finishedAt" IS NULL' : "`finishedAt` IS NULL")
}).success(function(downloads) {
downloads.forEach(function(download) {
expect(download.startedAt instanceof Date).toBeTrue()
expect(download.canceledAt instanceof Date).toBeTrue()
expect(download.finishedAt).toBeFalsy()
})
done()
})
})
})
})
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Sequelize = require("../index")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("Language Util"), function() {
describe("Plural", function(){
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
this.sequelize.options.language = 'es'
done()
}.bind(this)
})
})
it("should rename tables to their plural form...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
expect(table.tableName).toEqual('arboles')
expect(table2.tableName).toEqual('androides')
done()
})
it("should be able to pluralize/singularize associations...", function(done){
var table = this.sequelize.define('arbol', {name: Sequelize.STRING})
var table2 = this.sequelize.define('androide', {name: Sequelize.STRING})
var table3 = this.sequelize.define('hombre', {name: Sequelize.STRING})
table.hasOne(table2)
table2.belongsTo(table)
table3.hasMany(table2)
expect(table.associations.androides.identifier).toEqual('arbolId')
expect(table2.associations.arboles).toBeDefined()
expect(table3.associations.androideshombres).toBeDefined()
done()
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, QueryChainer = require("../lib/query-chainer")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, Migrator = require("../lib/migrator")
}
buster.spec.expose()
buster.testRunner.timeout = 10000
describe(Helpers.getTestDialectTeaser("Migrator"), function() {
before(function(done) {
this.init = function(options, callback) {
options = Helpers.Sequelize.Utils._.extend({
path: __dirname + '/assets/migrations',
logging: function(){}
}, options || {})
var migrator = new Migrator(this.sequelize, options)
migrator
.findOrCreateSequelizeMetaDAO({ force: true })
.success(function(SequelizeMeta) {
callback && callback(migrator, SequelizeMeta)
})
.error(function(err) { console.log(err) })
}.bind(this)
Helpers.initTests({ dialect: dialect, onComplete: done, context: this })
})
it("as", function() {
expect(1).toEqual(1)
})
describe('getUndoneMigrations', function() {
it("returns no files if timestamps are after the files timestamp", function(done) {
this.init({ from: 20120101010101 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(0)
done()
}.bind(this))
}.bind(this))
})
it("returns only files between from and to", function(done) {
this.init({ from: 19700101000000, to: 20111117063700 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(1)
expect(migrations[migrations.length - 1].filename).toEqual('20111117063700-createPerson.js')
done()
}.bind(this))
}.bind(this))
})
it("returns exactly the migration which is defined in from and to", function(done) {
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(1)
expect(migrations[migrations.length - 1].filename).toEqual('20111117063700-createPerson.js')
done()
}.bind(this))
}.bind(this))
})
it("returns also the file which is exactly options.from or options.to", function(done) {
this.init({ from: 20111117063700, to: 20111130161100 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(2)
expect(migrations[0].filename).toEqual('20111117063700-createPerson.js')
expect(migrations[1].filename).toEqual('20111130161100-emptyMigration.js')
done()
}.bind(this))
}.bind(this))
})
it("returns all files to options.to if no options.from is defined", function(done) {
this.init({ to: 20111130161100 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(2)
done()
}.bind(this))
}.bind(this))
})
it("returns all files from last migration id stored in database", function(done) {
this.init(undefined, function(migrator, SequelizeMeta) {
SequelizeMeta.create({ from: null, to: 20111117063700 }).success(function() {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).toBeNull()
expect(migrations.length).toEqual(6)
expect(migrations[0].filename).toEqual('20111130161100-emptyMigration.js')
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('migrations', function() {
before(function(done) {
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
this.migrator = migrator
this.migrator.migrate().success(done)
}.bind(this))
})
describe('executions', function() {
it("executes migration #20111117063700 and correctly creates the table", function(done) {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).toEqual([ 'Person' ])
done()
})
})
it("executes migration #20111117063700 and correctly adds isBetaMember", function(done) {
this.sequelize.getQueryInterface().describeTable('Person').success(function(data) {
var fields = Helpers.Sequelize.Utils._.keys(data).sort()
expect(fields).toEqual([ 'isBetaMember', 'name' ])
done()
})
})
it("executes migration #20111117063700 correctly up (createTable) and downwards (dropTable)", function(done) {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).toEqual([ 'Person' ])
this.migrator.migrate({ method: 'down' }).success(function() {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).toEqual([])
done()
}.bind(this))
}.bind(this))
}.bind(this))
})
it("executes the empty migration #20111130161100", function(done) {
this.init({ from: 20111130161100, to: 20111130161100 }, function(migrator) {
// this migration isn't actually testing anything but
// should not timeout
expect(1).toEqual(1)
migrator
.migrate()
.success(done)
.error(function(err) { console.log(err) })
})
})
})
describe('renameTable', function() {
before(function(done) {
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
this.migrator = migrator
this.migrator.migrate().success(done)
}.bind(this))
})
it("executes migration #20111205064000 and renames a table", function(done) {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).toContain('Person')
this.init({ from: 20111205064000, to: 20111205064000 }, function(migrator) {
migrator.migrate().success(function() {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).toEqual([ 'User' ])
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('addColumn', function() {
it('adds a column to the user table', function(done) {
this.init({ from: 20111117063700, to: 20111205162700 }, function(migrator) {
migrator.migrate().complete(function(err) {
this.sequelize.getQueryInterface().describeTable('User').complete(function(err, data) {
var signature = data.signature
, isAdmin = data.isAdmin
, shopId = data.shopId
expect(signature.allowNull).toEqual(true)
expect(isAdmin.allowNull).toEqual(false)
expect(isAdmin.defaultValue).toEqual(false)
expect(shopId.allowNull).toEqual(true)
done()
})
}.bind(this))
}.bind(this))
})
})
describe('removeColumn', function() {
it('removes the shopId column from user', function(done) {
this.init({ to: 20111206061400 }, function(migrator) {
migrator.migrate().success(function(){
this.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
, isAdmin = data.isAdmin
, shopId = data.shopId
expect(signature.allowNull).toEqual(true)
expect(isAdmin.allowNull).toEqual(false)
expect(isAdmin.defaultValue).toEqual(false)
expect(shopId).toBeFalsy()
done()
})
}.bind(this))
}.bind(this))
})
})
describe('changeColumn', function() {
it('changes the signature column from user to default "signature" + notNull', function(done) {
this.init({ to: 20111206063000 }, function(migrator) {
migrator.migrate().success(function() {
this.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
if (dialect === 'postgres') {
expect(signature.type).toEqual('CHARACTER VARYING')
} else {
expect(signature.type).toEqual('VARCHAR(255)')
}
expect(signature.allowNull).toEqual(false)
expect(signature.defaultValue).toEqual('Signature')
done()
})
}.bind(this))
}.bind(this))
})
})
})
describe('renameColumn', function() {
it("renames the signature column from user to sig", function(done) {
this.init({ to: 20111206163300 }, function(migrator) {
migrator.migrate().success(function(){
this.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
, sig = data.sig
expect(signature).toBeFalsy()
expect(sig).toBeTruthy()
done()
})
}.bind(this))
}.bind(this))
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
if (dialect.match(/^postgres/)) {
describe('[POSTGRES] hstore', function() {
const hstore = require('../../lib/dialects/postgres/hstore')
describe('stringifyPart', function() {
it("handles undefined values correctly", function() {
expect(hstore.stringifyPart(undefined)).toEqual('NULL')
})
it("handles null values correctly", function() {
expect(hstore.stringifyPart(null)).toEqual('NULL')
})
it("handles boolean values correctly", function() {
expect(hstore.stringifyPart(false)).toEqual('false')
expect(hstore.stringifyPart(true)).toEqual('true')
})
it("handles strings correctly", function() {
expect(hstore.stringifyPart('foo')).toEqual('"foo"')
})
it("handles strings with backslashes correctly", function() {
expect(hstore.stringifyPart("\\'literally\\'")).toEqual('"\\\\\'literally\\\\\'"')
})
it("handles arrays correctly", function() {
expect(hstore.stringifyPart([1,['2'],'"3"'])).toEqual('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
})
it("handles simple objects correctly", function() {
expect(hstore.stringifyPart({ test: 'value' })).toEqual('"{\\"test\\":\\"value\\"}"')
})
it("handles nested objects correctly", function () {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).toEqual('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
})
it("handles objects correctly", function() {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).toEqual('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
})
})
describe('stringify', function() {
it('should handle empty objects correctly', function() {
expect(hstore.stringify({ })).toEqual('')
})
it('should handle null values correctly', function () {
expect(hstore.stringify({ null: null })).toEqual('"null"=>NULL')
})
it('should handle simple objects correctly', function() {
expect(hstore.stringify({ test: 'value' })).toEqual('"test"=>"value"')
})
it('should handle nested objects correctly', function() {
expect(hstore.stringify({ test: { nested: 'value' } })).toEqual('"test"=>"{\\"nested\\":\\"value\\"}"')
})
it('should handle nested arrays correctly', function() {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).toEqual('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).toEqual('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')
})
})
describe('parse', function() {
it('should handle empty objects correctly', function() {
expect(hstore.parse('')).toEqual({ })
})
it('should handle simple objects correctly', function() {
expect(hstore.parse('"test"=>"value"')).toEqual({ test: 'value' })
})
it('should handle nested objects correctly', function() {
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).toEqual({ test: { nested: 'value' } })
})
it('should handle nested arrays correctly', function() {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).toEqual({ test: [ 1, '2', [ '"3"' ] ] })
})
it('should handle multiple keys with different types of values', function() {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).toEqual({ true: true, false: false, null: null, undefined: null, integer: 1, array: [1,'2'], object: { object: 'value' }})
})
})
})
}
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Promise"), function () {
before(function (done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function (sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne(self.ParanoidUser)
},
onComplete: function () {
self.User.sync({ force: true }).then(function () {
return self.HistoryLog.sync({ force: true })
}).then(function () {
return self.ParanoidUser.sync({force: true })
})
.then(function () {done()}, done)
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment(['aNumber'], 2)
}).then(function (user2) {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(2);
done();
}, done);
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1).then(function (user2) {
return user2.updateAttributes({
aNumber: user2.aNumber + 1
}).then(function (user3) {
return user1.increment(['aNumber'], 2)
}).then(function (user4) {
return self.User.find(1)
}).then(function (user5) {
expect(user5.aNumber).toBe(3);
done();
}, done)
});
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
}).then(function () {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
}, done);
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('should still work right with other concurrent decrements', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
var _done = _.after(3, function () {
self.User.find(1).then(function (user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return originalUser.updateAttributes({ username: 'Doe John' }).then(function () {
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
}, done)
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return self.User.find(originalUser.id).then(function (updater) {
return updater.updateAttributes({ username: 'Doe John' })
}).then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
}, done)
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).then(function () {
return Book.create({ title: 'A very old book' })
}).then(function (book) {
return Page.create({ content: 'om nom nom' }).then(function (page) {
return book.setPages([ page ]).then(function () {
return Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).then(function (leBook) {
return page.updateAttributes({ content: 'something totally different' }).then(function (page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
return leBook.reload().then(function (leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}, done)
})
});
describe('complete', function () {
it("gets triggered if an error occurs", function (done) {
this.User.find({ where: "asdasdasd" }).then(null, function (err) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function (done) {
this.User.count().then(function (result) {
expect(result).toBeDefined()
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function (done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function (done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function (done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1)
done()
})
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("QueryInterface"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) {
this.sequelize = sequelize
}.bind(this),
onComplete: function() {
this.interface = this.sequelize.getQueryInterface()
done()
}.bind(this)
})
})
describe('dropAllTables', function() {
it("should drop all tables", function(done) {
this.interface.dropAllTables().complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(0)
this.interface.createTable('table', { name: Helpers.Sequelize.STRING }).complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(1)
this.interface.dropAllTables().complete(function(err) {
expect(err).toBeNull()
this.interface.showAllTables().complete(function(err, tableNames) {
expect(err).toBeNull()
expect(tableNames.length).toEqual(0)
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('indexes', function() {
before(function(done) {
this.interface.createTable('User', {
username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN
}).success(done)
})
it('adds, reads and removes an index to the table', function(done) {
this.interface.addIndex('User', ['username', 'isAdmin']).complete(function(err) {
expect(err).toBeNull()
this.interface.showIndex('User').complete(function(err, indexes) {
expect(err).toBeNull()
var indexColumns = Helpers.Sequelize.Utils._.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).toEqual(['user_username_is_admin'])
this.interface.removeIndex('User', ['username', 'isAdmin']).complete(function(err) {
expect(err).toBeNull()
this.interface.showIndex('User').complete(function(err, indexes) {
expect(err).toBeNull()
indexColumns = Helpers.Sequelize.Utils._.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).toEqual([])
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
})
describe('describeTable', function() {
before(function(done) {
this.interface.createTable('User', {
username: Helpers.Sequelize.STRING,
isAdmin: Helpers.Sequelize.BOOLEAN,
enumVals: Helpers.Sequelize.ENUM('hello', 'world')
}).success(done)
})
it('reads the metadata of the table', function(done) {
this.interface.describeTable('User').complete(function(err, metadata) {
expect(err).toBeNull()
var username = metadata.username
var isAdmin = metadata.isAdmin
var enumVals = metadata.enumVals
expect(username.type).toEqual(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)')
expect(username.allowNull).toBeTrue()
expect(username.defaultValue).toBeNull()
expect(isAdmin.type).toEqual(dialect === 'postgres' ? 'BOOLEAN' : 'TINYINT(1)')
expect(isAdmin.allowNull).toBeTrue()
expect(isAdmin.defaultValue).toBeNull()
if (dialect === 'postgres') {
expect(enumVals.special).toBeArray();
expect(enumVals.special.length).toEqual(2);
}
done()
})
})
})
})
if(typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
var qq = function(str) {
if (dialect == 'postgres' || dialect == 'sqlite') {
return '"' + str + '"'
} else if (dialect == 'mysql') {
return '`' + str + '`'
} else {
return str
}
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize) { this.sequelize = sequelize }.bind(this),
onComplete: done
})
})
describe('isDefined', function() {
it("returns false if the dao wasn't defined before", function() {
expect(this.sequelize.isDefined('Project')).toBeFalse()
})
it("returns true if the dao was defined before", function() {
this.sequelize.define('Project', {
name: Helpers.Sequelize.STRING
})
expect(this.sequelize.isDefined('Project')).toBeTrue()
})
})
describe('query', function() {
before(function(done) {
this.User = this.sequelize.define('User', {
username: Helpers.Sequelize.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().success(done).error(function(err) {
console(err)
done()
})
})
it('executes a query the internal way', function(done) {
this.sequelize.query(this.insertQuery, null, { raw: true })
.complete(function(err, result) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(result).toBeNull()
done()
})
})
it('executes a query if only the sql is passed', function(done) {
this.sequelize.query(this.insertQuery)
.complete(function(err, result) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(result).not.toBeDefined()
done()
})
})
it('executes select queries correctly', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select * from " + qq(this.User.tableName) + "")
.complete(function(err, users) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
}.bind(this))
})
it('executes select queries correctly when quoteIdentifiers is false', function(done) {
this.sequelize.options.quoteIdentifiers = false
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select * from " + qq(this.User.tableName) + "")
.complete(function(err, users) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
}.bind(this))
})
it('executes select query and parses dot notation results', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize
.query("select username as " + qq("user.username") + " from " + qq(this.User.tableName) + "")
.complete(function(err, users) {
if (err) {
console.log(err)
}
expect(err).toBeNull()
expect(users.map(function(u){ return u.user })).toEqual([{'username':'john'}])
done()
})
}.bind(this))
})
if (dialect == 'mysql') {
it('executes stored procedures', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize.query('DROP PROCEDURE IF EXISTS foo').success(function() {
this.sequelize.query(
"CREATE PROCEDURE foo()\nSELECT * FROM " + this.User.tableName + ";"
).success(function() {
this.sequelize.query('CALL foo()').success(function(users) {
expect(users.map(function(u){ return u.username })).toEqual(['john'])
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
})
} else {
console.log('FIXME: I want to be supported in this dialect as well :-(')
}
it('uses the passed DAOFactory', function(done) {
this.sequelize.query(this.insertQuery).success(function() {
this.sequelize.query("SELECT * FROM " + qq(this.User.tableName) + ";", this.User).success(function(users) {
expect(users[0].__factory).toEqual(this.User)
done()
}.bind(this))
}.bind(this))
})
it('destructs dot separated attributes when doing a raw query', function(done) {
var tickChar = (dialect === 'postgres') ? '"' : '`'
, sql = "select 1 as " + Helpers.Sequelize.Utils.addTicks('foo.bar.baz', tickChar)
this.sequelize.query(sql, null, { raw: true }).success(function(result) {
expect(result).toEqual([ { 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).toEqual([{ foo: 1, bar: 2 }])
done()
})
})
})
describe('define', function() {
[
{ type: Helpers.Sequelize.ENUM, values: ['scheduled', 'active', 'finished']},
Helpers.Sequelize.ENUM('scheduled', 'active', 'finished')
].forEach(function(status) {
describe('enum', function() {
before(function(done) {
this.Review = this.sequelize.define('review', { status: status })
this.Review.sync({ force: true }).success(done)
})
it('raises an error if no values are defined', function() {
Helpers.assertException(function() {
this.sequelize.define('omnomnom', {
bla: { type: Helpers.Sequelize.ENUM }
})
}.bind(this), 'Values for ENUM haven\'t been defined.')
})
it('correctly stores values', function(done) {
this.Review.create({ status: 'active' }).success(function(review) {
expect(review.status).toEqual('active')
done()
})
})
it('correctly loads values', function(done) {
this.Review.create({ status: 'active' }).success(function() {
this.Review.findAll().success(function(reviews) {
expect(reviews[0].status).toEqual('active')
done()
})
}.bind(this))
})
it("doesn't save an instance if value is not in the range of enums", function() {
Helpers.assertException(function() {
this.Review.create({ status: 'fnord' })
}.bind(this), 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
})
})
})
describe('table', function() {
[
{ id: { type: Helpers.Sequelize.BIGINT } },
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } },
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } }
].forEach(function(customAttributes) {
it('should be able to override options on the default attributes', function(done) {
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue)
});
})
done()
})
})
})
})
})
})
...@@ -6,7 +6,7 @@ module.exports = { ...@@ -6,7 +6,7 @@ module.exports = {
pool: { maxConnections: 5, maxIdleTime: 30000}, pool: { maxConnections: 5, maxIdleTime: 30000},
rand: function() { rand: function() {
return parseInt(Math.random() * 999) return parseInt(Math.random() * 999, 10)
}, },
//make maxIdleTime small so that tests exit promptly //make maxIdleTime small so that tests exit promptly
...@@ -26,6 +26,6 @@ module.exports = { ...@@ -26,6 +26,6 @@ module.exports = {
database: 'sequelize_test', database: 'sequelize_test',
username: "postgres", username: "postgres",
port: 5432, port: 5432,
pool: { maxConnections: 5, maxIdleTime: 30} pool: { maxConnections: 5, maxIdleTime: 3000}
} }
} }
var chai = require('chai')
, expect = chai.expect
, config = require(__dirname + "/config/config")
, Support = require(__dirname + '/support')
, dialect = Support.getTestDialect()
, Sequelize = require(__dirname + '/../index')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Configuration"), function() {
describe('Connections problems should fail with a nice message', function() {
it("when we don't have the correct server details", function(done) {
// mysql is not properly supported due to the custom pooling system
if (dialect !== "postgres" && dialect !== "postgres-native") {
console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster
return done()
}
var seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect: dialect})
seq.query('select 1 as hello').error(function(err) {
expect(err.message).to.match(/Failed to find (.*?) Please double check your settings\./)
done()
})
})
it('when we don\'t have the correct login information', function(done) {
if (dialect !== "postgres" && dialect !== "postgres-native") {
console.log('This dialect doesn\'t support me :(')
expect(true).to.be.true // Silence Buster
return done()
}
var seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', {logging: false, host: config[dialect].host, port: 1, dialect: dialect})
seq.query('select 1 as hello').error(function(err) {
expect(err.message).to.match(/^Failed to authenticate/)
done()
})
})
it('when we don\'t have a valid dialect.', function(done) {
expect(function() {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {host: '0.0.0.1', port: config[dialect].port, dialect: undefined})
}).to.throw(Error, 'The dialect undefined is not supported.')
done()
})
})
describe('Instantiation with a URL string', function() {
it('should accept username, password, host, port, and database', function() {
var sequelize = new Sequelize('mysql://user:pass@example.com:9821/dbname')
var config = sequelize.config
var options = sequelize.options
expect(options.dialect).to.equal('mysql')
expect(config.database).to.equal('dbname')
expect(config.host).to.equal('example.com')
expect(config.username).to.equal('user')
expect(config.password).to.equal('pass')
expect(config.port).to.equal('9821')
})
it('should work with no authentication options', function(done) {
var sequelize = new Sequelize('mysql://example.com:9821/dbname')
var config = sequelize.config
expect(config.username).to.not.be.ok
expect(config.password).to.be.null
done()
})
it('should use the default port when no other is specified', function(done) {
var sequelize = new Sequelize('mysql://example.com/dbname')
var config = sequelize.config
// The default port should be set
expect(config.port).to.equal(3306)
done()
})
})
describe('Intantiation with arguments', function() {
it('should accept two parameters (database, username)', function(done) {
var sequelize = new Sequelize('dbname', 'root')
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
done()
})
it('should accept three parameters (database, username, password)', function(done) {
var sequelize = new Sequelize('dbname', 'root', 'pass')
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
expect(config.password).to.equal('pass')
done()
})
it('should accept four parameters (database, username, password, options)', function(done) {
var sequelize = new Sequelize('dbname', 'root', 'pass', { port: 999 })
var config = sequelize.config
expect(config.database).to.equal('dbname')
expect(config.username).to.equal('root')
expect(config.password).to.equal('pass')
expect(config.port).to.equal(999)
done()
})
})
})
/* jshint camelcase: false */
var chai = require('chai')
, Sequelize = require('../index')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAOFactory"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
secretValue: DataTypes.STRING,
data: DataTypes.STRING,
intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('constructor', function() {
it("uses the passed dao name as tablename if freezeTableName", function(done) {
var User = this.sequelize.define('FrozenUser', {}, { freezeTableName: true })
expect(User.tableName).to.equal('FrozenUser')
done()
})
it("uses the pluralized dao name as tablename unless freezeTableName", function(done) {
var User = this.sequelize.define('SuperUser', {}, { freezeTableName: false })
expect(User.tableName).to.equal('SuperUsers')
done()
})
it("uses checks to make sure dao factory isnt leaking on multiple define", function(done) {
this.sequelize.define('SuperUser', {}, { freezeTableName: false })
var factorySize = this.sequelize.daoFactoryManager.all.length
this.sequelize.define('SuperUser', {}, { freezeTableName: false })
var factorySize2 = this.sequelize.daoFactoryManager.all.length
expect(factorySize).to.equal(factorySize2)
done()
})
it("attaches class and instance methods", function(done) {
var User = this.sequelize.define('UserWithClassAndInstanceMethods', {}, {
classMethods: { doSmth: function(){ return 1 } },
instanceMethods: { makeItSo: function(){ return 2}}
})
expect(User.doSmth).to.exist
expect(User.doSmth()).to.equal(1)
expect(User.makeItSo).not.to.exist
expect(User.build().doSmth).not.to.exist
expect(User.build().makeItSo).to.exist
expect(User.build().makeItSo()).to.equal(2)
done()
})
it("throws an error if 2 autoIncrements are passed", function(done) {
var self = this
expect(function() {
self.sequelize.define('UserWithTwoAutoIncrements', {
userid: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
userscore: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }
})
}).to.throw(Error, 'Invalid DAO definition. Only one autoincrement field allowed.')
done()
})
it('throws an error if a custom model-wide validation is not a function', function(done) {
var self = this
expect(function() {
self.sequelize.define('Foo', {
field: Sequelize.INTEGER
}, {
validate: {
notFunction: 33
}
})
}).to.throw(Error, 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction')
done()
})
it('throws an error if a custom model-wide validation has the same name as a field', function(done) {
var self = this
expect(function() {
self.sequelize.define('Foo', {
field: Sequelize.INTEGER
}, {
validate: {
field: function() {}
}
})
}).to.throw(Error, 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field')
done()
})
})
describe('build', function() {
it("doesn't create database entries", function(done) {
this.User.build({ username: 'John Wayne' })
this.User.all().success(function(users) {
expect(users).to.have.length(0)
done()
})
})
it("fills the objects with default values", function(done) {
var Task = this.sequelize.define('TaskBuild', {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
})
expect(Task.build().title).to.equal('a task!')
expect(Task.build().foo).to.equal(2)
expect(Task.build().bar).to.not.be.ok
expect(Task.build().foobar).to.equal('asd')
expect(Task.build().flag).to.be.false
done()
})
it("fills the objects with default values", function(done) {
var Task = this.sequelize.define('TaskBuild', {
title: {type: Sequelize.STRING, defaultValue: 'a task!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
bar: {type: Sequelize.DATE},
foobar: {type: Sequelize.TEXT, defaultValue: 'asd'},
flag: {type: Sequelize.BOOLEAN, defaultValue: false}
}, { timestamps: false })
expect(Task.build().title).to.equal('a task!')
expect(Task.build().foo).to.equal(2)
expect(Task.build().bar).to.not.be.ok
expect(Task.build().foobar).to.equal('asd')
expect(Task.build().flag).to.be.false
done()
})
it("stores the the passed values in a special variable", function(done) {
var user = this.User.build({ username: 'John Wayne' })
expect(user.selectedValues).to.deep.equal({ username: 'John Wayne' })
done()
})
it("attaches getter and setter methods from attribute definition", function(done) {
var Product = this.sequelize.define('ProductWithSettersAndGetters1', {
price: {
type: Sequelize.INTEGER,
get : function() {
return 'answer = ' + this.getDataValue('price')
},
set : function(v) {
return this.setDataValue('price', v + 42)
}
}
})
expect(Product.build({price: 42}).price).to.equal('answer = 84')
var p = Product.build({price: 1})
expect(p.price).to.equal('answer = 43');
p.price = 0
expect(p.price).to.equal('answer = 42')
done()
})
it("attaches getter and setter methods from options", function(done) {
var Product = this.sequelize.define('ProductWithSettersAndGetters2', {
priceInCents: Sequelize.INTEGER
},{
setterMethods: {
price: function(value) {
this.dataValues.priceInCents = value * 100
}
},
getterMethods: {
price: function() {
return '$' + (this.getDataValue('priceInCents') / 100)
},
priceInCents: function() {
return this.dataValues.priceInCents
}
}
});
expect(Product.build({price: 20}).priceInCents).to.equal(20 * 100)
expect(Product.build({priceInCents: 30 * 100}).price).to.equal('$' + 30)
done()
})
it("attaches getter and setter methods from options only if not defined in attribute", function(done) {
var Product = this.sequelize.define('ProductWithSettersAndGetters3', {
price1: {
type: Sequelize.INTEGER,
set : function(v) { this.setDataValue('price1', v * 10) }
},
price2: {
type: Sequelize.INTEGER,
get : function() { return this.getDataValue('price2') * 10 }
}
},{
setterMethods: {
price1: function(v) { this.setDataValue('price1', v * 100) }
},
getterMethods: {
price2: function() { return '$' + this.getDataValue('price2') }
}
});
var p = Product.build({ price1: 1, price2: 2 })
expect(p.price1).to.equal(10)
expect(p.price2).to.equal(20)
done()
})
})
describe('findOrCreate', function () {
it("Returns instace if already existent. Single find field.", function(done) {
var self = this,
data = {
username: 'Username'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate({
username: user.username
}).success(function (_user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(created).to.be.false
done()
})
})
})
it("Returns instace if already existent. Multiple find fields.", function(done) {
var self = this,
data = {
username: 'Username',
data: 'ThisIsData'
};
this.User.create(data).success(function (user) {
self.User.findOrCreate(data).success(function (_user, created) {
expect(_user.id).to.equal(user.id)
expect(_user.username).to.equal('Username')
expect(_user.data).to.equal('ThisIsData')
expect(created).to.be.false
done()
})
})
})
it("creates new instance with default value.", function(done) {
var data = {
username: 'Username'
},
default_values = {
data: 'ThisIsData'
};
this.User.findOrCreate(data, default_values).success(function(user, created) {
expect(user.username).to.equal('Username')
expect(user.data).to.equal('ThisIsData')
expect(created).to.be.true
done()
})
})
})
describe('create', function() {
it("casts empty arrays correctly for postgresql", function(done) {
if (dialect !== "postgres" && dialect !== "postgresql-native") {
expect('').to.equal('')
return done()
}
var User = this.sequelize.define('UserWithArray', {
myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) },
mystr: { type: Sequelize.ARRAY(Sequelize.STRING) }
})
User.sync({force: true}).success(function() {
User.create({myvals: [], mystr: []}).on('sql', function(sql){
expect(sql.indexOf('ARRAY[]::INTEGER[]')).to.be.above(-1)
expect(sql.indexOf('ARRAY[]::VARCHAR[]')).to.be.above(-1)
done()
})
})
})
it("doesn't allow duplicated records with unique:true", function(done) {
var User = this.sequelize.define('UserWithUniqueUsername', {
username: { type: Sequelize.STRING, unique: true }
})
User.sync({ force: true }).success(function() {
User.create({ username:'foo' }).success(function() {
User.create({ username: 'foo' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (dialect === "mysql") {
expect(err.message).to.match(/.*Duplicate\ entry.*/)
} else {
expect(err.message).to.match(/.*duplicate\ key\ value.*/)
}
done()
})
})
})
})
it("raises an error if created object breaks definition contraints", function(done) {
var UserNull = this.sequelize.define('UserWithNonNullSmth', {
username: { type: Sequelize.STRING, unique: true },
smth: { type: Sequelize.STRING, allowNull: false }
})
UserNull.sync({ force: true }).success(function() {
UserNull.create({ username: 'foo', smth: null }).error(function(err) {
expect(err).to.exist
if (dialect === "mysql") {
// 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: 'foo', smth: 'foo' }).success(function() {
UserNull.create({ username: 'foo', smth: 'bar' }).error(function(err) {
expect(err).to.exist
if (dialect === "sqlite") {
expect(err.message).to.match(/.*SQLITE_CONSTRAINT.*/)
}
else if (dialect === "mysql") {
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('raises an error if you mess up the datatype', function(done) {
var self = this
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: Sequelize.DATe
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
expect(function() {
self.sequelize.define('UserBadDataType', {
activity_date: {type: Sequelize.DATe}
})
}).to.throw(Error, 'Unrecognized data type for field activity_date')
done()
})
it('sets a 64 bit int in bigint', function(done) {
var User = this.sequelize.define('UserWithBigIntFields', {
big: Sequelize.BIGINT
})
User.sync({ force: true }).success(function() {
User.create({ big: '9223372036854775807' }).on('success', function(user) {
expect(user.big).to.be.equal( '9223372036854775807' )
done()
})
})
})
it('sets auto increment fields', function(done) {
var User = this.sequelize.define('UserWithAutoIncrementField', {
userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false }
})
User.sync({ force: true }).success(function() {
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(1)
User.create({}).on('success', function(user) {
expect(user.userid).to.equal(2)
done()
})
})
})
})
it('allows the usage of options as attribute', function(done) {
var User = this.sequelize.define('UserWithNameAndOptions', {
name: Sequelize.STRING,
options: Sequelize.TEXT
})
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
User.sync({ force: true }).success(function() {
User
.create({ name: 'John Doe', options: options })
.success(function(user) {
expect(user.options).to.equal(options)
done()
})
})
})
it('allows sql logging', function(done) {
var User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', {
name: {type: Sequelize.STRING, unique: true},
smth: {type: Sequelize.STRING, allowNull: false}
})
User.sync({ force: true }).success(function() {
User
.create({ name: 'Fluffy Bunny', smth: 'else' })
.on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("INSERT")).to.be.above(-1)
done()
})
})
})
it('should only store the values passed in the whitelist', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data, ['username']).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).to.equal(data.username)
expect(_user.secretValue).not.to.equal(data.secretValue)
expect(_user.secretValue).to.equal(null)
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = { username: 'Peter', secretValue: '42' }
this.User.create(data).success(function(user) {
self.User.find(user.id).success(function(_user) {
expect(_user.username).to.equal(data.username)
expect(_user.secretValue).to.equal(data.secretValue)
done()
})
})
})
it('can omitt autoincremental columns', function(done) {
var self = this
, data = { title: 'Iliad' }
, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT]
, chain = new Sequelize.Utils.QueryChainer()
, chain2 = new Sequelize.Utils.QueryChainer()
, books = []
dataTypes.forEach(function(dataType, index) {
books[index] = self.sequelize.define('Book'+index, {
id: { type: dataType, primaryKey: true, autoIncrement: true },
title: Sequelize.TEXT
})
})
books.forEach(function(b) {
chain.add(b.sync({ force: true }))
})
chain.run().success(function() {
books.forEach(function(b) {
chain2.add(b.create(data))
})
chain2.run().success(function(results) {
results.forEach(function(book, index) {
expect(book.title).to.equal(data.title)
expect(book.author).to.equal(data.author)
expect(books[index].rawAttributes.id.type.toString())
.to.equal(dataTypes[index].toString())
})
done()
})
})
})
it('saves data with single quote', function(done) {
var quote = "single'quote"
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).to.equal(quote)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(quote)
done()
})
})
})
it('saves data with double quote', function(done) {
var quote = 'double"quote'
, self = this
this.User.create({ data: quote }).success(function(user) {
expect(user.data).to.equal(quote)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(quote)
done()
})
})
})
it('saves stringified JSON data', function(done) {
var json = JSON.stringify({ key: 'value' })
, self = this
this.User.create({ data: json }).success(function(user) {
expect(user.data).to.equal(json)
self.User.find({where: { id: user.id }}).success(function(user) {
expect(user.data).to.equal(json)
done()
})
})
})
it('stores the current date in createdAt', function(done) {
this.User.create({ username: 'foo' }).success(function(user) {
expect(parseInt(+user.createdAt/5000, 10)).to.be.closeTo(parseInt(+new Date()/5000, 10), 1.5)
done()
})
})
it('allows setting custom IDs', function(done) {
var self = this
this.User.create({ id: 42 }).success(function (user) {
expect(user.id).to.equal(42)
self.User.find(42).success(function (user) {
expect(user).to.exist
done()
})
})
})
describe('enums', function() {
it('correctly restores enum values', function(done) {
var self = this
, Item = self.sequelize.define('Item', {
state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }
})
Item.sync({ force: true }).success(function() {
Item.create({ state: 'available' }).success(function(_item) {
Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.id).to.equal(_item.id)
done()
})
})
})
})
})
})
describe('bulkCreate', function() {
it('properly handles disparate field lists', function(done) {
var self = this
, data = [{username: 'Peter', secretValue: '42' },
{username: 'Paul'},
{username: 'Steve'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({where: {username: 'Paul'}}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("Paul")
expect(users[0].secretValue).to.be.null
done()
})
})
})
it('inserts multiple values respecting the white list', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data, ['username']).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].secretValue).to.be.null;
expect(users[1].username).to.equal("Paul")
expect(users[1].secretValue).to.be.null;
done()
})
})
})
it('should store all values if no whitelist is specified', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '23'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].secretValue).to.equal('42')
expect(users[1].username).to.equal("Paul")
expect(users[1].secretValue).to.equal('23')
done()
})
})
})
it('saves data with single quote', function(done) {
var self = this
, quote = "Single'Quote"
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(quote)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(quote)
done()
})
})
})
it('saves data with double quote', function(done) {
var self = this
, quote = 'Double"Quote'
, data = [{ username: 'Peter', data: quote},
{ username: 'Paul', data: quote}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(quote)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(quote)
done()
})
})
})
it('saves stringified JSON data', function(done) {
var self = this
, json = JSON.stringify({ key: 'value' })
, data = [{ username: 'Peter', data: json},
{ username: 'Paul', data: json}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(users[0].data).to.equal(json)
expect(users[1].username).to.equal("Paul")
expect(users[1].data).to.equal(json)
done()
})
})
})
it('stores the current date in createdAt', function(done) {
var self = this
, data = [{ username: 'Peter'},
{ username: 'Paul'}]
this.User.bulkCreate(data).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].username).to.equal("Peter")
expect(parseInt(+users[0].createdAt/5000, 10)).to.equal(parseInt(+new Date()/5000, 10))
expect(users[1].username).to.equal("Paul")
expect(parseInt(+users[1].createdAt/5000, 10)).to.equal(parseInt(+new Date()/5000, 10))
done()
})
})
})
describe('enums', function() {
it('correctly restores enum values', function(done) {
var self = this
, Item = self.sequelize.define('Item', {
state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] },
name: Sequelize.STRING
})
Item.sync({ force: true }).success(function() {
Item.bulkCreate([{state: 'in_cart', name: 'A'}, { state: 'available', name: 'B'}]).success(function() {
Item.find({ where: { state: 'available' }}).success(function(item) {
expect(item.name).to.equal('B')
done()
})
})
})
})
})
})
describe('update', function() {
it('allows sql logging of updated statements', function(done) {
var User = this.sequelize.define('User', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
}, {
paranoid:true
})
User.sync({ force: true }).success(function() {
User.create({ name: 'meg', bio: 'none' }).success(function(u) {
expect(u).to.exist
expect(u).not.to.be.null
u.updateAttributes({name: 'brian'}).on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("UPDATE")).to.be.above(-1)
done()
})
})
})
})
it('updates only values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.update({username: 'Bill'}, {secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
users.forEach(function (user) {
if (user.secretValue == '42') {
expect(user.username).to.equal("Bill")
} else {
expect(user.username).to.equal("Bob")
}
})
done()
})
})
})
})
it('sets updatedAt to the current timestamp', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.update({username: 'Bill'}, {secretValue: '42'}).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
expect(users[0].username).to.equal("Bill")
expect(users[1].username).to.equal("Bill")
expect(users[2].username).to.equal("Bob")
expect(parseInt(+users[0].updatedAt/5000, 10)).to.equal(parseInt(+new Date()/5000, 10))
expect(parseInt(+users[1].updatedAt/5000, 10)).to.equal(parseInt(+new Date()/5000, 10))
done()
})
})
})
})
})
describe('destroy', function() {
it('deletes a record from the database if dao is not paranoid', function(done) {
var UserDestroy = this.sequelize.define('UserDestory', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
UserDestroy.sync({ force: true }).success(function() {
UserDestroy.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().success(function() {
UserDestroy.all().success(function(users) {
expect(users.length).to.equal(0)
done()
})
})
})
})
})
})
it('allows sql logging of delete statements', function(done) {
var UserDelete = this.sequelize.define('UserDelete', {
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
UserDelete.sync({ force: true }).success(function() {
UserDelete.create({name: 'hallo', bio: 'welt'}).success(function(u) {
UserDelete.all().success(function(users) {
expect(users.length).to.equal(1)
u.destroy().on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("DELETE")).to.be.above(-1)
done()
})
})
})
})
})
it('deletes values that match filter', function(done) {
var self = this
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
this.User.bulkCreate(data).success(function() {
self.User.destroy({secretValue: '42'})
.success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("Bob")
done()
})
})
})
})
it('sets deletedAt to the current timestamp if paranoid is true', function(done) {
var self = this
, ParanoidUser = self.sequelize.define('ParanoidUser', {
username: Sequelize.STRING,
secretValue: Sequelize.STRING,
data: Sequelize.STRING,
intVal: { type: Sequelize.INTEGER, defaultValue: 1}
}, {
paranoid: true
})
, data = [{ username: 'Peter', secretValue: '42' },
{ username: 'Paul', secretValue: '42' },
{ username: 'Bob', secretValue: '43' }]
ParanoidUser.sync({ force: true }).success(function() {
ParanoidUser.bulkCreate(data).success(function() {
var date = parseInt(+new Date()/5000, 10)
ParanoidUser.destroy({secretValue: '42'}).success(function() {
ParanoidUser.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
expect(users[0].username).to.equal("Peter")
expect(users[1].username).to.equal("Paul")
expect(users[2].username).to.equal("Bob")
expect(parseInt(+users[0].deletedAt/5000, 10)).to.equal(date)
expect(parseInt(+users[1].deletedAt/5000, 10)).to.equal(date)
done()
})
})
})
})
})
})
describe('special where conditions/smartWhere object', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'boo', intVal: 5, theDate: '2013-01-01 12:00'},
{username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00'}
]).success(function(user2){
done()
})
})
it('should be able to retun a record with primaryKey being null for new inserts', function(done) {
var Session = this.sequelize.define('Session', {
token: { type: DataTypes.TEXT, allowNull: false },
lastUpdate: { type: DataTypes.DATE, allowNull: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci',
omitNull: true
})
, User = this.sequelize.define('User', {
name: { type: DataTypes.STRING, allowNull: false, unique: true },
password: { type: DataTypes.STRING, allowNull: false },
isAdmin: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
}, {
charset: 'utf8',
collate: 'utf8_general_ci'
})
User.hasMany(Session, { as: 'Sessions' })
Session.belongsTo(User)
Session.sync({ force: true }).success(function() {
User.sync({ force: true }).success(function() {
User.create({name: 'Name1', password: '123', isAdmin: false}).success(function(user) {
var sess = Session.build({
lastUpdate: new Date(),
token: '123'
})
user.addSession(sess).success(function(u) {
expect(u.token).to.equal('123')
done()
})
})
})
})
})
it('should be able to find a row between a certain date', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row between a certain date and an additional where clause', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2013-01-02', '2013-01-11']
},
intVal: 10
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row not between a certain integer', function(done) {
this.User.findAll({
where: {
intVal: {
nbetween: [8, 10]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic', function(done) {
this.User.findAll({
where: {
theDate: {
between: ['2012-12-10', '2013-01-02'],
nbetween: ['2013-01-04', '2013-01-20']
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not between and between logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
between: [new Date('2012-12-10'), new Date('2013-01-02')],
nbetween: [new Date('2013-01-04'), new Date('2013-01-20')]
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using greater than or equal to logic with dates', function(done) {
this.User.findAll({
where: {
theDate: {
gte: new Date('2013-01-09')
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo2')
expect(users[0].intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than or equal to', function(done) {
this.User.find({
where: {
intVal: {
gte: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using greater than', function(done) {
this.User.find({
where: {
intVal: {
gt: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo2')
expect(user.intVal).to.equal(10)
done()
})
})
it('should be able to find a row using lesser than or equal to', function(done) {
this.User.find({
where: {
intVal: {
lte: 5
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find a row using lesser than', function(done) {
this.User.find({
where: {
intVal: {
lt: 6
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should have no problem finding a row using lesser and greater than', function(done) {
this.User.findAll({
where: {
intVal: {
lt: 6,
gt: 4
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
done()
})
})
it('should be able to find a row using not equal to logic', function(done) {
this.User.find({
where: {
intVal: {
ne: 10
}
}
}).success(function(user) {
expect(user.username).to.equal('boo')
expect(user.intVal).to.equal(5)
done()
})
})
it('should be able to find multiple users with any of the special where logic properties', function(done) {
this.User.findAll({
where: {
intVal: {
lte: 10
}
}
}).success(function(users) {
expect(users[0].username).to.equal('boo')
expect(users[0].intVal).to.equal(5)
expect(users[1].username).to.equal('boo2')
expect(users[1].intVal).to.equal(10)
done()
})
})
})
describe('find', function() {
describe('general / basic function', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.UserPrimary = self.sequelize.define('UserPrimary', {
specialKey: {
type: DataTypes.STRING,
primaryKey: true
}
})
self.UserPrimary.sync({force: true}).success(function() {
self.UserPrimary.create({specialKey: 'a string'}).success(function() {
self.user = user
done()
})
})
})
})
it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function(done) {
this.UserPrimary.find('a string').success(function(user) {
expect(user.specialKey).to.equal('a string')
done()
})
})
it('doesn\'t throw an error when entering in a non integer value', function(done) {
this.User.find('a string value').success(function(user) {
expect(user).to.be.null
done()
})
})
it('returns a single dao', function(done) {
var self = this
this.User.find(this.user.id).success(function(user) {
expect(Array.isArray(user)).to.not.be.ok
expect(user.id).to.equal(self.user.id)
expect(user.id).to.equal(1)
done()
})
})
it('returns a single dao given a string id', function(done) {
var self = this
this.User.find(this.user.id + '').success(function(user) {
expect(Array.isArray(user)).to.not.be.ok
expect(user.id).to.equal(self.user.id)
expect(user.id).to.equal(1)
done()
})
})
it("should make aliased attributes available", function(done) {
this.User.find({
where: { id: 1 },
attributes: ['id', ['username', 'name']]
}).success(function(user) {
expect(user.name).to.equal('barfooz')
done()
})
})
it("should not try to convert boolean values if they are not selected", function(done) {
var UserWithBoolean = this.sequelize.define('UserBoolean', {
active: Sequelize.BOOLEAN
})
UserWithBoolean.sync({force: true}).success(function () {
UserWithBoolean.create({ active: true }).success(function(user) {
UserWithBoolean.find({ where: { id: user.id }, attributes: [ 'id' ] }).success(function(user) {
expect(user.active).not.to.exist
done()
})
})
})
})
it('finds a specific user via where option', function(done) {
this.User.find({ where: { username: 'barfooz' } }).success(function(user) {
expect(user.username).to.equal('barfooz')
done()
})
})
it("doesn't find a user if conditions are not matching", function(done) {
this.User.find({ where: { username: 'foo' } }).success(function(user) {
expect(user).to.be.null
done()
})
})
it('allows sql logging', function(done) {
this.User.find({ where: { username: 'foo' } }).on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("SELECT")).to.be.above(-1)
done()
})
})
it('ignores passed limit option', function(done) {
this.User.find({ limit: 10 }).success(function(user) {
// it returns an object instead of an array
expect(Array.isArray(user)).to.not.be.ok
expect(user.hasOwnProperty('username')).to.be.ok
done()
})
})
it('finds entries via primary keys', function(done) {
var self = this
, UserPrimary = self.sequelize.define('UserWithPrimaryKey', {
identifier: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING
})
UserPrimary.sync({ force: true }).success(function() {
UserPrimary.create({
identifier: 'an identifier',
name: 'John'
}).success(function(u) {
expect(u.id).not.to.exist
UserPrimary.find('an identifier').success(function(u2) {
expect(u2.identifier).to.equal('an identifier')
expect(u2.name).to.equal('John')
done()
})
})
})
})
it('returns the selected fields as instance.selectedValues', function(done) {
var self = this
this.User.create({
username: 'JohnXOXOXO'
}).success(function() {
self.User.find({
where: { username: 'JohnXOXOXO' },
attributes: ['username']
}).success(function(user) {
expect(user.selectedValues).to.have.property('username', 'JohnXOXOXO')
done()
})
})
})
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) {
var self = this
self.Mission = self.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
})
self.Mission.belongsTo(self.User)
self.User.hasMany(self.Mission)
self.Mission.sync({ force: true }).success(function() {
self.Mission.create().success(function(mission) {
self.User.create({username: 'John DOE'}).success(function(user) {
mission.setUser(user).success(function() {
self.User.find({
where: { username: 'John DOE' },
attributes: ['username'],
include: [self.Mission]
}).success(function(user) {
expect(user.selectedValues).to.deep.equal({ username: 'John DOE' })
done()
})
})
})
})
})
})
it('always honors ZERO as primary key', function(_done) {
var self = this
, permutations = [
0,
'0',
{where: {id: 0}},
{where: {id: '0'}}
]
, done = _.after(2 * permutations.length, _done);
this.User.bulkCreate([{username: 'jack'}, {username: 'jack'}]).success(function() {
permutations.forEach(function(perm) {
self.User.find(perm).done(function(err, user) {
expect(err).to.be.null;
expect(user).to.be.null;
done();
}).on('sql', function(s) {
expect(s.indexOf(0)).not.to.equal(-1);
done();
})
})
})
})
})
describe('eager loading', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
this.init = function(callback) {
self.Task.sync({ force: true }).success(function() {
self.Worker.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
callback()
})
})
})
})
}
done()
})
describe('belongsTo', function() {
describe('generic', function() {
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ 1 ] })
}).to.throw(Error, 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
done()
})
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('returns the associated worker via task.worker', function(done) {
var self = this
this.Task.belongsTo(this.Worker)
this.init(function() {
self.task.setWorker(self.worker).success(function() {
self.Task.find({
where: { title: 'homework' },
include: [ self.Worker ]
}).complete(function(err, task) {
expect(err).to.be.null
expect(task).to.exist
expect(task.worker).to.exist
expect(task.worker.name).to.equal('worker')
done()
})
})
})
})
})
it('returns the private and public ip', function(done) {
var self = Object.create(this)
self.Domain = self.sequelize.define('Domain', { ip: Sequelize.STRING })
self.Environment = self.sequelize.define('Environment', { name: Sequelize.STRING })
self.Environment
.belongsTo(self.Domain, { as: 'PrivateDomain', foreignKey: 'privateDomainId' })
.belongsTo(self.Domain, { as: 'PublicDomain', foreignKey: 'publicDomainId' })
self.Domain.sync({ force: true }).success(function() {
self.Environment.sync({ force: true }).success(function() {
self.Domain.create({ ip: '192.168.0.1' }).success(function(privateIp) {
self.Domain.create({ ip: '91.65.189.19' }).success(function(publicIp) {
self.Environment.create({ name: 'environment' }).success(function(env) {
env.setPrivateDomain(privateIp).success(function() {
env.setPublicDomain(publicIp).success(function() {
self.Environment.find({
where: { name: 'environment' },
include: [
{ daoFactory: self.Domain, as: 'PrivateDomain' },
{ daoFactory: self.Domain, as: 'PublicDomain' }
]
}).complete(function(err, environment) {
expect(err).to.be.null
expect(environment).to.exist
expect(environment.privateDomain).to.exist
expect(environment.privateDomain.ip).to.equal('192.168.0.1')
expect(environment.publicDomain).to.exist
expect(environment.publicDomain.ip).to.equal('91.65.189.19')
done()
})
})
})
})
})
})
})
})
})
})
describe('hasOne', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasOne(this.Task)
this.init(function() {
self.worker.setTask(self.task).success(function() {
done()
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.task).to.exist
expect(worker.task.title).to.equal('homework')
done()
})
})
})
describe('hasOne with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
describe('alias', function(done) {
beforeEach(function(done) {
var self = this
this.Worker.hasOne(this.Task, { as: 'ToDo' })
this.init(function() {
self.worker.setToDo(self.task).success(function() {
done()
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDo).to.exist
expect(worker.toDo.title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, worker) {
expect(worker.toDo.title).to.equal('homework')
done()
})
})
})
})
describe('hasMany', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task)
this.init(function() {
self.worker.setTasks([ self.task ]).success(function() {
done()
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.find({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.tasks).to.exist
expect(worker.tasks[0].title).to.equal('homework')
done()
})
})
})
describe('hasMany with alias', function() {
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
describe('alias', function() {
beforeEach(function(done) {
var self = this
this.Worker.hasMany(this.Task, { as: 'ToDos' })
this.init(function() {
self.worker.setToDos([ self.task ]).success(function() {
done()
})
})
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.find({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(err).to.be.null
expect(worker).to.exist
expect(worker.toDos).to.exist
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.find({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDos' } ]
}).complete(function(err, worker) {
expect(worker.toDos[0].title).to.equal('homework')
done()
})
})
})
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, user) {
expect(user).to.be.instanceOf(self.User.DAO)
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(user).to.be.instanceOf(Object)
done()
})
})
})
})
describe('findAll', function() {
describe('eager loading', function() {
describe('belongsTo', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('TaskBelongsTo', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Task.belongsTo(self.Worker)
self.Worker.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
self.task.setWorker(self.worker).success(function() {
done()
})
})
})
})
})
})
it('throws an error about unexpected input if include contains a non-object', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ 1 ] })
}).to.throw(Error, 'Include unexpected. Element has to be either an instance of DAOFactory or an object.')
done()
})
it('throws an error about missing attributes if include contains an object with daoFactory', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Worker } ] })
}).to.throw(Error, 'Include malformed. Expected attributes: daoFactory, as!')
done()
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'TaskBelongsTo is not associated to Worker!')
done()
})
it('returns the associated worker via task.worker', function(done) {
this.Task.all({
where: { title: 'homework' },
include: [ this.Worker ]
}).complete(function(err, tasks) {
expect(err).to.be.null
expect(tasks).to.exist
expect(tasks[0].worker).to.exist
expect(tasks[0].worker.name).to.equal('worker')
done()
})
})
})
describe('hasOne', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('TaskHasOne', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasOne(self.Task)
self.Worker.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
self.worker.setTaskHasOne(self.task).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.all({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to TaskHasOne!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].taskHasOne).to.exist
expect(workers[0].taskHasOne.title).to.equal('homework')
done()
})
})
})
describe('hasOne with alias', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasOne(self.Task, { as: 'ToDo' })
self.Worker.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
self.worker.setToDo(self.task).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.all({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].toDo).to.exist
expect(workers[0].toDo.title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.all({
where: { name: 'worker' },
include: [ { model: this.Task, as: 'ToDo' } ]
}).complete(function(err, workers) {
expect(workers[0].toDo.title).to.equal('homework')
done()
})
})
})
describe('hasMany', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasMany(self.Task)
self.Worker.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
self.worker.setTasks([ self.task ]).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not associated', function(done) {
var self = this
expect(function() {
self.Task.findAll({ include: [ self.Worker ] })
}).to.throw(Error, 'Worker is not associated to Task!')
done()
})
it('returns the associated tasks via worker.tasks', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ this.Task ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].tasks).to.exist
expect(workers[0].tasks[0].title).to.equal('homework')
done()
})
})
})
describe('hasMany with alias', function() {
beforeEach(function(done) {
var self = this
self.Task = self.sequelize.define('Task', { title: Sequelize.STRING })
self.Worker = self.sequelize.define('Worker', { name: Sequelize.STRING })
self.Worker.hasMany(self.Task, { as: 'ToDos' })
self.Worker.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.Worker.create({ name: 'worker' }).success(function(worker) {
self.Task.create({ title: 'homework' }).success(function(task) {
self.worker = worker
self.task = task
self.worker.setToDos([ self.task ]).success(function() {
done()
})
})
})
})
})
})
it('throws an error if included DaoFactory is not referenced by alias', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ self.Task ] })
}).to.throw(Error, 'Task is not associated to Worker!')
done()
})
it('throws an error if alias is not associated', function(done) {
var self = this
expect(function() {
self.Worker.findAll({ include: [ { daoFactory: self.Task, as: 'Work' } ] })
}).to.throw(Error, 'Task (Work) is not associated to Worker!')
done()
})
it('returns the associated task via worker.task', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(err).to.be.null
expect(workers).to.exist
expect(workers[0].toDos).to.exist
expect(workers[0].toDos[0].title).to.equal('homework')
done()
})
})
it('returns the associated task via worker.task when daoFactory is aliased with model', function(done) {
this.Worker.findAll({
where: { name: 'worker' },
include: [ { daoFactory: this.Task, as: 'ToDos' } ]
}).complete(function(err, workers) {
expect(workers[0].toDos[0].title).to.equal('homework')
done()
})
})
})
describe('queryOptions', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'barfooz'}).success(function(user) {
self.user = user
done()
})
})
it("should return a DAO when queryOptions are not set", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}).done(function(err, users) {
users.forEach(function (user) {
expect(user).to.be.instanceOf(self.User.DAO)
})
done()
})
})
it("should return a DAO when raw is false", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function(err, users) {
users.forEach(function (user) {
expect(user).to.be.instanceOf(self.User.DAO)
})
done()
})
})
it("should return raw data when raw is true", function(done) {
var self = this
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function(err, users) {
users.forEach(function(user) {
expect(user).to.not.be.instanceOf(self.User.DAO)
expect(users[0]).to.be.instanceOf(Object)
})
done()
})
})
})
})
describe('normal findAll', function() {
beforeEach(function(done) {
var self = this
this.User.create({username: 'user', data: 'foobar', theDate: moment().toDate()}).success(function(user) {
self.User.create({username: 'user2', data: 'bar', theDate: moment().toDate()}).success(function(user2){
self.users = [user].concat(user2)
done()
})
})
})
it("finds all entries", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
done()
})
})
it("finds all users matching the passed conditions", function(done) {
this.User.findAll({where: "id != " + this.users[1].id}).success(function(users) {
expect(users.length).to.equal(1)
done()
})
})
it("can also handle array notation", function(done) {
var self = this
this.User.findAll({where: ['id = ?', this.users[1].id]}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].id).to.equal(self.users[1].id)
done()
})
})
it("sorts the results via id in ascending order", function(done) {
this.User.findAll().success(function(users) {
expect(users.length).to.equal(2);
expect(users[0].id).to.be.below(users[1].id)
done()
})
})
it("sorts the results via id in descending order", function(done) {
this.User.findAll({ order: "id DESC" }).success(function(users) {
expect(users[0].id).to.be.above(users[1].id)
done()
})
})
it("sorts the results via a date column", function(done) {
var self = this
self.User.create({username: 'user3', data: 'bar', theDate: moment().add('hours', 2).toDate()}).success(function(){
self.User.findAll({ order: 'theDate DESC' }).success(function(users) {
expect(users[0].id).to.be.above(users[2].id)
done()
})
})
})
it("handles offset and limit", function(done) {
var self = this
this.User.bulkCreate([{username: 'bobby'}, {username: 'tables'}]).success(function() {
self.User.findAll({ limit: 2, offset: 2 }).success(function(users) {
expect(users.length).to.equal(2)
expect(users[0].id).to.equal(3)
done()
})
})
})
})
})
describe('findAndCountAll', function() {
beforeEach(function(done) {
var self = this
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'},
{username: 'bobby', data: 'foo'}
]).success(function() {
self.User.all().success(function(users){
self.users = users
done()
})
})
})
it("handles where clause [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles where clause with ordering [only]", function(done) {
this.User.findAndCountAll({where: "id != " + this.users[0].id, order: 'id ASC'}).success(function(info) {
expect(info.count).to.equal(2)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles offset", function(done) {
this.User.findAndCountAll({offset: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(2)
done()
})
})
it("handles limit", function(done) {
this.User.findAndCountAll({limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
done()
})
})
it("handles offset and limit", function(done) {
this.User.findAndCountAll({offset: 1, limit: 1}).success(function(info) {
expect(info.count).to.equal(3)
expect(Array.isArray(info.rows)).to.be.ok
expect(info.rows.length).to.equal(1)
done()
})
})
})
describe('all', function() {
beforeEach(function(done) {
this.User.bulkCreate([
{username: 'user', data: 'foobar'},
{username: 'user2', data: 'bar'}
]).complete(function() {
done()
})
})
it("should return all users", function(done) {
this.User.all().on('success', function(users) {
expect(users.length).to.equal(2)
done()
})
})
})
describe('equals', function() {
it("correctly determines equality of objects", function(done) {
this.User.create({username: 'hallo', data: 'welt'}).success(function(u) {
expect(u.equals(u)).to.be.ok
done()
})
})
// sqlite can't handle multiple primary keys
if(dialect !== "sqlite") {
it("correctly determines equality with multiple primary keys", function(done) {
var userKeys = this.sequelize.define('userkeys', {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
userKeys.sync({ force: true }).success(function() {
userKeys.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equals(u)).to.be.ok
done()
})
})
})
}
})
describe('equalsOneOf', function() {
// sqlite can't handle multiple primary keys
if (dialect !== "sqlite") {
beforeEach(function(done) {
this.userKey = this.sequelize.define('userKeys', {
foo: {type: Sequelize.STRING, primaryKey: true},
bar: {type: Sequelize.STRING, primaryKey: true},
name: Sequelize.STRING,
bio: Sequelize.TEXT
})
this.userKey.sync({ force: true }).success(function(){
done()
})
})
it('determines equality if one is matching', function(done) {
this.userKey.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([u, {a: 1}])).to.be.ok
done()
})
})
it("doesn't determine equality if none is matching", function(done) {
this.userKey.create({foo: '1', bar: '2', name: 'hallo', bio: 'welt'}).success(function(u) {
expect(u.equalsOneOf([{b: 2}, {a: 1}])).to.not.be.ok
done()
})
})
}
})
describe('count', function() {
it('counts all created objects', function(done) {
var self = this
this.User.bulkCreate([{username: 'user1'}, {username: 'user2'}]).success(function() {
self.User.count().success(function(count) {
expect(count).to.equal(2)
done()
})
})
})
it('allows sql logging', function(done) {
this.User.count().on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("SELECT")).to.be.above(-1)
done()
})
})
it('filters object', function(done) {
var self = this
this.User.create({username: 'user1'}).success(function() {
self.User.create({username: 'foo'}).success(function() {
self.User.count({where: "username LIKE '%us%'"}).success(function(count) {
expect(count).to.equal(1)
done()
})
})
})
})
})
describe('min', function() {
beforeEach(function(done) {
var self = this
this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER
})
this.UserWithDec = this.sequelize.define('UserWithDec', {
value: Sequelize.DECIMAL(10, 3)
})
this.UserWithAge.sync({ force: true }).success(function() {
self.UserWithDec.sync({ force: true }).success(function() {
done()
})
})
})
it("should return the min value", function(done) {
var self = this
this.UserWithAge.bulkCreate([{age: 3}, { age: 2 }]).success(function() {
self.UserWithAge.min('age').success(function(min) {
expect(min).to.equal(2)
done()
})
})
})
it('allows sql logging', function(done) {
this.UserWithAge.min('age').on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("SELECT")).to.be.above(-1)
done()
})
})
it("should allow decimals in min", function(done){
var self = this
this.UserWithDec.bulkCreate([{value: 5.5}, {value: 3.5}]).success(function(){
self.UserWithDec.min('value').success(function(min){
expect(min).to.equal(3.5)
done()
})
})
})
})
describe('max', function() {
beforeEach(function(done) {
var self = this
this.UserWithAge = this.sequelize.define('UserWithAge', {
age: Sequelize.INTEGER,
order: Sequelize.INTEGER
})
this.UserWithDec = this.sequelize.define('UserWithDec', {
value: Sequelize.DECIMAL(10, 3)
})
this.UserWithAge.sync({ force: true }).success(function() {
self.UserWithDec.sync({ force: true }).success(function() {
done()
})
})
})
it("should return the max value for a field named the same as an SQL reserved keyword", function(done) {
var self = this
this.UserWithAge.bulkCreate([{age: 2, order: 3}, {age: 3, order: 5}]).success(function(){
self.UserWithAge.max('order').success(function(max) {
expect(max).to.equal(5)
done()
})
})
})
it("should return the max value", function(done) {
var self = this
self.UserWithAge.bulkCreate([{age: 2}, {age: 3}]).success(function() {
self.UserWithAge.max('age').success(function(max) {
expect(max).to.equal(3)
done()
})
})
})
it("should allow decimals in max", function(done) {
var self = this
this.UserWithDec.bulkCreate([{value: 3.5}, {value: 5.5}]).success(function(){
self.UserWithDec.max('value').success(function(max){
expect(max).to.equal(5.5)
done()
})
})
})
it('allows sql logging', function(done) {
this.UserWithAge.max('age').on('sql', function(sql) {
expect(sql).to.exist
expect(sql.toUpperCase().indexOf("SELECT")).to.be.above(-1)
done()
})
})
})
describe('schematic support', function() {
beforeEach(function(done){
var self = this;
this.UserPublic = this.sequelize.define('UserPublic', {
age: Sequelize.INTEGER
})
this.UserSpecial = this.sequelize.define('UserSpecial', {
age: Sequelize.INTEGER
})
self.sequelize.dropAllSchemas().success(function(){
self.sequelize.createSchema('schema_test').success(function(){
self.sequelize.createSchema('special').success(function(){
self.UserSpecial.schema('special').sync({force: true}).success(function(UserSpecialSync) {
self.UserSpecialSync = UserSpecialSync
done()
})
})
})
})
})
it("should be able to list schemas", function(done){
this.sequelize.showAllSchemas().success(function(schemas) {
expect(schemas).to.exist
expect(schemas[0]).to.be.instanceof(Array)
// sqlite & MySQL doesn't actually create schemas unless Model.sync() is called
// Postgres supports schemas natively
expect(schemas[0]).to.have.length((dialect === "postgres" || dialect === "postgres-native" ? 2 : 1))
done()
})
})
if (dialect === "mysql") {
it("should take schemaDelimiter into account if applicable", function(done){
var UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', {age: Sequelize.INTEGER}, {schema: 'hello', schemaDelimiter: '_'})
var UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', {age: Sequelize.INTEGER})
UserSpecialUnderscore.sync({force: true}).success(function(User){
UserSpecialDblUnderscore.schema('hello', '__').sync({force: true}).success(function(DblUser){
DblUser.create({age: 3}).on('sql', function(dblSql){
User.create({age: 3}).on('sql', function(sql){
expect(dblSql).to.exist
expect(dblSql.indexOf('INSERT INTO `hello__UserSpecialDblUnderscores`')).to.be.above(-1)
expect(sql).to.exist
expect(sql.indexOf('INSERT INTO `hello_UserSpecialUnderscores`')).to.be.above(-1)
done()
})
})
})
})
})
}
it("should be able to create and update records under any valid schematic", function(done){
var self = this
self.UserPublic.sync({ force: true }).success(function(UserPublicSync){
UserPublicSync.create({age: 3}).on('sql', function(UserPublic){
self.UserSpecialSync.schema('special').create({age: 3})
.on('sql', function(UserSpecial){
expect(UserSpecial).to.exist
expect(UserPublic).to.exist
if (dialect === "postgres") {
expect(self.UserSpecialSync.getTableName()).to.equal('"special"."UserSpecials"');
expect(UserSpecial.indexOf('INSERT INTO "special"."UserSpecials"')).to.be.above(-1)
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).to.be.above(-1)
} else if (dialect === "sqlite") {
expect(self.UserSpecialSync.getTableName()).to.equal('`special`.`UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).to.be.above(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1)
} else {
expect(self.UserSpecialSync.getTableName()).to.equal('`special.UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).to.be.above(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1)
}
})
.success(function(UserSpecial){
UserSpecial.updateAttributes({age: 5})
.on('sql', function(user){
expect(user).to.exist
if (dialect === "postgres") {
expect(user.indexOf('UPDATE "special"."UserSpecials"')).to.be.above(-1)
} else {
expect(user.indexOf('UPDATE `special.UserSpecials`')).to.be.above(-1)
}
done()
})
})
})
})
})
})
describe('references', function() {
this.timeout(3000)
beforeEach(function(done) {
var self = this
self.Author = self.sequelize.define('author', { firstName: Sequelize.STRING })
self.Author.sync({ force: true }).success(function() {
done()
})
})
afterEach(function(done) {
var self = this
self.sequelize.getQueryInterface().dropTable('posts', { force: true }).success(function() {
self.sequelize.getQueryInterface().dropTable('authors', { force: true }).success(function() {
done()
})
})
})
it('uses an existing dao factory and references the author table', function(done) {
var self = this
, Post = this.sequelize.define('post', {
title: Sequelize.STRING,
authorId: {
type: Sequelize.INTEGER,
references: this.Author,
referencesKey: "id"
}
})
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
}
else if (dialect === 'mysql') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
}
else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
} else {
throw new Error('Undefined dialect!')
}
done()
})
})
it('uses a table name as a string and references the author table', function(done) {
var self = this
, Post = self.sequelize.define('post', {
title: Sequelize.STRING,
authorId: {
type: Sequelize.INTEGER,
references: 'authors',
referencesKey: "id"
}
})
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
Post.sync().on('sql', function(sql) {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/)
}
else if (dialect === 'mysql') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/)
}
else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/)
} else {
throw new Error('Undefined dialect!')
}
done()
})
})
it("emits an error event as the referenced table name is invalid", function(done) {
var self = this
, Post = this.sequelize.define('post', {
title: Sequelize.STRING,
authorId: {
type: Sequelize.INTEGER,
references: '4uth0r5',
referencesKey: "id"
}
})
this.Author.hasMany(Post)
Post.belongsTo(this.Author)
Post.sync().success(function() {
if (dialect === 'sqlite') {
// sorry ... but sqlite is too stupid to understand whats going on ...
expect(1).to.equal(1)
done()
} else {
// the parser should not end up here ...
expect(2).to.equal(1)
done()
}
}).error(function(err) {
if (dialect === 'mysql') {
expect(err.message).to.match(/ER_CANNOT_ADD_FOREIGN|ER_CANT_CREATE_TABLE/)
}
else if (dialect === 'sqlite') {
// the parser should not end up here ... see above
expect(1).to.equal(2)
}
else if (dialect === 'postgres') {
expect(err.message).to.match(/relation "4uth0r5" does not exist/)
} else {
throw new Error('Undefined dialect!')
}
done()
})
})
})
})
/* jshint camelcase: false */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
chai.use(datetime)
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("DAO"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1,20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
this.User.sync({ force: true }).success(function() {
done()
})
})
describe('Escaping', function() {
it('is done properly for special characters', function(done) {
// Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a"
// But this causes sqlite to fail and exits the entire test suite immediately
var bio = dialect + "'\"\n" // Need to add the dialect here so in case of failure I know what DB it failed for
, self = this
this.User.create({ username: bio }).success(function(u1) {
self.User.find(u1.id).success(function(u2) {
expect(u2.username).to.equal(bio)
done()
})
})
})
})
describe('isNewRecord', function() {
it('returns true for non-saved objects', function(done) {
var user = this.User.build({ username: 'user' })
expect(user.id).to.be.null
expect(user.isNewRecord).to.be.ok
done()
})
it("returns false for saved objects", function(done) {
this.User.build({ username: 'user' }).save().success(function(user) {
expect(user.isNewRecord).to.not.be.ok
done()
})
})
it("returns false for created objects", function(done) {
this.User.create({ username: 'user' }).success(function(user) {
expect(user.isNewRecord).to.not.be.ok
done()
})
})
it("returns false for objects found by find method", function(done) {
var self = this
this.User.create({ username: 'user' }).success(function() {
self.User.create({ username: 'user' }).success(function(user) {
self.User.find(user.id).success(function(user) {
expect(user.isNewRecord).to.not.be.ok
done()
})
})
})
})
it("returns false for objects found by findAll method", function(done) {
var self = this
, users = []
for (var i = 0; i < 10; i++) {
users[users.length] = {username: 'user'}
}
this.User.bulkCreate(users).success(function() {
self.User.findAll().success(function(users) {
users.forEach(function(u) {
expect(u.isNewRecord).to.not.be.ok
})
done()
})
})
})
})
describe('increment', function () {
beforeEach(function(done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).complete(function(){
done()
})
})
it('with array', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment(['aNumber'], 2).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(2)
done()
})
})
})
})
it('with single field', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment('aNumber', 2).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(2)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function (err, user1) {
// Select the user again (simulating a concurrent query)
self.User.find(1).complete(function (err, user2) {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).complete(function () {
user1.increment(['aNumber'], 2).complete(function() {
self.User.find(1).complete(function(err, user5) {
expect(user5.aNumber).to.be.equal(3)
done()
})
})
})
})
})
})
it('should still work right with other concurrent increments', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
var _done = _.after(3, function() {
self.User.find(1).complete(function(err, user2) {
expect(user2.aNumber).to.equal(6)
done()
})
})
user1.increment(['aNumber'], 2).complete(_done)
user1.increment(['aNumber'], 2).complete(_done)
user1.increment(['aNumber'], 2).complete(_done)
})
})
it('with key value pair', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.increment({ 'aNumber': 1, 'bNumber': 2}).success(function() {
self.User.find(1).complete(function (err, user3) {
expect(user3.aNumber).to.be.equal(1)
expect(user3.bNumber).to.be.equal(2)
done()
})
})
})
})
})
describe('decrement', function () {
beforeEach(function(done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).complete(done)
})
it('with array', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement(['aNumber'], 2).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(-2)
done()
})
})
})
})
it('with single field', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement('aNumber', 2).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(-2)
done()
})
})
})
})
it('should still work right with other concurrent updates', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
// Select the user again (simulating a concurrent query)
self.User.find(1).complete(function(err, user2) {
user2.updateAttributes({
aNumber: user2.aNumber + 1
}).complete(function () {
user1.decrement(['aNumber'], 2).complete(function() {
self.User.find(1).complete(function(err, user5) {
expect(user5.aNumber).to.be.equal(-1)
done()
})
})
})
})
})
})
it('should still work right with other concurrent increments', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
var _done = _.after(3, function() {
self.User.find(1).complete(function (err, user2) {
expect(user2.aNumber).to.equal(-6)
done()
})
})
user1.decrement(['aNumber'], 2).complete(_done)
user1.decrement(['aNumber'], 2).complete(_done)
user1.decrement(['aNumber'], 2).complete(_done)
})
})
it('with key value pair', function(done) {
var self = this
this.User.find(1).complete(function(err, user1) {
user1.decrement({ 'aNumber': 1, 'bNumber': 2}).complete(function() {
self.User.find(1).complete(function(err, user3) {
expect(user3.aNumber).to.be.equal(-1)
expect(user3.bNumber).to.be.equal(-2)
done()
})
})
})
})
})
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function(done) {
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
originalUser.updateAttributes({ username: 'Doe John' }).complete(function() {
originalUser.reload().complete(function (err, updatedUser) {
expect(originalUser === updatedUser).to.be.true
done()
})
})
})
})
it("should update the values on all references to the DAO", function(done) {
var self = this
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
self.User.find(originalUser.id).complete(function(err, updater) {
updater.updateAttributes({ username: 'Doe John' }).complete(function() {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).to.equal('John Doe')
originalUser.reload().complete(function(err, updatedUser) {
expect(originalUser.username).to.equal('Doe John')
expect(updatedUser.username).to.equal('Doe John')
done()
})
})
})
})
})
it("should update read only attributes as well (updatedAt)", function(done) {
var self = this
this.timeout = 2000
this.User.create({ username: 'John Doe' }).complete(function(err, originalUser) {
var originallyUpdatedAt = originalUser.updatedAt
// Wait for a second, so updatedAt will actually be different
setTimeout(function () {
self.User.find(originalUser.id).complete(function(err, updater) {
updater.updateAttributes({ username: 'Doe John' }).complete(function () {
originalUser.reload().complete(function(err, updatedUser) {
expect(originalUser.updatedAt).to.be.above(originallyUpdatedAt)
expect(updatedUser.updatedAt).to.be.above(originallyUpdatedAt)
done()
})
})
})
}, 1000)
})
})
it("should update the associations as well", function(done) {
var Book = this.sequelize.define('Book', { title: DataTypes.STRING })
, Page = this.sequelize.define('Page', { content: DataTypes.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
Book.sync().success(function() {
Page.sync().success(function() {
Book.create({ title: 'A very old book' }).success(function(book) {
Page.create({ content: 'om nom nom' }).success(function(page) {
book.setPages([ page ]).success(function() {
Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).success(function(leBook) {
page.updateAttributes({ content: 'something totally different' }).success(function(page) {
expect(leBook.pages[0].content).to.equal('om nom nom')
expect(page.content).to.equal('something totally different')
leBook.reload().success(function(leBook) {
expect(leBook.pages[0].content).to.equal('something totally different')
expect(page.content).to.equal('something totally different')
done()
})
})
})
})
})
})
})
})
})
})
describe('default values', function() {
describe('current date', function() {
it('should store a date in touchedAt', function(done) {
var user = this.User.build({ username: 'a user'})
expect(user.touchedAt).to.be.instanceof(Date)
done()
})
it("should store the current date in touchedAt", function(done) {
var clock = sinon.useFakeTimers()
clock.tick(5000)
var user = this.User.build({ username: 'a user'})
clock.restore()
expect(+user.touchedAt).to.be.equal(5000)
done()
})
})
describe('allowNull date', function() {
it('should be just "null" and not Date with Invalid Date', function(done) {
var self = this
this.User.build({ username: 'a user'}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue).to.be.null
done()
})
})
})
it('should be the same valid date when saving the date', function(done) {
var self = this
var date = new Date()
this.User.build({ username: 'a user', dateAllowNullTrue: date}).save().success(function() {
self.User.find({where: {username: 'a user'}}).success(function(user) {
expect(user.dateAllowNullTrue.toString()).to.equal(date.toString())
done()
})
})
})
})
})
describe('complete', function() {
it("gets triggered if an error occurs", function(done) {
this.User.find({ where: "asdasdasd" }).complete(function(err) {
expect(err).to.exist
expect(err.message).to.exist
done()
})
})
it("gets triggered if everything was ok", function(done) {
this.User.count().complete(function(err, result) {
expect(err).to.be.null
expect(result).to.exist
done()
})
})
})
describe('save', function() {
this.timeout(3000) // for update timestamp
it('only updates fields in passed array', function(done) {
var self = this
, userId = null
, date = new Date(1990, 01, 01)
this.User.create({
username: 'foo',
touchedAt: new Date()
}).success(function(user) {
user.username = 'fizz'
user.touchedAt = date
user.save(['username']).success(function(){
// re-select user
self.User.find(user.id).success(function(user2) {
// name should have changed
expect(user2.username).to.equal('fizz')
// bio should be unchanged
expect(user2.birthDate).not.to.equal(date)
done()
})
})
})
})
it("stores an entry in the database", function(done) {
var username = 'user'
, User = this.User
, user = this.User.build({
username: username,
touchedAt: new Date(1984, 8, 23)
})
User.all().success(function(users) {
expect(users).to.have.length(0)
user.save().success(function(){
User.all().success(function(users) {
expect(users).to.have.length(1)
expect(users[0].username).to.equal(username)
expect(users[0].touchedAt).to.be.instanceof(Date)
expect(users[0].touchedAt).to.equalDate(new Date(1984, 8, 23))
done()
})
})
})
})
it("updates the timestamps", function(done) {
var now = Date.now()
, user = null
, updatedAt = null
, User = this.User
// timeout is needed, in order to check the update of the timestamp
var build = function(callback) {
user = User.build({ username: 'user' })
updatedAt = user.updatedAt
expect(updatedAt.getTime()).to.be.above(now)
setTimeout(function() {
user.save().success(function() {
expect(updatedAt.getTime()).to.be.below(user.updatedAt.getTime())
callback()
})
}, 1000)
}
// closures are fun :)
setTimeout(function() {
build(function() {
done()
})
}, 1000)
})
describe('without timestamps option', function() {
it("doesn't update the updatedAt column", function(done) {
var User2 = this.sequelize.define('User2', {
username: DataTypes.STRING,
updatedAt: DataTypes.DATE
}, { timestamps: false })
User2.sync().success(function() {
User2.create({ username: 'john doe' }).success(function(johnDoe) {
// sqlite and mysql return undefined, whereas postgres returns null
expect([undefined, null].indexOf(johnDoe.updatedAt)).not.to.be.equal(-1)
done()
})
})
})
})
it('should fail a validation upon creating', function(done){
this.User.create({aNumber: 0, validateTest: 'hello'}).error(function(err){
expect(err).to.exist
expect(err).to.be.instanceof(Object)
expect(err.validateTest).to.be.instanceof(Array)
expect(err.validateTest[0]).to.exist
expect(err.validateTest[0].indexOf('Invalid integer')).to.be.above(-1)
done()
})
})
it('should fail a validation upon building', function(done){
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.error(function(err){
expect(err).to.exist
expect(err).to.be.instanceof(Object)
expect(err.validateCustom).to.exist
expect(err.validateCustom).to.be.instanceof(Array)
expect(err.validateCustom[0]).to.exist
expect(err.validateCustom[0]).to.equal('Length failed.')
done()
})
})
it('should fail a validation when updating', function(done){
this.User.create({aNumber: 0}).success(function(user){
user.updateAttributes({validateTest: 'hello'}).error(function(err){
expect(err).to.exist
expect(err).to.be.instanceof(Object)
expect(err.validateTest).to.exist
expect(err.validateTest).to.be.instanceof(Array)
expect(err.validateTest[0]).to.exist
expect(err.validateTest[0].indexOf('Invalid integer')).to.be.above(-1)
done()
})
})
})
it('takes zero into account', function(done) {
this.User.build({ aNumber: 0 }).save([ 'aNumber' ]).success(function(user) {
expect(user.aNumber).to.equal(0)
done()
})
})
it('saves a record with no primary key', function(done){
var HistoryLog = this.sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
HistoryLog.sync().success(function() {
HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }).success(function(log) {
log.updateAttributes({ aNumber: 5 }).success(function(newLog){
expect(newLog.aNumber).to.equal(5)
done()
})
})
})
})
describe('eagerly loaded objects', function() {
beforeEach(function(done) {
var self = this
this.UserEager = this.sequelize.define('UserEagerLoadingSaves', {
username: DataTypes.STRING,
age: DataTypes.INTEGER
}, { timestamps: false })
this.ProjectEager = this.sequelize.define('ProjectEagerLoadingSaves', {
title: DataTypes.STRING,
overdue_days: DataTypes.INTEGER
}, { timestamps: false })
this.UserEager.hasMany(this.ProjectEager, { as: 'Projects' })
this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah' })
self.UserEager.sync({force: true}).success(function() {
self.ProjectEager.sync({force: true}).success(function() {
done()
})
})
})
it('saves one object that has a collection of eagerly loaded objects', function(done) {
var self = this
this.UserEager.create({ username: 'joe', age: 1 }).success(function(user) {
self.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).success(function(project1) {
self.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).success(function(project2) {
user.setProjects([project1, project2]).success(function() {
self.UserEager.find({where: {age: 1}, include: [{model: self.ProjectEager, as: 'Projects'}]}).success(function(user) {
expect(user.username).to.equal('joe')
expect(user.age).to.equal(1)
expect(user.projects).to.exist
expect(user.projects.length).to.equal(2)
user.age = user.age + 1 // happy birthday joe
user.save().success(function() {
expect(user.username).to.equal('joe')
expect(user.age).to.equal(2)
expect(user.projects).to.exist
expect(user.projects.length).to.equal(2)
done()
})
})
})
})
})
})
})
it('saves many objects that each a have collection of eagerly loaded objects', function(done) {
var self = this
this.UserEager.create({ username: 'bart', age: 20 }).success(function(bart) {
self.UserEager.create({ username: 'lisa', age: 20 }).success(function(lisa) {
self.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).success(function(detention1) {
self.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).success(function(detention2) {
self.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).success(function(exam1) {
self.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).success(function(exam2) {
bart.setProjects([detention1, detention2]).success(function() {
lisa.setProjects([exam1, exam2]).success(function() {
self.UserEager.findAll({where: {age: 20}, order: 'username ASC', include: [{model: self.ProjectEager, as: 'Projects'}]}).success(function(simpsons) {
var _bart, _lisa
expect(simpsons.length).to.equal(2)
_bart = simpsons[0]
_lisa = simpsons[1]
expect(_bart.projects).to.exist
expect(_lisa.projects).to.exist
expect(_bart.projects.length).to.equal(2)
expect(_lisa.projects.length).to.equal(2)
_bart.age = _bart.age + 1 // happy birthday bart - off to Moe's
_bart.save().success(function(savedbart) {
expect(savedbart.username).to.equal('bart')
expect(savedbart.age).to.equal(21)
_lisa.username = 'lsimpson'
_lisa.save().success(function(savedlisa) {
expect(savedlisa.username).to.equal('lsimpson')
expect(savedlisa.age).to.equal(20)
done()
})
})
})
})
})
})
})
})
})
})
})
})
it('saves many objects that each has one eagerly loaded object (to which they belong)', function(done) {
var self = this
this.UserEager.create({ username: 'poobah', age: 18 }).success(function(user) {
self.ProjectEager.create({ title: 'homework', overdue_days: 10 }).success(function(homework) {
self.ProjectEager.create({ title: 'party', overdue_days: 2 }).success(function(party) {
user.setProjects([homework, party]).success(function() {
self.ProjectEager.findAll({include: [{model: self.UserEager, as: 'Poobah'}]}).success(function(projects) {
expect(projects.length).to.equal(2)
expect(projects[0].poobah).to.exist
expect(projects[1].poobah).to.exist
expect(projects[0].poobah.username).to.equal('poobah')
expect(projects[1].poobah.username).to.equal('poobah')
projects[0].title = 'partymore'
projects[1].title = 'partymore'
projects[0].overdue_days = 0
projects[1].overdue_days = 0
projects[0].save().success(function() {
projects[1].save().success(function() {
self.ProjectEager.findAll({where: {title: 'partymore', overdue_days: 0}, include: [{model: self.UserEager, as: 'Poobah'}]}).success(function(savedprojects) {
expect(savedprojects.length).to.equal(2)
expect(savedprojects[0].poobah).to.exist
expect(savedprojects[1].poobah).to.exist
expect(savedprojects[0].poobah.username).to.equal('poobah')
expect(savedprojects[1].poobah.username).to.equal('poobah')
done()
})
})
})
})
})
})
})
})
})
})
})
describe('toJSON', function() {
beforeEach(function(done) {
var self = this
this.User = this.sequelize.define('UserWithUsernameAndAgeAndIsAdmin', {
username: DataTypes.STRING,
age: DataTypes.INTEGER,
isAdmin: DataTypes.BOOLEAN
}, { timestamps: false })
this.Project = this.sequelize.define('NiceProject', { title: DataTypes.STRING }, { timestamps: false })
this.User.hasMany(this.Project, { as: 'Projects' })
this.Project.belongsTo(this.User, { as: 'LovelyUser' })
this.User.sync({ force: true }).success(function() {
self.Project.sync({ force: true }).success(function() {
done()
})
})
})
it('returns an object containing all values', function(done) {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(user.toJSON()).to.deep.equal({ username: 'test.user', age: 99, isAdmin: true, id: null })
done()
})
it('returns a response that can be stringified', function(done) {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.stringify(user)).to.deep.equal('{"username":"test.user","age":99,"isAdmin":true,"id":null}')
done()
})
it('returns a response that can be stringified and then parsed', function(done) {
var user = this.User.build({ username: 'test.user', age: 99, isAdmin: true })
expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ username: 'test.user', age: 99, isAdmin: true, id: null })
done()
})
it('includes the eagerly loaded associations', function(done) {
var self = this
this.User.create({ username: 'fnord', age: 1, isAdmin: true }).success(function(user) {
self.Project.create({ title: 'fnord' }).success(function(project) {
user.setProjects([ project ]).success(function() {
self.User.findAll({include: [ { model: self.Project, as: 'Projects' } ]}).success(function(users) {
var _user = users[0]
expect(_user.projects).to.exist
expect(JSON.parse(JSON.stringify(_user)).projects).to.exist
self.Project.findAll({include: [ { model: self.User, as: 'LovelyUser' } ]}).success(function(projects) {
var _project = projects[0]
expect(_project.lovelyUser).to.exist
expect(JSON.parse(JSON.stringify(_project)).lovelyUser).to.exist
done()
})
})
})
})
})
})
})
describe('findAll', function() {
beforeEach(function(done) {
this.ParanoidUser = this.sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, { paranoid: true })
this.ParanoidUser.hasOne(this.ParanoidUser)
this.ParanoidUser.sync({ force: true }).success(function() {
done()
})
})
it("escapes a single single quotes properly in where clauses", function(done) {
var self = this
this.User
.create({ username: "user'name" })
.success(function() {
self.User.findAll({
where: { username: "user'name" }
}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("user'name")
done()
})
})
})
it("escapes two single quotes properly in where clauses", function(done) {
var self = this
this.User
.create({ username: "user''name" })
.success(function() {
self.User.findAll({
where: { username: "user''name" }
}).success(function(users) {
expect(users.length).to.equal(1)
expect(users[0].username).to.equal("user''name")
done()
})
})
})
it("returns the timestamps if no attributes have been specified", function(done) {
var self = this
this.User.create({ username: 'fnord' }).success(function() {
self.User.findAll().success(function(users) {
expect(users[0].createdAt).to.exist
done()
})
})
})
it("does not return the timestamps if the username attribute has been specified", function(done) {
var self = this
this.User.create({ username: 'fnord' }).success(function() {
self.User.findAll({ attributes: ['username'] }).success(function(users) {
expect(users[0].createdAt).not.to.exist
expect(users[0].username).to.exist
done()
})
})
})
it("creates the deletedAt property, when defining paranoid as true", function(done) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
self.ParanoidUser.findAll().success(function(users) {
expect(users[0].deletedAt).to.be.null
done()
})
})
})
it("sets deletedAt property to a specific date when deleting an instance", function(done) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
self.ParanoidUser.findAll().success(function(users) {
users[0].destroy().success(function(user) {
expect(user.deletedAt.getMonth).to.exist
done()
})
})
})
})
it("keeps the deletedAt-attribute with value null, when running updateAttributes", function(done) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
self.ParanoidUser.findAll().success(function(users) {
users[0].updateAttributes({username: 'newFnord'}).success(function(user) {
expect(user.deletedAt).to.be.null
done()
})
})
})
})
it("keeps the deletedAt-attribute with value null, when updating associations", function(done) {
var self = this
this.ParanoidUser.create({ username: 'fnord' }).success(function() {
self.ParanoidUser.findAll().success(function(users) {
self.ParanoidUser.create({ username: 'linkedFnord' }).success(function(linkedUser) {
users[0].setParanoidUser( linkedUser ).success(function(user) {
expect(user.deletedAt).to.be.null
done()
})
})
})
})
})
it("can reuse query option objects", function(done) {
var self = this
this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }}
self.User.findAll(query).success(function(users) {
expect(users[0].username).to.equal('fnord')
self.User.findAll(query).success(function(users) {
expect(users[0].username).to.equal('fnord')
done()
})
})
})
})
})
describe('find', function() {
it("can reuse query option objects", function(done) {
var self = this
this.User.create({ username: 'fnord' }).success(function() {
var query = { where: { username: 'fnord' }}
self.User.find(query).success(function(user) {
expect(user.username).to.equal('fnord')
self.User.find(query).success(function(user) {
expect(user.username).to.equal('fnord')
done()
})
})
})
})
})
describe('equals', function() {
it("can compare records with Date field", function(done) {
var self = this
this.User.create({ username: 'fnord' }).success(function(user1) {
var query = { where: { username: 'fnord' }}
self.User.find(query).success(function(user2) {
expect(user1.equals(user2)).to.be.true
done()
})
})
})
})
describe('values', function() {
it('returns all values', function(done) {
var User = this.sequelize.define('UserHelper', {
username: DataTypes.STRING
}, { timestamps: false, logging: false })
User.sync().success(function() {
var user = User.build({ username: 'foo' })
expect(user.values).to.deep.equal({ username: "foo", id: null })
done()
})
})
})
describe('updateAttributes', function() {
it("updates attributes in the database", function(done) {
this.User.create({ username: 'user' }).success(function(user) {
expect(user.username).to.equal('user')
user.updateAttributes({ username: 'person' }).success(function(user) {
expect(user.username).to.equal('person')
done()
})
})
})
it("ignores unknown attributes", function(done) {
this.User.create({ username: 'user' }).success(function(user) {
user.updateAttributes({ username: 'person', foo: 'bar'}).success(function(user) {
expect(user.username).to.equal('person')
expect(user.foo).not.to.exist
done()
})
})
})
it("doesn't update primary keys or timestamps", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
name: DataTypes.STRING,
bio: DataTypes.TEXT,
identifier: {type: DataTypes.STRING, primaryKey: true}
})
User.sync({ force: true }).success(function(){
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
var oldCreatedAt = user.createdAt
, oldIdentifier = user.identifier
user.updateAttributes({
name: 'foobar',
createdAt: new Date(2000, 1, 1),
identifier: 'another identifier'
}).success(function(user) {
expect((new Date(user.createdAt)).getTime()).to.equal((new Date(oldCreatedAt)).getTime())
expect(user.identifier).to.equal(oldIdentifier)
done()
})
})
})
})
it("uses primary keys in where clause", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
name: DataTypes.STRING,
bio: DataTypes.TEXT,
identifier: {type: DataTypes.STRING, primaryKey: true}
})
User.sync({ force: true }).success(function(){
User.create({
name: 'snafu',
identifier: 'identifier'
}).success(function(user) {
var emitter = user.updateAttributes({name: 'foobar'})
emitter.on('sql', function(sql) {
expect(sql).to.match(/WHERE [`"]identifier[`"]..identifier./)
done()
})
})
})
})
it('stores and restores null values', function(done) {
var Download = this.sequelize.define('download', {
startedAt: DataTypes.DATE,
canceledAt: DataTypes.DATE,
finishedAt: DataTypes.DATE
})
Download.sync().success(function() {
Download.create({
startedAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).to.be.true
expect(download.canceledAt).to.not.be.ok
expect(download.finishedAt).to.not.be.ok
download.updateAttributes({
canceledAt: new Date()
}).success(function(download) {
expect(download.startedAt instanceof Date).to.be.true
expect(download.canceledAt instanceof Date).to.be.true
expect(download.finishedAt).to.not.be.ok
Download.all({
where: (dialect === 'postgres' ? '"finishedAt" IS NULL' : "`finishedAt` IS NULL")
}).success(function(downloads) {
downloads.forEach(function(download) {
expect(download.startedAt instanceof Date).to.be.true
expect(download.canceledAt instanceof Date).to.be.true
expect(download.finishedAt).to.not.be.ok
done()
})
})
})
})
})
})
})
})
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, Sequelize = require("../index") , Sequelize = require(__dirname + '/../index')
, Helpers = require('./buster-helpers') , Support = require(__dirname + '/support')
, dialect = Helpers.getTestDialect() , config = require(__dirname + '/config/config')
}
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { describe(Support.getTestDialectTeaser("DaoValidator"), function() {
describe('validations', function() { describe('validations', function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
onComplete: function(sequelize) {
this.sequelize = sequelize
done()
}.bind(this)
})
}) //- before
var checks = { var checks = {
is: { is: {
spec: { args: ["[a-z]",'i'] }, spec: { args: ["[a-z]",'i'] },
...@@ -44,9 +32,9 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -44,9 +32,9 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
pass: "129.89.23.1" pass: "129.89.23.1"
} }
, isIPv6 : { , isIPv6 : {
fail: '1111:2222:3333::5555:', fail: '1111:2222:3333::5555:',
pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156' pass: 'fe80:0000:0000:0000:0204:61ff:fe9d:f156'
} }
, isAlpha : { , isAlpha : {
fail: "012", fail: "012",
pass: "abc" pass: "abc"
...@@ -116,72 +104,72 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -116,72 +104,72 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
fail: "a", fail: "a",
pass: "0" pass: "0"
} }
, len : { , len: {
spec: { args: [2,4] }, spec: { args: [2,4] },
fail: ["1", "12345"], fail: ["1", "12345"],
pass: ["12", "123", "1234"], pass: ["12", "123", "1234"],
raw: true raw: true
} }
, len: { , len$: {
spec: [2,4], spec: [2,4],
fail: ["1", "12345"], fail: ["1", "12345"],
pass: ["12", "123", "1234"], pass: ["12", "123", "1234"],
raw: true raw: true
} }
, isUUID : { , isUUID: {
spec: { args: 4 }, spec: { args: 4 },
fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479", fail: "f47ac10b-58cc-3372-a567-0e02b2c3d479",
pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479" pass: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
} }
, isDate : { , isDate: {
fail: "not a date", fail: "not a date",
pass: "2011-02-04" pass: "2011-02-04"
} }
, isAfter : { , isAfter: {
spec: { args: "2011-11-05" }, spec: { args: "2011-11-05" },
fail: "2011-11-04", fail: "2011-11-04",
pass: "2011-11-05" pass: "2011-11-05"
} }
, isBefore : { , isBefore: {
spec: { args: "2011-11-05" }, spec: { args: "2011-11-05" },
fail: "2011-11-06", fail: "2011-11-06",
pass: "2011-11-05" pass: "2011-11-05"
} }
, isIn : { , isIn: {
spec: { args: "abcdefghijk" }, spec: { args: "abcdefghijk" },
fail: "ghik", fail: "ghik",
pass: "ghij" pass: "ghij"
} }
, notIn : { , notIn: {
spec: { args: "abcdefghijk" }, spec: { args: "abcdefghijk" },
fail: "ghij", fail: "ghij",
pass: "ghik" pass: "ghik"
} }
, max : { , max: {
spec: { args: 23 }, spec: { args: 23 },
fail: "24", fail: "24",
pass: "23" pass: "23"
} }
, max : { , max$: {
spec: 23, spec: 23,
fail: "24", fail: "24",
pass: "23" pass: "23"
} }
, min : { , min: {
spec: { args: 23 }, spec: { args: 23 },
fail: "22", fail: "22",
pass: "23" pass: "23"
} }
, min : { , min$: {
spec: 23, spec: 23,
fail: "22", fail: "22",
pass: "23" pass: "23"
} }
, isArray : { , isArray: {
fail: 22, fail: 22,
pass: [22] pass: [22]
} }
, isCreditCard : { , isCreditCard: {
fail: "401288888888188f", fail: "401288888888188f",
pass: "4012888888881881" pass: "4012888888881881"
} }
...@@ -189,6 +177,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -189,6 +177,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
for (var validator in checks) { for (var validator in checks) {
if (checks.hasOwnProperty(validator)) { if (checks.hasOwnProperty(validator)) {
validator = validator.replace(/\$$/, '')
var validatorDetails = checks[validator] var validatorDetails = checks[validator]
if (!validatorDetails.hasOwnProperty("raw")) { if (!validatorDetails.hasOwnProperty("raw")) {
...@@ -214,7 +204,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -214,7 +204,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
validations[validator].msg = message validations[validator].msg = message
var UserFail = this.sequelize.define('User' + Math.random(), { var UserFail = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
...@@ -222,20 +212,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -222,20 +212,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
}) })
var failingUser = UserFail.build({ name : failingValue }) var failingUser = UserFail.build({ name : failingValue })
, errors = undefined; , errors = undefined
failingUser.validate().done( function(err,_errors) { failingUser.validate().done( function(err,_errors) {
expect(_errors).not.toBeNull(); expect(_errors).to.not.be.null
expect(_errors).toEqual({ name : [message] }); expect(_errors).to.deep.eql({ name : [message] })
done(); done()
}); })
}); })
} }
//////////////////////////// ////////////////////////////
// test the success cases // // test the success cases //
//////////////////////////// ////////////////////////////
for (var j = 0; j < validatorDetails.pass.length; j++) { for (var j = 0; j < validatorDetails.pass.length; j++) {
var succeedingValue = validatorDetails.pass[j] var succeedingValue = validatorDetails.pass[j]
...@@ -250,29 +240,83 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -250,29 +240,83 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
validations[validator].msg = validator + "(" + succeedingValue + ")" validations[validator].msg = validator + "(" + succeedingValue + ")"
var UserSuccess = this.sequelize.define('User' + Math.random(), { var UserSuccess = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: validations validate: validations
} }
}) })
var successfulUser = UserSuccess.build({ name: succeedingValue }); var successfulUser = UserSuccess.build({ name: succeedingValue })
successfulUser.validate().success( function() { successfulUser.validate().success( function() {
expect(arguments.length).toBe(0); expect(arguments).to.have.length(0)
done(); done()
}).error(function(err) { }).error(function(err) {
expect(err).toBe({}) expect(err).to.be.deep.equal({})
done(); done()
}); })
}) })
} }
} }
} }
describe('#create', function() {
beforeEach(function(done) {
var self = this
var Project = this.sequelize.define('Project', {
name: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: 'unknown',
validate: {
isIn: [['unknown', 'hello', 'test']]
}
}
})
var Task = this.sequelize.define('Task', {
something: Sequelize.INTEGER
})
Project.hasOne(Task)
Task.hasOne(Project)
Project.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
self.Project = Project
self.Task = Task
done()
})
})
})
it('correctly validates using create method ', function(done) {
var self = this
this.Project.create({}).success(function(project) {
self.Task.create({something: 1}).success(function(task) {
project.setTask(task).success(function(task) {
expect(task.ProjectId).to.not.be.null
task.setProject(project).success(function(project) {
expect(project.ProjectId).to.not.be.null
done()
})
})
})
})
})
it('correctly throws an error using create method ', function(done) {
var self = this
this.Project.create({name: 'nope'}).error(function(err) {
expect(err).to.have.ownProperty('name')
done()
})
})
})
it('correctly validates using custom validation methods', function(done) { it('correctly validates using custom validation methods', function(done) {
var User = this.sequelize.define('User' + Math.random(), { var User = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
...@@ -290,22 +334,21 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -290,22 +334,21 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = User.build({ name : "3" }) var failingUser = User.build({ name : "3" })
failingUser.validate().success(function(errors) { failingUser.validate().success(function(errors) {
expect(errors).toEqual({ name: ["name should equal '2'"] }) expect(errors).to.deep.equal({ name: ["name should equal '2'"] })
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
successfulUser.validate().success(function() { successfulUser.validate().success(function() {
expect(arguments.length).toBe(0); expect(arguments).to.have.length(0)
done(); done()
}).error(function(err) { }).error(function(err) {
expect(err).toBe({}); expect(err).to.deep.equal({})
done(); done()
}) })
}); })
}) })
it('skips other validations if allowNull is true and the value is null', function(done) { it('skips other validations if allowNull is true and the value is null', function(done) {
var User = this.sequelize.define('User' + Math.random(), { var User = this.sequelize.define('User' + config.rand(), {
age: { age: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true, allowNull: true,
...@@ -319,8 +362,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -319,8 +362,8 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
.build({ age: -1 }) .build({ age: -1 })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeNull(null) expect(errors).not.to.be.null
expect(errors).toEqual({ age: ['must be positive'] }) expect(errors).to.deep.equal({ age: ['must be positive'] })
User.build({ age: null }).validate().success(function() { User.build({ age: null }).validate().success(function() {
User.build({ age: 1 }).validate().success(function() { User.build({ age: 1 }).validate().success(function() {
...@@ -331,7 +374,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -331,7 +374,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
}) })
it('validates a model with custom model-wide validation methods', function(done) { it('validates a model with custom model-wide validation methods', function(done) {
var Foo = this.sequelize.define('Foo' + Math.random(), { var Foo = this.sequelize.define('Foo' + config.rand(), {
field1: { field1: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true allowNull: true
...@@ -356,38 +399,37 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -356,38 +399,37 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
.build({ field1: null, field2: null }) .build({ field1: null, field2: null })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeNull() expect(errors).not.to.be.null
expect(errors).toEqual({ 'xnor': ['xnor failed'] }) expect(errors).to.deep.equal({ 'xnor': ['xnor failed'] })
Foo Foo
.build({ field1: 33, field2: null }) .build({ field1: 33, field2: null })
.validate() .validate()
.success(function(errors) { .success(function(errors) {
expect(errors).not.toBeDefined() expect(errors).not.exist
done() done()
}) })
}) })
}) })
it('validates model with a validator whose arg is an Array successfully twice in a row', function(done){ it('validates model with a validator whose arg is an Array successfully twice in a row', function(done){
var Foo = this.sequelize.define('Foo' + Math.random(), { var Foo = this.sequelize.define('Foo' + config.rand(), {
bar: { bar: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
isIn: [['a', 'b']] isIn: [['a', 'b']]
} }
} }
}), foo; }), foo
foo = Foo foo = Foo.build({bar:'a'})
.build({bar:'a'});
foo.validate().success(function(errors){ foo.validate().success(function(errors){
expect(errors).not.toBeDefined() expect(errors).not.to.exist
foo.validate().success(function(errors){ foo.validate().success(function(errors){
expect(errors).not.toBeDefined() expect(errors).not.to.exist
done(); done()
}); })
}); })
}); })
}) })
}) })
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, Sequelize = require("../index") , Sequelize = require(__dirname + '/../index')
, Helpers = require('./buster-helpers') , Support = require(__dirname + '/support')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose() chai.Assertion.includeStack = true
describe(Helpers.getTestDialectTeaser('DataTypes'), function() { describe(Support.getTestDialectTeaser('DataTypes'), function() {
it('should return DECIMAL for the default decimal type', function() { it('should return DECIMAL for the default decimal type', function(done) {
expect(Sequelize.DECIMAL).toEqual('DECIMAL'); expect(Sequelize.DECIMAL.toString()).to.equal('DECIMAL')
}); done()
})
it('should return DECIMAL(10,2) for the default decimal type with arguments', function() { it('should return DECIMAL(10,2) for the default decimal type with arguments', function(done) {
expect(Sequelize.DECIMAL(10, 2)).toEqual('DECIMAL(10,2)'); expect(Sequelize.DECIMAL(10, 2)).to.equal('DECIMAL(10,2)')
}); done()
})
var tests = [ var tests = [
[Sequelize.STRING, 'STRING', 'VARCHAR(255)'], [Sequelize.STRING, 'STRING', 'VARCHAR(255)'],
...@@ -59,8 +59,9 @@ describe(Helpers.getTestDialectTeaser('DataTypes'), function() { ...@@ -59,8 +59,9 @@ describe(Helpers.getTestDialectTeaser('DataTypes'), function() {
] ]
tests.forEach(function(test) { tests.forEach(function(test) {
it('transforms "' + test[1] + '" to "' + test[2] + '"', function() { it('transforms "' + test[1] + '" to "' + test[2] + '"', function(done) {
expect(test[0]).toEqual(test[2]) expect(test[0].toString()).to.equal(test[2])
done()
}) })
}) })
}) })
if(typeof require === 'function') { /* jshint camelcase: false */
const buster = require("buster") var chai = require('chai')
, CustomEventEmitter = require("../../lib/emitters/custom-event-emitter") , expect = chai.expect
, Helpers = require('../buster-helpers') , Support = require(__dirname + '/../support')
, dialect = Helpers.getTestDialect() , sinon = require('sinon')
} , CustomEventEmitter = require("../../lib/emitters/custom-event-emitter")
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
describe(Support.getTestDialectTeaser("CustomEventEmitter"), function () {
describe(Helpers.getTestDialectTeaser("CustomEventEmitter"), function() { describe("proxy", function () {
describe("proxy", function () { it("should correctly work with success listeners", function(done) {
/* Tests could _probably_ be run synchronously, but for future proofing we're basing it on the events */ var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
it("should correctly work with success listeners", function (done) { , success = sinon.spy()
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter() emitter.success(success)
, success = this.spy() proxy.success(function () {
process.nextTick(function () {
emitter.success(success) expect(success.called).to.be.true
proxy.success(function () { done()
process.nextTick(function () { })
expect(success.called).toEqual(true) })
done();
}) proxy.proxy(emitter)
}) proxy.emit('success')
})
proxy.proxy(emitter)
proxy.emit('success') it("should correctly work with error listeners", function(done) {
}) var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
it("should correctly work with error listeners", function (done) { , error = sinon.spy()
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter() emitter.error(error)
, error = this.spy() proxy.error(function() {
process.nextTick(function() {
emitter.error(error) expect(error.called).to.be.true
proxy.error(function () { done()
process.nextTick(function () { })
expect(error.called).toEqual(true) })
done();
}) proxy.proxy(emitter)
}) proxy.emit('error')
})
proxy.proxy(emitter)
proxy.emit('error') it("should correctly work with complete/done listeners", function(done) {
}) var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter()
it("should correctly work with complete/done listeners", function (done) { , complete = sinon.spy()
var emitter = new CustomEventEmitter()
, proxy = new CustomEventEmitter() emitter.complete(complete)
, complete = this.spy() proxy.complete(function() {
process.nextTick(function() {
emitter.complete(complete) expect(complete.called).to.be.true
proxy.complete(function () { done()
process.nextTick(function () { })
expect(complete.called).toEqual(true) })
done();
}) proxy.proxy(emitter)
}) proxy.emit('success')
})
proxy.proxy(emitter) })
proxy.emit('success') })
})
});
});
\ No newline at end of file
var chai = require('chai')
, expect = chai.expect
, Sequelize = require(__dirname + '/../index')
, Support = require(__dirname + '/support')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Language Util"), function() {
before(function(done) {
this.sequelize.options.language = 'es'
done()
})
after(function(done) {
this.sequelize.options.language = 'en'
done()
})
describe("Plural", function(){
it("should rename tables to their plural form...", function(done){
var self = this
, table = self.sequelize.define('arbol', {name: Sequelize.STRING})
, table2 = self.sequelize.define('androide', {name: Sequelize.STRING})
expect(table.tableName).to.equal('arboles')
expect(table2.tableName).to.equal('androides')
done()
})
it("should be able to pluralize/singularize associations...", function(done){
var self = this
, table = self.sequelize.define('arbol', {name: Sequelize.STRING})
, table2 = self.sequelize.define('androide', {name: Sequelize.STRING})
, table3 = self.sequelize.define('hombre', {name: Sequelize.STRING})
table.hasOne(table2)
table2.belongsTo(table)
table3.hasMany(table2)
expect(table.associations.androides.identifier).to.equal('arbolId')
expect(table2.associations.arboles).to.exist
expect(table3.associations.androideshombres).to.exist
done()
})
})
})
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, QueryChainer = require("../lib/query-chainer")
, Migrator = require("../lib/migrator")
, dialect = Support.getTestDialect()
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Migrator"), function() {
beforeEach(function() {
this.init = function(options, callback) {
options = Support.Sequelize.Utils._.extend({
path: __dirname + '/assets/migrations',
logging: function(){}
}, options || {})
var migrator = new Migrator(this.sequelize, options)
migrator
.findOrCreateSequelizeMetaDAO({ force: true })
.success(function(SequelizeMeta) {
callback && callback(migrator, SequelizeMeta)
})
.error(function(err) { console.log(err) })
}.bind(this)
})
describe('getUndoneMigrations', function() {
it("returns no files if timestamps are after the files timestamp", function(done) {
this.init({ from: 20120101010101 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations.length).to.equal(0)
done()
})
})
})
it("returns only files between from and to", function(done) {
this.init({ from: 19700101000000, to: 20111117063700 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations.length).to.equal(1)
expect(migrations[migrations.length - 1].filename).to.equal('20111117063700-createPerson.js')
done()
})
})
})
it("returns exactly the migration which is defined in from and to", function(done) {
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations.length).to.equal(1)
expect(migrations[migrations.length - 1].filename).to.equal('20111117063700-createPerson.js')
done()
})
})
})
it("returns also the file which is exactly options.from or options.to", function(done) {
this.init({ from: 20111117063700, to: 20111130161100 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations).to.have.length(2)
expect(migrations[0].filename).to.equal('20111117063700-createPerson.js')
expect(migrations[1].filename).to.equal('20111130161100-emptyMigration.js')
done()
})
})
})
it("returns all files to options.to if no options.from is defined", function(done) {
this.init({ to: 20111130161100 }, function(migrator) {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations).to.have.length(2)
done()
})
})
})
it("returns all files from last migration id stored in database", function(done) {
this.init(undefined, function(migrator, SequelizeMeta) {
SequelizeMeta.create({ from: null, to: 20111117063700 }).success(function() {
migrator.getUndoneMigrations(function(err, migrations) {
expect(err).to.be.null
expect(migrations).to.have.length(6)
expect(migrations[0].filename).to.equal('20111130161100-emptyMigration.js')
done()
})
})
})
})
})
describe('migrations', function() {
beforeEach(function(done) {
var self = this
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
self.migrator = migrator
self.migrator.migrate().success(done)
})
})
describe('executions', function() {
it("executes migration #20111117063700 and correctly creates the table", function(done) {
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).to.eql([ 'Person' ])
done()
})
})
it("executes migration #20111117063700 and correctly adds isBetaMember", function(done) {
this.sequelize.getQueryInterface().describeTable('Person').success(function(data) {
var fields = Object.keys(data).sort()
expect(fields).to.eql([ 'isBetaMember', 'name' ])
done()
})
})
it("executes migration #20111117063700 correctly up (createTable) and downwards (dropTable)", function(done) {
var self = this
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).to.eql([ 'Person' ])
self.migrator.migrate({ method: 'down' }).success(function() {
self.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).to.eql([])
done()
})
})
})
})
it("executes the empty migration #20111130161100", function(done) {
this.init({ from: 20111130161100, to: 20111130161100 }, function(migrator) {
// this migration isn't actually testing anything but
// should not timeout
// expect(1).to.equal(1)
migrator
.migrate()
.success(done)
.error(function(err) { console.log(err) })
})
})
})
describe('renameTable', function() {
beforeEach(function(done) {
var self = this
this.init({ from: 20111117063700, to: 20111117063700 }, function(migrator) {
self.migrator = migrator
self.migrator.migrate().success(done)
})
})
it("executes migration #20111205064000 and renames a table", function(done) {
var self = this
this.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e) { return e != 'SequelizeMeta' })
expect(tableNames).to.include('Person')
self.init({ from: 20111205064000, to: 20111205064000 }, function(migrator) {
migrator.migrate().success(function() {
self.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
tableNames = tableNames.filter(function(e){ return e != 'SequelizeMeta' })
expect(tableNames).to.eql([ 'User' ])
done()
})
})
})
})
})
})
describe('addColumn', function() {
it('adds a column to the user table', function(done) {
var self = this
this.init({ from: 20111117063700, to: 20111205162700 }, function(migrator) {
migrator.migrate().complete(function(err) {
self.sequelize.getQueryInterface().describeTable('User').complete(function(err, data) {
var signature = data.signature
, isAdmin = data.isAdmin
, shopId = data.shopId
expect(signature.allowNull).to.be.true
expect(isAdmin.allowNull).to.be.false
if (dialect === "postgres" || dialect === "postgres-native" || dialect === "sqlite") {
expect(isAdmin.defaultValue).to.be.false
} else {
expect(isAdmin.defaultValue).to.equal("0")
}
expect(shopId.allowNull).to.be.true
done()
})
})
})
})
})
describe('removeColumn', function() {
it('removes the shopId column from user', function(done) {
var self = this
this.init({ to: 20111206061400 }, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
, isAdmin = data.isAdmin
, shopId = data.shopId
expect(signature.allowNull).to.be.true
expect(isAdmin.allowNull).to.be.false
if (dialect === "postgres" || dialect === "postgres-native" || dialect === "sqlite") {
expect(isAdmin.defaultValue).to.be.false
} else {
expect(isAdmin.defaultValue).to.equal("0")
}
expect(shopId).to.be.not.ok
done()
})
})
})
})
})
describe('changeColumn', function() {
it('changes the signature column from user to default "signature" + notNull', function(done) {
var self = this
this.init({ to: 20111206063000 }, function(migrator) {
migrator.migrate().success(function() {
self.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
if (dialect === 'postgres') {
expect(signature.type).to.equal('CHARACTER VARYING')
} else {
expect(signature.type).to.equal('VARCHAR(255)')
}
expect(signature.allowNull).to.equal(false)
expect(signature.defaultValue).to.equal('Signature')
done()
})
})
})
})
})
})
describe('renameColumn', function() {
it("renames the signature column from user to sig", function(done) {
var self = this
this.init({ to: 20111206163300 }, function(migrator) {
migrator.migrate().success(function(){
self.sequelize.getQueryInterface().describeTable('User').success(function(data) {
var signature = data.signature
, sig = data.sig
expect(signature).to.not.be.ok
expect(sig).to.be.ok
done()
})
})
})
})
})
})
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 (dialect.match(/^mysql/)) {
describe('[MYSQL Specific] Associations', function() {
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
it("should create a table wp_table1wp_table2s", function(done) {
var Table2 = this.sequelize.define('wp_table2', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('wp_table1', {foo: DataTypes.STRING})
, self = this
Table1.hasMany(Table2)
Table2.hasMany(Table1)
Table1.sync({ force: true }).success(function() {
Table2.sync({ force: true }).success(function() {
expect(self.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).to.exist
done()
})
})
})
})
describe('when join table name is specified', function() {
beforeEach(function(done){
var Table2 = this.sequelize.define('ms_table1', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('ms_table2', {foo: DataTypes.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
Table1.sync({ force: true }).success(function() {
Table2.sync({ force: true }).success(function() {
done()
})
})
})
it("should not use only a specified name", function() {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
})
})
})
describe('HasMany', function() {
beforeEach(function(done) {
//prevent periods from occurring in the table name since they are used to delimit (table.column)
this.User = this.sequelize.define('User' + Math.ceil(Math.random()*10000000), { name: DataTypes.STRING })
this.Task = this.sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: DataTypes.STRING })
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
var self = this
, users = []
, tasks = []
for (var i = 0; i < 5; ++i) {
users[users.length] = {name: 'User' + Math.random()}
}
for (var x = 0; x < 5; ++x) {
tasks[tasks.length] = {name: 'Task' + Math.random()}
}
this.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
done()
})
})
})
})
})
describe('addDAO / getDAO', function() {
beforeEach(function(done) {
var self = this
self.user = null
self.task = null
self.User.all().success(function(_users) {
self.Task.all().success(function(_tasks) {
self.user = _users[0]
self.task = _tasks[0]
done()
})
})
})
it('should correctly add an association to the dao', function(done) {
var self = this
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).to.equal(0)
self.user.addTask(self.task).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).to.equal(1)
done()
})
})
})
})
})
describe('removeDAO', function() {
beforeEach(function(done) {
var self = this
self.user = null
self.tasks = null
self.User.all().success(function(_users) {
self.Task.all().success(function(_tasks) {
self.user = _users[0]
self.tasks = _tasks
done()
})
})
})
it("should correctly remove associated objects", function(done) {
var self = this
self.user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).to.equal(0)
self.user.setTasks(self.tasks).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).to.equal(self.tasks.length)
self.user.removeTask(self.tasks[0]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).to.equal(self.tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
}
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 (dialect.match(/^mysql/)) {
describe('[MYSQL Specific] Connector Manager', function() {
this.timeout(10000)
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: 'user1'}).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)
})
})
})
})
})
}
/* jshint camelcase: false */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
chai.Assertion.includeStack = true
if (dialect.match(/^mysql/)) {
describe("[MYSQL Specific] DAOFactory", function () {
describe('constructor', function() {
it("handles extended attributes (unique)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: { type: DataTypes.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).to.deep.equal({username:"VARCHAR(255) UNIQUE",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
done()
})
it("handles extended attributes (default)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).to.deep.equal({username:"VARCHAR(255) DEFAULT 'foo'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
done()
})
it("handles extended attributes (null)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).to.deep.equal({username:"VARCHAR(255) NOT NULL",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
done()
})
it("handles extended attributes (comment)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, comment: 'This be\'s a comment'}
}, { timestamps: false })
expect(User.attributes).to.deep.equal({username:"VARCHAR(255) COMMENT 'This be\\'s a comment'",id:"INTEGER NOT NULL auto_increment PRIMARY KEY"})
done()
})
it("handles extended attributes (primaryKey)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: {type: DataTypes.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).to.deep.equal({username:"VARCHAR(255) PRIMARY KEY"})
done()
})
it("adds timestamps", function(done) {
var User1 = this.sequelize.define('User' + config.rand(), {})
var User2 = this.sequelize.define('User' + config.rand(), {}, { timestamps: true })
expect(User1.attributes).to.deep.equal({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
expect(User2.attributes).to.deep.equal({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
done()
})
it("adds deletedAt if paranoid", function(done) {
var User = this.sequelize.define('User' + config.rand(), {}, { paranoid: true })
expect(User.attributes).to.deep.equal({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deletedAt:"DATETIME", updatedAt:"DATETIME NOT NULL", createdAt:"DATETIME NOT NULL"})
done()
})
it("underscores timestamps if underscored", function(done) {
var User = this.sequelize.define('User' + config.rand(), {}, { paranoid: true, underscored: true })
expect(User.attributes).to.deep.equal({id:"INTEGER NOT NULL auto_increment PRIMARY KEY", deleted_at:"DATETIME", updated_at:"DATETIME NOT NULL", created_at:"DATETIME NOT NULL"})
done()
})
it('omits text fields with defaultValues', function(done) {
var User = this.sequelize.define('User' + config.rand(), {name: {type: DataTypes.TEXT, defaultValue: 'helloworld'}})
expect(User.attributes.name).to.equal('TEXT')
done()
})
it('omits blobs fields with defaultValues', function(done) {
var User = this.sequelize.define('User' + config.rand(), {name: {type: DataTypes.STRING.BINARY, defaultValue: 'helloworld'}})
expect(User.attributes.name).to.equal('VARCHAR(255) BINARY')
done()
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
foo: {type: DataTypes.STRING, primaryKey: true},
bar: DataTypes.STRING
})
expect(User.primaryKeys).to.deep.equal({"foo":"VARCHAR(255) PRIMARY KEY"})
done()
})
})
})
}
var config = require("../config/config") /* jshint camelcase: false */
, Sequelize = require("../../index") var chai = require('chai')
, sequelize = new Sequelize(config.mysql.database, config.mysql.username, config.mysql.password, { pool: config.mysql.pool, logging: false, host: config.mysql.host, port: config.mysql.port }) , expect = chai.expect
, Helpers = new (require("../config/helpers"))(sequelize) , Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, util = require("util")
, _ = require('lodash')
, QueryGenerator = require("../../lib/dialects/mysql/query-generator") , QueryGenerator = require("../../lib/dialects/mysql/query-generator")
, util = require("util")
describe('QueryGenerator', function() { chai.Assertion.includeStack = true
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var suites = { if (dialect.match(/^mysql/)) {
describe("[MYSQL Specific] QueryGenerator", function () {
var suites = {
attributesToSQL: [
{
arguments: [{id: 'INTEGER'}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'}
},
{
arguments: [{id: {type: 'INTEGER'}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false}}],
expectation: {id: 'INTEGER NOT NULL'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: true}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}],
expectation: {id: 'INTEGER auto_increment PRIMARY KEY'}
},
{
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}],
expectation: {id: 'INTEGER DEFAULT 0'}
},
{
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
{
arguments: [{id: {type: 'INTEGER', comment: "I'm a comment!" }}],
expectation: {id: "INTEGER COMMENT 'I\\'m a comment!'" }
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
}
],
attributesToSQL: [ createTableQuery: [
{ {
arguments: [{id: 'INTEGER'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;"
}, },
{ {
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], arguments: ['myTable', {title: "INTEGER COMMENT 'I\\'m a comment!'"}],
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER COMMENT 'I\\'m a comment!') ENGINE=InnoDB;"
}, },
{ {
arguments: [{id: {type: 'INTEGER'}}], arguments: ['myTable', {title: "INTEGER"}, {comment: "I'm a comment!"}],
expectation: {id: 'INTEGER'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', allowNull: false}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}],
expectation: {id: 'INTEGER NOT NULL'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', allowNull: true}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'utf8', collate: 'utf8_unicode_ci'}],
expectation: {id: 'INTEGER'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: {id: 'INTEGER auto_increment PRIMARY KEY'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: {id: 'INTEGER DEFAULT 0'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', unique: true}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: {id: 'INTEGER UNIQUE'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER , PRIMARY KEY (`id`)) ENGINE=InnoDB;"
}, },
{ {
arguments: [{id: {type: 'INTEGER', comment: "I'm a comment!" }}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: {id: "INTEGER COMMENT 'I\\'m a comment!'" } expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER, FOREIGN KEY (`otherId`) REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION) ENGINE=InnoDB;"
}, }
{ ],
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
},
],
createTableQuery: [ dropTableQuery: [
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable'],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB;" expectation: "DROP TABLE IF EXISTS `myTable`;"
}, }
{ ],
arguments: ['myTable', {title: "INTEGER COMMENT 'I\\'m a comment!'"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER COMMENT 'I\\'m a comment!') ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: "INTEGER"}, {comment: "I'm a comment!"}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` INTEGER) COMMENT 'I\\'m a comment!' ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {engine: 'MyISAM'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=MyISAM;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'utf8', collate: 'utf8_unicode_ci'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_unicode_ci;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
},
{
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}, {charset: 'latin1'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255)) ENGINE=InnoDB DEFAULT CHARSET=latin1;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER , PRIMARY KEY (`id`)) ENGINE=InnoDB;"
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER, FOREIGN KEY (`otherId`) REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION) ENGINE=InnoDB;"
}
],
dropTableQuery: [ selectQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS `myTable`;" expectation: "SELECT * FROM `myTable`;",
} context: QueryGenerator
], }, {
arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT `id`, `name` FROM `myTable`;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo\\';DROP TABLE myTable;';",
context: QueryGenerator
}, {
arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;",
context: QueryGenerator
}, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as `count` FROM `foo`;',
context: QueryGenerator
}, {
arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM `myTable` WHERE foo='bar';",
context: QueryGenerator
}, {
arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name", "title"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator
}, {
title: 'uses default limit if only offset is specified',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 18440000000000000000;",
context: QueryGenerator
}, {
title: 'multiple where arguments',
arguments: ['myTable', {where: {boat: 'canoe', weather: 'cold'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat`='canoe' AND `myTable`.`weather`='cold';",
context: QueryGenerator
}, {
title: 'no where arguments (object)',
arguments: ['myTable', {where: {}}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (string)',
arguments: ['myTable', {where: ''}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (null)',
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}
],
selectQuery: [ insertQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable', {name: 'foo'}],
expectation: "SELECT * FROM `myTable`;", expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');"
context: QueryGenerator }, {
}, { arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
arguments: ['myTable', {attributes: ['id', 'name']}], expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;');"
expectation: "SELECT `id`, `name` FROM `myTable`;", }, {
context: QueryGenerator arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}],
}, { expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');"
arguments: ['myTable', {where: {id: 2}}], }, {
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;", arguments: ['myTable', {name: 'foo', foo: 1}],
context: QueryGenerator expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);"
}, { }, {
arguments: ['myTable', {where: {name: 'foo'}}], arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo';", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);"
context: QueryGenerator }, {
}, { arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);",
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`name`='foo\\';DROP TABLE myTable;';", context: {options: {omitNull: false}}
context: QueryGenerator }, {
}, { arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
arguments: ['myTable', {where: 2}], expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);",
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`id`=2;", context: {options: {omitNull: true}}
context: QueryGenerator }, {
}, { arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}],
arguments: ['foo', { attributes: [['count(*)', 'count']] }], expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);",
expectation: 'SELECT count(*) as `count` FROM `foo`;', context: {options: {omitNull: true}}
context: QueryGenerator }, {
}, { arguments: ['myTable', {foo: false}],
arguments: ['myTable', {where: "foo='bar'"}], expectation: "INSERT INTO `myTable` (`foo`) VALUES (false);"
expectation: "SELECT * FROM `myTable` WHERE foo='bar';", }, {
context: QueryGenerator arguments: ['myTable', {foo: true}],
}, { expectation: "INSERT INTO `myTable` (`foo`) VALUES (true);"
arguments: ['myTable', {order: "id DESC"}], }
expectation: "SELECT * FROM `myTable` ORDER BY id DESC;", ],
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: ["name", "title"]}],
expectation: "SELECT * FROM `myTable` GROUP BY `name`, `title`;",
context: QueryGenerator
}, {
arguments: ['myTable', {group: "name", order: "id DESC"}],
expectation: "SELECT * FROM `myTable` GROUP BY `name` ORDER BY id DESC;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM `myTable` LIMIT 10;",
context: QueryGenerator
}, {
arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 10;",
context: QueryGenerator
}, {
title: 'uses default limit if only offset is specified',
arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM `myTable` LIMIT 2, 18440000000000000000;",
context: QueryGenerator
}, {
title: 'multiple where arguments',
arguments: ['myTable', {where: {boat: 'canoe', weather: 'cold'}}],
expectation: "SELECT * FROM `myTable` WHERE `myTable`.`boat`='canoe' AND `myTable`.`weather`='cold';",
context: QueryGenerator
}, {
title: 'no where arguments (object)',
arguments: ['myTable', {where: {}}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (string)',
arguments: ['myTable', {where: ''}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}, {
title: 'no where arguments (null)',
arguments: ['myTable', {where: null}],
expectation: "SELECT * FROM `myTable` WHERE 1=1;",
context: QueryGenerator
}
],
insertQuery: [ bulkInsertQuery: [
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');" expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;');" expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]],
expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');" expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55'),('bar','2012-03-27 10:01:55');"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);" expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);" expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL);"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: undefined}, {name: 'bar', foo: 2, undefinedValue: undefined}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} context: {options: {omitNull: true}} // Note: As above
}, { }, {
arguments: ['myTable', {foo: false}], arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`foo`) VALUES (false);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);"
}, { }
arguments: ['myTable', {foo: true}], ],
expectation: "INSERT INTO `myTable` (`foo`) VALUES (true);"
}
],
bulkInsertQuery: [ updateQuery: [
{ {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo\\';DROP TABLE myTable;'),('bar');" expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55'),('bar','2012-03-27 10:01:55');" expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1),('bar',2);" expectation: "UPDATE `myTable` SET `name`='foo\\';DROP TABLE myTable;' WHERE `name`='foo'"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',NULL);" expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'",
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: undefined}, {name: 'bar', foo: 2, undefinedValue: undefined}]], arguments: ['myTable', {bar: false}, {name: 'foo'}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", expectation: "UPDATE `myTable` SET `bar`=false WHERE `name`='foo'"
context: {options: {omitNull: true}} // Note: As above }, {
}, { arguments: ['myTable', {bar: true}, {name: 'foo'}],
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], expectation: "UPDATE `myTable` SET `bar`=true WHERE `name`='foo'"
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);" }
} ],
],
updateQuery: [ deleteQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo'}],
expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `name`='foo' LIMIT 1"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', 1],
expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 1"
}, { },{
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', undefined, {truncate: true}],
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'" expectation: "TRUNCATE `myTable`"
}, { },{
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', 1, {limit: 10, truncate: true}],
expectation: "UPDATE `myTable` SET `name`='foo\\';DROP TABLE myTable;' WHERE `name`='foo'" expectation: "TRUNCATE `myTable`"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', 1, {limit: 10}],
expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'" expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 10"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'", expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10"
context: {options: {omitNull: false}} }, {
}, { arguments: ['myTable', {name: 'foo'}, {limit: null}],
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'", }
context: {options: {omitNull: true}} ],
}, {
arguments: ['myTable', {bar: false}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=false WHERE `name`='foo'"
}, {
arguments: ['myTable', {bar: true}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=true WHERE `name`='foo'"
}
],
deleteQuery: [ addIndexQuery: [
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['User', ['username', 'isAdmin']],
expectation: "DELETE FROM `myTable` WHERE `name`='foo' LIMIT 1" expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)'
}, { }, {
arguments: ['myTable', 1], arguments: [
expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 1" 'User', [
},{ { attribute: 'username', length: 10, order: 'ASC'},
arguments: ['myTable', undefined, {truncate: true}], 'isAdmin'
expectation: "TRUNCATE `myTable`" ]
},{ ],
arguments: ['myTable', 1, {limit: 10, truncate: true}], expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)"
expectation: "TRUNCATE `myTable`" }, {
}, { arguments: [
arguments: ['myTable', 1, {limit: 10}], 'User', ['username', 'isAdmin'], { parser: 'foo', indicesType: 'FULLTEXT', indexName: 'bar'}
expectation: "DELETE FROM `myTable` WHERE `id`=1 LIMIT 10" ],
}, { expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin) WITH PARSER foo"
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], }
expectation: "DELETE FROM `myTable` WHERE `name`='foo\\';DROP TABLE myTable;' LIMIT 10" ],
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}
],
addIndexQuery: [ showIndexQuery: [
{ {
arguments: ['User', ['username', 'isAdmin']], arguments: ['User'],
expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)' expectation: 'SHOW INDEX FROM User'
}, { }, {
arguments: [ arguments: ['User', { database: 'sequelize' }],
'User', [ expectation: "SHOW INDEX FROM User FROM sequelize"
{ attribute: 'username', length: 10, order: 'ASC'}, }
'isAdmin' ],
]
],
expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)"
}, {
arguments: [
'User', ['username', 'isAdmin'], { parser: 'foo', indicesType: 'FULLTEXT', indexName: 'bar'}
],
expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin) WITH PARSER foo"
}
],
showIndexQuery: [ removeIndexQuery: [
{ {
arguments: ['User'], arguments: ['User', 'user_foo_bar'],
expectation: 'SHOW INDEX FROM User' expectation: "DROP INDEX user_foo_bar ON User"
}, { }, {
arguments: ['User', { database: 'sequelize' }], arguments: ['User', ['foo', 'bar']],
expectation: "SHOW INDEX FROM User FROM sequelize" expectation: "DROP INDEX user_foo_bar ON User"
} }
], ],
removeIndexQuery: [ hashToWhereConditions: [
{ {
arguments: ['User', 'user_foo_bar'], arguments: [{ id: [1,2,3] }],
expectation: "DROP INDEX user_foo_bar ON User" expectation: "`id` IN (1,2,3)"
}, { },
arguments: ['User', ['foo', 'bar']], {
expectation: "DROP INDEX user_foo_bar ON User" arguments: [{ id: [] }],
} expectation: "`id` IN (NULL)"
], },
{
arguments: [{ maple: false, bacon: true }],
expectation: "`maple`=false AND `bacon`=true"
},
{
arguments: [{ beaver: [false, true] }],
expectation: "`beaver` IN (false,true)"
},
{
arguments: [{birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55))}],
expectation: "`birthday`='2011-07-01 10:01:55'"
},
{
arguments: [{ birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55)),
otherday: new Date(Date.UTC(2013, 6, 2, 10, 1, 22)) }],
expectation: "`birthday`='2011-07-01 10:01:55' AND `otherday`='2013-07-02 10:01:22'"
},
{
arguments: [{ birthday: [new Date(Date.UTC(2011, 6, 1, 10, 1, 55)), new Date(Date.UTC(2013, 6, 2, 10, 1, 22))] }],
expectation: "`birthday` IN ('2011-07-01 10:01:55','2013-07-02 10:01:22')"
}
]
}
hashToWhereConditions: [ _.each(suites, function(tests, suiteTitle) {
{ describe(suiteTitle, function() {
arguments: [{ id: [1,2,3] }], tests.forEach(function(test) {
expectation: "`id` IN (1,2,3)" var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
}, it(title, function(done) {
{ // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
arguments: [{ id: [] }], var context = test.context || {options: {}};
expectation: "`id` IN (NULL)" QueryGenerator.options = context.options
}, var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
{ expect(conditions).to.deep.equal(test.expectation)
arguments: [{ maple: false, bacon: true }], done()
expectation: "`maple`=false AND `bacon`=true" })
},
{
arguments: [{ beaver: [false, true] }],
expectation: "`beaver` IN (false,true)"
},
{
arguments: [{birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55))}],
expectation: "`birthday`='2011-07-01 10:01:55'"
},
{
arguments: [{ birthday: new Date(Date.UTC(2011, 6, 1, 10, 1, 55)),
otherday: new Date(Date.UTC(2013, 6, 2, 10, 1, 22)) }],
expectation: "`birthday`='2011-07-01 10:01:55' AND `otherday`='2013-07-02 10:01:22'"
},
{
arguments: [{ birthday: [new Date(Date.UTC(2011, 6, 1, 10, 1, 55)), new Date(Date.UTC(2013, 6, 2, 10, 1, 22))] }],
expectation: "`birthday` IN ('2011-07-01 10:01:55','2013-07-02 10:01:22')"
}
]
}
Sequelize.Utils._.each(suites, function(tests, suiteTitle) {
describe(suiteTitle, function() {
tests.forEach(function(test) {
var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function() {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation)
}) })
}) })
}) })
}) })
}) }
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, config = require(__dirname + '/../config/config')
, DataTypes = require(__dirname + "/../../lib/data-types")
chai.Assertion.includeStack = true
if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] associations', function() {
describe('many-to-many', function() {
describe('where tables have the same prefix', function() {
it("should create a table wp_table1wp_table2s", function(done) {
var Table2 = this.sequelize.define('wp_table2', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('wp_table1', {foo: DataTypes.STRING})
Table1.hasMany(Table2)
Table2.hasMany(Table1)
expect(this.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).to.exist
done()
})
})
describe('when join table name is specified', function() {
beforeEach(function(done){
var Table2 = this.sequelize.define('ms_table1', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('ms_table2', {foo: DataTypes.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
done()
})
it("should not use a combined name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
done()
})
it("should use the specified name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
done()
})
})
})
describe('HasMany', function() {
describe('addDAO / getDAO', function() {
beforeEach(function(done) {
var self = this
//prevent periods from occurring in the table name since they are used to delimit (table.column)
this.User = this.sequelize.define('User' + config.rand(), { name: DataTypes.STRING })
this.Task = this.sequelize.define('Task' + config.rand(), { name: DataTypes.STRING })
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
var self = this
, users = []
, tasks = []
for (var i = 0; i < 5; ++i) {
users[users.length] = {name: 'User' + Math.random()}
}
for (var x = 0; x < 5; ++x) {
tasks[tasks.length] = {name: 'Task' + Math.random()}
}
self.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
self.User.all().success(function(_users) {
self.Task.all().success(function(_tasks) {
self.user = _users[0]
self.task = _tasks[0]
done()
})
})
})
})
})
})
})
it('should correctly add an association to the dao', function(done) {
var self = this
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(0)
self.user.addTask(self.task).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(1)
done()
})
})
})
})
})
describe('removeDAO', function() {
it("should correctly remove associated objects", function(done) {
var self = this
, users = []
, tasks = []
//prevent periods from occurring in the table name since they are used to delimit (table.column)
this.User = this.sequelize.define('User' + config.rand(), { name: DataTypes.STRING })
this.Task = this.sequelize.define('Task' + config.rand(), { name: DataTypes.STRING })
this.users = null
this.tasks = null
this.User.hasMany(this.Task, {as:'Tasks'})
this.Task.hasMany(this.User, {as:'Users'})
for (var i = 0; i < 5; ++i) {
users[users.length] = {id: i, name: 'User' + Math.random()}
}
for (var x = 0; x < 5; ++x) {
tasks[tasks.length] = {id: i, name: 'Task' + Math.random()}
}
self.User.sync({ force: true }).success(function() {
self.Task.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
self.User.all().success(function(_users) {
self.Task.all().success(function(_tasks) {
self.user = _users[0]
self.task = _tasks[0]
self.users = _users
self.tasks = _tasks
self.user.getTasks().on('success', function(__tasks) {
expect(__tasks).to.have.length(0)
self.user.setTasks(self.tasks).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(self.tasks.length)
self.user.removeTask(self.tasks[0]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) {
expect(_tasks).to.have.length(self.tasks.length - 1)
done()
})
})
})
})
})
})
})
})
})
})
})
})
})
})
})
}
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, Helpers = require('../buster-helpers') , Support = require(__dirname + '/../support')
, dialect = Helpers.getTestDialect() , dialect = Support.getTestDialect()
} , config = require(__dirname + '/../config/config')
, DataTypes = require(__dirname + "/../../lib/data-types")
buster.spec.expose() chai.Assertion.includeStack = true
if (dialect.match(/^postgres/)) { if (dialect.match(/^postgres/)) {
describe('[POSTGRES] DAO', function() { describe('[POSTGRES Specific] DAO', function() {
before(function(done) { beforeEach(function(done) {
var self = this this.sequelize.options.quoteIdentifiers = true
this.User = this.sequelize.define('User', {
Helpers.initTests({ username: DataTypes.STRING,
dialect: dialect, email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
beforeComplete: function(sequelize, DataTypes) { document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
})
},
onComplete: function() {
self.User.sync({ force: true }).success(done)
}
}) })
this.User.sync({ force: true }).success(function() {
done()
})
})
afterEach(function(done) {
this.sequelize.options.quoteIdentifiers = true
done()
}) })
describe('model', function() { describe('model', function() {
it("create handles array correctly", function(done) { it("create handles array correctly", function(done) {
var self = this
this.User this.User
.create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }) .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] })
.success(function(oldUser) { .success(function(oldUser) {
expect(oldUser.email).toEqual(['foo@bar.com', 'bar@baz.com']) expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com'])
done() done()
}) })
.error(function(err) { .error(function(err) {
...@@ -49,15 +45,15 @@ if (dialect.match(/^postgres/)) { ...@@ -49,15 +45,15 @@ if (dialect.match(/^postgres/)) {
this.User this.User
.create({ username: 'user', email: ['foo@bar.com'], document: { created: { test: '"value"' }}}) .create({ username: 'user', email: ['foo@bar.com'], document: { created: { test: '"value"' }}})
.success(function(newUser) { .success(function(newUser) {
expect(newUser.document).toEqual({ created: { test: '"value"' }}) expect(newUser.document).to.deep.equal({ created: { test: '"value"' }})
// Check to see if updating an hstore field works // Check to see if updating an hstore field works
newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){ newUser.updateAttributes({document: {should: 'update', to: 'this', first: 'place'}}).success(function(oldUser){
// Postgres always returns keys in alphabetical order (ascending) // Postgres always returns keys in alphabetical order (ascending)
expect(oldUser.document).toEqual({first: 'place', should: 'update', to: 'this'}) expect(oldUser.document).to.deep.equal({first: 'place', should: 'update', to: 'this'})
// Check to see if the default value for an hstore field works // Check to see if the default value for an hstore field works
self.User.create({ username: 'user2', email: ['bar@baz.com']}).success(function(defaultUser){ self.User.create({ username: 'user2', email: ['bar@baz.com']}).success(function(defaultUser){
expect(defaultUser.document).toEqual({default: 'value'}) expect(defaultUser.document).to.deep.equal({default: 'value'})
done() done()
}) })
}) })
...@@ -65,61 +61,47 @@ if (dialect.match(/^postgres/)) { ...@@ -65,61 +61,47 @@ if (dialect.match(/^postgres/)) {
.error(console.log) .error(console.log)
}) })
}) })
})
describe('[POSTGRES] Unquoted identifiers', function() {
before(function(done) {
var self = this
Helpers.initTests({ describe('[POSTGRES] Unquoted identifiers', function() {
dialect: dialect, it("can insert and select", function(done) {
beforeComplete: function(sequelize, DataTypes) { var self = this
self.sequelize = sequelize this.sequelize.options.quoteIdentifiers = false
self.sequelize.options.quoteIdentifiers = false this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false
self.User = sequelize.define('User', { this.User = this.sequelize.define('Userxs', {
username: DataTypes.STRING, username: DataTypes.STRING,
fullName: DataTypes.STRING // Note mixed case fullName: DataTypes.STRING // Note mixed case
}) }, {
}, quoteIdentifiers: false
onComplete: function() {
// We can create a table with non-quoted identifiers
self.User.sync({ force: true }).success(done)
}
})
})
it("can insert and select", function(done) {
var self = this
self.User
.create({ username: 'user', fullName: "John Smith" })
.success(function(user) {
// We can insert into a table with non-quoted identifiers
expect(user.id).toBeDefined()
expect(user.id).not.toBeNull()
expect(user.username).toEqual('user')
expect(user.fullName).toEqual('John Smith')
// We can query by non-quoted identifiers
self.User.find({
where: {fullName: "John Smith"}
})
.success(function(user2) {
// We can map values back to non-quoted identifiers
expect(user2.id).toEqual(user.id)
expect(user2.username).toEqual('user')
expect(user2.fullName).toEqual('John Smith')
done();
})
.error(function(err) {
console.log(err)
})
}) })
.error(function(err) {
console.log(err) this.User.sync({ force: true }).success(function() {
self.User
.create({ username: 'user', fullName: "John Smith" })
.success(function(user) {
// We can insert into a table with non-quoted identifiers
expect(user.id).to.exist
expect(user.id).not.to.be.null
expect(user.username).to.equal('user')
expect(user.fullName).to.equal('John Smith')
// We can query by non-quoted identifiers
self.User.find({
where: {fullName: "John Smith"}
})
.success(function(user2) {
self.sequelize.options.quoteIndentifiers = true
self.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true
self.sequelize.options.logging = false
// We can map values back to non-quoted identifiers
expect(user2.id).to.equal(user.id)
expect(user2.username).to.equal('user')
expect(user2.fullName).to.equal('John Smith')
done()
})
})
}) })
})
}) })
}) })
} }
/* jshint camelcase: false */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, dialect = Support.getTestDialect()
, hstore = require(__dirname + '/../../lib/dialects/postgres/hstore')
chai.Assertion.includeStack = true
if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] hstore', function() {
describe('stringifyPart', function() {
it("handles undefined values correctly", function(done) {
expect(hstore.stringifyPart(undefined)).to.equal('NULL')
done()
})
it("handles null values correctly", function(done) {
expect(hstore.stringifyPart(null)).to.equal('NULL')
done()
})
it("handles boolean values correctly", function(done) {
expect(hstore.stringifyPart(false)).to.equal('false')
expect(hstore.stringifyPart(true)).to.equal('true')
done()
})
it("handles strings correctly", function(done) {
expect(hstore.stringifyPart('foo')).to.equal('"foo"')
done()
})
it("handles strings with backslashes correctly", function(done) {
expect(hstore.stringifyPart("\\'literally\\'")).to.equal('"\\\\\'literally\\\\\'"')
done()
})
it("handles arrays correctly", function(done) {
expect(hstore.stringifyPart([1,['2'],'"3"'])).to.equal('"[1,[\\"2\\"],\\"\\\\\\"3\\\\\\"\\"]"')
done()
})
it("handles simple objects correctly", function(done) {
expect(hstore.stringifyPart({ test: 'value' })).to.equal('"{\\"test\\":\\"value\\"}"')
done()
})
it("handles nested objects correctly", function(done) {
expect(hstore.stringifyPart({ test: { nested: 'value' } })).to.equal('"{\\"test\\":{\\"nested\\":\\"value\\"}}"')
done()
})
it("handles objects correctly", function(done) {
expect(hstore.stringifyPart({test: {nested: {value: {including: '"string"'}}}})).to.equal('"{\\"test\\":{\\"nested\\":{\\"value\\":{\\"including\\":\\"\\\\\\"string\\\\\\"\\"}}}}"')
done()
})
})
describe('stringify', function() {
it('should handle empty objects correctly', function(done) {
expect(hstore.stringify({ })).to.equal('')
done()
})
it('should handle null values correctly', function(done) {
expect(hstore.stringify({ null: null })).to.equal('"null"=>NULL')
done()
})
it('should handle simple objects correctly', function(done) {
expect(hstore.stringify({ test: 'value' })).to.equal('"test"=>"value"')
done()
})
it('should handle nested objects correctly', function(done) {
expect(hstore.stringify({ test: { nested: 'value' } })).to.equal('"test"=>"{\\"nested\\":\\"value\\"}"')
done()
})
it('should handle nested arrays correctly', function(done) {
expect(hstore.stringify({ test: [ 1, '2', [ '"3"' ] ] })).to.equal('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')
done()
})
it('should handle multiple keys with different types of values', function(done) {
expect(hstore.stringify({ true: true, false: false, null: null, undefined: undefined, integer: 1, array: [1,'2'], object: { object: 'value' }})).to.equal('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')
done()
})
})
describe('parse', function() {
it('should handle empty objects correctly', function(done) {
expect(hstore.parse('')).to.deep.equal({ })
done()
})
it('should handle simple objects correctly', function(done) {
expect(hstore.parse('"test"=>"value"')).to.deep.equal({ test: 'value' })
done()
})
it('should handle nested objects correctly', function(done) {
expect(hstore.parse('"test"=>"{\\"nested\\":\\"value\\"}"')).to.deep.equal({ test: { nested: 'value' } })
done()
})
it('should handle nested arrays correctly', function(done) {
expect(hstore.parse('"test"=>"[1,\\"2\\",[\\"\\\\\\"3\\\\\\"\\"]]"')).to.deep.equal({ test: [ 1, '2', [ '"3"' ] ] })
done()
})
it('should handle multiple keys with different types of values', function(done) {
expect(hstore.parse('"true"=>true,"false"=>false,"null"=>NULL,"undefined"=>NULL,"integer"=>1,"array"=>"[1,\\"2\\"]","object"=>"{\\"object\\":\\"value\\"}"')).to.deep.equal({ true: true, false: false, null: null, undefined: null, integer: "1", array: [1,'2'], object: { object: 'value' }})
done()
})
})
})
}
var config = require("../config/config") /* jshint camelcase: false */
, Sequelize = require("../../index") var chai = require('chai')
, sequelize = new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, { , expect = chai.expect
logging: false, , QueryGenerator = require("../../lib/dialects/postgres/query-generator")
dialect: 'postgres', , Support = require(__dirname + '/../support')
port: config.postgres.port , dialect = Support.getTestDialect()
}) , DataTypes = require(__dirname + "/../../lib/data-types")
, Helpers = new (require("../config/helpers"))(sequelize) , moment = require('moment')
, QueryGenerator = require("../../lib/dialects/postgres/query-generator") , util = require("util")
, util = require("util") , _ = require('lodash')
describe('QueryGenerator', function() { chai.Assertion.includeStack = true
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var suites = { if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] QueryGenerator', function() {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
})
this.User.sync({ force: true }).success(function() {
done()
})
})
attributesToSQL: [ var suites = {
{ attributesToSQL: [
arguments: [{id: 'INTEGER'}], {
expectation: {id: 'INTEGER'} arguments: [{id: 'INTEGER'}],
}, expectation: {id: 'INTEGER'}
{ },
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], {
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}],
}, expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'}
{ },
arguments: [{id: {type: 'INTEGER'}}], {
expectation: {id: 'INTEGER'} arguments: [{id: {type: 'INTEGER'}}],
}, expectation: {id: 'INTEGER'}
{ },
arguments: [{id: {type: 'INTEGER', allowNull: false}}], {
expectation: {id: 'INTEGER NOT NULL'} arguments: [{id: {type: 'INTEGER', allowNull: false}}],
}, expectation: {id: 'INTEGER NOT NULL'}
{ },
arguments: [{id: {type: 'INTEGER', allowNull: true}}], {
expectation: {id: 'INTEGER'} arguments: [{id: {type: 'INTEGER', allowNull: true}}],
}, expectation: {id: 'INTEGER'}
{ },
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}], {
expectation: {id: 'INTEGER SERIAL PRIMARY KEY'} arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}],
}, expectation: {id: 'INTEGER SERIAL PRIMARY KEY'}
{ },
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}], {
expectation: {id: 'INTEGER DEFAULT 0'} arguments: [{id: {type: 'INTEGER', defaultValue: 0}}],
}, expectation: {id: 'INTEGER DEFAULT 0'}
{ },
arguments: [{id: {type: 'INTEGER', unique: true}}], {
expectation: {id: 'INTEGER UNIQUE'} arguments: [{id: {type: 'INTEGER', unique: true}}],
}, expectation: {id: 'INTEGER UNIQUE'}
{ },
arguments: [{id: {type: 'INTEGER', comment: "I'm a comment!" }}], {
expectation: {id: "INTEGER COMMENT ON COLUMN <%= table %>.\"id\" IS 'I''m a comment!'" } arguments: [{id: {type: 'INTEGER', comment: "I'm a comment!" }}],
}, expectation: {id: "INTEGER COMMENT ON COLUMN <%= table %>.\"id\" IS 'I''m a comment!'" }
{ },
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}], {
expectation: {id: 'INTEGER REFERENCES "Bar" ("id")'} arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
}, expectation: {id: 'INTEGER REFERENCES "Bar" ("id")'}
{ },
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}], {
expectation: {id: 'INTEGER REFERENCES "Bar" ("pk")'} arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
}, expectation: {id: 'INTEGER REFERENCES "Bar" ("pk")'}
{ },
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}], {
expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE'} arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
}, expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON DELETE CASCADE'}
{ },
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}], {
expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT'} arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
}, expectation: {id: 'INTEGER REFERENCES "Bar" ("id") ON UPDATE RESTRICT'}
{ },
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}], {
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT'} arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
}, expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES "Bar" ("id") ON DELETE CASCADE ON UPDATE RESTRICT'}
},
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}], arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id)'}, expectation: {id: 'INTEGER REFERENCES Bar (id)'},
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}], arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES Bar (pk)'}, expectation: {id: 'INTEGER REFERENCES Bar (pk)'},
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}], arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE'}, expectation: {id: 'INTEGER REFERENCES Bar (id) ON DELETE CASCADE'},
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}], arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT'}, expectation: {id: 'INTEGER REFERENCES Bar (id) ON UPDATE RESTRICT'},
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}], arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT'}, expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES Bar (id) ON DELETE CASCADE ON UPDATE RESTRICT'},
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
], ],
createTableQuery: [ createTableQuery: [
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));", expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));",
}, },
{ {
arguments: ['myTable', {title: "INTEGER COMMENT ON COLUMN <%= table %>.\"title\" IS 'I''m a comment!'"}], arguments: ['myTable', {title: "INTEGER COMMENT ON COLUMN <%= table %>.\"title\" IS 'I''m a comment!'"}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER ); COMMENT ON COLUMN \"myTable\".\"title\" IS 'I''m a comment!';", expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER ); COMMENT ON COLUMN \"myTable\".\"title\" IS 'I''m a comment!';",
}, },
{ {
arguments: ['myTable', {title: "INTEGER"}, {comment: "I'm a comment!"}], arguments: ['myTable', {title: "INTEGER"}, {comment: "I'm a comment!"}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER); COMMENT ON TABLE \"myTable\" IS 'I''m a comment!';", expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" INTEGER); COMMENT ON TABLE \"myTable\" IS 'I''m a comment!';",
}, },
{ {
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));" expectation: "CREATE TABLE IF NOT EXISTS \"mySchema\".\"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS \"enum_myTable_title\"; CREATE TYPE \"enum_myTable_title\" AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));" expectation: "DROP TYPE IF EXISTS \"enum_myTable_title\"; CREATE TYPE \"enum_myTable_title\" AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" \"enum_myTable_title\", \"name\" VARCHAR(255));"
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"id\" INTEGER , PRIMARY KEY (\"id\"));" expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"id\" INTEGER , PRIMARY KEY (\"id\"));"
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES "otherTable" ("id") ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"otherId\" INTEGER REFERENCES \"otherTable\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION);" expectation: "CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255), \"otherId\" INTEGER REFERENCES \"otherTable\" (\"id\") ON DELETE CASCADE ON UPDATE NO ACTION);"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));", expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['mySchema.myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: "CREATE TABLE IF NOT EXISTS mySchema.myTable (title VARCHAR(255), name VARCHAR(255));", expectation: "CREATE TABLE IF NOT EXISTS mySchema.myTable (title VARCHAR(255), name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: "DROP TYPE IF EXISTS enum_myTable_title; CREATE TYPE enum_myTable_title AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));", expectation: "DROP TYPE IF EXISTS enum_myTable_title; CREATE TYPE enum_myTable_title AS ENUM(\"A\", \"B\", \"C\"); CREATE TABLE IF NOT EXISTS myTable (title enum_myTable_title, name VARCHAR(255));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), id INTEGER , PRIMARY KEY (id));", expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), id INTEGER , PRIMARY KEY (id));",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), otherId INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION);", expectation: "CREATE TABLE IF NOT EXISTS myTable (title VARCHAR(255), name VARCHAR(255), otherId INTEGER REFERENCES otherTable (id) ON DELETE CASCADE ON UPDATE NO ACTION);",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
dropTableQuery: [ dropTableQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS \"myTable\";" expectation: "DROP TABLE IF EXISTS \"myTable\";"
}, },
{ {
arguments: ['mySchema.myTable'], arguments: ['mySchema.myTable'],
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\";" expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\";"
}, },
{ {
arguments: ['myTable', {cascade: true}], arguments: ['myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS \"myTable\" CASCADE;" expectation: "DROP TABLE IF EXISTS \"myTable\" CASCADE;"
}, },
{ {
arguments: ['mySchema.myTable', {cascade: true}], arguments: ['mySchema.myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\" CASCADE;" expectation: "DROP TABLE IF EXISTS \"mySchema\".\"myTable\" CASCADE;"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "DROP TABLE IF EXISTS myTable;", expectation: "DROP TABLE IF EXISTS myTable;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['mySchema.myTable'], arguments: ['mySchema.myTable'],
expectation: "DROP TABLE IF EXISTS mySchema.myTable;", expectation: "DROP TABLE IF EXISTS mySchema.myTable;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['myTable', {cascade: true}], arguments: ['myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS myTable CASCADE;", expectation: "DROP TABLE IF EXISTS myTable CASCADE;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: ['mySchema.myTable', {cascade: true}], arguments: ['mySchema.myTable', {cascade: true}],
expectation: "DROP TABLE IF EXISTS mySchema.myTable CASCADE;", expectation: "DROP TABLE IF EXISTS mySchema.myTable CASCADE;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
selectQuery: [ selectQuery: [
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "SELECT * FROM \"myTable\";" expectation: "SELECT * FROM \"myTable\";"
}, { }, {
arguments: ['myTable', {attributes: ['id', 'name']}], arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT \"id\", \"name\" FROM \"myTable\";" expectation: "SELECT \"id\", \"name\" FROM \"myTable\";"
}, { }, {
arguments: ['myTable', {where: {id: 2}}], arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, { }, {
arguments: ['myTable', {where: {name: 'foo'}}], arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo';" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo';"
}, { }, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo'';DROP TABLE myTable;';" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo'';DROP TABLE myTable;';"
}, { }, {
arguments: ['myTable', {where: 2}], arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, { }, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }], arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as \"count\" FROM \"foo\";' expectation: 'SELECT count(*) as \"count\" FROM \"foo\";'
}, { }, {
arguments: ['myTable', {where: "foo='bar'"}], arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';" expectation: "SELECT * FROM \"myTable\" WHERE foo='bar';"
}, { }, {
arguments: ['myTable', {order: "id DESC"}], arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM \"myTable\" ORDER BY \"id\" DESC;" expectation: "SELECT * FROM \"myTable\" ORDER BY \"id\" DESC;"
}, { }, {
arguments: ['myTable', {group: "name"}], arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";" expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";"
}, { }, {
arguments: ['myTable', {group: ["name"]}], arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";" expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\";"
}, { }, {
arguments: ['myTable', {group: ["name","title"]}], arguments: ['myTable', {group: ["name","title"]}],
expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\", \"title\";" expectation: "SELECT * FROM \"myTable\" GROUP BY \"name\", \"title\";"
}, { }, {
arguments: ['myTable', {limit: 10}], arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM \"myTable\" LIMIT 10;" expectation: "SELECT * FROM \"myTable\" LIMIT 10;"
}, { }, {
arguments: ['myTable', {limit: 10, offset: 2}], arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM \"myTable\" LIMIT 10 OFFSET 2;" expectation: "SELECT * FROM \"myTable\" LIMIT 10 OFFSET 2;"
}, { }, {
title: 'uses offset even if no limit was passed', title: 'uses offset even if no limit was passed',
arguments: ['myTable', {offset: 2}], arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM \"myTable\" OFFSET 2;" expectation: "SELECT * FROM \"myTable\" OFFSET 2;"
}, { }, {
arguments: ['mySchema.myTable'], arguments: ['mySchema.myTable'],
expectation: "SELECT * FROM \"mySchema\".\"myTable\";" expectation: "SELECT * FROM \"mySchema\".\"myTable\";"
}, { }, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}], arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';" expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable'], arguments: ['myTable'],
expectation: "SELECT * FROM myTable;", expectation: "SELECT * FROM myTable;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {attributes: ['id', 'name']}], arguments: ['myTable', {attributes: ['id', 'name']}],
expectation: "SELECT id, name FROM myTable;", expectation: "SELECT id, name FROM myTable;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {where: {id: 2}}], arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM myTable WHERE myTable.id=2;", expectation: "SELECT * FROM myTable WHERE myTable.id=2;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {where: {name: 'foo'}}], arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM myTable WHERE myTable.name='foo';", expectation: "SELECT * FROM myTable WHERE myTable.name='foo';",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM myTable WHERE myTable.name='foo'';DROP TABLE myTable;';", expectation: "SELECT * FROM myTable WHERE myTable.name='foo'';DROP TABLE myTable;';",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {where: 2}], arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM myTable WHERE myTable.id=2;", expectation: "SELECT * FROM myTable WHERE myTable.id=2;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }], arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as count FROM foo;', expectation: 'SELECT count(*) as count FROM foo;',
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {where: "foo='bar'"}], arguments: ['myTable', {where: "foo='bar'"}],
expectation: "SELECT * FROM myTable WHERE foo='bar';", expectation: "SELECT * FROM myTable WHERE foo='bar';",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {order: "id DESC"}], arguments: ['myTable', {order: "id DESC"}],
expectation: "SELECT * FROM myTable ORDER BY id DESC;", expectation: "SELECT * FROM myTable ORDER BY id DESC;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {group: "name"}], arguments: ['myTable', {group: "name"}],
expectation: "SELECT * FROM myTable GROUP BY name;", expectation: "SELECT * FROM myTable GROUP BY name;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {group: ["name"]}], arguments: ['myTable', {group: ["name"]}],
expectation: "SELECT * FROM myTable GROUP BY name;", expectation: "SELECT * FROM myTable GROUP BY name;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {group: ["name","title"]}], arguments: ['myTable', {group: ["name","title"]}],
expectation: "SELECT * FROM myTable GROUP BY name, title;", expectation: "SELECT * FROM myTable GROUP BY name, title;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {limit: 10}], arguments: ['myTable', {limit: 10}],
expectation: "SELECT * FROM myTable LIMIT 10;", expectation: "SELECT * FROM myTable LIMIT 10;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {limit: 10, offset: 2}], arguments: ['myTable', {limit: 10, offset: 2}],
expectation: "SELECT * FROM myTable LIMIT 10 OFFSET 2;", expectation: "SELECT * FROM myTable LIMIT 10 OFFSET 2;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
title: 'uses offset even if no limit was passed', title: 'uses offset even if no limit was passed',
arguments: ['myTable', {offset: 2}], arguments: ['myTable', {offset: 2}],
expectation: "SELECT * FROM myTable OFFSET 2;", expectation: "SELECT * FROM myTable OFFSET 2;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable'], arguments: ['mySchema.myTable'],
expectation: "SELECT * FROM mySchema.myTable;", expectation: "SELECT * FROM mySchema.myTable;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}], arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM mySchema.myTable WHERE mySchema.myTable.name='foo'';DROP TABLE mySchema.myTable;';", expectation: "SELECT * FROM mySchema.myTable WHERE mySchema.myTable.name='foo'';DROP TABLE mySchema.myTable;';",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
insertQuery: [ insertQuery: [
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1) RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;"
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL) RETURNING *;",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: undefined}], arguments: ['myTable', {name: 'foo', nullValue: undefined}],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo'}], arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo') RETURNING *;"
}, { }, {
arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}], arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;"
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}],
expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1}], arguments: ['myTable', {name: 'foo', foo: 1}],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;", expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL) RETURNING *;",
context: {options: {omitNull: false, quoteIdentifiers: false}} context: {options: {omitNull: false, quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: null}], arguments: ['myTable', {name: 'foo', nullValue: null}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} context: {options: {omitNull: true, quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', nullValue: undefined}], arguments: ['myTable', {name: 'foo', nullValue: undefined}],
expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} context: {options: {omitNull: true, quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo'}], arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}], arguments: ['mySchema.myTable', {name: JSON.stringify({info: 'Look ma a " quote'})}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
bulkInsertQuery: [ bulkInsertQuery: [
{ {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\") VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], arguments: ['myTable', [{name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {name: 'bar', birthday: moment("2012-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"birthday\") VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"foo\") VALUES ('foo',1),('bar',2) RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;" expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;"
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]], arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]],
expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO \"myTable\" (\"name\",\"nullValue\") VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true}} // Note: As above context: {options: {omitNull: true}} // Note: As above
}, { }, {
arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'),('bar') RETURNING *;"
}, { }, {
arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]], arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;"
}, { }, {
arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]], arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;" expectation: "INSERT INTO \"mySchema\".\"myTable\" (\"name\") VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]], arguments: ['myTable', [{name: "foo';DROP TABLE myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;", expectation: "INSERT INTO myTable (name) VALUES ('foo'';DROP TABLE myTable;'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {name: 'bar', birthday: new Date(Date.UTC(2012, 2, 27, 10, 1, 55))}]], arguments: ['myTable', [{name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {name: 'bar', birthday: moment("2012-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}]],
expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;", expectation: "INSERT INTO myTable (name,birthday) VALUES ('foo','2011-03-27 10:01:55.000 +00:00'),('bar','2012-03-27 10:01:55.000 +00:00') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]], arguments: ['myTable', [{name: 'foo', foo: 1}, {name: 'bar', foo: 2}]],
expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2) RETURNING *;", expectation: "INSERT INTO myTable (name,foo) VALUES ('foo',1),('bar',2) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {quoteIdentifiers: false, omitNull: false}}, context: {options: {quoteIdentifiers: false, omitNull: false}},
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]], arguments: ['myTable', [{name: 'foo', nullValue: null}, {name: 'bar', nullValue: null}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
}, { }, {
arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]], arguments: ['myTable', [{name: 'foo', nullValue: undefined}, {name: 'bar', nullValue: undefined}]],
expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;", expectation: "INSERT INTO myTable (name,nullValue) VALUES ('foo',NULL),('bar',NULL) RETURNING *;",
context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: As above context: {options: {omitNull: true, quoteIdentifiers: false}} // Note: As above
}, { }, {
arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['mySchema.myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]], arguments: ['mySchema.myTable', [{name: JSON.stringify({info: 'Look ma a " quote'})}, {name: JSON.stringify({info: 'Look ma another " quote'})}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('{\"info\":\"Look ma a \\\" quote\"}'),('{\"info\":\"Look ma another \\\" quote\"}') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]], arguments: ['mySchema.myTable', [{name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'bar'}]],
expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;", expectation: "INSERT INTO mySchema.myTable (name) VALUES ('foo'';DROP TABLE mySchema.myTable;'),('bar') RETURNING *;",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
updateQuery: [ updateQuery: [
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, 2],
expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"name\"='foo'';DROP TABLE myTable;' WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *"
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *", expectation: "UPDATE \"myTable\" SET \"bar\"=2,\"nullValue\"=NULL WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: false}} context: {options: {omitNull: false}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *", expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}],
expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *", expectation: "UPDATE \"myTable\" SET \"bar\"=2 WHERE \"name\"='foo' RETURNING *",
context: {options: {omitNull: true}} context: {options: {omitNull: true}}
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['mySchema.myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *" expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo',\"birthday\"='2011-03-27 10:01:55.000 +00:00' WHERE \"id\"=2 RETURNING *"
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *" expectation: "UPDATE \"mySchema\".\"myTable\" SET \"name\"='foo'';DROP TABLE mySchema.myTable;' WHERE \"name\"='foo' RETURNING *"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, 2], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, 2],
expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", expectation: "UPDATE myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {bar: 2}, {name: 'foo'}], arguments: ['myTable', {bar: 2}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {name: 'foo'}],
expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET name='foo'';DROP TABLE myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2,nullValue=NULL WHERE name='foo' RETURNING *",
context: {options: {omitNull: false, quoteIdentifiers: false}}, context: {options: {omitNull: false, quoteIdentifiers: false}},
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {omitNull: true, quoteIdentifiers: false}}, context: {options: {omitNull: true, quoteIdentifiers: false}},
}, { }, {
arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}], arguments: ['myTable', {bar: 2, nullValue: undefined}, {name: 'foo'}],
expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *", expectation: "UPDATE myTable SET bar=2 WHERE name='foo' RETURNING *",
context: {options: {omitNull: true, quoteIdentifiers: false}}, context: {options: {omitNull: true, quoteIdentifiers: false}},
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo', birthday: new Date(Date.UTC(2011, 2, 27, 10, 1, 55))}, {id: 2}], arguments: ['mySchema.myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}],
expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *", expectation: "UPDATE mySchema.myTable SET name='foo',birthday='2011-03-27 10:01:55.000 +00:00' WHERE id=2 RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {name: 'foo'}],
expectation: "UPDATE mySchema.myTable SET name='foo'';DROP TABLE mySchema.myTable;' WHERE name='foo' RETURNING *", expectation: "UPDATE mySchema.myTable SET name='foo'';DROP TABLE mySchema.myTable;' WHERE name='foo' RETURNING *",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
deleteQuery: [ deleteQuery: [
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo' LIMIT 1)" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo' LIMIT 1)"
}, { }, {
arguments: ['myTable', 1], arguments: ['myTable', 1],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"id\"=1 LIMIT 1)" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"id\"=1 LIMIT 1)"
}, { }, {
arguments: ['myTable', undefined, {truncate: true}], arguments: ['myTable', undefined, {truncate: true}],
expectation: "TRUNCATE \"myTable\"" expectation: "TRUNCATE \"myTable\""
}, { }, {
arguments: ['myTable', 1, {limit: 10, truncate: true}], arguments: ['myTable', 1, {limit: 10, truncate: true}],
expectation: "TRUNCATE \"myTable\"" expectation: "TRUNCATE \"myTable\""
}, { }, {
arguments: ['myTable', 1, {limit: 10}], arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"id\"=1 LIMIT 10)" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"id\"=1 LIMIT 10)"
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;' LIMIT 10)" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;' LIMIT 10)"
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo'}], arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo' LIMIT 1)" expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo' LIMIT 1)"
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}],
expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)" expectation: "DELETE FROM \"mySchema\".\"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)"
}, { }, {
arguments: ['myTable', {name: 'foo'}, {limit: null}], arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo')" expectation: "DELETE FROM \"myTable\" WHERE \"id\" IN (SELECT \"id\" FROM \"myTable\" WHERE \"name\"='foo')"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['myTable', {name: 'foo'}], arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo' LIMIT 1)", expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo' LIMIT 1)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', 1], arguments: ['myTable', 1],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 1)", expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 1)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', 1, {limit: 10}], arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 10)", expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE id=1 LIMIT 10)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo'';DROP TABLE myTable;' LIMIT 10)", expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo'';DROP TABLE myTable;' LIMIT 10)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: 'foo'}], arguments: ['mySchema.myTable', {name: 'foo'}],
expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo' LIMIT 1)", expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo' LIMIT 1)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}], arguments: ['mySchema.myTable', {name: "foo';DROP TABLE mySchema.myTable;"}, {limit: 10}],
expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)", expectation: "DELETE FROM mySchema.myTable WHERE id IN (SELECT id FROM mySchema.myTable WHERE name='foo'';DROP TABLE mySchema.myTable;' LIMIT 10)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['myTable', {name: 'foo'}, {limit: null}], arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo')", expectation: "DELETE FROM myTable WHERE id IN (SELECT id FROM myTable WHERE name='foo')",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
addIndexQuery: [ addIndexQuery: [
{ {
arguments: ['User', ['username', 'isAdmin']], arguments: ['User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\", \"isAdmin\")' expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\", \"isAdmin\")'
}, { }, {
arguments: [ arguments: [
'User', [ 'User', [
{ attribute: 'username', length: 10, order: 'ASC'}, { attribute: 'username', length: 10, order: 'ASC'},
'isAdmin' 'isAdmin'
] ]
], ],
expectation: "CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\"(10) ASC, \"isAdmin\")" expectation: "CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\"(10) ASC, \"isAdmin\")"
}, { }, {
arguments: [ arguments: [
'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'} 'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'}
], ],
expectation: "CREATE FULLTEXT INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")" expectation: "CREATE FULLTEXT INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")"
}, { }, {
arguments: ['mySchema.User', ['username', 'isAdmin']], arguments: ['mySchema.User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"mySchema\".\"User\" (\"username\", \"isAdmin\")' expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"mySchema\".\"User\" (\"username\", \"isAdmin\")'
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['User', ['username', 'isAdmin']], arguments: ['User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)', expectation: 'CREATE INDEX user_username_is_admin ON User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: [ arguments: [
'User', [ 'User', [
{ attribute: 'username', length: 10, order: 'ASC'}, { attribute: 'username', length: 10, order: 'ASC'},
'isAdmin' 'isAdmin'
] ]
], ],
expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)", expectation: "CREATE INDEX user_username_is_admin ON User (username(10) ASC, isAdmin)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: [ arguments: [
'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'} 'User', ['username', 'isAdmin'], { indicesType: 'FULLTEXT', indexName: 'bar'}
], ],
expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin)", expectation: "CREATE FULLTEXT INDEX bar ON User (username, isAdmin)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['mySchema.User', ['username', 'isAdmin']], arguments: ['mySchema.User', ['username', 'isAdmin']],
expectation: 'CREATE INDEX user_username_is_admin ON mySchema.User (username, isAdmin)', expectation: 'CREATE INDEX user_username_is_admin ON mySchema.User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
// FIXME: not implemented // FIXME: not implemented
//showIndexQuery: [ //showIndexQuery: [
// { // {
// arguments: ['User'], // arguments: ['User'],
// expectation: 'SHOW INDEX FROM \"User\"' // expectation: 'SHOW INDEX FROM \"User\"'
// }, { // }, {
// arguments: ['User', { database: 'sequelize' }], // arguments: ['User', { database: 'sequelize' }],
// expectation: "SHOW INDEX FROM \"User\" FROM \"sequelize\"" // expectation: "SHOW INDEX FROM \"User\" FROM \"sequelize\""
// } // }
//], //],
removeIndexQuery: [ removeIndexQuery: [
{ {
arguments: ['User', 'user_foo_bar'], arguments: ['User', 'user_foo_bar'],
expectation: "DROP INDEX IF EXISTS \"user_foo_bar\"" expectation: "DROP INDEX IF EXISTS \"user_foo_bar\""
}, { }, {
arguments: ['User', ['foo', 'bar']], arguments: ['User', ['foo', 'bar']],
expectation: "DROP INDEX IF EXISTS \"user_foo_bar\"" expectation: "DROP INDEX IF EXISTS \"user_foo_bar\""
}, { }, {
arguments: ['User', 'mySchema.user_foo_bar'], arguments: ['User', 'mySchema.user_foo_bar'],
expectation: "DROP INDEX IF EXISTS \"mySchema\".\"user_foo_bar\"" expectation: "DROP INDEX IF EXISTS \"mySchema\".\"user_foo_bar\""
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: ['User', 'user_foo_bar'], arguments: ['User', 'user_foo_bar'],
expectation: "DROP INDEX IF EXISTS user_foo_bar", expectation: "DROP INDEX IF EXISTS user_foo_bar",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['User', ['foo', 'bar']], arguments: ['User', ['foo', 'bar']],
expectation: "DROP INDEX IF EXISTS user_foo_bar", expectation: "DROP INDEX IF EXISTS user_foo_bar",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, { }, {
arguments: ['User', 'mySchema.user_foo_bar'], arguments: ['User', 'mySchema.user_foo_bar'],
expectation: "DROP INDEX IF EXISTS mySchema.user_foo_bar", expectation: "DROP INDEX IF EXISTS mySchema.user_foo_bar",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
} }
], ],
hashToWhereConditions: [ hashToWhereConditions: [
{ {
arguments: [{ id: [1,2,3] }], arguments: [{ id: [1,2,3] }],
expectation: "\"id\" IN (1,2,3)" expectation: "\"id\" IN (1,2,3)"
}, },
{ {
arguments: [{ id: [] }], arguments: [{ id: [] }],
expectation: "\"id\" IN (NULL)" expectation: "\"id\" IN (NULL)"
}, },
// Variants when quoteIdentifiers is false // Variants when quoteIdentifiers is false
{ {
arguments: [{ id: [1,2,3] }], arguments: [{ id: [1,2,3] }],
expectation: "id IN (1,2,3)", expectation: "id IN (1,2,3)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
{ {
arguments: [{ id: [] }], arguments: [{ id: [] }],
expectation: "id IN (NULL)", expectation: "id IN (NULL)",
context: {options: {quoteIdentifiers: false}} context: {options: {quoteIdentifiers: false}}
}, },
] ]
} }
Sequelize.Utils._.each(suites, function(tests, suiteTitle) { _.each(suites, function(tests, suiteTitle) {
describe(suiteTitle, function() { describe(suiteTitle, function() {
tests.forEach(function(test) { afterEach(function(done) {
var title = test.title || 'Postgres correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) this.sequelize.options.quoteIdentifiers = true
it(title, function() { QueryGenerator.options.quoteIdentifiers = true
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly done()
var context = test.context || {options: {}}; })
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation) tests.forEach(function(test) {
var title = test.title || 'Postgres correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}};
QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).to.deep.equal(test.expectation)
done()
})
}) })
}) })
}) })
}) })
}) }
\ No newline at end of file
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Promise"), function () {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
this.User.sync({ force: true }).then(function() { done() })
})
describe('increment', function () {
beforeEach(function(done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
})
it('with array', function(done) {
var self = this
this.User
.find(1)
.then(function(user) {
expect(user.id).to.equal(1)
return user.increment(['aNumber'], 2)
})
.then(function(user) {
// The following assertion would rock hard, but it's not implemented :(
// expect(user.aNumber).to.equal(2)
return self.User.find(1)
})
.then(function(user) {
expect(user.aNumber).to.equal(2)
done()
})
});
it('should still work right with other concurrent updates', function(done) {
var self = this
// Select something
this.User
.find(1)
.then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1)
.then(function (user2) {
return user2
.updateAttributes({ aNumber: user2.aNumber + 1 })
.then(function() { return user1.increment(['aNumber'], 2) })
.then(function() { return self.User.find(1) })
.then(function(user5) {
expect(user5.aNumber).to.equal(3)
done()
})
})
})
})
it('with key value pair', function(done) {
var self = this
this.User
.find(1)
.then(function(user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
})
.then(function () {
return self.User.find(1)
})
.then(function (user3) {
expect(user3.aNumber).to.equal(1)
expect(user3.bNumber).to.equal(2)
done()
})
})
})
describe('decrement', function () {
beforeEach(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
})
it('with array', function(done) {
var self = this
this.User
.find(1)
.then(function(user1) {
return user1.decrement(['aNumber'], 2)
})
.then(function(user2) {
return self.User.find(1)
})
.then(function(user3) {
expect(user3.aNumber).to.equal(-2)
done()
})
})
it('with single field', function(done) {
var self = this
this.User
.find(1)
.then(function(user1) {
return user1.decrement(['aNumber'], 2)
})
.then(function(user3) {
return self.User.find(1)
})
.then(function (user3) {
expect(user3.aNumber).to.equal(-2)
done()
})
})
it('should still work right with other concurrent decrements', function(done) {
var self = this
this.User
.find(1)
.then(function(user1) {
var _done = _.after(3, function() {
self.User
.find(1)
.then(function(user2) {
expect(user2.aNumber).to.equal(-6)
done()
})
})
user1.decrement(['aNumber'], 2).done(_done)
user1.decrement(['aNumber'], 2).done(_done)
user1.decrement(['aNumber'], 2).done(_done)
})
})
})
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function(done) {
this.User
.create({ username: 'John Doe' })
.then(function(originalUser) {
return originalUser
.updateAttributes({ username: 'Doe John' })
.then(function () {
return originalUser.reload()
})
.then(function(updatedUser) {
expect(originalUser === updatedUser).to.be.true
done()
})
})
})
it("should update the values on all references to the DAO", function(done) {
var self = this
this.User
.create({ username: 'John Doe' })
.then(function(originalUser) {
return self.User
.find(originalUser.id)
.then(function(updater) {
return updater.updateAttributes({ username: 'Doe John' })
})
.then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).to.equal('John Doe')
return originalUser.reload()
}).then(function(updatedUser) {
expect(originalUser.username).to.equal('Doe John')
expect(updatedUser.username).to.equal('Doe John')
done()
})
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: DataTypes.STRING })
, Page = this.sequelize.define('Page', { content: DataTypes.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
Book
.sync({ force: true })
.then(function() {
Page
.sync({ force: true })
.then(function() {
return Book.create({ title: 'A very old book' })
})
.then(function (book) {
return Page
.create({ content: 'om nom nom' })
.then(function(page) {
return book
.setPages([ page ])
.then(function() {
return Book
.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
})
.then(function (leBook) {
return page
.updateAttributes({ content: 'something totally different' })
.then(function (page) {
expect(leBook.pages[0].content).to.equal('om nom nom')
expect(page.content).to.equal('something totally different')
return leBook
.reload()
.then(function (leBook) {
expect(leBook.pages[0].content).to.equal('something totally different')
expect(page.content).to.equal('something totally different')
done()
})
})
})
})
})
}, done)
})
})
})
describe('complete', function () {
it("gets triggered if an error occurs", function(done) {
this.User.find({ where: "asdasdasd" }).then(null, function(err) {
expect(err).to.be.ok
expect(err.message).to.be.ok
done()
})
})
it("gets triggered if everything was ok", function(done) {
this.User.count().then(function(result) {
expect(result).to.not.be.undefined
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function(done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function(err) {
expect(err).to.be.ok
expect(err).to.be.an("object")
expect(err.validateTest).to.be.an("array")
expect(err.validateTest[0]).to.be.ok
expect(err.validateTest[0].indexOf('Invalid integer')).to.be.greaterThan(-1)
done()
});
})
it('should fail a validation upon building', function(done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function(err) {
expect(err).to.be.ok
expect(err).to.be.an("object")
expect(err.validateCustom).to.be.ok
expect(err.validateCustom).to.be.an("array")
expect(err.validateCustom[0]).to.be.ok
expect(err.validateCustom[0]).to.equal('Length failed.')
done()
})
})
it('should fail a validation when updating', function(done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function(err) {
expect(err).to.be.ok
expect(err).to.be.an("object")
expect(err.validateTest).to.be.ok
expect(err.validateTest).to.be.an("array")
expect(err.validateTest[0]).to.be.ok
expect(err.validateTest[0].indexOf('Invalid integer')).to.be.greaterThan(-1)
done()
})
})
})
})
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, QueryChainer = require("../lib/query-chainer") , Support = require(__dirname + '/support')
, CustomEventEmitter = require("../lib/emitters/custom-event-emitter") , QueryChainer = require("../lib/query-chainer")
, Helpers = require('./buster-helpers') , CustomEventEmitter = require("../lib/emitters/custom-event-emitter")
, dialect = Helpers.getTestDialect()
} chai.Assertion.includeStack = true
buster.spec.expose() describe(Support.getTestDialectTeaser("QueryChainer"), function () {
buster.testRunner.timeout = 1000 beforeEach(function(done) {
describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
before(function() {
this.queryChainer = new QueryChainer() this.queryChainer = new QueryChainer()
done()
}) })
describe('add', function() { describe('add', function() {
it('adds a new serial item if method is passed', function() { it('adds a new serial item if method is passed', function(done) {
expect(this.queryChainer.serials.length).toEqual(0) expect(this.queryChainer.serials.length).to.equal(0)
this.queryChainer.add({}, 'foo') this.queryChainer.add({}, 'foo')
expect(this.queryChainer.serials.length).toEqual(1) expect(this.queryChainer.serials.length).to.equal(1)
done()
}) })
it('adds a new emitter if no method is passed', function() { it('adds a new emitter if no method is passed', function(done) {
expect(this.queryChainer.emitters.length).toEqual(0) expect(this.queryChainer.emitters.length).to.equal(0)
this.queryChainer.add(new CustomEventEmitter()) this.queryChainer.add(new CustomEventEmitter())
expect(this.queryChainer.emitters.length).toEqual(1) expect(this.queryChainer.emitters.length).to.equal(1)
done()
}) })
}) })
...@@ -37,7 +37,7 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -37,7 +37,7 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter2) this.queryChainer.add(emitter2)
this.queryChainer.run().success(function() { this.queryChainer.run().success(function() {
assert(true) expect(true).to.be.true
done() done()
}).error(function(err) { }).error(function(err) {
console.log(err) console.log(err)
...@@ -53,9 +53,9 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -53,9 +53,9 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter1) this.queryChainer.add(emitter1)
this.queryChainer.run().success(function(results) { this.queryChainer.run().success(function(results) {
expect(results).toBeDefined() expect(results).to.exist
expect(results.length).toEqual(1) expect(results).to.have.length(1)
expect(results[0]).toEqual(1) expect(results[0]).to.equal(1)
done() done()
}) })
...@@ -72,8 +72,8 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -72,8 +72,8 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter3) this.queryChainer.add(emitter3)
this.queryChainer.run().success(function(results) { this.queryChainer.run().success(function(results) {
expect(results.length).toEqual(3) expect(results).to.have.length(3)
expect(results).toEqual([1,2,3]) expect(results).to.include.members([1,2,3])
done() done()
}) })
...@@ -90,10 +90,10 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -90,10 +90,10 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter2) this.queryChainer.add(emitter2)
this.queryChainer.run().success(function(results, result1, result2) { this.queryChainer.run().success(function(results, result1, result2) {
expect(result1).toBeDefined() expect(result1).to.exist
expect(result2).toBeDefined() expect(result2).to.exist
expect(result1).toEqual(1) expect(result1).to.equal(1)
expect(result2).toEqual(2) expect(result2).to.equal(2)
done() done()
}) })
...@@ -111,7 +111,7 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -111,7 +111,7 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter2, 'run') this.queryChainer.add(emitter2, 'run')
this.queryChainer.runSerially().success(function() { this.queryChainer.runSerially().success(function() {
assert(true) expect(true).to.be.true
done() done()
}) })
}) })
...@@ -122,9 +122,9 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -122,9 +122,9 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter1, 'run') this.queryChainer.add(emitter1, 'run')
this.queryChainer.runSerially().success(function(results) { this.queryChainer.runSerially().success(function(results) {
expect(results).toBeDefined() expect(results).to.exist
expect(results.length).toEqual(1) expect(results).to.have.length(1)
expect(results[0]).toEqual(1) expect(results[0]).to.equal(1)
done() done()
}) })
}) })
...@@ -139,8 +139,8 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -139,8 +139,8 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter3, 'run') this.queryChainer.add(emitter3, 'run')
this.queryChainer.runSerially().success(function(results) { this.queryChainer.runSerially().success(function(results) {
expect(results.length).toEqual(3) expect(results).to.have.length(3)
expect(results).toEqual([1,2,3]) expect(results).to.contain.members([1,2,3])
done() done()
}) })
}) })
...@@ -153,10 +153,10 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() { ...@@ -153,10 +153,10 @@ describe(Helpers.getTestDialectTeaser("QueryChainer"), function() {
this.queryChainer.add(emitter2, 'run') this.queryChainer.add(emitter2, 'run')
this.queryChainer.runSerially().success(function(results, result1, result2) { this.queryChainer.runSerially().success(function(results, result1, result2) {
expect(result1).toBeDefined() expect(result1).to.exist
expect(result2).toBeDefined() expect(result2).to.exist
expect(result1).toEqual(1) expect(result1).to.equal(1)
expect(result2).toEqual(2) expect(result2).to.equal(2)
done() done()
}) })
}) })
......
/* jshint multistr: true */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("QueryGenerators"), function () {
describe("comments", function() {
it("should create a comment for a column", function(done) {
var self = this
, User = this.sequelize.define('User', {
username: {type: DataTypes.STRING, comment: 'Some lovely info for my DBA'}
})
User.sync({ force: true }).success(function() {
var sql = ''
if (dialect === "mysql") {
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()
})
})
})
})
})
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("QueryInterface"), function () {
beforeEach(function(done) {
this.sequelize.options.quoteIdenifiers = true
this.queryInterface = this.sequelize.getQueryInterface()
done()
})
describe('dropAllTables', function() {
it("should drop all tables", function(done) {
var self = this
this.queryInterface.dropAllTables().complete(function(err) {
expect(err).to.be.null
self.queryInterface.showAllTables().complete(function(err, tableNames) {
expect(err).to.be.null
expect(tableNames).to.be.empty
self.queryInterface.createTable('table', { name: DataTypes.STRING }).complete(function(err) {
expect(err).to.be.null
self.queryInterface.showAllTables().complete(function(err, tableNames) {
expect(err).to.be.null
expect(tableNames).to.have.length(1)
self.queryInterface.dropAllTables().complete(function(err) {
expect(err).to.be.null
self.queryInterface.showAllTables().complete(function(err, tableNames) {
expect(err).to.be.null
expect(tableNames).to.be.empty
done()
})
})
})
})
})
})
})
})
describe('indexes', function() {
beforeEach(function(done) {
var self = this
this.queryInterface.dropTable('Users').success(function() {
self.queryInterface.createTable('Users', {
username: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN
}).success(function() {
done()
})
})
})
it('adds, reads and removes an index to the table', function(done) {
var self = this
this.queryInterface.addIndex('Users', ['username', 'isAdmin']).complete(function(err) {
expect(err).to.be.null
self.queryInterface.showIndex('Users').complete(function(err, indexes) {
expect(err).to.be.null
var indexColumns = _.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).to.include('users_username_is_admin')
self.queryInterface.removeIndex('Users', ['username', 'isAdmin']).complete(function(err) {
expect(err).to.be.null
self.queryInterface.showIndex('Users').complete(function(err, indexes) {
expect(err).to.be.null
indexColumns = _.uniq(indexes.map(function(index) { return index.name }))
expect(indexColumns).to.be.empty
done()
})
})
})
})
})
})
describe('describeTable', function() {
it('reads the metadata of the table', function(done) {
var self = this
var Users = self.sequelize.define('_Users', {
username: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
enumVals: DataTypes.ENUM('hello', 'world')
}, { freezeTableName: true })
Users.sync({ force: true }).success(function() {
self.queryInterface.describeTable('_Users').complete(function(err, metadata) {
expect(err).to.be.null
var username = metadata.username
var isAdmin = metadata.isAdmin
var enumVals = metadata.enumVals
expect(username.type).to.equal(dialect === 'postgres' ? 'CHARACTER VARYING' : 'VARCHAR(255)')
expect(username.allowNull).to.be.true
expect(username.defaultValue).to.be.null
expect(isAdmin.type).to.equal(dialect === 'postgres' ? 'BOOLEAN' : 'TINYINT(1)')
expect(isAdmin.allowNull).to.be.true
expect(isAdmin.defaultValue).to.be.null
if (dialect === "postgres" || dialect === "postgres-native") {
expect(enumVals.special).to.be.instanceof(Array)
expect(enumVals.special).to.have.length(2);
}
done()
})
})
})
})
})
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/support')
, DataTypes = require(__dirname + "/../lib/data-types")
, dialect = Support.getTestDialect()
, _ = require('lodash')
, Sequelize = require(__dirname + '/../index')
, config = require(__dirname + "/config/config")
, moment = require('moment')
chai.Assertion.includeStack = true
var qq = function(str) {
if (dialect == 'postgres' || dialect == 'sqlite') {
return '"' + str + '"'
} else if (dialect == 'mysql') {
return '`' + str + '`'
} else {
return str
}
}
describe(Support.getTestDialectTeaser("Sequelize"), function () {
describe('constructor', function() {
it('should pass the global options correctly', function(done) {
var sequelize = Support.createSequelizeInstance({ logging: false, define: { underscored:true } })
, DAO = sequelize.define('dao', {name: DataTypes.STRING})
expect(DAO.options.underscored).to.be.ok
done()
})
it('should correctly set the host and the port', function(done) {
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')
done()
})
})
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('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 and parses 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.map(function(u){ return u.user })).to.deep.equal([{'username':'john'}])
done()
})
})
})
})
if (dialect == 'mysql') {
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].__factory).to.equal(self.User)
done()
})
})
})
it('destructs dot separated attributes when doing a raw query', function(done) {
var tickChar = (dialect === 'postgres') ? '"' : '`'
, sql = "select 1 as " + Sequelize.Utils.addTicks('foo.bar.baz', tickChar)
this.sequelize.query(sql, null, { raw: 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('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()
})
})
})
describe('define', function() {
it("adds a new dao to the dao manager", function(done) {
expect(this.sequelize.daoFactoryManager.all.length).to.equal(0)
this.sequelize.define('foo', { title: DataTypes.STRING })
expect(this.sequelize.daoFactoryManager.all.length).to.equal(1)
done()
})
it("overwrites global options", function(done) {
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')
done()
})
it("inherits global collate option", function(done) {
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')
done()
})
it("inherits global classMethods and instanceMethods", function(done) {
var sequelize = Support.createSequelizeInstance({
define: {
classMethods : { globalClassMethod : function() {} },
instanceMethods : { globalInstanceMethod : function() {} }
}
})
var 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')
done()
})
it("uses the passed tableName", function(done) {
var self = this
, Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' })
Photo.sync({ force: true }).success(function() {
self.sequelize.getQueryInterface().showAllTables().success(function(tableNames) {
expect(tableNames).to.include('photos')
done()
})
})
})
})
describe('sync', function() {
it("synchronizes all daos", function(done) {
var Project = this.sequelize.define('project' + config.rand(), { title: DataTypes.STRING })
var Task = this.sequelize.define('task' + config.rand(), { title: DataTypes.STRING })
Project.sync({ force: true }).success(function() {
Task.sync({ force: true }).success(function() {
Project.create({title: 'bla'}).success(function() {
Task.create({title: 'bla'}).success(function(task){
expect(task).to.exist
expect(task.title).to.equal('bla')
done()
})
})
})
})
})
it('works with correct database credentials', function(done) {
var User = this.sequelize.define('User', { username: DataTypes.STRING })
User.sync().success(function() {
expect(true).to.be.true
done()
})
})
it("fails with incorrect database credentials", function(done) {
// sqlite doesn't have a concept of database credentials
if (dialect === "sqlite") {
expect(true).to.be.true
return done()
}
var sequelize2 = Support.getSequelizeInstance('foo', 'bar', null, { logging: false })
, User2 = sequelize2.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT })
User2.sync().error(function(err) {
if (dialect === "postgres" || dialect === "postgres-native") {
expect(err.message).to.equal('role "bar" does not exist')
} else {
expect(err.message.toString()).to.match(/.*Access\ denied.*/)
}
done()
})
})
})
describe('drop should work', function() {
it('correctly succeeds', function(done) {
var User = this.sequelize.define('Users', {username: DataTypes.STRING })
User.sync({ force: true }).success(function() {
User.drop().success(function() {
expect(true).to.be.true
done()
})
})
})
})
describe('import', function() {
it("imports a dao definition from a file", function(done) {
var Project = this.sequelize.import(__dirname + "/assets/project")
expect(Project).to.exist
done()
})
})
describe('define', function() {
[
{ type: DataTypes.ENUM, values: ['scheduled', 'active', 'finished']},
DataTypes.ENUM('scheduled', 'active', 'finished')
].forEach(function(status) {
describe('enum', function() {
beforeEach(function(done) {
this.Review = this.sequelize.define('review', { status: status })
this.Review.sync({ force: true }).success(function() {
done()
})
})
it('raises an error if no values are defined', function(done) {
var self = this
expect(function() {
self.sequelize.define('omnomnom', {
bla: { type: DataTypes.ENUM }
})
}).to.throw(Error, 'Values for ENUM haven\'t been defined.')
done()
})
it('correctly stores values', function(done) {
this.Review.create({ status: 'active' }).success(function(review) {
expect(review.status).to.equal('active')
done()
})
})
it('correctly loads values', function(done) {
var self = this
this.Review.create({ status: 'active' }).success(function() {
self.Review.findAll().success(function(reviews) {
expect(reviews[0].status).to.equal('active')
done()
})
})
})
it("doesn't save an instance if value is not in the range of enums", function(done) {
var self = this
expect(function() {
self.Review.create({ status: 'fnord' })
}).to.throw(Error, 'Value "fnord" for ENUM status is out of allowed scope. Allowed values: scheduled, active, finished')
done()
})
})
})
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(done) {
var Picture = this.sequelize.define('picture', _.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue)
})
})
done()
})
})
})
})
})
})
/* jshint camelcase: false */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, dbFile = __dirname + '/test.sqlite'
, storages = [dbFile]
chai.Assertion.includeStack = true
if (dialect === 'sqlite') {
describe('[SQLITE Specific] DAOFactory', function() {
after(function(done) {
this.sequelize.options.storage = ':memory:'
done()
})
beforeEach(function(done) {
this.sequelize.options.storage = dbFile
this.User = this.sequelize.define('User', {
age: DataTypes.INTEGER,
name: DataTypes.STRING,
bio: DataTypes.TEXT
})
this.User.sync({ force: true }).success(function() {
done()
})
})
storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() {
after(function(done) {
if (storage === dbFile) {
require("fs").writeFile(dbFile, '', function() {
done()
})
}
})
describe('create', function() {
it('creates a table entry', function(done) {
var self = this
this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }).success(function(user) {
expect(user.age).to.equal(21)
expect(user.name).to.equal('John Wayne')
expect(user.bio).to.equal('noot noot')
self.User.all().success(function(users) {
var usernames = users.map(function(user) {
return user.name
})
expect(usernames).to.contain('John Wayne')
done()
})
})
})
it('should allow the creation of an object with options as attribute', function(done) {
var Person = this.sequelize.define('Person', {
name: DataTypes.STRING,
options: DataTypes.TEXT
})
Person.sync({ force: true }).success(function() {
var options = JSON.stringify({ foo: 'bar', bar: 'foo' })
Person.create({
name: 'John Doe',
options: options
}).success(function(people) {
expect(people.options).to.deep.equal(options)
done()
})
})
})
it('should allow the creation of an object with a boolean (true) as attribute', function(done) {
var Person = this.sequelize.define('Person', {
name: DataTypes.STRING,
has_swag: DataTypes.BOOLEAN
})
Person.sync({ force: true }).success(function() {
Person.create({
name: 'John Doe',
has_swag: true
}).success(function(people) {
expect(people.has_swag).to.be.ok
done()
})
})
})
it('should allow the creation of an object with a boolean (false) as attribute', function(done) {
var Person = this.sequelize.define('Person', {
name: DataTypes.STRING,
has_swag: DataTypes.BOOLEAN
})
Person.sync({ force: true }).success(function() {
Person.create({
name: 'John Doe',
has_swag: false
}).success(function(people) {
expect(people.has_swag).to.not.be.ok
done()
})
})
})
})
describe('.find', function() {
beforeEach(function(done) {
this.User.create({name: 'user', bio: 'footbar'}).success(function() {
done()
})
})
it("finds normal lookups", function(done) {
this.User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).to.equal('user')
done()
})
})
it("should make aliased attributes available", function(done) {
this.User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).to.equal('user')
done()
})
})
})
describe('.all', function() {
beforeEach(function(done) {
this.User.bulkCreate([
{name: 'user', bio: 'foobar'},
{name: 'user', bio: 'foobar'}
]).success(function() {
done()
})
})
it("should return all users", function(done) {
this.User.all().on('success', function(users) {
expect(users).to.have.length(2)
done()
})
})
})
describe('.min', function() {
it("should return the min value", function(done) {
var self = this
, users = []
for (var i = 2; i < 5; i++) {
users[users.length] = {age: i}
}
this.User.bulkCreate(users).success(function() {
self.User.min('age').on('success', function(min) {
expect(min).to.equal(2)
done()
})
})
})
})
describe('.max', function() {
it("should return the max value", function(done) {
var self = this
, users = []
for (var i = 2; i <= 5; i++) {
users[users.length] = {age: i}
}
this.User.bulkCreate(users).success(function() {
self.User.max('age').on('success', function(min) {
expect(min).to.equal(5)
done()
})
})
})
})
})
})
})
}
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, Helpers = require('../buster-helpers') , Support = require(__dirname + '/../support')
, dialect = Helpers.getTestDialect() , DataTypes = require(__dirname + "/../../lib/data-types")
} , dialect = Support.getTestDialect()
buster.spec.expose() chai.Assertion.includeStack = true
if (dialect === 'sqlite') { if (dialect === 'sqlite') {
describe('[SQLITE] DAO', function() { describe('[SQLITE Specific] DAO', function() {
before(function(done) { beforeEach(function(done) {
var self = this this.User = this.sequelize.define('User', {
username: DataTypes.STRING
Helpers.initTests({ })
dialect: 'sqlite', this.User.sync({ force: true }).success(function() {
beforeComplete: function(sequelize, DataTypes) { done()
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: DataTypes.STRING
})
},
onComplete: function() {
self.User.sync({ force: true }).success(done)
}
}) })
}) })
...@@ -32,20 +23,15 @@ if (dialect === 'sqlite') { ...@@ -32,20 +23,15 @@ if (dialect === 'sqlite') {
this.User this.User
.create({ username: 'user', createdAt: new Date(2011, 04, 04) }) .create({ username: 'user', createdAt: new Date(2011, 04, 04) })
.success(function(oldUser) { .success(function() {
self.User self.User.create({ username: 'new user' }).success(function() {
.create({ username: 'new user' }) self.User.findAll({
.success(function(newUser) { where: ['createdAt > ?', new Date(2012, 01, 01)]
self.User.findAll({ }).success(function(users) {
where: ['createdAt > ?', new Date(2012, 01, 01)] expect(users).to.have.length(1)
}).success(function(users) { done()
expect(users.length).toEqual(1)
done()
})
}) })
}) })
.error(function(err) {
console.log(err)
}) })
}) })
}) })
......
var Sequelize = require("../../index") var chai = require('chai')
, sequelize = new Sequelize(null, null, null, { dialect: 'sqlite' }) , expect = chai.expect
, Helpers = new (require("../config/helpers"))(sequelize) , Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, util = require("util")
, _ = require('lodash')
, moment = require('moment')
, QueryGenerator = require("../../lib/dialects/sqlite/query-generator") , QueryGenerator = require("../../lib/dialects/sqlite/query-generator")
, util = require("util");
describe('QueryGenerator', function() { chai.Assertion.includeStack = true
beforeEach(function() { Helpers.sync() })
afterEach(function() { Helpers.drop() })
var suites = { if (dialect === 'sqlite') {
describe('[SQLITE Specific] QueryGenerator', function() {
beforeEach(function(done) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING
})
this.User.sync({ force: true }).success(function() {
done()
})
})
var suites = {
attributesToSQL: [
{
arguments: [{id: 'INTEGER'}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'}
},
{
arguments: [{id: {type: 'INTEGER'}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false}}],
expectation: {id: 'INTEGER NOT NULL'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: true}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}],
expectation: {id: 'INTEGER PRIMARY KEY AUTOINCREMENT'}
},
{
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}],
expectation: {id: 'INTEGER DEFAULT 0'}
},
{
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
},
],
attributesToSQL: [ createTableQuery: [
{ {
arguments: [{id: 'INTEGER'}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));"
}, },
{ {
arguments: [{id: 'INTEGER', foo: 'VARCHAR(255)'}], arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}],
expectation: {id: 'INTEGER', foo: 'VARCHAR(255)'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));"
}, },
{ {
arguments: [{id: {type: 'INTEGER'}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}],
expectation: {id: 'INTEGER'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER PRIMARY KEY);"
}, },
{ {
arguments: [{id: {type: 'INTEGER', allowNull: false}}], arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}],
expectation: {id: 'INTEGER NOT NULL'} expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION);"
}, }
{ ],
arguments: [{id: {type: 'INTEGER', allowNull: true}}],
expectation: {id: 'INTEGER'}
},
{
arguments: [{id: {type: 'INTEGER', primaryKey: true, autoIncrement: true}}],
expectation: {id: 'INTEGER PRIMARY KEY AUTOINCREMENT'}
},
{
arguments: [{id: {type: 'INTEGER', defaultValue: 0}}],
expectation: {id: 'INTEGER DEFAULT 0'}
},
{
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', referencesKey: 'pk'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`pk`)'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onDelete: 'CASCADE'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON DELETE CASCADE'}
},
{
arguments: [{id: {type: 'INTEGER', references: 'Bar', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER REFERENCES `Bar` (`id`) ON UPDATE RESTRICT'}
},
{
arguments: [{id: {type: 'INTEGER', allowNull: false, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: 'RESTRICT'}}],
expectation: {id: 'INTEGER NOT NULL DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
},
],
createTableQuery: [ insertQuery: [
{ {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}], arguments: ['myTable', { name: 'foo' }],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255));" expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');"
}, }, {
{ arguments: ['myTable', { name: "'bar'" }],
arguments: ['myTable', {title: 'ENUM("A", "B", "C")', name: 'VARCHAR(255)'}], expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar''');"
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` ENUM(\"A\", \"B\", \"C\"), `name` VARCHAR(255));" }, {
}, arguments: ['myTable', { name: "bar", value: null }],
{ expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);"
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', id: 'INTEGER PRIMARY KEY'}], }, {
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `id` INTEGER PRIMARY KEY);" arguments: ['myTable', { name: "bar", value: undefined }],
}, expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);"
{ }, {
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)', otherId: 'INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION'}], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}],
expectation: "CREATE TABLE IF NOT EXISTS `myTable` (`title` VARCHAR(255), `name` VARCHAR(255), `otherId` INTEGER REFERENCES `otherTable` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION);" expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55');"
} }, {
], arguments: ['myTable', { name: "foo", value: true }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1);"
}, {
arguments: ['myTable', { name: "foo", value: false }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0);"
}, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);"
}, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);",
context: {options: {omitNull: false}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);",
context: {options: {omitNull: true}}
}, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}],
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);",
context: {options: {omitNull: true}}
}
],
insertQuery: [ bulkInsertQuery: [
{ {
arguments: ['myTable', { name: 'foo' }], arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo');" expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');"
}, { }, {
arguments: ['myTable', { name: "'bar'" }], arguments: ['myTable', [{name: "'bar'"}, {name: 'foo'}]],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar''');" expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');"
}, { }, {
arguments: ['myTable', { name: "bar", value: null }], arguments: ['myTable', [{name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {name: 'bar', birthday: moment("2012-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);" expectation: "INSERT INTO `myTable` (`name`,`birthday`) VALUES ('foo','2011-03-27 10:01:55'),('bar','2012-03-27 10:01:55');"
}, { }, {
arguments: ['myTable', { name: "bar", value: undefined }], arguments: ['myTable', [{name: "bar", value: null}, {name: 'foo', value: 1}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);"
}, { }, {
arguments: ['myTable', { name: "foo", value: true }], arguments: ['myTable', [{name: "bar", value: undefined}, {name: 'bar', value: 2}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);"
}, { }, {
arguments: ['myTable', { name: "foo", value: false }], arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', [{name: "foo", value: false}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);"
}, { }, {
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL);", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);"
context: {options: {omitNull: false}} }, {
}, { arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: null}], expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", context: {options: {omitNull: false}}
context: {options: {omitNull: true}} }, {
}, { arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
arguments: ['myTable', {name: 'foo', foo: 1, nullValue: undefined}], expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
expectation: "INSERT INTO `myTable` (`name`,`foo`) VALUES ('foo',1);", context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not
context: {options: {omitNull: true}} }, {
} arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
], expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} // Note: As above
}
],
bulkInsertQuery: [ updateQuery: [
{ {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}]], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, {id: 2}],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('foo'),('bar');" expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "'bar'"}, {name: 'foo'}]], arguments: ['myTable', {name: 'foo', birthday: moment("2011-03-27 10:01:55 +0000", "YYYY-MM-DD HH:mm:ss Z").toDate()}, 2],
expectation: "INSERT INTO `myTable` (`name`) VALUES ('''bar'''),('foo');" expectation: "UPDATE `myTable` SET `name`='foo',`birthday`='2011-03-27 10:01:55' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "bar", value: null}, {name: 'foo', value: 1}]], arguments: ['myTable', { name: 'foo' }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('foo',1);" expectation: "UPDATE `myTable` SET `name`='foo' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "bar", value: undefined}, {name: 'bar', value: 2}]], arguments: ['myTable', { name: "'bar'" }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('bar',NULL),('bar',2);" expectation: "UPDATE `myTable` SET `name`='''bar''' WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], arguments: ['myTable', { name: 'bar', value: null }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',1),('bar',0);" expectation: "UPDATE `myTable` SET `name`='bar',`value`=NULL WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: "foo", value: false}, {name: 'bar', value: false}]], arguments: ['myTable', { name: 'bar', value: undefined }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',0),('bar',0);" expectation: "UPDATE `myTable` SET `name`='bar',`value`=NULL WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], arguments: ['myTable', { flag: true }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);" expectation: "UPDATE `myTable` SET `flag`=1 WHERE `id`=2"
}, { }, {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], arguments: ['myTable', { flag: false }, { id: 2 }],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", expectation: "UPDATE `myTable` SET `flag`=0 WHERE `id`=2"
context: {options: {omitNull: false}} }, {
}, { arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'"
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", }, {
context: {options: {omitNull: true}} // Note: We don't honour this because it makes little sense when some rows may have nulls and others not arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
}, { expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'",
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], context: {options: {omitNull: false}}
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", }, {
context: {options: {omitNull: true}} // Note: As above arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
} expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'",
], context: {options: {omitNull: true}}
}
],
updateQuery: [ deleteQuery: [
{ {
arguments: ['myTable', { name: 'foo' }, { id: 2 }], arguments: ['myTable', {name: 'foo'}],
expectation: "UPDATE `myTable` SET `name`='foo' WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}, { }, {
arguments: ['myTable', { name: "'bar'" }, { id: 2 }], arguments: ['myTable', 1],
expectation: "UPDATE `myTable` SET `name`='''bar''' WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, { }, {
arguments: ['myTable', { name: 'bar', value: null }, { id: 2 }], arguments: ['myTable', 1, {truncate: true}],
expectation: "UPDATE `myTable` SET `name`='bar',`value`=NULL WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, { }, {
arguments: ['myTable', { name: 'bar', value: undefined }, { id: 2 }], arguments: ['myTable', 1, {limit: 10}],
expectation: "UPDATE `myTable` SET `name`='bar',`value`=NULL WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, { }, {
arguments: ['myTable', { flag: true }, { id: 2 }], arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "UPDATE `myTable` SET `flag`=1 WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `name`='foo'';DROP TABLE myTable;'"
}, { }, {
arguments: ['myTable', { flag: false }, { id: 2 }], arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "UPDATE `myTable` SET `flag`=0 WHERE `id`=2" expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}, { }
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}], ]
expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'" }
}, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=2,`nullValue`=NULL WHERE `name`='foo'",
context: {options: {omitNull: false}}
}, {
arguments: ['myTable', {bar: 2, nullValue: null}, {name: 'foo'}],
expectation: "UPDATE `myTable` SET `bar`=2 WHERE `name`='foo'",
context: {options: {omitNull: true}}
}
],
deleteQuery: [
{
arguments: ['myTable', {name: 'foo'}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}, {
arguments: ['myTable', 1],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', 1, {truncate: true}],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', 1, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `id`=1"
}, {
arguments: ['myTable', {name: "foo';DROP TABLE myTable;"}, {limit: 10}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'';DROP TABLE myTable;'"
}, {
arguments: ['myTable', {name: 'foo'}, {limit: null}],
expectation: "DELETE FROM `myTable` WHERE `name`='foo'"
}
]
};
Sequelize.Utils._.each(suites, function(tests, suiteTitle) { _.each(suites, function(tests, suiteTitle) {
describe(suiteTitle, function() { describe(suiteTitle, function() {
tests.forEach(function(test) { tests.forEach(function(test) {
var title = test.title || 'SQLite correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) var title = test.title || 'SQLite correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function() { it(title, function(done) {
// Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly
var context = test.context || {options: {}}; var context = test.context || {options: {}};
QueryGenerator.options = context.options QueryGenerator.options = context.options
var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments) var conditions = QueryGenerator[suiteTitle].apply(QueryGenerator, test.arguments)
expect(conditions).toEqual(test.expectation) expect(conditions).to.deep.equal(test.expectation)
done()
})
}) })
}) })
}) })
}) })
}); }
File mode changed
const Sequelize = require(__dirname + "/../index") var fs = require('fs')
, DataTypes = require(__dirname + "/../lib/data-types") , Sequelize = require(__dirname + "/../index")
, config = require(__dirname + "/config/config") , DataTypes = require(__dirname + "/../lib/data-types")
, fs = require('fs') , config = require(__dirname + "/config/config")
, buster = require("buster")
var BusterHelpers = module.exports = { var Support = {
Sequelize: Sequelize, Sequelize: Sequelize,
initTests: function(options) { initTests: function(options) {
...@@ -30,25 +29,41 @@ var BusterHelpers = module.exports = { ...@@ -30,25 +29,41 @@ var BusterHelpers = module.exports = {
options.dialect = options.dialect || 'mysql' options.dialect = options.dialect || 'mysql'
options.logging = (options.hasOwnProperty('logging') ? options.logging : false) options.logging = (options.hasOwnProperty('logging') ? options.logging : false)
options.pool = options.pool || config.pool
var sequelizeOptions = { var sequelizeOptions = {
logging: options.logging, logging: options.logging,
dialect: options.dialect, dialect: options.dialect,
port: process.env.SEQ_PORT || config[options.dialect].port port: options.port || process.env.SEQ_PORT || config[options.dialect].port,
pool: options.pool
}
if (!!options.host) {
sequelizeOptions.host = options.host
}
if (!!options.define) {
sequelizeOptions.define = options.define
} }
if (process.env.DIALECT === 'postgres-native') { if (process.env.DIALECT === 'postgres-native') {
sequelizeOptions.native = true sequelizeOptions.native = true
} }
return new Sequelize( return this.getSequelizeInstance(
process.env.SEQ_DB || config[options.dialect].database, process.env.SEQ_DB || config[options.dialect].database,
process.env.SEQ_USER || process.env.SEQ_USERNAME || config[options.dialect].username, process.env.SEQ_USER || process.env.SEQ_USERNAME || config[options.dialect].username,
process.env.SEQ_PW || process.env.SEQ_PASSWORD || config[options.dialect].password, process.env.SEQ_PW || process.env.SEQ_PASSWORD || config[options.dialect].password,
sequelizeOptions sequelizeOptions
) )
}, },
getSequelizeInstance: function(db, user, pass, options) {
options = options || {};
options.dialect = options.dialect || this.getTestDialect()
return new Sequelize(db, user, pass, options)
},
clearDatabase: function(sequelize, callback) { clearDatabase: function(sequelize, callback) {
sequelize sequelize
.getQueryInterface() .getQueryInterface()
...@@ -66,6 +81,14 @@ var BusterHelpers = module.exports = { ...@@ -66,6 +81,14 @@ var BusterHelpers = module.exports = {
}) })
}, },
checkMatchForDialects: function(dialect, value, expectations) {
if (!!expectations[dialect]) {
expect(value).to.match(expectations[dialect])
} else {
throw new Error('Undefined expectation for "' + dialect + '"!')
}
},
getTestDialect: function() { getTestDialect: function() {
var envDialect = process.env.DIALECT || 'mysql' var envDialect = process.env.DIALECT || 'mysql'
...@@ -88,22 +111,20 @@ var BusterHelpers = module.exports = { ...@@ -88,22 +111,20 @@ var BusterHelpers = module.exports = {
} }
return "[" + dialect.toUpperCase() + "] " + moduleName return "[" + dialect.toUpperCase() + "] " + moduleName
},
checkMatchForDialects: function(dialect, value, expectations) {
if (!!expectations[dialect]) {
expect(value).toMatch(expectations[dialect])
} else {
throw new Error('Undefined expectation for "' + dialect + '"!')
}
},
assertException: function(block, msg) {
try {
block()
throw new Error('Passed function did not throw an error')
} catch(e) {
buster.assert.equals(e.message, msg)
}
} }
} }
var sequelize = Support.createSequelizeInstance({ dialect: Support.getTestDialect() })
before(function(done) {
this.sequelize = sequelize
done()
})
beforeEach(function(done) {
Support.clearDatabase(this.sequelize, function() {
done()
})
})
module.exports = Support
var chai = require('chai')
, expect = chai.expect
, Utils = require(__dirname + '/../lib/utils')
, Support = require(__dirname + '/support')
chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser("Utils"), function() {
describe('removeCommentsFromFunctionString', function() {
it("removes line comments at the start of a line", function(done) {
var functionWithLineComments = function() {
// noot noot
}
var string = functionWithLineComments.toString()
, result = Utils.removeCommentsFromFunctionString(string)
expect(result).not.to.match(/.*noot.*/)
done()
})
it("removes lines comments in the middle of a line", function(done) {
var functionWithLineComments = function() {
alert(1) // noot noot
}
var string = functionWithLineComments.toString()
, result = Utils.removeCommentsFromFunctionString(string)
expect(result).not.to.match(/.*noot.*/)
done()
})
it("removes range comments", function(done) {
var s = function() {
alert(1) /*
noot noot
*/
alert(2) /*
foo
*/
}.toString()
var result = Utils.removeCommentsFromFunctionString(s)
expect(result).not.to.match(/.*noot.*/)
expect(result).not.to.match(/.*foo.*/)
expect(result).to.match(/.*alert\(2\).*/)
done()
})
})
describe('argsArePrimaryKeys', function() {
it("doesn't detect primary keys if primareyKeys and values have different lengths", function(done) {
expect(Utils.argsArePrimaryKeys([1,2,3], [1])).to.be.false
done()
})
it("doesn't detect primary keys if primary keys are hashes or arrays", function(done) {
expect(Utils.argsArePrimaryKeys([[]], [1])).to.be.false
done()
})
it('detects primary keys if length is correct and data types are matching', function(done) {
expect(Utils.argsArePrimaryKeys([1,2,3], ["INTEGER", "INTEGER", "INTEGER"])).to.be.true
done()
})
it("detects primary keys if primary keys are dates and lengths are matching", function(done) {
expect(Utils.argsArePrimaryKeys([new Date()], ['foo'])).to.be.true
done()
})
})
describe('underscore', function() {
describe('underscoredIf', function() {
it('is defined', function(done) {
expect(Utils._.underscoredIf).to.be.ok
done()
})
it('underscores if second param is true', function(done) {
expect(Utils._.underscoredIf('fooBar', true)).to.equal('foo_bar')
done()
})
it("doesn't underscore if second param is false", function(done) {
expect(Utils._.underscoredIf('fooBar', false)).to.equal('fooBar')
done()
})
})
describe('camelizeIf', function() {
it('is defined', function(done) {
expect(Utils._.camelizeIf).to.be.ok
done()
})
it('camelizes if second param is true', function(done) {
expect(Utils._.camelizeIf('foo_bar', true)).to.equal('fooBar')
done()
})
it("doesn't camelize if second param is false", function(done) {
expect(Utils._.underscoredIf('fooBar', true)).to.equal('foo_bar')
done()
})
})
})
describe('isHash', function() {
it('doesn\'t match arrays', function(done) {
expect(Utils.isHash([])).to.be.false
done()
})
it('doesn\'t match null', function(done) {
expect(Utils.isHash(null)).to.be.false
done()
})
it('matches plain objects', function(done) {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
}
}
expect(Utils.isHash(values)).to.be.true
done()
})
it('matches plain objects with length property/key', function(done) {
var values = {
'name': {
'first': 'Foo',
'last': 'Bar'
},
'length': 1
}
expect(Utils.isHash(values)).to.be.true
done()
})
})
describe('format', function() {
it('should format where clause correctly when the value is truthy', function(done) {
var where = ['foo = ?', 1]
expect(Utils.format(where)).to.equal('foo = 1')
done()
})
it('should format where clause correctly when the value is false', function(done) {
var where = ['foo = ?', 0]
expect(Utils.format(where)).to.equal('foo = 0')
done()
})
})
})
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!