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

Commit 3ed27be1 by Sascha Depold

Merge branch 'master' into features/first_steps_for_syntax_sugar

Conflicts:
	package.json
	spec/dao-factory.spec.js
2 parents 551c4e7e 95a23b6d
Showing with 1932 additions and 1767 deletions
...@@ -3,7 +3,7 @@ before_script: ...@@ -3,7 +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"
notifications: notifications:
email: email:
...@@ -18,4 +18,4 @@ env: ...@@ -18,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
...@@ -139,30 +139,27 @@ $ npm install ...@@ -139,30 +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). lovely [Mocha](http://visionmedia.github.io/mocha/) 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 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 with buster.js $ # alternatively you can pass database credentials with $variables when testing
$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password buster-test $ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password make test
``` ```
### 5. That's all ### ### 5. That's all ###
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,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,6 +23,10 @@ ...@@ -21,6 +23,10 @@
- [BUG] bulkCreate would have problems with a disparate field list [#738](https://github.com/sequelize/sequelize/pull/738). 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
- [BUG] You can now use .find() on any single integer primary key when throwing just a number as an argument [#796](https://github.com/sequelize/sequelize/pull/796). thanks to durango
- [BUG] Adding unique to a column for Postgres in the migrator should be fixed [#795](https://github.com/sequelize/sequelize/pull/795). 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 +48,12 @@ ...@@ -42,8 +48,12 @@
- [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 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] 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
- [FEATURE] bulkCreate() now has a third argument which gives you the ability to validate each row before attempting to bulkInsert [#797](https://github.com/sequelize/sequelize/pull/797). thanks to durango
- [FEATURE] Added `isDirty` to model instances. [#798](https://github.com/sequelize/sequelize/pull/798). Thanks to mstorgaard
- [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
......
...@@ -43,7 +43,7 @@ module.exports = (function() { ...@@ -43,7 +43,7 @@ module.exports = (function() {
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
}) })
...@@ -55,12 +55,12 @@ module.exports = (function() { ...@@ -55,12 +55,12 @@ module.exports = (function() {
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 }))
} }
......
...@@ -70,27 +70,35 @@ module.exports = (function() { ...@@ -70,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()
} }
......
...@@ -277,14 +277,20 @@ module.exports = (function() { ...@@ -277,14 +277,20 @@ module.exports = (function() {
} }
var primaryKeys = this.primaryKeys var primaryKeys = this.primaryKeys
, keys = Object.keys(primaryKeys)
, keysLength = keys.length
// options is not a hash but an id // options is not a hash but an id
if (typeof options === 'number') { if (typeof options === 'number') {
options = { where: options } var oldOption = options
options = { where: {} }
if (keysLength === 1) {
options.where[keys[0]] = oldOption
} else {
options.where.id = oldOption
}
} else if (Utils._.size(primaryKeys) && Utils.argsArePrimaryKeys(arguments, primaryKeys)) { } else if (Utils._.size(primaryKeys) && Utils.argsArePrimaryKeys(arguments, primaryKeys)) {
var where = {} var where = {}
, self = this
, keys = Object.keys(primaryKeys)
Utils._.each(arguments, function(arg, i) { Utils._.each(arguments, function(arg, i) {
var key = keys[i] var key = keys[i]
...@@ -315,7 +321,6 @@ module.exports = (function() { ...@@ -315,7 +321,6 @@ module.exports = (function() {
this.options.whereCollection = options.where || null this.options.whereCollection = options.where || null
} else if (typeof options === "string") { } else if (typeof options === "string") {
var where = {} var where = {}
, keys = Object.keys(primaryKeys)
if (this.primaryKeyCount === 1) { if (this.primaryKeyCount === 1) {
where[primaryKeys[keys[0]]] = options; where[primaryKeys[keys[0]]] = options;
...@@ -404,7 +409,7 @@ module.exports = (function() { ...@@ -404,7 +409,7 @@ module.exports = (function() {
} }
DAOFactory.prototype.build = function(values, options) { DAOFactory.prototype.build = function(values, options) {
options = options || { isNewRecord: true } options = options || { isNewRecord: true, isDirty: true }
var self = this var self = this
, instance = new this.DAO(values, this.options, options.isNewRecord) , instance = new this.DAO(values, this.options, options.isNewRecord)
...@@ -412,6 +417,7 @@ module.exports = (function() { ...@@ -412,6 +417,7 @@ module.exports = (function() {
instance.isNewRecord = options.isNewRecord instance.isNewRecord = options.isNewRecord
instance.daoFactoryName = this.name instance.daoFactoryName = this.name
instance.daoFactory = this instance.daoFactory = this
instance.isDirty = options.isDirty
return instance return instance
} }
...@@ -461,13 +467,31 @@ module.exports = (function() { ...@@ -461,13 +467,31 @@ module.exports = (function() {
* generated IDs and other default values in a way that can be mapped to * generated IDs and other default values in a way that can be mapped to
* multiple records * multiple records
*/ */
DAOFactory.prototype.bulkCreate = function(records, fields) { DAOFactory.prototype.bulkCreate = function(records, fields, options) {
options = options || {}
options.validate = options.validate || false
fields = fields || []
var self = this var self = this
, daos = records.map(function(v) { return self.build(v) })
, 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'
, errors = []
fields = fields || [] , daos = records.map(function(v) {
var build = self.build(v)
if (options.validate === true) {
var valid = build.validate({skip: fields})
if (valid !== null) {
errors[errors.length] = {record: v, errors: valid}
}
}
return build
})
if (options.validate === true && errors.length > 0) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', errors)
}).run()
}
// 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 = []
...@@ -485,7 +509,7 @@ module.exports = (function() { ...@@ -485,7 +509,7 @@ module.exports = (function() {
values[updatedAtAttr] = Utils.now() values[updatedAtAttr] = Utils.now()
} }
records.push(values); records.push(values)
}) })
// Validate enums // Validate enums
......
var Validator = require("validator") var Validator = require("validator")
, Utils = require("./utils") , Utils = require("./utils")
var DaoValidator = module.exports = function(model) { var DaoValidator = module.exports = function(model, options) {
options = options || {}
options.skip = options.skip || []
this.model = model this.model = model
this.options = options
} }
DaoValidator.prototype.validate = function() { DaoValidator.prototype.validate = function() {
...@@ -32,17 +36,19 @@ var validateModel = function() { ...@@ -32,17 +36,19 @@ var validateModel = function() {
} }
var validateAttributes = function() { var validateAttributes = function() {
var errors = {} var self = this
, errors = {}
// for each field and value // for each field and value
Utils._.each(this.model.dataValues, function(value, field) { Utils._.each(this.model.dataValues, function(value, field) {
var rawAttribute = this.model.rawAttributes[field] var rawAttribute = self.model.rawAttributes[field]
, hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined))) , hasAllowedNull = ((rawAttribute === undefined || rawAttribute.allowNull === true) && ((value === null) || (value === undefined)))
, isSkipped = self.options.skip.length > 0 && self.options.skip.indexOf(field) === -1
if (this.model.validators.hasOwnProperty(field) && !hasAllowedNull) { if (self.model.validators.hasOwnProperty(field) && !hasAllowedNull && !isSkipped) {
errors = Utils._.merge(errors, validateAttribute.call(this, value, field)) errors = Utils._.merge(errors, validateAttribute.call(self, value, field))
} }
}.bind(this)) // for each field })
return errors return errors
} }
......
...@@ -84,10 +84,15 @@ module.exports = (function() { ...@@ -84,10 +84,15 @@ module.exports = (function() {
DAO.prototype.getDataValue = function(name) { DAO.prototype.getDataValue = function(name) {
return this.dataValues && this.dataValues.hasOwnProperty(name) ? this.dataValues[name] : this[name] return this.dataValues && this.dataValues.hasOwnProperty(name) ? this.dataValues[name] : this[name]
} }
DAO.prototype.get = DAO.prototype.getDataValue
DAO.prototype.setDataValue = function(name, value) { DAO.prototype.setDataValue = function(name, value) {
if (Utils.hasChanged(this.dataValues[name], value)) {
this.isDirty = true
}
this.dataValues[name] = value this.dataValues[name] = value
} }
DAO.prototype.set = DAO.prototype.setDataValue
// if an array with field names is passed to save() // if an array with field names is passed to save()
// only those fields will be updated // only those fields will be updated
...@@ -120,10 +125,24 @@ module.exports = (function() { ...@@ -120,10 +125,24 @@ module.exports = (function() {
for (var attrName in this.daoFactory.rawAttributes) { for (var attrName in this.daoFactory.rawAttributes) {
if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) { if (this.daoFactory.rawAttributes.hasOwnProperty(attrName)) {
var definition = this.daoFactory.rawAttributes[attrName] var definition = this.daoFactory.rawAttributes[attrName]
, isEnum = (definition.type && (definition.type.toString() === DataTypes.ENUM.toString())) , isEnum = definition.type && (definition.type.toString() === DataTypes.ENUM.toString())
, isHstore = (!!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type) , isHstore = !!definition.type && !!definition.type.type && definition.type.type === DataTypes.HSTORE.type
, hasValue = (typeof values[attrName] !== 'undefined') , hasValue = values[attrName] !== undefined
, valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1) , isMySQL = this.daoFactory.daoFactoryManager.sequelize.options.dialect === "mysql"
, ciCollation = !!this.daoFactory.options.collate && this.daoFactory.options.collate.match(/_ci$/i)
, valueOutOfScope
if (isEnum && isMySQL && ciCollation && hasValue) {
var scopeIndex = (definition.values || []).map(function(d) { return d.toLowerCase() }).indexOf(values[attrName].toLowerCase())
valueOutOfScope = scopeIndex === -1
// We'll return what the actual case will be, since a simple SELECT query would do the same...
if (!valueOutOfScope) {
values[attrName] = definition.values[scopeIndex]
}
} else {
valueOutOfScope = ((definition.values || []).indexOf(values[attrName]) === -1)
}
if (isEnum && hasValue && valueOutOfScope) { if (isEnum && hasValue && valueOutOfScope) {
throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', ')) throw new Error('Value "' + values[attrName] + '" for ENUM ' + attrName + ' is out of allowed scope. Allowed values: ' + definition.values.join(', '))
...@@ -149,6 +168,7 @@ module.exports = (function() { ...@@ -149,6 +168,7 @@ module.exports = (function() {
}).run() }).run()
} }
else if (this.isNewRecord) { else if (this.isNewRecord) {
this.isDirty = false
return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values) return this.QueryInterface.insert(this, this.QueryInterface.QueryGenerator.addSchema(this.__factory), values)
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id }; var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : { id: this.id };
...@@ -157,6 +177,7 @@ module.exports = (function() { ...@@ -157,6 +177,7 @@ module.exports = (function() {
identifier = this.__options.whereCollection; identifier = this.__options.whereCollection;
} }
this.isDirty = false
var tableName = this.QueryInterface.QueryGenerator.addSchema(this.__factory) var tableName = this.QueryInterface.QueryGenerator.addSchema(this.__factory)
, query = this.QueryInterface.update(this, tableName, values, identifier) , query = this.QueryInterface.update(this, tableName, values, identifier)
...@@ -191,6 +212,7 @@ module.exports = (function() { ...@@ -191,6 +212,7 @@ module.exports = (function() {
this[valueName] = obj.values[valueName] this[valueName] = obj.values[valueName]
} }
} }
this.isDirty = false
emitter.emit('success', this) emitter.emit('success', this)
}.bind(this)) }.bind(this))
}.bind(this)).run() }.bind(this)).run()
...@@ -201,8 +223,8 @@ module.exports = (function() { ...@@ -201,8 +223,8 @@ module.exports = (function() {
* *
* @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries. * @return null if and only if validation successful; otherwise an object containing { field name : [error msgs] } entries.
*/ */
DAO.prototype.validate = function() { DAO.prototype.validate = function(object) {
var validator = new DaoValidator(this) var validator = new DaoValidator(this, object)
, errors = validator.validate() , errors = validator.validate()
return (Utils._.isEmpty(errors) ? null : errors) return (Utils._.isEmpty(errors) ? null : errors)
...@@ -224,14 +246,21 @@ module.exports = (function() { ...@@ -224,14 +246,21 @@ module.exports = (function() {
readOnlyAttributes.push('updatedAt') readOnlyAttributes.push('updatedAt')
readOnlyAttributes.push('deletedAt') readOnlyAttributes.push('deletedAt')
var isDirty = this.isDirty
Utils._.each(updates, function(value, attr) { Utils._.each(updates, function(value, attr) {
var updateAllowed = ( var updateAllowed = (
(readOnlyAttributes.indexOf(attr) == -1) && (readOnlyAttributes.indexOf(attr) == -1) &&
(readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) && (readOnlyAttributes.indexOf(Utils._.underscored(attr)) == -1) &&
(self.attributes.indexOf(attr) > -1) (self.attributes.indexOf(attr) > -1)
) )
updateAllowed && (self[attr] = value) if (updateAllowed) {
if (Utils.hasChanged(self[attr], value)) {
isDirty = true
}
self[attr] = value
}
}) })
this.isDirty = isDirty
} }
DAO.prototype.destroy = function() { DAO.prototype.destroy = function() {
...@@ -330,7 +359,19 @@ module.exports = (function() { ...@@ -330,7 +359,19 @@ module.exports = (function() {
// (the same is true for __defineSetter and 'prototype' getters) // (the same is true for __defineSetter and 'prototype' getters)
if (has !== true) { if (has !== true) {
this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; }); this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; });
this.__defineSetter__(attribute, has.set || function(v) { this.dataValues[attribute] = v; }); this.__defineSetter__(attribute, has.set || function(v) {
if (Utils.hasChanged(this.dataValues[attribute], v)) {
//Only dirty the object if the change is not due to id, touchedAt, createdAt or updatedAt being initiated
var updatedAtAttr = this.__options.underscored ? 'updated_at' : 'updatedAt'
, createdAtAttr = this.__options.underscored ? 'created_at' : 'createdAt'
, touchedAtAttr = this.__options.underscored ? 'touched_at' : 'touchedAt'
if (this.dataValues[attribute] || (attribute != 'id' && attribute != touchedAtAttr && attribute != createdAtAttr && attribute != updatedAtAttr)) {
this.isDirty = true
}
}
this.dataValues[attribute] = v
});
} }
this[attribute] = value; this[attribute] = value;
......
...@@ -262,7 +262,7 @@ module.exports = (function() { ...@@ -262,7 +262,7 @@ module.exports = (function() {
result = transformRowsWithEagerLoadingIntoDaos.call(this, results) result = transformRowsWithEagerLoadingIntoDaos.call(this, results)
} else { } else {
result = results.map(function(result) { result = results.map(function(result) {
return this.callee.build(result, { isNewRecord: false }) return this.callee.build(result, { isNewRecord: false, isDirty: false })
}.bind(this)) }.bind(this))
} }
...@@ -287,7 +287,7 @@ module.exports = (function() { ...@@ -287,7 +287,7 @@ module.exports = (function() {
var transformRowWithEagerLoadingIntoDao = function(result, dao) { var transformRowWithEagerLoadingIntoDao = function(result, dao) {
// let's build the actual dao instance first... // let's build the actual dao instance first...
dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false }) dao = dao || this.callee.build(result[this.callee.tableName], { isNewRecord: false, isDirty: false })
// ... and afterwards the prefetched associations // ... and afterwards the prefetched associations
for (var tableName in result) { for (var tableName in result) {
...@@ -323,7 +323,7 @@ module.exports = (function() { ...@@ -323,7 +323,7 @@ module.exports = (function() {
accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1) accessor = accessor.slice(0,1).toLowerCase() + accessor.slice(1)
associationData.forEach(function(data) { associationData.forEach(function(data) {
var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false }) var daoInstance = associatedDaoFactory.build(data, { isNewRecord: false, isDirty: false })
, isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers) , isEmpty = !Utils.firstValueOfHash(daoInstance.identifiers)
if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) { if (['BelongsTo', 'HasOne'].indexOf(association.associationType) > -1) {
......
...@@ -82,7 +82,9 @@ module.exports = (function() { ...@@ -82,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) {
...@@ -168,11 +170,12 @@ module.exports = (function() { ...@@ -168,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);
} }
} }
...@@ -180,16 +183,16 @@ module.exports = (function() { ...@@ -180,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;
...@@ -209,8 +212,10 @@ module.exports = (function() { ...@@ -209,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
}; };
...@@ -265,12 +270,12 @@ module.exports = (function() { ...@@ -265,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
} }
} }
...@@ -295,7 +300,7 @@ module.exports = (function() { ...@@ -295,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) {
...@@ -342,8 +347,6 @@ module.exports = (function() { ...@@ -342,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)
...@@ -383,4 +386,4 @@ module.exports = (function() { ...@@ -383,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() {
...@@ -377,7 +376,7 @@ module.exports = (function() { ...@@ -377,7 +376,7 @@ module.exports = (function() {
}, options || {}) }, options || {})
return Utils._.compact([ return Utils._.compact([
"CREATE", options.indicesType, "INDEX IF NOT EXISTS", options.indexName, "CREATE", options.indicesType, "INDEX", options.indexName,
(options.indexType ? ('USING ' + options.indexType) : undefined), (options.indexType ? ('USING ' + options.indexType) : undefined),
"ON", tableName, '(' + transformedAttributes.join(', ') + ')', "ON", tableName, '(' + transformedAttributes.join(', ') + ')',
(options.parser ? "WITH PARSER " + options.parser : undefined) (options.parser ? "WITH PARSER " + options.parser : undefined)
...@@ -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)
} }
......
...@@ -6,11 +6,12 @@ module.exports = (function() { ...@@ -6,11 +6,12 @@ module.exports = (function() {
var pgModule = config.dialectModulePath || 'pg' 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(pgModule).native : require(pgModule) 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
...@@ -18,43 +19,62 @@ module.exports = (function() { ...@@ -18,43 +19,62 @@ 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) { // we really want pendingQueries to increment as fast as possible...
setTimeout(function() { self.pendingQueries++
self.pendingQueries == 0 && self.disconnect.call(self)
}, 100) return new Utils.CustomEventEmitter(function(emitter) {
} self.connect()
.on('error', function(err) {
// zero-out the previous increment
self.pendingQueries--
self.endQuery.call(self)
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) {
self.pendingQueries--
emitter.emit('success', results)
self.endQuery.call(self)
})
.error(function(err) {
self.pendingQueries--
emitter.emit('error', err)
self.endQuery.call(self)
})
.on('sql', function(sql) { emitter.emit('sql', sql) })
})
}).run()
} }
ConnectorManager.prototype.connect = function() { ConnectorManager.prototype.connect = function() {
...@@ -63,7 +83,8 @@ module.exports = (function() { ...@@ -63,7 +83,8 @@ module.exports = (function() {
// 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
...@@ -71,48 +92,66 @@ module.exports = (function() { ...@@ -71,48 +92,66 @@ 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 if (this.client !== null) {
this.client = new this.pg.Client(uri) connectCallback(null, this.client)
this.client.connect(connectCallback) } else {
//create one-off client
this.client = new this.pg.Client(uri)
this.client.connect(connectCallback)
}
} }
return emitter return emitter
} }
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)
}) })
...@@ -189,6 +189,15 @@ module.exports = (function() { ...@@ -189,6 +189,15 @@ module.exports = (function() {
definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier("enum_" + tableName + "_" + attributeName)) definition = definition.replace(/^ENUM\(.+\)/, this.quoteIdentifier("enum_" + tableName + "_" + attributeName))
} }
if (definition.match(/UNIQUE;*$/)) {
definition = definition.replace(/UNIQUE;*$/, '')
attrSql += Utils._.template(query.replace('ALTER COLUMN', ''))({
tableName: this.quoteIdentifiers(tableName),
query: 'ADD CONSTRAINT ' + this.quoteIdentifier(attributeName + '_unique_idx') + ' UNIQUE (' + this.quoteIdentifier(attributeName) + ')'
})
}
attrSql += Utils._.template(query)({ attrSql += Utils._.template(query)({
tableName: this.quoteIdentifiers(tableName), tableName: this.quoteIdentifiers(tableName),
query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition query: this.quoteIdentifier(attributeName) + ' TYPE ' + definition
......
...@@ -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)
} }
......
...@@ -22,7 +22,7 @@ module.exports = (function() { ...@@ -22,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 = ""
......
...@@ -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;
}
CustomEventEmitter.prototype.proxy = function(emitter) { CustomEventEmitter.prototype.proxy = function(emitter) {
proxyEventKeys.forEach(function (eventKey) { proxyEventKeys.forEach(function (eventKey) {
this.on(eventKey, function (result) { this.on(eventKey, function (result) {
......
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(){}
......
...@@ -88,9 +88,12 @@ var Utils = module.exports = { ...@@ -88,9 +88,12 @@ var Utils = module.exports = {
type = typeof where[i] type = typeof where[i]
_where[i] = _where[i] || {} _where[i] = _where[i] || {}
if (Array.isArray(where[i])) { if (where[i] === null) {
// skip nulls
}
else if (Array.isArray(where[i])) {
_where[i].in = _where[i].in || [] _where[i].in = _where[i].in || []
_where[i].in.concat(where[i]); _where[i].in.concat(where[i])
} }
else if (type === "object") { else if (type === "object") {
Object.keys(where[i]).forEach(function(ii) { Object.keys(where[i]).forEach(function(ii) {
...@@ -249,6 +252,23 @@ var Utils = module.exports = { ...@@ -249,6 +252,23 @@ var Utils = module.exports = {
isHash: function(obj) { isHash: function(obj) {
return Utils._.isObject(obj) && !Array.isArray(obj); return Utils._.isObject(obj) && !Array.isArray(obj);
}, },
hasChanged: function(attrValue, value) {
//If attribute value is Date, check value as a date
if (Utils._.isDate(attrValue) && !Utils._.isDate(value)) {
value = new Date(value)
}
if (Utils._.isDate(attrValue)) {
return attrValue.valueOf() !== value.valueOf()
}
//If both of them are empty, don't set as changed
if ((attrValue === undefined || attrValue === null || attrValue === '') && (value === undefined || value === null || value === '')) {
return false
}
return attrValue !== value
},
argsArePrimaryKeys: function(args, primaryKeys) { argsArePrimaryKeys: function(args, primaryKeys) {
var result = (args.length == Object.keys(primaryKeys).length) var result = (args.length == Object.keys(primaryKeys).length)
if (result) { if (result) {
...@@ -380,6 +400,6 @@ var Utils = module.exports = { ...@@ -380,6 +400,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": {
...@@ -37,21 +41,24 @@ ...@@ -37,21 +41,24 @@
"lingo": "~0.0.5", "lingo": "~0.0.5",
"validator": "~1.3.0", "validator": "~1.3.0",
"moment": "~2.1.0", "moment": "~2.1.0",
"commander": "~1.3.0", "commander": "~2.0.0",
"dottie": "0.0.8-0", "dottie": "0.0.8-0",
"toposort-class": "~0.2.0", "toposort-class": "~0.2.0",
"generic-pool": "2.0.3", "generic-pool": "2.0.4",
"promise": "~3.2.0", "promise": "~3.2.0",
"sql": "~0.26.0" "sql": "~0.26.0"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "~2.1.12", "sqlite3": "~2.1.12",
"mysql": "~2.0.0-alpha8", "mysql": "~2.0.0-alpha8",
"pg": "~2.1.0", "pg": "~2.2.0",
"buster": "~0.6.3", "buster": "~0.6.3",
"watchr": "~2.4.3", "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,13 +68,7 @@ ...@@ -61,13 +68,7 @@
], ],
"main": "index", "main": "index",
"scripts": { "scripts": {
"test": "npm run test-buster", "test": "make all",
"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": "node_modules/.bin/buster-test",
"test-buster-mysql": "DIALECT=mysql node_modules/.bin/buster-test",
"test-buster-postgres": "DIALECT=postgres node_modules/.bin/buster-test",
"test-buster-postgres-native": "DIALECT=postgres-native node_modules/.bin/buster-test",
"test-buster-sqlite": "DIALECT=sqlite node_modules/.bin/buster-test",
"docs": "node_modules/.bin/yuidoc . -o docs" "docs": "node_modules/.bin/yuidoc . -o docs"
}, },
"bin": { "bin": {
......
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('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('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)
})
it('can handle multiple associations just fine', function(done) {
var Emit = require('events').EventEmitter
, emit = new Emit()
, User = this.sequelize.define('User', { username: Sequelize.STRING })
, Event = this.sequelize.define('Event', { name: Sequelize.STRING })
, Place = this.sequelize.define('Place', { name: Sequelize.STRING })
, theSQL = ''
, count = 0
, theEvent
Event.belongsTo(User)
Event.hasOne(Place)
emit.on('sql', function(sql){
++count
theSQL = sql
if (count > 1) {
emit.emit('spy')
}
})
emit.on('find', function(e) {
++count
theEvent = e
if (count > 1) {
emit.emit('spy')
}
})
emit.on('spy', function(){
expect(theSQL.match(/WHERE ["`]Events["`].["`]id["`]=1;$/)).not.toBeNull()
expect(theEvent.name).toEqual('Bob Marley Concert')
expect(theEvent.place.name).toEqual('Backyard')
expect(theEvent.user.username).toEqual('Dan')
done()
})
this.sequelize.sync({force: true }).success(function() {
User.create({username: 'Dan'}).success(function(user){
Event.create({name: 'Bob Marley Concert'}).success(function(event) {
Place.create({name: 'Backyard'}).success(function(place) {
event.setUser(user).success(function(){
event.setPlace(place).success(function(){
Event.find({where: {id: 1}, include: [User, Place]}).success(function(theEvent){
emit.emit('find', theEvent)
})
.on('sql', function(sql){
emit.emit('sql', sql)
})
})
})
})
})
})
})
})
})
})
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')
, 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()
})
})
})
/* jshint camelcase: false */
if(typeof require === 'function') {
const buster = require("buster")
, config = require('../config/config')
, Helpers = require('../buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 1000
if (dialect.match(/^mysql/)) {
describe('[MYSQL] DAOFactory', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
self.User = self.sequelize.define('User', { age: DataTypes.INTEGER, name: DataTypes.STRING, bio: DataTypes.TEXT })
},
onComplete: function() {
self.sequelize.sync({ force: true }).success(done)
}
})
})
describe('constructor', function() {
it("handles extended attributes (unique)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: { type: Helpers.Sequelize.STRING, unique: true }
}, { timestamps: false })
expect(User.attributes).toEqual({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: Helpers.Sequelize.STRING, defaultValue: 'foo'}
}, { timestamps: false })
expect(User.attributes).toEqual({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: Helpers.Sequelize.STRING, allowNull: false}
}, { timestamps: false })
expect(User.attributes).toEqual({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: Helpers.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"})
done()
})
it("handles extended attributes (primaryKey)", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
username: {type: Helpers.Sequelize.STRING, primaryKey: true}
}, { timestamps: false })
expect(User.attributes).toEqual({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).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"})
done()
})
it("adds deletedAt if paranoid", function(done) {
var User = this.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"})
done()
})
it("underscores timestamps if underscored", function(done) {
var User = this.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"})
done()
})
})
describe('primaryKeys', function() {
it("determines the correct primaryKeys", function(done) {
var User = this.sequelize.define('User' + config.rand(), {
foo: {type: Helpers.Sequelize.STRING, primaryKey: true},
bar: Helpers.Sequelize.STRING
})
expect(User.primaryKeys).toEqual({"foo":"VARCHAR(255) PRIMARY KEY"})
done()
})
})
})
}
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()
})
})
})
})
module.exports = {
up: function(migration, DataTypes, done) {
migration.addColumn('User', 'uniqueName', { type: DataTypes.STRING }).complete(function() {
migration.changeColumn('User', 'uniqueName', { type: DataTypes.STRING, allowNull: false, unique: true }).complete(done)
})
},
down: function(migration, DataTypes, done) {
migration.removeColumn('User', 'uniqueName').complete(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()
})
})
})
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")) {
...@@ -202,7 +192,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -202,7 +192,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
for (var i = 0; i < validatorDetails.fail.length; i++) { for (var i = 0; i < validatorDetails.fail.length; i++) {
var failingValue = validatorDetails.fail[i] var failingValue = validatorDetails.fail[i]
it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function() { it('correctly specifies an instance as invalid using a value of "' + failingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {} var validations = {}
, message = validator + "(" + failingValue + ")" , message = validator + "(" + failingValue + ")"
...@@ -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
...@@ -224,18 +214,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -224,18 +214,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = UserFail.build({ name : failingValue }) var failingUser = UserFail.build({ name : failingValue })
, errors = failingUser.validate() , errors = failingUser.validate()
expect(errors).not.toBeNull() expect(errors).not.to.be.null
expect(errors).toEqual({ name : [message] }) expect(errors).to.eql({ name : [message] })
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]
it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function() { it('correctly specifies an instance as valid using a value of "' + succeedingValue + '" for the validation "' + validator + '"', function(done) {
var validations = {} var validations = {}
if (validatorDetails.hasOwnProperty('spec')) { if (validatorDetails.hasOwnProperty('spec')) {
...@@ -246,7 +238,7 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -246,7 +238,7 @@ 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
...@@ -254,14 +246,62 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -254,14 +246,62 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
}) })
var successfulUser = UserSuccess.build({ name: succeedingValue }) var successfulUser = UserSuccess.build({ name: succeedingValue })
expect(successfulUser.validate()).toBeNull() expect(successfulUser.validate()).to.be.null
done()
}) })
} }
} }
} }
it('correctly validates using custom validation methods', function() { describe('#create', function() {
var User = this.sequelize.define('User' + Math.random(), { 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 validates using custom validation methods', function(done) {
var User = this.sequelize.define('User' + config.rand(), {
name: { name: {
type: Sequelize.STRING, type: Sequelize.STRING,
validate: { validate: {
...@@ -277,15 +317,17 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -277,15 +317,17 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = User.build({ name : "3" }) var failingUser = User.build({ name : "3" })
, errors = failingUser.validate() , errors = failingUser.validate()
expect(errors).not.toBeNull(null) expect(errors).not.to.be.null
expect(errors).toEqual({ name: ["name should equal '2'"] }) expect(errors).to.eql({ name: ["name should equal '2'"] })
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull() expect(successfulUser.validate()).to.be.null
done()
}) })
it('skips other validations if allowNull is true and the value is null', function() { 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,
...@@ -298,18 +340,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -298,18 +340,20 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingUser = User.build({ age: -1 }) var failingUser = User.build({ age: -1 })
, errors = failingUser.validate() , errors = failingUser.validate()
expect(errors).not.toBeNull(null) expect(errors).not.to.be.null
expect(errors).toEqual({ age: ['must be positive'] }) expect(errors).to.eql({ age: ['must be positive'] })
var successfulUser1 = User.build({ age: null }) var successfulUser1 = User.build({ age: null })
expect(successfulUser1.validate()).toBeNull() expect(successfulUser1.validate()).to.be.null
var successfulUser2 = User.build({ age: 1 }) var successfulUser2 = User.build({ age: 1 })
expect(successfulUser2.validate()).toBeNull() expect(successfulUser2.validate()).to.be.null
done()
}) })
it('validates a model with custom model-wide validation methods', function() { 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
...@@ -331,11 +375,12 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() { ...@@ -331,11 +375,12 @@ describe(Helpers.getTestDialectTeaser("DaoValidator"), function() {
var failingFoo = Foo.build({ field1: null, field2: null }) var failingFoo = Foo.build({ field1: null, field2: null })
, errors = failingFoo.validate() , errors = failingFoo.validate()
expect(errors).not.toBeNull() expect(errors).not.to.be.null
expect(errors).toEqual({ 'xnor': ['xnor failed'] }) expect(errors).to.eql({ 'xnor': ['xnor failed'] })
var successfulFoo = Foo.build({ field1: 33, field2: null }) var successfulFoo = Foo.build({ field1: 33, field2: null })
expect(successfulFoo.validate()).toBeNull() expect(successfulFoo.validate()).to.be.null
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()
})
})
})
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()
} , sinon = require('sinon')
, DataTypes = require(__dirname + "/../../lib/data-types")
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
if (dialect.match(/^postgres/)) {
describe('[POSTGRES] associations', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
},
onComplete: function() {
self.sequelize.sync({ force: true }).success(done)
}
})
})
if (dialect.match(/^mysql/)) {
describe('[MYSQL Specific] Associations', function() {
describe('many-to-many', function() { describe('many-to-many', function() {
describe('where tables have the same prefix', function() { describe('where tables have the same prefix', function() {
it("should create a table wp_table1wp_table2s", function(done) { it("should create a table wp_table1wp_table2s", function(done) {
var Table2 = this.sequelize.define('wp_table2', {foo: Helpers.Sequelize.STRING}) var Table2 = this.sequelize.define('wp_table2', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('wp_table1', {foo: Helpers.Sequelize.STRING}) , Table1 = this.sequelize.define('wp_table1', {foo: DataTypes.STRING})
, self = this
Table1.hasMany(Table2) Table1.hasMany(Table2)
Table2.hasMany(Table1) Table2.hasMany(Table1)
Table1.sync({ force: true }).success(function() {
expect(this.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined() Table2.sync({ force: true }).success(function() {
done() expect(self.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).to.exist
done()
})
})
}) })
}) })
describe('when join table name is specified', function() { describe('when join table name is specified', function() {
before(function(done){ beforeEach(function(done){
var Table2 = this.sequelize.define('ms_table1', {foo: Helpers.Sequelize.STRING}) var Table2 = this.sequelize.define('ms_table1', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('ms_table2', {foo: Helpers.Sequelize.STRING}) , Table1 = this.sequelize.define('ms_table2', {foo: DataTypes.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'}) Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'}) Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
done() Table1.sync({ force: true }).success(function() {
}) Table2.sync({ force: true }).success(function() {
done()
it("should not use a combined name", function(done) { })
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.toBeDefined() })
done()
}) })
it("should use the specified name", function(done) { it("should not use only a specified name", function() {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined() expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
done() expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
}) })
}) })
}) })
describe('HasMany', function() { describe('HasMany', function() {
before(function(done) { beforeEach(function(done) {
//prevent periods from occurring in the table name since they are used to delimit (table.column) //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: Helpers.Sequelize.STRING }) 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: Helpers.Sequelize.STRING }) this.Task = this.sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: DataTypes.STRING })
this.users = null this.users = null
this.tasks = null this.tasks = null
...@@ -82,15 +72,19 @@ if (dialect.match(/^postgres/)) { ...@@ -82,15 +72,19 @@ if (dialect.match(/^postgres/)) {
tasks[tasks.length] = {name: 'Task' + Math.random()} tasks[tasks.length] = {name: 'Task' + Math.random()}
} }
this.sequelize.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() { self.Task.sync({ force: true }).success(function() {
self.Task.bulkCreate(tasks).success(done) self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(function() {
done()
})
})
}) })
}) })
}) })
describe('addDAO / getDAO', function() { describe('addDAO / getDAO', function() {
before(function(done) { beforeEach(function(done) {
var self = this var self = this
self.user = null self.user = null
...@@ -109,10 +103,10 @@ if (dialect.match(/^postgres/)) { ...@@ -109,10 +103,10 @@ if (dialect.match(/^postgres/)) {
var self = this var self = this
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0) expect(_tasks.length).to.equal(0)
self.user.addTask(self.task).on('success', function() { self.user.addTask(self.task).on('success', function() {
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1) expect(_tasks.length).to.equal(1)
done() done()
}) })
}) })
...@@ -121,7 +115,7 @@ if (dialect.match(/^postgres/)) { ...@@ -121,7 +115,7 @@ if (dialect.match(/^postgres/)) {
}) })
describe('removeDAO', function() { describe('removeDAO', function() {
before(function(done) { beforeEach(function(done) {
var self = this var self = this
self.user = null self.user = null
...@@ -140,13 +134,13 @@ if (dialect.match(/^postgres/)) { ...@@ -140,13 +134,13 @@ if (dialect.match(/^postgres/)) {
var self = this var self = this
self.user.getTasks().on('success', function(__tasks) { self.user.getTasks().on('success', function(__tasks) {
expect(__tasks.length).toEqual(0) expect(__tasks.length).to.equal(0)
self.user.setTasks(self.tasks).on('success', function() { self.user.setTasks(self.tasks).on('success', function() {
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(self.tasks.length) expect(_tasks.length).to.equal(self.tasks.length)
self.user.removeTask(self.tasks[0]).on('success', function() { self.user.removeTask(self.tasks[0]).on('success', function() {
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(self.tasks.length - 1) expect(_tasks.length).to.equal(self.tasks.length - 1)
done() 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()
} , sinon = require('sinon')
, DataTypes = require(__dirname + "/../../lib/data-types")
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
if (dialect.match(/^mysql/)) { if (dialect.match(/^mysql/)) {
describe('[MYSQL] Connector Manager', function() { describe('[MYSQL Specific] Connector Manager', function() {
before(function(done) { this.timeout(10000)
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
},
onComplete: function() {
self.sequelize.sync({ force: true }).success(done)
}
})
})
it('works correctly after being idle', function(done) { it('works correctly after being idle', function(done) {
this.timeout = 1000 * 10 var User = this.sequelize.define('User', { username: DataTypes.STRING })
var User = this.sequelize.define('User', { username: Helpers.Sequelize.STRING }) , spy = sinon.spy()
, spy = this.spy()
User.sync({force: true}).on('success', function() { User.sync({force: true}).on('success', function() {
User.create({username: 'user1'}).on('success', function() { User.create({username: 'user1'}).on('success', function() {
User.count().on('success', function(count) { User.count().on('success', function(count) {
expect(count).toEqual(1) expect(count).to.equal(1)
spy() spy()
setTimeout(function() { setTimeout(function() {
User.count().on('success', function(count) { User.count().on('success', function(count) {
expect(count).toEqual(1) expect(count).to.equal(1)
spy() spy()
if (spy.calledTwice) { if (spy.calledTwice) {
done() 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")
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('validations', function() {
describe('enums', function() {
it('enum data type should be case insensitive if my collation allows it', function(done) {
var User = this.sequelize.define('User' + config.rand(), {
mood: {
type: DataTypes.ENUM,
values: ['HAPPY', 'sad', 'WhatEver']
}
}, {
collate: 'utf8_general_ci'
})
User.sync({ force: true }).success(function() {
User.create({mood: 'happy'}).success(function(user) {
expect(user).to.exist
expect(user.mood).to.equal('HAPPY')
var u = User.build({mood: 'SAD'})
u.save().success(function(_user) {
expect(_user).to.exist
expect(_user.mood).to.equal('sad')
done()
})
})
})
})
it('enum data type should be case sensitive if my collation enforces it', function(done) {
var User = this.sequelize.define('User' + config.rand(), {
mood: {
type: DataTypes.ENUM,
values: ['HAPPY', 'sad', 'WhatEver']
}
}, {
collate: 'latin1_bin'
})
User.sync({ force: true }).success(function() {
expect(function() {
User.create({mood: 'happy'})
}).to.throw(Error, 'Value "happy" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
expect(function() {
var u = User.build({mood: 'SAD'})
u.save()
}).to.throw(Error, 'Value "SAD" for ENUM mood is out of allowed scope. Allowed values: HAPPY, sad, WhatEver')
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()
})
})
})
}
/* jshint camelcase: false */ /* jshint camelcase: false */
if(typeof require === 'function') { var chai = require('chai')
const buster = require("buster") , expect = chai.expect
, config = require('../config/config') , Support = require(__dirname + '/../support')
, Helpers = require('../buster-helpers') , dialect = Support.getTestDialect()
, dialect = Helpers.getTestDialect() , util = require("util")
, QueryGenerator = require("../../lib/dialects/mysql/query-generator") , _ = require('lodash')
, util = require("util") , QueryGenerator = require("../../lib/dialects/mysql/query-generator")
} chai.Assertion.includeStack = true
buster.spec.expose()
buster.testRunner.timeout = 1000
if (dialect.match(/^mysql/)) { if (dialect.match(/^mysql/)) {
describe('[MYSQL] QueryGenerator', function() { describe("[MYSQL Specific] QueryGenerator", function () {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
},
onComplete: function() {
self.sequelize.sync({ force: true }).success(done)
}
})
})
var suites = { var suites = {
attributesToSQL: [ attributesToSQL: [
{ {
...@@ -85,7 +68,7 @@ if (dialect.match(/^mysql/)) { ...@@ -85,7 +68,7 @@ if (dialect.match(/^mysql/)) {
{ {
arguments: [{id: {type: 'INTEGER', allowNull: false, autoIncrement: true, defaultValue: 1, references: 'Bar', onDelete: 'CASCADE', onUpdate: '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'} expectation: {id: 'INTEGER NOT NULL auto_increment DEFAULT 1 REFERENCES `Bar` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT'}
}, }
], ],
createTableQuery: [ createTableQuery: [
...@@ -425,7 +408,7 @@ if (dialect.match(/^mysql/)) { ...@@ -425,7 +408,7 @@ if (dialect.match(/^mysql/)) {
] ]
} }
Helpers.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 || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) var title = test.title || 'MySQL correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
...@@ -434,7 +417,7 @@ if (dialect.match(/^mysql/)) { ...@@ -434,7 +417,7 @@ if (dialect.match(/^mysql/)) {
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() done()
}) })
}) })
......
/* jshint camelcase: false */ var chai = require('chai')
if(typeof require === 'function') { , expect = chai.expect
const buster = require("buster") , Support = require(__dirname + '/../support')
, config = require('../config/config') , dialect = Support.getTestDialect()
, Helpers = require('../buster-helpers') , config = require(__dirname + '/../config/config')
, dialect = Helpers.getTestDialect() , DataTypes = require(__dirname + "/../../lib/data-types")
}
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
if (dialect.match(/^mysql/)) {
describe('[MYSQL] Associations', function() {
before(function(done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
self.sequelize = sequelize
},
onComplete: function() {
self.sequelize.sync({ force: true }).success(done)
}
})
})
if (dialect.match(/^postgres/)) {
describe('[POSTGRES Specific] associations', function() {
describe('many-to-many', function() { describe('many-to-many', function() {
describe('where tables have the same prefix', function() { describe('where tables have the same prefix', function() {
it("should create a table wp_table1wp_table2s", function(done) { it("should create a table wp_table1wp_table2s", function(done) {
var Table2 = this.sequelize.define('wp_table2', {foo: Helpers.Sequelize.STRING}) var Table2 = this.sequelize.define('wp_table2', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('wp_table1', {foo: Helpers.Sequelize.STRING}) , Table1 = this.sequelize.define('wp_table1', {foo: DataTypes.STRING})
, self = this
Table1.hasMany(Table2) Table1.hasMany(Table2)
Table2.hasMany(Table1) Table2.hasMany(Table1)
this.sequelize.sync({ force: true }).success(function() {
expect(self.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).toBeDefined() expect(this.sequelize.daoFactoryManager.getDAO('wp_table1swp_table2s')).to.exist
done() done()
})
}) })
}) })
describe('when join table name is specified', function() { describe('when join table name is specified', function() {
before(function(done){ beforeEach(function(done){
var Table2 = this.sequelize.define('ms_table1', {foo: Helpers.Sequelize.STRING}) var Table2 = this.sequelize.define('ms_table1', {foo: DataTypes.STRING})
, Table1 = this.sequelize.define('ms_table2', {foo: Helpers.Sequelize.STRING}) , Table1 = this.sequelize.define('ms_table2', {foo: DataTypes.STRING})
Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'}) Table1.hasMany(Table2, {joinTableName: 'table1_to_table2'})
Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'}) Table2.hasMany(Table1, {joinTableName: 'table1_to_table2'})
this.sequelize.sync({ force: true }).success(done) done()
}) })
it("should not use a combined name", function(done) { it("should not use a combined name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.toBeDefined() expect(this.sequelize.daoFactoryManager.getDAO('ms_table1sms_table2s')).not.to.exist
done() done()
}) })
it("should use the specified name", function(done) { it("should use the specified name", function(done) {
expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).toBeDefined() expect(this.sequelize.daoFactoryManager.getDAO('table1_to_table2')).to.exist
done() done()
}) })
}) })
}) })
describe('HasMany', function() { describe('HasMany', function() {
before(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: Helpers.Sequelize.STRING })
this.Task = this.sequelize.define('Task' + Math.ceil(Math.random()*10000000), { name: Helpers.Sequelize.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.sequelize.sync({ force: true }).success(function() {
self.User.bulkCreate(users).success(function() {
self.Task.bulkCreate(tasks).success(done)
})
})
})
describe('addDAO / getDAO', function() { describe('addDAO / getDAO', function() {
before(function(done) { beforeEach(function(done) {
var self = this var self = this
self.user = null //prevent periods from occurring in the table name since they are used to delimit (table.column)
self.task = null 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'})
self.User.all().success(function(_users) { var self = this
self.Task.all().success(function(_tasks) { , users = []
self.user = _users[0] , tasks = []
self.task = _tasks[0]
done() 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()
})
})
})
})
}) })
}) })
}) })
...@@ -114,10 +92,10 @@ if (dialect.match(/^mysql/)) { ...@@ -114,10 +92,10 @@ if (dialect.match(/^mysql/)) {
var self = this var self = this
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(0) expect(_tasks).to.have.length(0)
self.user.addTask(self.task).on('success', function() { self.user.addTask(self.task).on('success', function() {
self.user.getTasks().on('success', function(_tasks) { self.user.getTasks().on('success', function(_tasks) {
expect(_tasks.length).toEqual(1) expect(_tasks).to.have.length(1)
done() done()
}) })
}) })
...@@ -126,33 +104,54 @@ if (dialect.match(/^mysql/)) { ...@@ -126,33 +104,54 @@ if (dialect.match(/^mysql/)) {
}) })
describe('removeDAO', function() { describe('removeDAO', function() {
before(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) { it("should correctly remove associated objects", function(done) {
var self = this var self = this
, users = []
self.user.getTasks().on('success', function(__tasks) { , tasks = []
expect(__tasks.length).toEqual(0)
self.user.setTasks(self.tasks).on('success', function() { //prevent periods from occurring in the table name since they are used to delimit (table.column)
self.user.getTasks().on('success', function(_tasks) { this.User = this.sequelize.define('User' + config.rand(), { name: DataTypes.STRING })
expect(_tasks.length).toEqual(self.tasks.length) this.Task = this.sequelize.define('Task' + config.rand(), { name: DataTypes.STRING })
self.user.removeTask(self.tasks[0]).on('success', function() { this.users = null
self.user.getTasks().on('success', function(_tasks) { this.tasks = null
expect(_tasks.length).toEqual(self.tasks.length - 1)
done() 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()
})
})
})
}
if(typeof require === 'function') { /* jshint camelcase: false */
const buster = require("buster") var chai = require('chai')
, Helpers = require('../buster-helpers') , expect = chai.expect
, dialect = Helpers.getTestDialect() , QueryGenerator = require("../../lib/dialects/postgres/query-generator")
, QueryGenerator = require("../../lib/dialects/postgres/query-generator") , Support = require(__dirname + '/../support')
, util = require("util") , dialect = Support.getTestDialect()
, moment = require('moment') , DataTypes = require(__dirname + "/../../lib/data-types")
} , moment = require('moment')
, util = require("util")
, _ = require('lodash')
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
if (dialect.match(/^postgres/)) { if (dialect.match(/^postgres/)) {
describe('[POSTGRES] QueryGenerator', function() { describe('[POSTGRES Specific] QueryGenerator', function() {
before(function(done) { beforeEach(function(done) {
var self = this this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
Helpers.initTests({ email: {type: DataTypes.ARRAY(DataTypes.TEXT)},
dialect: dialect, document: {type: DataTypes.HSTORE, defaultValue: '"default"=>"value"'}
beforeComplete: function(sequelize, DataTypes) { })
self.sequelize = sequelize this.User.sync({ force: true }).success(function() {
done()
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)
}
}) })
}) })
...@@ -776,8 +768,14 @@ if (dialect.match(/^postgres/)) { ...@@ -776,8 +768,14 @@ if (dialect.match(/^postgres/)) {
] ]
} }
Helpers.Sequelize.Utils._.each(suites, function(tests, suiteTitle) { _.each(suites, function(tests, suiteTitle) {
describe(suiteTitle, function() { describe(suiteTitle, function() {
afterEach(function(done) {
this.sequelize.options.quoteIdentifiers = true
QueryGenerator.options.quoteIdentifiers = true
done()
})
tests.forEach(function(test) { tests.forEach(function(test) {
var title = test.title || 'Postgres correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments) var title = test.title || 'Postgres correctly returns ' + test.expectation + ' for ' + util.inspect(test.arguments)
it(title, function(done) { it(title, function(done) {
...@@ -785,7 +783,7 @@ if (dialect.match(/^postgres/)) { ...@@ -785,7 +783,7 @@ if (dialect.match(/^postgres/)) {
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() 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("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 */ /* 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')
if (typeof require === 'function') { chai.Assertion.includeStack = true
var buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
}
buster.spec.expose()
buster.testRunner.timeout = 2000
describe(Helpers.getTestDialectTeaser("QueryGenerators"), function() {
before(function(done) {
Helpers.initTests({
dialect: dialect,
beforeComplete: function(sequelize, DataTypes) {
this.sequelize = sequelize
this.DataTypes = DataTypes
}.bind(this),
onComplete: function() {
this.interface = this.sequelize.getQueryInterface()
done()
}.bind(this)
})
})
describe(Support.getTestDialectTeaser("QueryGenerators"), function () {
describe("comments", function() { describe("comments", function() {
it("should create a comment for a column", function(done) { it("should create a comment for a column", function(done) {
var self = this var self = this
, User = this.sequelize.define('User', { , User = this.sequelize.define('User', {
username: {type: this.DataTypes.STRING, comment: 'Some lovely info for my DBA'} username: {type: DataTypes.STRING, comment: 'Some lovely info for my DBA'}
}) })
User.sync().success(function(){ User.sync({ force: true }).success(function() {
var sql = '' var sql = ''
if (dialect === "mysql") { 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\''; 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\'';
...@@ -45,16 +30,16 @@ describe(Helpers.getTestDialectTeaser("QueryGenerators"), function() { ...@@ -45,16 +30,16 @@ describe(Helpers.getTestDialectTeaser("QueryGenerators"), function() {
} }
else if (dialect === "sqlite") { else if (dialect === "sqlite") {
// sqlite doesn't support comments except for explicit comments in the file // sqlite doesn't support comments except for explicit comments in the file
expect(true).toBeTrue() expect(true).to.be.true
return done() return done()
} else { } else {
console.log('FIXME: This dialect is not supported :('); console.log('FIXME: This dialect is not supported :(');
expect(true).toBeTrue() expect(true).to.be.true
return done() return done()
} }
self.sequelize.query(sql, null, {raw: true}).success(function(result) { self.sequelize.query(sql, null, {raw: true}).success(function(result) {
expect(result[0].cmt).toEqual('Some lovely info for my DBA'); expect(result[0].cmt).to.equal('Some lovely info for my DBA');
done() 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()
})
})
})
})
})
/* jshint camelcase: false */ /* jshint camelcase: false */
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")
, dbFile = __dirname + '/test.sqlite' , dialect = Support.getTestDialect()
, storages = [dbFile] , dbFile = __dirname + '/test.sqlite'
, DataTypes = require(__dirname + "/../../lib/data-types") , storages = [dbFile]
}
buster.spec.expose() chai.Assertion.includeStack = true
buster.testRunner.timeout = 1000
if (dialect === 'sqlite') { if (dialect === 'sqlite') {
describe('[SQLITE] DAOFactory', function() { describe('[SQLITE Specific] DAOFactory', function() {
before(function(done) { after(function(done) {
var self = this this.sequelize.options.storage = ':memory:'
done()
Helpers.initTests({ })
dialect: 'sqlite',
beforeComplete: function(sequelize, DataTypes) { beforeEach(function(done) {
self.sequelize = sequelize this.sequelize.options.storage = dbFile
this.User = this.sequelize.define('User', {
self.User = sequelize.define('User', { age: DataTypes.INTEGER,
age: DataTypes.INTEGER, name: DataTypes.STRING,
name: DataTypes.STRING, bio: DataTypes.TEXT
bio: DataTypes.TEXT })
}) this.User.sync({ force: true }).success(function() {
}, done()
onComplete: function() {
self.User.sync({ force: true }).success(done)
}
}) })
}) })
storages.forEach(function(storage) { storages.forEach(function(storage) {
describe('with storage "' + storage + '"', function() { describe('with storage "' + storage + '"', function() {
after(function(done) { after(function(done) {
if (storage == dbFile) { if (storage === dbFile) {
require("fs").unlink(__dirname + '/test.sqlite', done) require("fs").writeFile(dbFile, '', function() {
done()
})
} }
}) })
describe('create', function() { describe('create', function() {
it('creates a table entry', function(done) { it('creates a table entry', function(done) {
var self = this var self = this
this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }).success(function(user) { this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }).success(function(user) {
expect(user.age).toEqual(21) expect(user.age).to.equal(21)
expect(user.name).toEqual('John Wayne') expect(user.name).to.equal('John Wayne')
expect(user.bio).toEqual('noot noot') expect(user.bio).to.equal('noot noot')
self.User.all().success(function(users) { self.User.all().success(function(users) {
var usernames = users.map(function(user) { var usernames = users.map(function(user) {
return user.name return user.name
}) })
expect(usernames).toEqual(['John Wayne']) expect(usernames).to.contain('John Wayne')
done() done()
}) })
}) })
...@@ -73,7 +69,7 @@ if (dialect === 'sqlite') { ...@@ -73,7 +69,7 @@ if (dialect === 'sqlite') {
name: 'John Doe', name: 'John Doe',
options: options options: options
}).success(function(people) { }).success(function(people) {
expect(people.options).toEqual(options) expect(people.options).to.deep.equal(options)
done() done()
}) })
}) })
...@@ -90,7 +86,7 @@ if (dialect === 'sqlite') { ...@@ -90,7 +86,7 @@ if (dialect === 'sqlite') {
name: 'John Doe', name: 'John Doe',
has_swag: true has_swag: true
}).success(function(people) { }).success(function(people) {
expect(people.has_swag).toBeTruthy(); expect(people.has_swag).to.be.ok
done() done()
}) })
}) })
...@@ -107,7 +103,7 @@ if (dialect === 'sqlite') { ...@@ -107,7 +103,7 @@ if (dialect === 'sqlite') {
name: 'John Doe', name: 'John Doe',
has_swag: false has_swag: false
}).success(function(people) { }).success(function(people) {
expect(people.has_swag).toBeFalsy(); expect(people.has_swag).to.not.be.ok
done() done()
}) })
}) })
...@@ -115,36 +111,40 @@ if (dialect === 'sqlite') { ...@@ -115,36 +111,40 @@ if (dialect === 'sqlite') {
}) })
describe('.find', function() { describe('.find', function() {
before(function(done) { beforeEach(function(done) {
this.User.create({name: 'user', bio: 'footbar'}).success(done) this.User.create({name: 'user', bio: 'footbar'}).success(function() {
done()
})
}) })
it("finds normal lookups", function(done) { it("finds normal lookups", function(done) {
this.User.find({ where: { name:'user' } }).success(function(user) { this.User.find({ where: { name:'user' } }).success(function(user) {
expect(user.name).toEqual('user') expect(user.name).to.equal('user')
done() done()
}) })
}) })
it("should make aliased attributes available", function(done) { it("should make aliased attributes available", function(done) {
this.User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) { this.User.find({ where: { name:'user' }, attributes: ['id', ['name', 'username']] }).success(function(user) {
expect(user.username).toEqual('user') expect(user.username).to.equal('user')
done() done()
}) })
}) })
}) })
describe('.all', function() { describe('.all', function() {
before(function(done) { beforeEach(function(done) {
this.User.bulkCreate([ this.User.bulkCreate([
{name: 'user', bio: 'foobar'}, {name: 'user', bio: 'foobar'},
{name: 'user', bio: 'foobar'} {name: 'user', bio: 'foobar'}
]).success(done) ]).success(function() {
done()
})
}) })
it("should return all users", function(done) { it("should return all users", function(done) {
this.User.all().on('success', function(users) { this.User.all().on('success', function(users) {
expect(users.length).toEqual(2) expect(users).to.have.length(2)
done() done()
}) })
}) })
...@@ -161,7 +161,7 @@ if (dialect === 'sqlite') { ...@@ -161,7 +161,7 @@ if (dialect === 'sqlite') {
this.User.bulkCreate(users).success(function() { this.User.bulkCreate(users).success(function() {
self.User.min('age').on('success', function(min) { self.User.min('age').on('success', function(min) {
expect(min).toEqual(2) expect(min).to.equal(2)
done() done()
}) })
}) })
...@@ -179,7 +179,7 @@ if (dialect === 'sqlite') { ...@@ -179,7 +179,7 @@ if (dialect === 'sqlite') {
this.User.bulkCreate(users).success(function() { this.User.bulkCreate(users).success(function() {
self.User.max('age').on('success', function(min) { self.User.max('age').on('success', function(min) {
expect(min).toEqual(5); expect(min).to.equal(5)
done() done()
}) })
}) })
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!