diff options
| author | Jules Laplace <jules@okfoc.us> | 2012-09-24 16:22:07 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2012-09-24 16:22:07 -0400 |
| commit | 686106d544ecc3b6ffd4db2b665d3bc879a58d8c (patch) | |
| tree | a5b5e50237cef70e12f0745371896e96f5f6d578 /node_modules/mongoose/test | |
ok
Diffstat (limited to 'node_modules/mongoose/test')
27 files changed, 15405 insertions, 0 deletions
diff --git a/node_modules/mongoose/test/collection.test.js b/node_modules/mongoose/test/collection.test.js new file mode 100644 index 0000000..54c247c --- /dev/null +++ b/node_modules/mongoose/test/collection.test.js @@ -0,0 +1,116 @@ + +var start = require('./common') + , mongoose = start.mongoose + , Collection = require('../lib/collection'); + +module.exports = { + + 'test buffering of commands until connection is established': function(beforeExit){ + var db = mongoose.createConnection() + , collection = db.collection('test-buffering-collection') + , connected = false + , inserted = false; + + collection.insert({ }, function(){ + connected.should.be.true; + inserted = true; + db.close(); + }); + + var uri = 'mongodb://localhost/mongoose_test'; + db.open(process.env.MONGOOSE_TEST_URI || uri, function(err){ + connected = !err; + }); + + beforeExit(function(){ + connected.should.be.true; + inserted.should.be.true; + }); + }, + + 'test methods that should throw (unimplemented)': function () { + var collection = new Collection('test', mongoose.connection) + , thrown = false; + + try { + collection.getIndexes(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.update(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.save(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.insert(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.find(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.findOne(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.findAndModify(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + + try { + collection.ensureIndex(); + } catch (e) { + /unimplemented/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + thrown = false; + } + +}; diff --git a/node_modules/mongoose/test/common.js b/node_modules/mongoose/test/common.js new file mode 100644 index 0000000..ffb3783 --- /dev/null +++ b/node_modules/mongoose/test/common.js @@ -0,0 +1,145 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('../') + , should = require('should') + , Table = require('cli-table') + , Mongoose = mongoose.Mongoose + , Collection = mongoose.Collection + , Assertion = should.Assertion + , startTime = Date.now() + , queryCount = 0 + , opened = 0 + , closed = 0; + +/** + * Override all Collection related queries to keep count + */ + +[ 'ensureIndex' + , 'findAndModify' + , 'findOne' + , 'find' + , 'insert' + , 'save' + , 'update' + , 'remove' + , 'count' + , 'distinct' +].forEach(function (method) { + + var oldMethod = Collection.prototype[method]; + + Collection.prototype[method] = function () { + queryCount++; + return oldMethod.apply(this, arguments); + }; + +}); + +/** + * Override Collection#onOpen to keep track of connections + */ + +var oldOnOpen = Collection.prototype.onOpen; + +Collection.prototype.onOpen = function(){ + opened++; + return oldOnOpen.apply(this, arguments); +}; + +/** + * Override Collection#onClose to keep track of disconnections + */ + +var oldOnClose = Collection.prototype.onClose; + +Collection.prototype.onClose = function(){ + closed++; + return oldOnClose.apply(this, arguments); +}; + +/** + * Assert that a connection is open or that mongoose connections are open. + * Examples: + * mongoose.should.be.connected; + * db.should.be.connected; + * + * @api public + */ + +Assertion.prototype.__defineGetter__('connected', function(){ + if (this.obj instanceof Mongoose) + this.obj.connections.forEach(function(connected){ + c.should.be.connected; + }); + else + this.obj.readyState.should.eql(1); +}); + +/** + * Assert that a connection is closed or that a mongoose connections are closed. + * Examples: + * mongoose.should.be.disconnected; + * db.should.be.disconnected; + * + * @api public + */ + +Assertion.prototype.__defineGetter__('disconnected', function(){ + if (this.obj instanceof Mongoose) + this.obj.connections.forEach(function(){ + c.should.be.disconnected; + }); + else + this.obj.readyState.should.eql(0); +}); + +/** + * Create a connection to the test database. + * You can set the environmental variable MONGOOSE_TEST_URI to override this. + * + * @api private + */ + +module.exports = function (options) { + var uri; + + if (options && options.uri) { + uri = options.uri; + delete options.uri; + } else { + uri = process.env.MONGOOSE_TEST_URI || + 'mongodb://localhost/mongoose_test' + } + + return mongoose.createConnection(uri, options); +}; + +/** + * Provide stats for tests + */ + +process.on('beforeExit', function(){ + var table = new Table({ + head: ['Stat', 'Time (ms)'] + , colWidths: [23, 15] + }); + + table.push( + ['Queries run', queryCount] + , ['Time ellapsed', Date.now() - startTime] + , ['Connections opened', opened] + , ['Connections closed', closed] + ); + + console.error(table.toString()); +}); + +/** + * Module exports. + */ + +module.exports.mongoose = mongoose; diff --git a/node_modules/mongoose/test/connection.test.js b/node_modules/mongoose/test/connection.test.js new file mode 100644 index 0000000..92c162a --- /dev/null +++ b/node_modules/mongoose/test/connection.test.js @@ -0,0 +1,310 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Schema = mongoose.Schema + +/** + * Test. + */ + +module.exports = { + + 'test closing a connection that\'s already closed': function (beforeExit) { + var db = mongoose.createConnection() + , called = false; + + db.readyState.should.eql(0); + db.close(function (err) { + should.strictEqual(err, null); + called = true; + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'test connection args': function (beforeExit) { + var db = mongoose.createConnection('mongodb://localhost/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.pass); + should.strictEqual(undefined, db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('mongodb://localhost:27000/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.pass); + should.strictEqual(undefined, db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal('27000'); + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual('psw', db.pass); + should.strictEqual('aaron', db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal('27000'); + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { db: { forceServerObjectId: true }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { server: { auto_reconnect: false }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.false; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + var called1 = false; + db = mongoose.createConnection('mongodb://aaron:psw@localhost:27000/fake', { server: { auto_reconnect: true }}, function () { + called1 = true; + }); + beforeExit(function () { + called1.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.close(); + + var called2 = false; + db = mongoose.createConnection('mongodb://localhost/fake', function () { + called2 = true; + }); + beforeExit(function () { + called2.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('mongodb:///fake', function (err) { + err.message.should.equal('Missing connection hostname.'); + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.name); + should.strictEqual(undefined, db.host); + should.strictEqual(undefined, db.port); + db.close(); + + db = mongoose.createConnection('mongodb://localhost', function (err) { + err.message.should.equal('Missing connection database.'); + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.name); + should.strictEqual(undefined, db.host); + should.strictEqual(undefined, db.port); + db.close(); + + var called3 = false; + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, { server: { auto_reconnect: false }}, function () { + called3 = true; + }); + beforeExit(function () { + called3.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.false; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + var called4 = false; + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, function () { + called4 = true; + }); + beforeExit(function () { + called4.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', 28000, { server: { auto_reconnect: true }}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28000); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', 28001); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(28001); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker', { blah: 1 }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.options.blah.should.equal(1); + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + + var called5 = false + db = mongoose.createConnection('127.0.0.1', 'faker', function () { + called5 = true; + }); + beforeExit(function () { + called5.should.be.true; + }); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + + db = mongoose.createConnection('127.0.0.1', 'faker'); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + db.name.should.equal('faker'); + db.host.should.equal('127.0.0.1'); + db.port.should.equal(27017); + db.close(); + + // Test connecting using user/pass in hostname + db = mongoose.createConnection('aaron:psw@localhost', 'fake', 27000); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual('psw', db.pass); + should.strictEqual('aaron', db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27000); + db.close(); + + // Test connecting using user/pass options + db = mongoose.createConnection('localhost', 'fake', 27000, {user: 'aaron', pass: 'psw'}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual('psw', db.pass); + should.strictEqual('aaron', db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27000); + db.close(); + + // Test connecting using only user option - which shouldn't work + db = mongoose.createConnection('localhost', 'fake', 27000, {user: 'no_pass'}); + db.options.should.be.a('object'); + db.options.server.should.be.a('object'); + db.options.server.auto_reconnect.should.be.true; + db.options.db.should.be.a('object'); + db.options.db.forceServerObjectId.should.be.false; + should.strictEqual(undefined, db.pass); + should.strictEqual(undefined, db.user); + db.name.should.equal('fake'); + db.host.should.equal('localhost'); + db.port.should.equal(27000); + db.close(); + }, + + 'connection.model allows passing a schema': function () { + var db = start(); + var MyModel = db.model('MyModelasdf', new Schema({ + name: String + })); + + MyModel.schema.should.be.an.instanceof(Schema); + MyModel.prototype.schema.should.be.an.instanceof(Schema); + + var m = new MyModel({name:'aaron'}); + m.name.should.eql('aaron'); + db.close(); + }, + + 'connection error event fires with one listener': function (exit) { + var db= start({ uri: 'mongodb://localasdfads/fakeeee'}) + , called = false; + db.on('error', function () { + // this callback has no params which triggered the bug #759 + called = true; + }); + exit(function () { + called.should.be.true; + }); + } + +}; diff --git a/node_modules/mongoose/test/crash.test.js b/node_modules/mongoose/test/crash.test.js new file mode 100644 index 0000000..67e6c86 --- /dev/null +++ b/node_modules/mongoose/test/crash.test.js @@ -0,0 +1,36 @@ + +// GH-407 + +var start = require('./common') + , mongoose = start.mongoose + , should = require('should') + +exports['test mongodb crash with invalid objectid string'] = function () { + var db = mongoose.createConnection("mongodb://localhost/test-crash"); + + var IndexedGuy = new mongoose.Schema({ + name: { type: String } + }); + + var Guy = db.model('Guy', IndexedGuy); + Guy.find({ + _id: { + $in: [ + '4e0de2a6ee47bff98000e145', + '4e137bd81a6a8e00000007ac', + '', + '4e0e2ca0795666368603d974'] + } + }, function (err) { + db.close(); + + // should is acting strange + try { + should.strictEqual(err.message, "Invalid ObjectId"); + } catch (er) { + console.error(err); + throw er; + } + }); + +} diff --git a/node_modules/mongoose/test/document.strict.test.js b/node_modules/mongoose/test/document.strict.test.js new file mode 100644 index 0000000..f9b8118 --- /dev/null +++ b/node_modules/mongoose/test/document.strict.test.js @@ -0,0 +1,178 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + , ObjectId = Schema.ObjectId + , DocumentObjectId = mongoose.Types.ObjectId + , DocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = mongoose.Types.Embedded + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , MongooseError = mongoose.Error; + +module.exports = { + + 'strict mode': function(){ + var db = start(); + + var lax = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }); + + var strict = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }, { strict: true }); + + var Lax = db.model('Lax', lax); + var Strict = db.model('Strict', strict); + + var l = new Lax({content: 'sample', rouge: 'data'}); + l._strictMode.should.be.false; + l = l.toObject(); + l.content.should.equal('sample') + l.rouge.should.equal('data'); + should.exist(l.rouge); + + var s = new Strict({content: 'sample', rouge: 'data'}); + s._strictMode.should.be.true; + s = s.toObject(); + s.should.have.property('ts'); + s.content.should.equal('sample'); + s.should.not.have.property('rouge'); + should.not.exist(s.rouge); + + // instance override + var instance = new Lax({content: 'sample', rouge: 'data'}, true); + instance._strictMode.should.be.true; + instance = instance.toObject(); + instance.content.should.equal('sample') + should.not.exist(instance.rouge); + instance.should.have.property('ts') + + // hydrate works as normal, but supports the schema level flag. + var s2 = new Strict({content: 'sample', rouge: 'data'}, false); + s2._strictMode.should.be.false; + s2 = s2.toObject(); + s2.should.have.property('ts') + s2.content.should.equal('sample'); + s2.should.have.property('rouge'); + should.exist(s2.rouge); + + // testing init + var s3 = new Strict(); + s3.init({content: 'sample', rouge: 'data'}); + var s3obj = s3.toObject(); + s3.content.should.equal('sample'); + s3.should.not.have.property('rouge'); + should.not.exist(s3.rouge); + + // strict on create + Strict.create({content: 'sample2', rouge: 'data'}, function(err, doc){ + db.close(); + doc.content.should.equal('sample2'); + doc.should.not.have.property('rouge'); + should.not.exist(doc.rouge); + }); + }, + + 'embedded doc strict mode': function(){ + var db = start(); + + var lax = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }); + + var strict = new Schema({ + ts : { type: Date, default: Date.now } + , content: String + }, { strict: true }); + + var Lax = db.model('EmbeddedLax', new Schema({ dox: [lax] }), 'embdoc'+random()); + var Strict = db.model('EmbeddedStrict', new Schema({ dox: [strict] }), 'embdoc'+random()); + + var l = new Lax({ dox: [{content: 'sample', rouge: 'data'}] }); + l.dox[0]._strictMode.should.be.false; + l = l.dox[0].toObject(); + l.content.should.equal('sample') + l.rouge.should.equal('data'); + should.exist(l.rouge); + + var s = new Strict({ dox: [{content: 'sample', rouge: 'data'}] }); + s.dox[0]._strictMode.should.be.true; + s = s.dox[0].toObject(); + s.should.have.property('ts'); + s.content.should.equal('sample'); + s.should.not.have.property('rouge'); + should.not.exist(s.rouge); + + // testing init + var s3 = new Strict(); + s3.init({dox: [{content: 'sample', rouge: 'data'}]}); + var s3obj = s3.toObject(); + s3.dox[0].content.should.equal('sample'); + s3.dox[0].should.not.have.property('rouge'); + should.not.exist(s3.dox[0].rouge); + + // strict on create + Strict.create({dox:[{content: 'sample2', rouge: 'data'}]}, function(err, doc){ + db.close(); + doc.dox[0].content.should.equal('sample2'); + doc.dox[0].should.not.have.property('rouge'); + should.not.exist(doc.dox[0].rouge); + }); + }, + + 'strict mode virtuals': function () { + var db = start(); + + var getCount = 0 + , setCount = 0; + + var strictSchema = new Schema({ + email: String + , prop: String + }, {strict: true}); + + strictSchema + .virtual('myvirtual') + .get(function() { + getCount++; + return 'ok'; + }) + .set(function(v) { + setCount++; + this.prop = v; + }); + + var StrictModel = db.model('StrictVirtual', strictSchema); + + var strictInstance = new StrictModel({ + email: 'hunter@skookum.com' + , myvirtual: 'test' + }); + + db.close(); + getCount.should.equal(0); + setCount.should.equal(1); + + strictInstance.myvirtual = 'anotherone'; + var myvirtual = strictInstance.myvirtual; + + getCount.should.equal(1); + setCount.should.equal(2); + } +} diff --git a/node_modules/mongoose/test/document.test.js b/node_modules/mongoose/test/document.test.js new file mode 100644 index 0000000..7c1b244 --- /dev/null +++ b/node_modules/mongoose/test/document.test.js @@ -0,0 +1,912 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId + , Document = require('../lib/document') + , DocumentObjectId = mongoose.Types.ObjectId; + +/** + * Test Document constructor. + */ + +function TestDocument () { + Document.apply(this, arguments); +}; + +/** + * Inherits from Document. + */ + +TestDocument.prototype.__proto__ = Document.prototype; + +/** + * Set a dummy schema to simulate compilation. + */ + +var em = new Schema({ title: String, body: String }); +var schema = TestDocument.prototype.schema = new Schema({ + test : String + , oids : [ObjectId] + , numbers : [Number] + , nested : { + age : Number + , cool : ObjectId + , deep : { x: String } + , path : String + , setr : String + } + , nested2 : { + nested: String + , yup : { + nested : Boolean + , yup : String + , age : Number + } + } + , em: [em] +}); + +schema.virtual('nested.agePlus2').get(function (v) { + return this.nested.age + 2; +}); +schema.virtual('nested.setAge').set(function (v) { + this.nested.age = v; +}); +schema.path('nested.path').get(function (v) { + return this.nested.age + (v ? v : ''); +}); +schema.path('nested.setr').set(function (v) { + return v + ' setter'; +}); + +/** + * Method subject to hooks. Simply fires the callback once the hooks are + * executed. + */ + +TestDocument.prototype.hooksTest = function(fn){ + fn(null, arguments); +}; + +/** + * Test. + */ + +module.exports = { + 'test shortcut getters': function(){ + var doc = new TestDocument(); + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + } + }); + + doc.test.should.eql('test'); + doc.oids.should.be.an.instanceof(Array); + (doc.nested.age == 5).should.be.true; + DocumentObjectId.toString(doc.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + doc.nested.agePlus2.should.eql(7); + doc.nested.path.should.eql('5my path'); + doc.nested.setAge = 10; + (doc.nested.age == 10).should.be.true; + doc.nested.setr = 'set it'; + doc.getValue('nested.setr').should.eql('set it setter'); + + var doc2 = new TestDocument(); + doc2.init({ + test : 'toop' + , oids : [] + , nested : { + age : 2 + , cool : DocumentObjectId.fromString('4cf70857337498f95900001c') + , deep : { x: 'yay' } + } + }); + + doc2.test.should.eql('toop'); + doc2.oids.should.be.an.instanceof(Array); + (doc2.nested.age == 2).should.be.true; + + // GH-366 + should.strictEqual(doc2.nested.bonk, undefined); + should.strictEqual(doc2.nested.nested, undefined); + should.strictEqual(doc2.nested.test, undefined); + should.strictEqual(doc2.nested.age.test, undefined); + should.strictEqual(doc2.nested.age.nested, undefined); + should.strictEqual(doc2.oids.nested, undefined); + should.strictEqual(doc2.nested.deep.x, 'yay'); + should.strictEqual(doc2.nested.deep.nested, undefined); + should.strictEqual(doc2.nested.deep.cool, undefined); + should.strictEqual(doc2.nested2.yup.nested, undefined); + should.strictEqual(doc2.nested2.yup.nested2, undefined); + should.strictEqual(doc2.nested2.yup.yup, undefined); + should.strictEqual(doc2.nested2.yup.age, undefined); + doc2.nested2.yup.should.be.a('object'); + + doc2.nested2.yup = { + age: 150 + , yup: "Yesiree" + , nested: true + }; + + should.strictEqual(doc2.nested2.nested, undefined); + should.strictEqual(doc2.nested2.yup.nested, true); + should.strictEqual(doc2.nested2.yup.yup, "Yesiree"); + (doc2.nested2.yup.age == 150).should.be.true; + doc2.nested2.nested = "y"; + should.strictEqual(doc2.nested2.nested, "y"); + should.strictEqual(doc2.nested2.yup.nested, true); + should.strictEqual(doc2.nested2.yup.yup, "Yesiree"); + (doc2.nested2.yup.age == 150).should.be.true; + + DocumentObjectId.toString(doc2.nested.cool).should.eql('4cf70857337498f95900001c'); + + doc.oids.should.not.equal(doc2.oids); + }, + + 'test shortcut setters': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'Test' + , nested : { + age : 5 + } + }); + + doc.isModified('test').should.be.false; + doc.test = 'Woot'; + doc.test.should.eql('Woot'); + doc.isModified('test').should.be.true; + + doc.isModified('nested.age').should.be.false; + doc.nested.age = 2; + (doc.nested.age == 2).should.be.true; + doc.isModified('nested.age').should.be.true; + }, + + 'test accessor of id': function () { + var doc = new TestDocument(); + doc._id.should.be.an.instanceof(DocumentObjectId); + }, + + 'test shortcut of id hexString': function () { + var doc = new TestDocument() + , _id = doc._id.toString(); + doc.id.should.be.a('string'); + }, + + 'test toObject clone': function(){ + var doc = new TestDocument(); + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : new DocumentObjectId + } + }); + + var copy = doc.toObject(); + + copy.test._marked = true; + copy.nested._marked = true; + copy.nested.age._marked = true; + copy.nested.cool._marked = true; + + should.strictEqual(doc._doc.test._marked, undefined); + should.strictEqual(doc._doc.nested._marked, undefined); + should.strictEqual(doc._doc.nested.age._marked, undefined); + should.strictEqual(doc._doc.nested.cool._marked, undefined); + }, + + 'toObject options': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'test' + , oids : [] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + } + }); + + var clone = doc.toObject({ getters: true, virtuals: false }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('5my path'); + should.equal(undefined, clone.nested.agePlus2); + + clone = doc.toObject({ virtuals: true }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('my path'); + clone.nested.agePlus2.should.eql(7); + + clone = doc.toObject({ getters: true }); + + clone.test.should.eql('test'); + clone.oids.should.be.an.instanceof(Array); + (clone.nested.age == 5).should.be.true; + DocumentObjectId.toString(clone.nested.cool).should.eql('4c6c2d6240ced95d0e00003c'); + clone.nested.path.should.eql('5my path'); + clone.nested.agePlus2.should.eql(7); + }, + + 'test hooks system': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , awaiting = 0 + , called = false; + + // serial + doc.pre('hooksTest', function(next){ + steps++; + setTimeout(function(){ + // make sure next step hasn't executed yet + steps.should.eql(1); + next(); + }, 50); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + // parallel + doc.pre('hooksTest', true, function(next, done){ + steps++; + steps.should.eql(3); + setTimeout(function(){ + steps.should.eql(4); + }, 10); + setTimeout(function(){ + steps++; + done(); + }, 110); + next(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + setTimeout(function(){ + steps.should.eql(4); + }, 10); + setTimeout(function(){ + steps++; + done(); + }, 110); + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(7); + called.should.be.true; + }); + }, + + 'test that calling next twice doesnt break': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next){ + steps++; + next(); + next(); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test that calling done twice doesnt break': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test that calling done twice on the same doesnt mean completion': + function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + }); + + doc.hooksTest(function(err){ + should.strictEqual(err, null); + called = true; + }); + + beforeExit(function(){ + steps.should.eql(2); + called.should.be.false; + }); + }, + + 'test hooks system errors from a serial hook': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next){ + steps++; + next(); + }); + + doc.pre('hooksTest', function(next){ + steps++; + next(new Error); + }); + + doc.pre('hooksTest', function(next){ + steps++; + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(3); + called.should.be.true; + }); + }, + + 'test hooks system erros from last serial hook': function(beforeExit){ + var doc = new TestDocument() + , called = false; + + doc.pre('hooksTest', function(next){ + next(new Error()); + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + called = true; + }); + + beforeExit(function(){ + called.should.be.true; + }); + }, + + 'test mutating incoming args via middleware': function (beforeExit) { + var doc = new TestDocument(); + + doc.pre('set', function(next, path, val){ + next(path, 'altered-' + val); + }); + + doc.set('test', 'me'); + + beforeExit(function(){ + doc.test.should.equal('altered-me'); + }); + }, + + 'test hooks system errors from a parallel hook': function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(); + }); + + doc.pre('hooksTest', true, function(next, done){ + steps++; + next(); + done(new Error); + }); + + doc.hooksTest(function(err){ + err.should.be.an.instanceof(Error); + steps++; + called = true; + }); + + beforeExit(function(){ + steps.should.eql(4); + called.should.be.true; + }); + }, + + 'test that its not necessary to call the last next in the parallel chain': + function(beforeExit){ + var doc = new TestDocument() + , steps = 0 + , called = false; + + doc.pre('hooksTest', function(next, done){ + next(); + done(); + }); + + doc.pre('hooksTest', function(next, done){ + done(); + }); + + doc.hooksTest(function(){ + called = true; + }); + + beforeExit(function(){ + called.should.be.true; + }); + }, + + 'test passing two arguments to a method subject to hooks and return value': + function (beforeExit) { + var doc = new TestDocument() + , called = false; + + doc.pre('hooksTest', function (next) { + next(); + }); + + doc.hooksTest(function (err, args) { + args.should.have.length(2); + args[1].should.eql('test'); + called = true; + }, 'test') + + beforeExit(function () { + called.should.be.true; + }); + }, + + // gh-746 + 'hooking set works with document arrays': function () { + var db = start(); + + var child = new Schema({ text: String }); + + child.pre('set', function (next, path, value, type) { + next(path, value, type); + }); + + var schema = new Schema({ + name: String + , e: [child] + }); + + var S = db.model('docArrayWithHookedSet', schema); + + var s = new S({ name: "test" }); + s.e = [{ text: 'hi' }]; + s.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }, + + 'test jsonifying an object': function () { + var doc = new TestDocument({ test: 'woot' }) + , oidString = DocumentObjectId.toString(doc._id); + + // convert to json string + var json = JSON.stringify(doc); + + // parse again + var obj = JSON.parse(json); + + obj.test.should.eql('woot'); + obj._id.should.eql(oidString); + }, + + 'toObject should not set undefined values to null': function () { + var doc = new TestDocument() + , obj = doc.toObject(); + + delete obj._id; + obj.should.eql({ numbers: [], oids: [], em: [] }); + }, + + // GH-209 + 'MongooseErrors should be instances of Error': function () { + var MongooseError = require('../lib/error') + , err = new MongooseError("Some message"); + err.should.be.an.instanceof(Error); + }, + 'ValidationErrors should be instances of Error': function () { + var ValidationError = Document.ValidationError + , err = new ValidationError(new TestDocument); + err.should.be.an.instanceof(Error); + }, + + 'methods on embedded docs should work': function () { + var db = start() + , ESchema = new Schema({ name: String }) + + ESchema.methods.test = function () { + return this.name + ' butter'; + } + ESchema.statics.ten = function () { + return 10; + } + + var E = db.model('EmbeddedMethodsAndStaticsE', ESchema); + var PSchema = new Schema({ embed: [ESchema] }); + var P = db.model('EmbeddedMethodsAndStaticsP', PSchema); + + var p = new P({ embed: [{name: 'peanut'}] }); + should.equal('function', typeof p.embed[0].test); + should.equal('function', typeof E.ten); + p.embed[0].test().should.equal('peanut butter'); + E.ten().should.equal(10); + + // test push casting + p = new P; + p.embed.push({name: 'apple'}); + should.equal('function', typeof p.embed[0].test); + should.equal('function', typeof E.ten); + p.embed[0].test().should.equal('apple butter'); + + db.close(); + }, + + 'setting a positional path does not cast value to array': function () { + var doc = new TestDocument; + doc.init({ numbers: [1,3] }); + doc.numbers[0].should.eql(1); + doc.numbers[1].valueOf().should.eql(3); + doc.set('numbers.1', 2); + doc.numbers[0].should.eql(1); + doc.numbers[1].valueOf().should.eql(2); + }, + + 'no maxListeners warning should occur': function () { + var db = start(); + + var traced = false; + var trace = console.trace; + + console.trace = function () { + traced = true; + console.trace = trace; + } + + var schema = new Schema({ + title: String + , embed1: [new Schema({name:String})] + , embed2: [new Schema({name:String})] + , embed3: [new Schema({name:String})] + , embed4: [new Schema({name:String})] + , embed5: [new Schema({name:String})] + , embed6: [new Schema({name:String})] + , embed7: [new Schema({name:String})] + , embed8: [new Schema({name:String})] + , embed9: [new Schema({name:String})] + , embed10: [new Schema({name:String})] + , embed11: [new Schema({name:String})] + }); + + var S = db.model('noMaxListeners', schema); + + var s = new S({ title: "test" }); + db.close(); + traced.should.be.false + }, + + 'isSelected': function () { + var doc = new TestDocument(); + + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + , em: [{ title: 'gocars' }] + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; // even if no data + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; // not a path + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; // not a path + doc.isSelected('noway').should.be.true; // not a path + doc.isSelected('notapath').should.be.true; // not a path but in the _doc + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; // not a path + + var selection = { + 'test': 1 + , 'numbers': 1 + , 'nested.deep': 1 + , 'oids': 1 + } + + doc = new TestDocument(undefined, selection); + + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + deep : { x: 'a string' } + } + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; // even if no data + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.false; + doc.isSelected('nested.cool').should.be.false; + doc.isSelected('nested.path').should.be.false; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.false; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.false; + doc.isSelected('notapath').should.be.false; + doc.isSelected('em').should.be.false; + doc.isSelected('em.title').should.be.false; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + 'em.title': 1 + } + + doc = new TestDocument(undefined, selection); + + doc.init({ + em: [{ title: 'one' }] + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.false; + doc.isSelected('numbers').should.be.false; + doc.isSelected('oids').should.be.false; + doc.isSelected('nested').should.be.false; + doc.isSelected('nested.age').should.be.false; + doc.isSelected('nested.cool').should.be.false; + doc.isSelected('nested.path').should.be.false; + doc.isSelected('nested.deep').should.be.false; + doc.isSelected('nested.nope').should.be.false; + doc.isSelected('nested.deep.x').should.be.false; + doc.isSelected('nested.deep.x.no').should.be.false; + doc.isSelected('nested.deep.y').should.be.false; + doc.isSelected('noway').should.be.false; + doc.isSelected('notapath').should.be.false; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + 'em': 0 + } + + doc = new TestDocument(undefined, selection); + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + }); + + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.false; + doc.isSelected('em.title').should.be.false; + doc.isSelected('em.body').should.be.false; + doc.isSelected('em.nonpath').should.be.false; + + var selection = { + '_id': 0 + } + + doc = new TestDocument(undefined, selection); + doc.init({ + test : 'test' + , numbers : [4,5,6,7] + , nested : { + age : 5 + , cool : DocumentObjectId.fromString('4c6c2d6240ced95d0e00003c') + , path : 'my path' + , deep : { x: 'a string' } + } + , notapath: 'i am not in the schema' + }); + + doc.isSelected('_id').should.be.false; + doc.isSelected('nested.deep.x.no').should.be.true; + + doc = new TestDocument({ test: 'boom' }); + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; + + doc = new TestDocument({ test: 'boom' }, true); + doc.isSelected('_id').should.be.true; + doc.isSelected('test').should.be.true; + doc.isSelected('numbers').should.be.true; + doc.isSelected('oids').should.be.true; + doc.isSelected('nested').should.be.true; + doc.isSelected('nested.age').should.be.true; + doc.isSelected('nested.cool').should.be.true; + doc.isSelected('nested.path').should.be.true; + doc.isSelected('nested.deep').should.be.true; + doc.isSelected('nested.nope').should.be.true; + doc.isSelected('nested.deep.x').should.be.true; + doc.isSelected('nested.deep.x.no').should.be.true; + doc.isSelected('nested.deep.y').should.be.true; + doc.isSelected('noway').should.be.true; + doc.isSelected('notapath').should.be.true; + doc.isSelected('em').should.be.true; + doc.isSelected('em.title').should.be.true; + doc.isSelected('em.body').should.be.true; + doc.isSelected('em.nonpath').should.be.true; + }, + + 'unselected required fields should pass validation': function () { + var db = start() + , Tschema = new Schema({ name: String, req: { type: String, required: true }}) + , T = db.model('unselectedRequiredFieldValidation', Tschema); + + var t = new T({ name: 'teeee', req: 'i am required' }); + t.save(function (err) { + should.strictEqual(null, err); + T.findById(t).select('name').exec(function (err, t) { + should.strictEqual(null, err); + should.strictEqual(undefined, t.req); + t.name = 'wooo'; + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t).select('name').exec(function (err, t) { + should.strictEqual(null, err); + t.req = undefined; + t.save(function (err) { + err = String(err); + var invalid = /Validator "required" failed for path req/.test(err); + invalid.should.be.true; + t.req = 'it works again' + t.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js b/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js new file mode 100644 index 0000000..2b3d13d --- /dev/null +++ b/node_modules/mongoose/test/drivers/node-mongodb-native/collection.test.js @@ -0,0 +1,63 @@ + +/** + * Module dependencies. + */ + +var start = require('../../common') + , mongoose = start.mongoose + , should = require('should') + , Schema = mongoose.Schema; + +/** + * Setup. + */ + +mongoose.model('NativeDriverTest', new Schema({ + title: String +})); + +/** + * Test. + */ + +module.exports = { + + 'test that trying to implement a sparse index works': function () { + var db = start() + , NativeTestCollection = db.model('NativeDriverTest'); + + NativeTestCollection.collection.ensureIndex({ title: 1 }, { sparse: true }, function (err) { + should.strictEqual(!!err, false); + NativeTestCollection.collection.getIndexes(function (err, indexes) { + db.close(); + should.strictEqual(!!err, false); + indexes.should.be.instanceof(Object); + indexes['title_1'].should.eql([['title', 1]]); + }); + }); + }, + + 'test that the -native traditional ensureIndex spec syntax for fields works': function () { + var db = start() + , NativeTestCollection = db.model('NativeDriverTest'); + + NativeTestCollection.collection.ensureIndex([['a', 1]], function () { + db.close(); + }); + }, + + 'unique index fails passes error': function () { + var db = start() + , schema = new Schema({ title: String }) + , NativeTestCollection = db.model('NativeDriverTestUnique', schema) + + NativeTestCollection.create({ title: 'x' }, {title:'x'}, function (err) { + should.strictEqual(!!err, false); + + NativeTestCollection.collection.ensureIndex({ title: 1 }, { unique: true, safe: true }, function (err) { + db.close(); + ;/E11000 duplicate key error index/.test(err.message).should.equal(true); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/dropdb.js b/node_modules/mongoose/test/dropdb.js new file mode 100755 index 0000000..0b806e7 --- /dev/null +++ b/node_modules/mongoose/test/dropdb.js @@ -0,0 +1,7 @@ +var start = require('./common') +var db = start(); +db.on('open', function () { + db.db.dropDatabase(function () { + process.exit(); + }); +}); diff --git a/node_modules/mongoose/test/index.test.js b/node_modules/mongoose/test/index.test.js new file mode 100644 index 0000000..db0625d --- /dev/null +++ b/node_modules/mongoose/test/index.test.js @@ -0,0 +1,235 @@ + +var url = require('url') + , start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , Mongoose = mongoose.Mongoose + , Schema = mongoose.Schema; + +module.exports = { + + 'test connecting to the demo database': function(beforeExit){ + var db = start() + , connected = false; + + db.on('open', function(){ + connected = true; + db.close(); + }); + + beforeExit(function(){ + connected.should.be.true; + }); + }, + + 'test default connection': function(beforeExit){ + var db = mongoose.connection + , uri = 'mongodb://localhost/mongoose_test' + , connected = false; + + mongoose.connect(process.env.MONGOOSE_TEST_URI || uri); + db.on('open', function(){ + connected = true; + db.close(); + }); + + beforeExit(function(){ + connected.should.be.true; + }); + }, + + 'test setting options': function(){ + var mongoose = new Mongoose(); + + mongoose.set('a', 'b'); + mongoose.set('long option', 'c'); + + mongoose.get('a').should.eql('b'); + mongoose.set('a').should.eql('b'); + mongoose.get('long option').should.eql('c'); + }, + + 'test declaring global plugins': function (beforeExit) { + var mong = new Mongoose() + , schema = new Schema() + , called = 0; + + mong.plugin(function (s) { + s.should.equal(schema); + called++; + }); + + schema.plugin(function (s) { + s.should.equal(schema); + called++; + }); + + mong.model('GlobalPlugins', schema); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test disconnection of all connections': function (beforeExit) { + var mong = new Mongoose() + , uri = 'mongodb://localhost/mongoose_test' + , connections = 0 + , disconnections = 0; + + mong.connect(process.env.MONGOOSE_TEST_URI || uri); + var db = mong.connection; + + db.on('open', function(){ + connections++; + }); + + db.on('close', function () { + disconnections++; + }); + + var db2 = mong.createConnection(process.env.MONGOOSE_TEST_URI || uri); + + db2.on('open', function () { + connections++; + }); + + db2.on('close', function () { + disconnections++; + }); + + mong.disconnect(); + + beforeExit(function () { + connections.should.eql(2); + disconnections.should.eql(2); + }); + }, + + 'test disconnection of all connections callback': function (beforeExit) { + var mong = new Mongoose() + , uri = 'mongodb://localhost/mongoose_test' + , called = false; + + mong.connect(process.env.MONGOOSE_TEST_URI || uri); + + mong.connection.on('open', function () { + mong.disconnect(function () { + called = true; + }); + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'try accessing a model that hasn\'t been defined': function () { + var mong = new Mongoose() + , thrown = false; + + try { + mong.model('Test'); + } catch (e) { + /hasn't been registered/.test(e.message).should.be.true; + thrown = true; + } + + thrown.should.be.true; + }, + + 'test connecting with a signature of host, database, function': function (){ + var mong = new Mongoose() + , uri = process.env.MONGOOSE_TEST_URI || 'mongodb://localhost/mongoose_test'; + + uri = url.parse(uri); + + mong.connect(uri.hostname, uri.pathname.substr(1), function (err) { + should.strictEqual(err, null); + mong.connection.close(); + }); + }, + + 'test connecting to a replica set': function () { + var uri = process.env.MONGOOSE_SET_TEST_URI; + + if (!uri) { + console.log('\033[30m', '\n', 'You\'re not testing replica sets!' + , '\n', 'Please set the MONGOOSE_SET_TEST_URI env variable.', '\n' + , 'e.g: `mongodb://localhost:27017/db,mongodb://localhost…`', '\n' + , '\033[39m'); + return; + } + + var mong = new Mongoose(); + + mong.connectSet(uri, function (err) { + should.strictEqual(err, null); + + mong.model('Test', new mongoose.Schema({ + test: String + })); + + var Test = mong.model('Test') + , test = new Test(); + + test.test = 'aa'; + test.save(function (err) { + should.strictEqual(err, null); + + Test.findById(test._id, function (err, doc) { + should.strictEqual(err, null); + + doc.test.should.eql('aa'); + + mong.connection.close(); + }); + }); + }); + }, + + 'test initializing a new Connection to a replica set': function () { + var uri = process.env.MONGOOSE_SET_TEST_URI; + + if (!uri) return; + + var mong = new Mongoose(true); + + var conn = mong.createSetConnection(uri, function (err) { + should.strictEqual(err, null); + + mong.model('ReplSetTwo', new mongoose.Schema({ + test: String + })); + + var Test = conn.model('ReplSetTwo') + , test = new Test(); + + test.test = 'aa'; + test.save(function (err) { + should.strictEqual(err, null); + + Test.findById(test._id, function (err, doc) { + should.strictEqual(err, null); + + doc.test.should.eql('aa'); + + conn.close(); + }); + }); + }); + }, + + 'test public exports': function () { + mongoose.version.should.be.a('string'); + mongoose.Collection.should.be.a('function'); + mongoose.Connection.should.be.a('function'); + mongoose.Schema.should.be.a('function'); + mongoose.SchemaType.should.be.a('function'); + mongoose.Query.should.be.a('function'); + mongoose.Promise.should.be.a('function'); + mongoose.Model.should.be.a('function'); + mongoose.Document.should.be.a('function'); + } + +}; diff --git a/node_modules/mongoose/test/model.querying.test.js b/node_modules/mongoose/test/model.querying.test.js new file mode 100644 index 0000000..2f5d53e --- /dev/null +++ b/node_modules/mongoose/test/model.querying.test.js @@ -0,0 +1,2352 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ObjectId = Schema.ObjectId + , MongooseBuffer = mongoose.Types.Buffer + , DocumentObjectId = mongoose.Types.ObjectId; + +/** + * Setup. + */ + +var Comments = new Schema(); + +Comments.add({ + title : String + , date : Date + , body : String + , comments : [Comments] +}); + +var BlogPostB = new Schema({ + title : String + , author : String + , slug : String + , date : Date + , meta : { + date : Date + , visitors : Number + } + , published : Boolean + , mixed : {} + , numbers : [Number] + , tags : [String] + , sigs : [Buffer] + , owners : [ObjectId] + , comments : [Comments] + , def : { type: String, default: 'kandinsky' } +}); + +mongoose.model('BlogPostB', BlogPostB); +var collection = 'blogposts_' + random(); + +var ModSchema = new Schema({ + num: Number +}); +mongoose.model('Mod', ModSchema); + +var geoSchema = new Schema({ loc: { type: [Number], index: '2d'}}); + +module.exports = { + + 'test that find returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + // query + BlogPostB.find({}).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.find({}, {}).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.find({}, []).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.find({}, {}, {}).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.find({}, [], {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that findOne returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + // query + BlogPostB.findOne({}).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.findOne({}, {}).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.findOne({}, []).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.findOne({}, {}, {}).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.findOne({}, [], {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that an empty find does not hang': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + + function fn () { + db.close(); + }; + + BlogPostB.find({}, fn); + }, + + 'test that a query is executed when a callback is passed': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 5 + , q = { _id: new DocumentObjectId }; // make sure the query is fast + + function fn () { + --count || db.close(); + }; + + // query + BlogPostB.find(q, fn).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.find(q, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.find(q, [], fn).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.find(q, {}, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.find(q, [], {}, fn).should.be.an.instanceof(Query); + }, + + 'test that query is executed where a callback for findOne': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 5 + , q = { _id: new DocumentObjectId }; // make sure the query is fast + + function fn () { + --count || db.close(); + }; + + // query + BlogPostB.findOne(q, fn).should.be.an.instanceof(Query); + + // query, fields + BlogPostB.findOne(q, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array) + BlogPostB.findOne(q, [], fn).should.be.an.instanceof(Query); + + // query, fields, options + BlogPostB.findOne(q, {}, {}, fn).should.be.an.instanceof(Query); + + // query, fields (array), options + BlogPostB.findOne(q, [], {}, fn).should.be.an.instanceof(Query); + }, + + 'test that count returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.count({}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that count Query executes when you pass a callback': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , pending = 2 + + function fn () { + if (--pending) return; + db.close(); + }; + + BlogPostB.count({}, fn).should.be.an.instanceof(Query); + BlogPostB.count(fn).should.be.an.instanceof(Query); + }, + + 'test that distinct returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.distinct('title', {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that distinct Query executes when you pass a callback': function () { + var db = start(); + var Address = new Schema({ zip: String }); + Address = db.model('Address', Address, 'addresses_' + random()); + + Address.create({ zip: '10010'}, { zip: '10010'}, { zip: '99701'}, function (err, a1, a2, a3) { + should.strictEqual(null, err); + var query = Address.distinct('zip', {}, function (err, results) { + should.strictEqual(null, err); + results.should.eql(['10010', '99701']); + db.close(); + }); + query.should.be.an.instanceof(Query); + }); + }, + + + 'test that update returns a Query': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.update({}, {}).should.be.an.instanceof(Query); + BlogPostB.update({}, {}, {}).should.be.an.instanceof(Query); + + db.close(); + }, + + 'test that update Query executes when you pass a callback': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , count = 2; + + function fn () { + --count || db.close(); + }; + + BlogPostB.update({title: random()}, {}, fn).should.be.an.instanceof(Query); + + BlogPostB.update({title: random()}, {}, {}, fn).should.be.an.instanceof(Query); + }, + + 'test finding a document': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ title: title }, function (err, doc) { + should.strictEqual(err, null); + doc.get('title').should.eql(title); + doc.isNew.should.be.false; + + db.close(); + }); + }); + }, + + 'test finding a document byId': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Edwald ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var pending = 2; + + BlogPostB.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + doc.should.be.an.instanceof(BlogPostB); + doc.get('title').should.eql(title); + --pending || db.close(); + }); + + BlogPostB.findById(post.get('_id').toHexString(), function (err, doc) { + should.strictEqual(err, null); + doc.should.be.an.instanceof(BlogPostB); + doc.get('title').should.eql(title); + --pending || db.close(); + }); + }); + }, + + 'test finding documents': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ title: title }, function (err, docs) { + should.strictEqual(err, null); + docs.should.have.length(2); + + docs[0].get('title').should.eql(title); + docs[0].isNew.should.be.false; + + docs[1].get('title').should.eql(title); + docs[1].isNew.should.be.false; + + db.close(); + }); + }); + }); + }, + + 'test finding documents where an array that contains one specific member': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({numbers: [100, 101, 102]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({numbers: 100}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test counting documents': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Wooooot ' + random(); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + var post = new BlogPostB(); + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.count({ title: title }, function (err, count) { + should.strictEqual(err, null); + + count.should.be.a('number'); + count.should.eql(2); + + db.close(); + }); + }); + }); + }, + + 'test query casting': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , title = 'Loki ' + random(); + + var post = new BlogPostB() + , id = DocumentObjectId.toString(post.get('_id')); + + post.set('title', title); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ _id: id }, function (err, doc) { + should.strictEqual(err, null); + + doc.get('title').should.equal(title); + db.close(); + }); + }); + }, + + 'test a query that includes a casting error': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.find({ date: 'invalid date' }, function (err) { + err.should.be.an.instanceof(Error); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test findOne queries that require casting for $modifiers': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: -10 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ 'meta.visitors': { $gt: '-20', $lt: -1 } }, + function (err, found) { + found.get('meta.visitors') + .valueOf().should.equal(post.get('meta.visitors').valueOf()); + found.id; + found.get('_id').should.eql(post.get('_id')); + db.close(); + }); + }); + }, + + 'test find queries that require casting for $modifiers': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: -75 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ 'meta.visitors': { $gt: '-100', $lt: -50 } }, + function (err, found) { + should.strictEqual(err, null); + + found.should.have.length(1); + found[0].get('_id').should.eql(post.get('_id')); + found[0].get('meta.visitors').valueOf() + .should.equal(post.get('meta.visitors').valueOf()); + db.close(); + }); + }); + }, + + // GH-199 + 'test find queries where $in cast the values wherein the array': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB() + , id = DocumentObjectId.toString(post._id); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ _id: { $in: [id] } }, function (err, doc) { + should.strictEqual(err, null); + + DocumentObjectId.toString(doc._id).should.eql(id); + db.close(); + }); + }); + }, + + // GH-232 + 'test find queries where $nin cast the values wherein the array': function () { + var db = start() + , NinSchema = new Schema({ + num: Number + }); + mongoose.model('Nin', NinSchema); + var Nin = db.model('Nin', 'nins_' + random()); + Nin.create({ num: 1 }, function (err, one) { + should.strictEqual(err, null); + Nin.create({ num: 2 }, function (err, two) { + should.strictEqual(err, null); + Nin.create({num: 3}, function (err, three) { + should.strictEqual(err, null); + Nin.find({ num: {$nin: [2]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + db.close(); + }); + }); + }); + }); + }, + + 'test find queries with $ne with single value against array': function () { + var db = start(); + var schema = new Schema({ + ids: [Schema.ObjectId] + , b: Schema.ObjectId + }); + + var NE = db.model('NE_Test', schema, 'nes__' + random()); + + var id1 = new DocumentObjectId; + var id2 = new DocumentObjectId; + var id3 = new DocumentObjectId; + var id4 = new DocumentObjectId; + + NE.create({ ids: [id1, id4], b: id3 }, function (err, ne1) { + should.strictEqual(err, null); + NE.create({ ids: [id2, id4], b: id3 },function (err, ne2) { + should.strictEqual(err, null); + + var query = NE.find({ 'b': id3.toString(), 'ids': { $ne: id1 }}); + query.run(function (err, nes1) { + should.strictEqual(err, null); + nes1.length.should.eql(1); + + NE.find({ b: { $ne: [1] }}, function (err, nes2) { + err.message.should.eql("Invalid ObjectId"); + + NE.find({ b: { $ne: 4 }}, function (err, nes3) { + err.message.should.eql("Invalid ObjectId"); + + NE.find({ b: id3, ids: { $ne: id4 }}, function (err, nes4) { + db.close(); + should.strictEqual(err, null); + nes4.length.should.eql(0); + }); + }); + }); + }); + + }); + }); + + }, + + 'test for findById where partial initialization': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , queries = 5; + + var post = new BlogPostB(); + + post.title = 'hahaha'; + post.slug = 'woot'; + post.meta.visitors = 53; + post.tags = ['humidity', 'soggy']; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.true; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.true; + doc.meta.visitors.valueOf().should.equal(53); + doc.tags.length.should.equal(2); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), ['title'], function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), { slug: 0 }, function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.true; + doc.meta.visitors.valueOf().should.equal(53); + doc.tags.length.should.equal(2); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), { title:1 }, function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.true; + doc.isInit('slug').should.be.false; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + + BlogPostB.findById(post.get('_id'), ['slug'], function (err, doc) { + should.strictEqual(err, null); + doc.isInit('title').should.be.false; + doc.isInit('slug').should.be.true; + doc.isInit('date').should.be.false; + doc.isInit('meta.visitors').should.be.false; + should.strictEqual(undefined, doc.meta.visitors); + should.strictEqual(undefined, doc.tags); + --queries || db.close(); + }); + }); + }, + + 'findOne where subset of fields excludes _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({title: 'subset 1'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({title: 'subset 1'}, {title: 1, _id: 0}, function (err, found) { + should.strictEqual(err, null); + should.strictEqual(undefined, found._id); + found.title.should.equal('subset 1'); + db.close(); + }); + }); + }, + + 'test find where subset of fields, excluding _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + BlogPostB.create({title: 'subset 1', author: 'me'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({title: 'subset 1'}, {title: 1, _id: 0}, function (err, found) { + should.strictEqual(err, null); + should.strictEqual(undefined, found[0]._id); + found[0].title.should.equal('subset 1'); + should.strictEqual(undefined, found[0].def); + should.strictEqual(undefined, found[0].author); + should.strictEqual(false, Array.isArray(found[0].comments)); + db.close(); + }); + }); + }, + + // gh-541 + 'find subset of fields excluding embedded doc _id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'LOTR', comments: [{ title: ':)' }]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.find({_id: created}, { _id: 0, 'comments._id': 0 }, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(undefined, found[0]._id); + found[0].title.should.equal('LOTR'); + should.strictEqual('kandinsky', found[0].def); + should.strictEqual(undefined, found[0].author); + should.strictEqual(true, Array.isArray(found[0].comments)); + found[0].comments.length.should.equal(1); + found[0].comments[0].title.should.equal(':)'); + should.strictEqual(undefined, found[0].comments[0]._id); + // gh-590 + should.strictEqual(null, found[0].comments[0].id); + }); + }); + }, + + + 'exluded fields should be undefined': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , date = new Date + + BlogPostB.create({title: 'subset 1', author: 'me', meta: { date: date }}, function (err, created) { + should.strictEqual(err, null); + var id = created.id; + BlogPostB.findById(created.id, {title: 0, 'meta.date': 0, owners: 0}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + should.strictEqual(undefined, found.title); + should.strictEqual('kandinsky', found.def); + should.strictEqual('me', found.author); + should.strictEqual(true, Array.isArray(found.comments)); + should.equal(undefined, found.meta.date); + found.comments.length.should.equal(0); + should.equal(undefined, found.owners); + }); + }); + }, + + 'exluded fields should be undefined and defaults applied to other fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , id = new DocumentObjectId + , date = new Date + + BlogPostB.collection.insert({ _id: id, title: 'hahaha1', meta: { date: date }}, function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(id, {title: 0}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found._id.should.eql(id); + should.strictEqual(undefined, found.title); + should.strictEqual('kandinsky', found.def); + should.strictEqual(undefined, found.author); + should.strictEqual(true, Array.isArray(found.comments)); + should.equal(date.toString(), found.meta.date.toString()); + found.comments.length.should.equal(0); + }); + }); + }, + + 'test for find where partial initialization': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , queries = 4; + + var post = new BlogPostB(); + + post.title = 'hahaha'; + post.slug = 'woot'; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.find({ _id: post.get('_id') }, function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.true; + docs[0].isInit('date').should.be.false; + should.strictEqual('kandinsky', docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, ['title'], function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.false; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, { slug: 0, def: 0 }, function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.true; + docs[0].isInit('slug').should.be.false; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + + BlogPostB.find({ _id: post.get('_id') }, ['slug'], function (err, docs) { + should.strictEqual(err, null); + docs[0].isInit('title').should.be.false; + docs[0].isInit('slug').should.be.true; + docs[0].isInit('date').should.be.false; + should.strictEqual(undefined, docs[0].def); + --queries || db.close(); + }); + }); + }, + + // GH-204 + 'test query casting when finding by Date': function () { + var db = start() + , P = db.model('BlogPostB', collection); + + var post = new P; + + post.meta.date = new Date(); + + post.save(function (err) { + should.strictEqual(err, null); + + P.findOne({ _id: post._id, 'meta.date': { $lte: Date.now() } }, function (err, doc) { + should.strictEqual(err, null); + + DocumentObjectId.toString(doc._id).should.eql(DocumentObjectId.toString(post._id)); + doc.meta.date = null; + doc.save(function (err) { + should.strictEqual(err, null); + P.findById(doc._id, function (err, doc) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(doc.meta.date, null); + }); + }); + }); + }); + }, + + // gh-523 + 'null boolean default is allowed': function () { + var db = start() + , s1 = new Schema({ b: { type: Boolean, default: null }}) + , M1 = db.model('NullDateDefaultIsAllowed1', s1) + , s2 = new Schema({ b: { type: Boolean, default: false }}) + , M2 = db.model('NullDateDefaultIsAllowed2', s2) + , s3 = new Schema({ b: { type: Boolean, default: true }}) + , M3 = db.model('NullDateDefaultIsAllowed3', s3) + + db.close(); + + var m1 = new M1; + should.strictEqual(null, m1.b); + var m2 = new M2; + should.strictEqual(false, m2.b); + var m3 = new M3; + should.strictEqual(true, m3.b); + }, + + // GH-220 + 'test querying if an array contains at least a certain single member': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB(); + + post.tags.push('cat'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({tags: 'cat'}, function (err, doc) { + should.strictEqual(err, null); + + doc.id; + doc._id.should.eql(post._id); + db.close(); + }); + }); + }, + + 'test querying if an array contains one of multiple members $in a set': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB(); + + post.tags.push('football'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({tags: {$in: ['football', 'baseball']}}, function (err, doc) { + should.strictEqual(err, null); + doc.id; + doc._id.should.eql(post._id); + + BlogPostB.findOne({ _id: post._id, tags: /otba/i }, function (err, doc) { + should.strictEqual(err, null); + doc.id; + doc._id.should.eql(post._id); + + db.close(); + }) + }); + }); + }, + + 'test querying if an array contains one of multiple members $in a set 2': function () { + var db = start() + , BlogPostA = db.model('BlogPostB', collection) + + var post = new BlogPostA({ tags: ['gooberOne'] }); + + post.save(function (err) { + should.strictEqual(err, null); + + var query = {tags: {$in:[ 'gooberOne' ]}}; + + BlogPostA.findOne(query, function (err, returned) { + done(); + should.strictEqual(err, null); + ;(!!~returned.tags.indexOf('gooberOne')).should.be.true; + returned.id; + returned._id.should.eql(post._id); + }); + }); + + post.collection.insert({ meta: { visitors: 9898, a: null } }, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostA.findOne({_id: b[0]._id}, function (err, found) { + done(); + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + }) + }); + + var pending = 2; + function done () { + if (--pending) return; + db.close(); + } + }, + + 'test querying via $where a string': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({ title: 'Steve Jobs', author: 'Steve Jobs'}, function (err, created) { + should.strictEqual(err, null); + + BlogPostB.findOne({ $where: "this.title && this.title === this.author" }, function (err, found) { + should.strictEqual(err, null); + + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test querying via $where a function': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({ author: 'Atari', slug: 'Atari'}, function (err, created) { + should.strictEqual(err, null); + + BlogPostB.findOne({ $where: function () { + return (this.author && this.slug && this.author === this.slug); + } }, function (err, found) { + should.strictEqual(err, null); + + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test find where $exists': function () { + var db = start() + , ExistsSchema = new Schema({ + a: Number + , b: String + }); + mongoose.model('Exists', ExistsSchema); + var Exists = db.model('Exists', 'exists_' + random()); + Exists.create({ a: 1}, function (err, aExisting) { + should.strictEqual(err, null); + Exists.create({b: 'hi'}, function (err, bExisting) { + should.strictEqual(err, null); + Exists.find({b: {$exists: true}}, function (err, docs) { + should.strictEqual(err, null); + db.close(); + docs.should.have.length(1); + }); + }); + }); + }, + + 'test finding based on nested fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB({ + meta: { + visitors: 5678 + } + }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findOne({ 'meta.visitors': 5678 }, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors') + .valueOf().should.equal(post.get('meta.visitors').valueOf()); + found.id; + found.get('_id').should.eql(post.get('_id')); + db.close(); + }); + }); + }, + + // GH-242 + 'test finding based on embedded document fields': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({comments: [{title: 'i should be queryable'}], numbers: [1,2,33333], tags:['yes', 'no']}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({'comments.title': 'i should be queryable'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.findOne({'comments.0.title': 'i should be queryable'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + // GH-463 + BlogPostB.findOne({'numbers.2': 33333}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.findOne({'tags.1': 'no'}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }); + }); + }); + }, + + // GH-389 + 'find nested doc using string id': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({comments: [{title: 'i should be queryable by _id'}, {title:'me too me too!'}]}, function (err, created) { + should.strictEqual(err, null); + var id = created.comments[1]._id.toString(); + BlogPostB.findOne({'comments._id': id}, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(!! found, true, 'Find by nested doc id hex string fails'); + found.id; + found._id.should.eql(created._id); + }); + }); + }, + + 'test finding where $elemMatch': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , dateAnchor = +new Date; + + BlogPostB.create({comments: [{title: 'elemMatch', date: dateAnchor + 5}]}, function (err, createdAfter) { + should.strictEqual(err, null); + BlogPostB.create({comments: [{title: 'elemMatch', date: dateAnchor - 5}]}, function (err, createdBefore) { + should.strictEqual(err, null); + BlogPostB.find({'comments': {'$elemMatch': {title: 'elemMatch', date: {$gt: dateAnchor}}}}, + function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(createdAfter._id); + db.close(); + } + ); + }); + }); + }, + + 'test finding where $mod': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.find({num: {$mod: [2, 1]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._id); + db.close(); + }); + }); + }); + }, + + 'test finding where $not': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.find({num: {$not: {$mod: [2, 1]}}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + db.close(); + }); + }); + }); + }, + + 'test finding where $or': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + + Mod.create({num: 1}, {num: 2, str: 'two'}, function (err, one, two) { + should.strictEqual(err, null); + + var pending = 3; + test1(); + test2(); + test3(); + + function test1 () { + Mod.find({$or: [{num: 1}, {num: 2}]}, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(one._id); + found[1]._id.should.eql(two._id); + }); + } + + function test2 () { + Mod.find({ $or: [{ str: 'two'}, {str:'three'}] }, function (err, found) { + if (err) console.error(err); + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + }); + } + + function test3 () { + Mod.find({$or: [{num: 1}]}).$or([{ str: 'two' }]).run(function (err, found) { + if (err) console.error(err); + done(); + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(one._id); + found[1]._id.should.eql(two._id); + }); + } + + function done () { + if (--pending) return; + db.close(); + } + }); + }, + + 'finding where #nor': function () { + var db = start() + , Mod = db.model('Mod', 'nor_' + random()); + + Mod.create({num: 1}, {num: 2, str: 'two'}, function (err, one, two) { + should.strictEqual(err, null); + + var pending = 3; + test1(); + test2(); + test3(); + + function test1 () { + Mod.find({$nor: [{num: 1}, {num: 3}]}, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(two._id); + }); + } + + function test2 () { + Mod.find({ $nor: [{ str: 'two'}, {str:'three'}] }, function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._id); + }); + } + + function test3 () { + Mod.find({$nor: [{num: 2}]}).$nor([{ str: 'two' }]).run(function (err, found) { + done(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(one._id); + }); + } + + function done () { + if (--pending) return; + db.close(); + } + }); + }, + + 'test finding where $ne': function () { + var db = start() + , Mod = db.model('Mod', 'mods_' + random()); + Mod.create({num: 1}, function (err, one) { + should.strictEqual(err, null); + Mod.create({num: 2}, function (err, two) { + should.strictEqual(err, null); + Mod.create({num: 3}, function (err, three) { + should.strictEqual(err, null); + Mod.find({num: {$ne: 1}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(two._id); + found[1]._id.should.eql(three._id); + db.close(); + }); + }); + }); + }); + }, + + 'test finding null matches null and undefined': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection + random()); + + BlogPostB.create( + { title: 'A', author: null } + , { title: 'B' }, function (err, createdA, createdB) { + should.strictEqual(err, null); + BlogPostB.find({author: null}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + }); + }, + + 'test finding STRICT null matches': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection + random()); + + BlogPostB.create( + { title: 'A', author: null} + , { title: 'B' }, function (err, createdA, createdB) { + should.strictEqual(err, null); + BlogPostB.find({author: {$in: [null], $exists: true}}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(createdA._id); + }); + }); + }, + + 'setting a path to undefined should retain the value as undefined': function () { + var db = start() + , B = db.model('BlogPostB', collection + random()) + + var doc = new B; + doc.title='css3'; + doc._delta().$set.title.should.equal('css3'); + doc.title = undefined; + doc._delta().$unset.title.should.equal(1); + should.strictEqual(undefined, doc._delta().$set); + + doc.title='css3'; + doc.author = 'aaron'; + doc.numbers = [3,4,5]; + doc.meta.date = new Date; + doc.meta.visitors = 89; + doc.comments = [{ title: 'thanksgiving', body: 'yuuuumm' }]; + doc.comments.push({ title: 'turkey', body: 'cranberries' }); + + doc.save(function (err) { + should.strictEqual(null, err); + B.findById(doc._id, function (err, b) { + should.strictEqual(null, err); + b.title.should.equal('css3'); + b.author.should.equal('aaron'); + should.equal(b.meta.date.toString(), doc.meta.date.toString()); + b.meta.visitors.valueOf().should.equal(doc.meta.visitors.valueOf()); + b.comments.length.should.equal(2); + b.comments[0].title.should.equal('thanksgiving'); + b.comments[0].body.should.equal('yuuuumm'); + b.comments[1].title.should.equal('turkey'); + b.comments[1].body.should.equal('cranberries'); + b.title = undefined; + b.author = null; + b.meta.date = undefined; + b.meta.visitors = null; + b.comments[0].title = null; + b.comments[0].body = undefined; + b.save(function (err) { + should.strictEqual(null, err); + B.findById(b._id, function (err, b) { + should.strictEqual(null, err); + should.strictEqual(undefined, b.title); + should.strictEqual(null, b.author); + + should.strictEqual(undefined, b.meta.date); + should.strictEqual(null, b.meta.visitors); + should.strictEqual(null, b.comments[0].title); + should.strictEqual(undefined, b.comments[0].body); + b.comments[1].title.should.equal('turkey'); + b.comments[1].body.should.equal('cranberries'); + + b.meta = undefined; + b.comments = undefined; + b.save(function (err) { + should.strictEqual(null, err); + B.collection.findOne({ _id: b._id}, function (err, b) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(undefined, b.meta); + should.strictEqual(undefined, b.comments); + }); + }); + }); + }); + }); + }); + }, + + 'test finding strings via regular expressions': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'Next to Normal'}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({title: /^Next/}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + var reg = '^Next to Normal$'; + + BlogPostB.find({ title: { $regex: reg }}, function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(1); + found[0].id; + found[0]._id.should.eql(created._id); + + BlogPostB.findOne({ title: { $regex: reg }}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.where('title').$regex(reg).findOne(function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + + BlogPostB.where('title').$regex(/^Next/).findOne(function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + }); + }); + }); + }); + }); + }); + }, + + 'test finding a document whose arrays contain at least $all values': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create( + {numbers: [-1,-2,-3,-4], meta: { visitors: 4 }} + , {numbers: [0,-1,-2,-3,-4]} + , function (err, whereoutZero, whereZero) { + should.strictEqual(err, null); + + BlogPostB.find({numbers: {$all: [-1, -2, -3, -4]}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + BlogPostB.find({'meta.visitors': {$all: [4] }}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(whereoutZero._id); + BlogPostB.find({numbers: {$all: [0, -1]}}, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(whereZero._id); + }); + }); + }); + }); + }, + + 'test finding a document whose arrays contain at least $all string values': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + var post = new BlogPostB({ title: "Aristocats" }); + + post.tags.push('onex'); + post.tags.push('twox'); + post.tags.push('threex'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPostB.findById(post._id, function (err, post) { + should.strictEqual(err, null); + + BlogPostB.find({ title: { '$all': ['Aristocats']}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({ title: { '$all': [/^Aristocats/]}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({tags: { '$all': ['onex','twox','threex']}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.find({tags: { '$all': [/^onex/i]}}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + + BlogPostB.findOne({tags: { '$all': /^two/ }}, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.id.should.eql(post.id); + }); + }); + }); + }); + }); + }); + + }); + }, + + 'find using #all with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection + '_nestedElemMatch'); + + var post = new P({ title: "nested elemMatch" }); + post.comments.push({ title: 'comment A' }, { title: 'comment B' }, { title: 'comment C' }) + + var id0 = post.comments[0]._id; + var id1 = post.comments[1]._id; + var id2 = post.comments[2]._id; + + post.save(function (err) { + should.strictEqual(null, err); + + var query0 = { $elemMatch: { _id: id1, title: 'comment B' }}; + var query1 = { $elemMatch: { _id: id2.toString(), title: 'comment C' }}; + + P.findOne({ comments: { $all: [query0, query1] }}, function (err, p) { + db.close(); + should.strictEqual(null, err); + p.id.should.equal(post.id); + }); + }); + }, + + 'find using #or with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection); + + var post = new P({ title: "nested elemMatch" }); + post.comments.push({ title: 'comment D' }, { title: 'comment E' }, { title: 'comment F' }) + + var id0 = post.comments[0]._id; + var id1 = post.comments[1]._id; + var id2 = post.comments[2]._id; + + post.save(function (err) { + should.strictEqual(null, err); + + var query0 = { comments: { $elemMatch: { title: 'comment Z' }}}; + var query1 = { comments: { $elemMatch: { _id: id1.toString(), title: 'comment E' }}}; + + P.findOne({ $or: [query0, query1] }, function (err, p) { + db.close(); + should.strictEqual(null, err); + p.id.should.equal(post.id); + }); + }); + }, + + 'find using #nor with nested #elemMatch': function () { + var db = start() + , P = db.model('BlogPostB', collection + '_norWithNestedElemMatch'); + + var p0 = { title: "nested $nor elemMatch1", comments: [] }; + + var p1 = { title: "nested $nor elemMatch0", comments: [] }; + p1.comments.push({ title: 'comment X' }, { title: 'comment Y' }, { title: 'comment W' }) + + P.create(p0, p1, function (err, post0, post1) { + should.strictEqual(null, err); + + var id = post1.comments[1]._id; + + var query0 = { comments: { $elemMatch: { title: 'comment Z' }}}; + var query1 = { comments: { $elemMatch: { _id: id.toString(), title: 'comment Y' }}}; + + P.find({ $nor: [query0, query1] }, function (err, posts) { + db.close(); + should.strictEqual(null, err); + posts.length.should.equal(1); + posts[0].id.should.equal(post0.id); + }); + }); + + }, + + 'test finding documents where an array of a certain $size': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({numbers: [1,2,3,4,5,6,7,8,9,10]}, function (err, whereoutZero) { + should.strictEqual(err, null); + BlogPostB.create({numbers: [11,12,13,14,15,16,17,18,19,20]}, function (err, whereZero) { + should.strictEqual(err, null); + BlogPostB.create({numbers: [1,2,3,4,5,6,7,8,9,10,11]}, function (err, found) { + BlogPostB.find({numbers: {$size: 10}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + BlogPostB.find({numbers: {$size: 11}}, function (err, found) { + should.strictEqual(err, null); + found.should.have.length(1); + db.close(); + }); + }); + }); + }); + }); + }, + + 'test finding documents where an array where the $slice operator': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({numbers: [500,600,700,800]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findById(created._id, {numbers: {$slice: 2}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(500); + found.numbers[1].should.equal(600); + BlogPostB.findById(created._id, {numbers: {$slice: -2}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(700); + found.numbers[1].should.equal(800); + BlogPostB.findById(created._id, {numbers: {$slice: [1, 2]}}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + found.numbers.should.have.length(2); + found.numbers[0].should.equal(600); + found.numbers[1].should.equal(700); + db.close(); + }); + }); + }); + }); + }, + + 'test finding documents with a specifc Buffer in their array': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({sigs: [new Buffer([1, 2, 3]), + new Buffer([4, 5, 6]), + new Buffer([7, 8, 9])]}, function (err, created) { + should.strictEqual(err, null); + BlogPostB.findOne({sigs: new Buffer([1, 2, 3])}, function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + var query = { sigs: { "$in" : [new Buffer([3, 3, 3]), new Buffer([4, 5, 6])] } }; + BlogPostB.findOne(query, function (err, found) { + should.strictEqual(err, null); + db.close(); + }); + }); + }); + }, + + 'test limits': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'first limit'}, function (err, first) { + should.strictEqual(err, null); + BlogPostB.create({title: 'second limit'}, function (err, second) { + should.strictEqual(err, null); + BlogPostB.create({title: 'third limit'}, function (err, third) { + should.strictEqual(err, null); + BlogPostB.find({title: /limit$/}).limit(2).find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(first._id); + found[1]._id.should.eql(second._id); + db.close(); + }); + }); + }); + }); + }, + + 'test skips': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: 'first skip'}, function (err, first) { + should.strictEqual(err, null); + BlogPostB.create({title: 'second skip'}, function (err, second) { + should.strictEqual(err, null); + BlogPostB.create({title: 'third skip'}, function (err, third) { + should.strictEqual(err, null); + BlogPostB.find({title: /skip$/}).skip(1).limit(2).find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + found[0]._id.should.eql(second._id); + found[1]._id.should.eql(third._id); + db.close(); + }); + }); + }); + }); + }, + + 'test sorts': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({meta: {visitors: 100}}, function (err, least) { + should.strictEqual(err, null); + BlogPostB.create({meta: {visitors: 300}}, function (err, largest) { + should.strictEqual(err, null); + BlogPostB.create({meta: {visitors: 200}}, function (err, middle) { + should.strictEqual(err, null); + BlogPostB + .where('meta.visitors').gt(99).lt(301) + .sort('meta.visitors', -1) + .find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(3); + found[0]._id.should.eql(largest._id); + found[1]._id.should.eql(middle._id); + found[2]._id.should.eql(least._id); + db.close(); + }); + }); + }); + }); + }, + + 'test backwards compatibility with previously existing null values in db': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB(); + + post.collection.insert({ meta: { visitors: 9898, a: null } }, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostB.findOne({_id: b[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + db.close(); + }) + }) + }, + + 'test backwards compatibility with unused values in db': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection) + , post = new BlogPostB(); + + post.collection.insert({ meta: { visitors: 9898, color: 'blue'}}, {}, function (err, b) { + should.strictEqual(err, null); + + BlogPostB.findOne({_id: b[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.get('meta.visitors').valueOf().should.eql(9898); + found.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + }) + }) + }, + + 'test streaming cursors with #each': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: "The Wrestler", tags: ["movie"]}, function (err, wrestler) { + should.strictEqual(err, null); + BlogPostB.create({title: "Black Swan", tags: ["movie"]}, function (err, blackswan) { + should.strictEqual(err, null); + BlogPostB.create({title: "Pi", tags: ["movie"]}, function (err, pi) { + should.strictEqual(err, null); + var found = {}; + BlogPostB + .find({tags: "movie"}) + .sort('title', -1) + .each(function (err, post) { + should.strictEqual(err, null); + if (post) found[post.title] = 1; + else { + found.should.have.property("The Wrestler", 1); + found.should.have.property("Black Swan", 1); + found.should.have.property("Pi", 1); + db.close(); + } + }); + }); + }); + }); + }, + + 'test streaming cursors with #each and manual iteration': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.create({title: "Bleu", tags: ["Krzysztof Kieślowski"]}, function (err, wrestler) { + should.strictEqual(err, null); + BlogPostB.create({title: "Blanc", tags: ["Krzysztof Kieślowski"]}, function (err, blackswan) { + should.strictEqual(err, null); + BlogPostB.create({title: "Rouge", tags: ["Krzysztof Kieślowski"]}, function (err, pi) { + should.strictEqual(err, null); + var found = {}; + BlogPostB + .find({tags: "Krzysztof Kieślowski"}) + .each(function (err, post, next) { + should.strictEqual(err, null); + if (post) { + found[post.title] = 1; + process.nextTick(next); + } else { + found.should.have.property("Bleu", 1); + found.should.have.property("Blanc", 1); + found.should.have.property("Rouge", 1); + db.close(); + } + }); + }); + }); + }); + }, + + '$gt, $lt, $lte, $gte work on strings': function () { + var db = start() + var D = db.model('D', new Schema({dt: String}), collection); + + D.create({ dt: '2011-03-30' }, done); + D.create({ dt: '2011-03-31' }, done); + D.create({ dt: '2011-04-01' }, done); + D.create({ dt: '2011-04-02' }, done); + + var pending = 3; + function done (err) { + if (err) db.close(); + should.strictEqual(err, null); + + if (--pending) return; + + pending = 2; + + D.find({ 'dt': { $gte: '2011-03-30', $lte: '2011-04-01' }}).sort('dt', 1).run(function (err, docs) { + if (--pending) db.close(); + should.strictEqual(err, null); + docs.length.should.eql(3); + docs[0].dt.should.eql('2011-03-30'); + docs[1].dt.should.eql('2011-03-31'); + docs[2].dt.should.eql('2011-04-01'); + docs.some(function (d) { return '2011-04-02' === d.dt }).should.be.false; + }); + + D.find({ 'dt': { $gt: '2011-03-30', $lt: '2011-04-02' }}).sort('dt', 1).run(function (err, docs) { + if (--pending) db.close(); + should.strictEqual(err, null); + docs.length.should.eql(2); + docs[0].dt.should.eql('2011-03-31'); + docs[1].dt.should.eql('2011-04-01'); + docs.some(function (d) { return '2011-03-30' === d.dt }).should.be.false; + docs.some(function (d) { return '2011-04-02' === d.dt }).should.be.false; + }); + } + }, + + 'nested mixed queries (x.y.z)': function () { + var db = start() + , BlogPostB = db.model('BlogPostB', collection); + + BlogPostB.find({ 'mixed.nested.stuff': 'skynet' }, function (err, docs) { + db.close(); + should.strictEqual(err, null); + }); + }, + + // GH-336 + 'finding by Date field works': function () { + var db = start() + , Test = db.model('TestDateQuery', new Schema({ date: Date }), 'datetest_' + random()) + , now = new Date; + + Test.create({ date: now }, { date: new Date(now-10000) }, function (err, a, b) { + should.strictEqual(err, null); + Test.find({ date: now }, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(1); + }); + }); + }, + + // GH-309 + 'using $near with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo1', geoSchema, 'geospatial'+random()); + + Test.create({ loc: [ 10, 20 ]}, { loc: [ 40, 90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { $near: [30, 40] }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(2); + }); + }, 700); + }); + }, + + // GH-586 + 'using $within with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo2', geoSchema, collection + 'geospatial'); + + Test.create({ loc: [ 35, 50 ]}, { loc: [ -40, -90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { '$within': { '$box': [[30,40], [40,60]] }}}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(1); + }); + }, 700); + }); + }, + + // GH-610 + 'using nearSphere with Arrays works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo3', geoSchema, "y"+random()); + + Test.create({ loc: [ 10, 20 ]}, { loc: [ 40, 90 ]}, function (err) { + should.strictEqual(err, null); + setTimeout(function () { + Test.find({ loc: { $nearSphere: [30, 40] }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(2); + }); + }, 700); + }); + }, + + 'using $maxDistance with Array works (geo-spatial)': function () { + var db = start() + , Test = db.model('Geo4', geoSchema, "x"+random()); + + Test.create({ loc: [ 20, 80 ]}, { loc: [ 25, 30 ]}, function (err, docs) { + should.strictEqual(!!err, false); + setTimeout(function () { + Test.find({ loc: { $near: [25, 31], $maxDistance: 1 }}, function (err, docs) { + should.strictEqual(err, null); + docs.length.should.equal(1); + Test.find({ loc: { $near: [25, 32], $maxDistance: 1 }}, function (err, docs) { + db.close(); + should.strictEqual(err, null); + docs.length.should.equal(0); + }); + }); + }, 500); + }); + }, + + '$type tests': function () { + var db = start() + , B = db.model('BlogPostB', collection); + + B.find({ title: { $type: "asd" }}, function (err, posts) { + err.message.should.eql("$type parameter must be Number"); + + B.find({ title: { $type: 2 }}, function (err, posts) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(Array.isArray(posts), true); + }); + }); + }, + + 'buffers find using available types': function () { + var db = start() + , BufSchema = new Schema({ name: String, block: Buffer }) + , Test = db.model('Buffer', BufSchema, "buffers"); + + var docA = { name: 'A', block: new Buffer('über') }; + var docB = { name: 'B', block: new Buffer("buffer shtuffs are neat") }; + var docC = { name: 'C', block: 'hello world' }; + + Test.create(docA, docB, docC, function (err, a, b, c) { + should.strictEqual(err, null); + b.block.toString('utf8').should.equal('buffer shtuffs are neat'); + a.block.toString('utf8').should.equal('über'); + c.block.toString('utf8').should.equal('hello world'); + + Test.findById(a._id, function (err, a) { + should.strictEqual(err, null); + a.block.toString('utf8').should.equal('über'); + + Test.findOne({ block: 'buffer shtuffs are neat' }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('buffer shtuffs are neat'); + + Test.findOne({ block: /buffer/i }, function (err, rb) { + err.message.should.eql('Cast to buffer failed for value "/buffer/i"') + Test.findOne({ block: [195, 188, 98, 101, 114] }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('über'); + + Test.findOne({ block: 'aGVsbG8gd29ybGQ=' }, function (err, rb) { + should.strictEqual(err, null); + should.strictEqual(rb, null); + + Test.findOne({ block: new Buffer('aGVsbG8gd29ybGQ=', 'base64') }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('hello world'); + + Test.findOne({ block: new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64') }, function (err, rb) { + should.strictEqual(err, null); + rb.block.toString('utf8').should.equal('hello world'); + + Test.remove({}, function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }); + }); + }); + + }); + }, + + 'buffer tests using conditionals': function () { + // $in $nin etc + var db = start() + , BufSchema = new Schema({ name: String, block: Buffer }) + , Test = db.model('Buffer2', BufSchema, "buffer_"+random()); + + var docA = { name: 'A', block: new MongooseBuffer([195, 188, 98, 101, 114]) }; //über + var docB = { name: 'B', block: new MongooseBuffer("buffer shtuffs are neat") }; + var docC = { name: 'C', block: new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64') }; + + Test.create(docA, docB, docC, function (err, a, b, c) { + should.strictEqual(err, null); + a.block.toString('utf8').should.equal('über'); + b.block.toString('utf8').should.equal('buffer shtuffs are neat'); + c.block.toString('utf8').should.equal('hello world'); + + Test.find({ block: { $in: [[195, 188, 98, 101, 114], "buffer shtuffs are neat", new Buffer('aGVsbG8gd29ybGQ=', 'base64')] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + Test.find({ block: { $in: ['über', 'hello world'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $in: ['über'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(1); + tests[0].block.toString('utf8').should.equal('über'); + }); + + Test.find({ block: { $nin: ['über'] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $nin: [[195, 188, 98, 101, 114], new Buffer('aGVsbG8gd29ybGQ=', 'base64')] }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(1); + tests[0].block.toString('utf8').should.equal('buffer shtuffs are neat'); + }); + + Test.find({ block: { $ne: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $gt: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + }); + + Test.find({ block: { $gte: 'über' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + Test.find({ block: { $lt: new Buffer('buffer shtuffs are neat') }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(2); + tests[0].block.toString('utf8').should.equal('über'); + }); + + Test.find({ block: { $lte: 'buffer shtuffs are neat' }}, function (err, tests) { + done(); + should.strictEqual(err, null); + tests.length.should.equal(3); + }); + + var pending = 9; + function done () { + if (--pending) return; + Test.remove({}, function (err) { + db.close(); + should.strictEqual(err, null); + }); + } + }); + }, + + // gh-591 + 'querying Mixed types with elemMatch': function () { + var db = start() + , S = new Schema({ a: [{}], b: Number }) + , M = db.model('QueryingMixedArrays', S, random()) + + var m = new M; + m.a = [1,2,{ name: 'Frodo' },'IDK', {name: 100}]; + m.b = 10; + + m.save(function (err) { + should.strictEqual(null, err); + + M.find({ a: { name: 'Frodo' }, b: '10' }, function (err, docs) { + should.strictEqual(null, err); + docs[0].a.length.should.equal(5); + docs[0].b.valueOf().should.equal(10); + + var query = { + a: { + $elemMatch: { name: 100 } + } + } + + M.find(query, function (err, docs) { + db.close(); + should.strictEqual(null, err); + docs[0].a.length.should.equal(5); + }); + }); + }); + }, + + // gh-599 + 'regex with Array should work': function () { + var db = start() + , B = db.model('BlogPostB', random()) + + B.create({ tags: 'wooof baaaark meeeeow'.split(' ') }, function (err, b) { + should.strictEqual(null, err); + B.findOne({ tags: /ooof$/ }, function (err, doc) { + should.strictEqual(null, err); + should.strictEqual(true, !! doc); + ;(!! ~doc.tags.indexOf('meeeeow')).should.be.true; + + B.findOne({ tags: {$regex: 'eow$' } }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(true, !! doc); + ;(!! ~doc.tags.indexOf('meeeeow')).should.be.true; + }); + }); + }); + }, + + // gh-640 + 'updating a number to null': function () { + var db = start() + var B = db.model('BlogPostB') + var b = new B({ meta: { visitors: null }}); + b.save(function (err) { + should.strictEqual(null, err); + B.findById(b, function (err, b) { + should.strictEqual(null, err); + should.strictEqual(b.meta.visitors, null); + + B.update({ _id: b._id }, { meta: { visitors: null }}, function (err, docs) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + // gh-690 + 'using $all with ObjectIds': function () { + var db = start() + + var SSchema = new Schema({ name: String }); + var PSchema = new Schema({ sub: [SSchema] }); + + var P = db.model('usingAllWithObjectIds', PSchema); + var sub = [{ name: 'one' }, { name: 'two' }, { name: 'three' }]; + + P.create({ sub: sub }, function (err, p) { + should.strictEqual(null, err); + + var o0 = p.sub[0]._id; + var o1 = p.sub[1]._id; + var o2 = p.sub[2]._id; + + P.findOne({ 'sub._id': { $all: [o1, o2] }}, function (err, doc) { + should.strictEqual(null, err); + doc.id.should.equal(p.id); + + P.findOne({ 'sub._id': { $all: [o0, new DocumentObjectId] }}, function (err, doc) { + should.strictEqual(null, err); + should.equal(false, !!doc); + + P.findOne({ 'sub._id': { $all: [o2] }}, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(p.id); + }); + }); + }); + }); + }, + + 'using $all with Dates': function () { + var db = start() + + var SSchema = new Schema({ d: Date }); + var PSchema = new Schema({ sub: [SSchema] }); + + var P = db.model('usingAllWithDates', PSchema); + var sub = [ + { d: new Date } + , { d: new Date(Date.now()-10000) } + , { d: new Date(Date.now()-30000) } + ]; + + P.create({ sub: sub }, function (err, p) { + should.strictEqual(null, err); + + var o0 = p.sub[0].d; + var o1 = p.sub[1].d; + var o2 = p.sub[2].d; + + P.findOne({ 'sub.d': { $all: [o1, o2] }}, function (err, doc) { + should.strictEqual(null, err); + doc.id.should.equal(p.id); + + P.findOne({ 'sub.d': { $all: [o0, new Date] }}, function (err, doc) { + should.strictEqual(null, err); + should.equal(false, !!doc); + + P.findOne({ 'sub.d': { $all: [o2] }}, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(p.id); + }); + }); + }); + }); + }, + + 'excluding paths by schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: false } + }); + + var S = db.model('ExcludingBySchemaType', schema); + S.create({ thin: true, name: 'the excluded' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the excluded'); + S.findById(s, function (err, s) { + db.close(); + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + should.strictEqual(undefined, s.name); + }); + }); + }, + + 'excluding paths through schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: false} + }); + + var S = db.model('ExcludingBySchemaType', schema); + S.create({ thin: true, name: 'the excluded' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the excluded'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + should.strictEqual(undefined, s.name); + } + + S.findById(s).exclude('thin').exec(done); + S.find({ _id: s._id }).select('thin').exec(done); + }); + }, + + 'including paths through schematype': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: true } + }); + + var S = db.model('IncludingBySchemaType', schema); + S.create({ thin: true, name: 'the included' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the included'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.true; + s.name.should.equal('the included'); + } + + S.findById(s).exclude('thin').exec(done); + S.find({ _id: s._id }).select('thin').exec(done); + }); + }, + + 'overriding schematype select options': function () { + var db =start() + + var selected = new Schema({ + thin: Boolean + , name: { type: String, select: true } + }); + var excluded = new Schema({ + thin: Boolean + , name: { type: String, select: false } + }); + + var S = db.model('OverriddingSelectedBySchemaType', selected); + var E = db.model('OverriddingExcludedBySchemaType', excluded); + + var pending = 4; + + S.create({ thin: true, name: 'the included' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('the included'); + + S.find({ _id: s._id }).select('thin name').exec(function (err, s) { + --pending || db.close(); + s = s[0]; + should.strictEqual(null, err); + s.isSelected('name').should.be.true; + s.isSelected('thin').should.be.true; + s.name.should.equal('the included'); + s.thin.should.be.true; + }); + + S.findById(s).exclude('name').exec(function (err, s) { + --pending || db.close(); + should.strictEqual(null, err); + s.isSelected('name').should.be.false; + s.isSelected('thin').should.be.true; + should.equal(undefined, s.name); + should.equal(true, s.thin); + }) + }); + + E.create({ thin: true, name: 'the excluded' },function (err, e) { + should.strictEqual(null, err); + e.name.should.equal('the excluded'); + + E.find({ _id: e._id }).select('thin name').exec(function (err, e) { + --pending || db.close(); + e = e[0]; + should.strictEqual(null, err); + e.isSelected('name').should.be.true; + e.isSelected('thin').should.be.true; + e.name.should.equal('the excluded'); + e.thin.should.be.true; + }); + + E.findById(e).exclude('name').exec(function (err, e) { + --pending || db.close(); + should.strictEqual(null, err); + e.isSelected('name').should.be.false; + e.isSelected('thin').should.be.true; + should.equal(undefined, e.name); + should.equal(true, e.thin); + }) + }); + }, + + 'conflicting schematype path selection should error': function () { + var db =start() + + var schema = new Schema({ + thin: Boolean + , name: { type: String, select: true } + , conflict: { type: String, select: false} + }); + + var S = db.model('ConflictingBySchemaType', schema); + S.create({ thin: true, name: 'bing', conflict: 'crosby' },function (err, s) { + should.strictEqual(null, err); + s.name.should.equal('bing'); + s.conflict.should.equal('crosby'); + + var pending = 2; + function done (err, s) { + --pending || db.close(); + if (Array.isArray(s)) s = s[0]; + should.equal(true, !!err); + } + + S.findById(s).exec(done); + S.find({ _id: s._id }).exec(done); + }); + }, +}; diff --git a/node_modules/mongoose/test/model.ref.test.js b/node_modules/mongoose/test/model.ref.test.js new file mode 100644 index 0000000..967b6e4 --- /dev/null +++ b/node_modules/mongoose/test/model.ref.test.js @@ -0,0 +1,1528 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId + , DocObjectId = mongoose.Types.ObjectId + +/** + * Setup. + */ + +/** + * User schema. + */ + +var User = new Schema({ + name : String + , email : String + , gender : { type: String, enum: ['male', 'female'], default: 'male' } + , age : { type: Number, default: 21 } + , blogposts : [{ type: ObjectId, ref: 'RefBlogPost' }] +}); + +/** + * Comment subdocument schema. + */ + +var Comment = new Schema({ + _creator : { type: ObjectId, ref: 'RefUser' } + , content : String +}); + +/** + * Blog post schema. + */ + +var BlogPost = new Schema({ + _creator : { type: ObjectId, ref: 'RefUser' } + , title : String + , comments : [Comment] + , fans : [{ type: ObjectId, ref: 'RefUser' }] +}); + +var posts = 'blogposts_' + random() + , users = 'users_' + random(); + +mongoose.model('RefBlogPost', BlogPost); +mongoose.model('RefUser', User); + +/** + * Tests. + */ + +module.exports = { + + 'test populating a single reference': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + post._creator.email.should.equal('rauchg@gmail.com'); + }); + }); + }); + }, + + 'test an error in single reference population propagates': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts + '1') + , User = db.model('RefUser', users + '1'); + + User.create({ + name: 'Guillermo' + , email: 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + var origFind = User.findOne; + + // mock an error + User.findOne = function () { + var args = Array.prototype.map.call(arguments, function (arg) { + return 'function' == typeof arg ? function () { + arg(new Error('woot')); + } : arg; + }); + return origFind.apply(this, args); + }; + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.equal('woot'); + }); + }); + }); + }, + + 'test populating with partial fields selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', ['email']) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.isInit('name').should.be.false; + post._creator.email.should.equal('rauchg@gmail.com'); + }); + }); + }); + }, + + 'populating single oid with partial field selection and filter': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Banana' + , email : 'cats@example.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', 'email', { name: 'Peanut' }) + .run(function (err, post) { + should.strictEqual(err, null); + should.strictEqual(post._creator, null); + + BlogPost + .findById(post._id) + .populate('_creator', 'email', { name: 'Banana' }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + post._creator.should.be.an.instanceof(User); + post._creator.isInit('name').should.be.false; + post._creator.email.should.equal('cats@example.com'); + }); + }); + }); + }); + }, + + 'test populating and changing a reference': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + post._creator.email.should.equal('rauchg@gmail.com'); + + User.create({ + name : 'Aaron' + , email : 'aaron@learnboost.com' + }, function (err, newCreator) { + should.strictEqual(err, null); + + post._creator = newCreator._id; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('Aaron'); + post._creator.email.should.equal('aaron@learnboost.com'); + }); + }); + }); + }); + }); + }); + }, + + 'test populating with partial fields selection and changing ref': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Guillermo' + , email : 'rauchg@gmail.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', {'name': 1}) + .run(function (err, post) { + should.strictEqual(err, null); + + post._creator.should.be.an.instanceof(User); + post._creator.name.should.equal('Guillermo'); + + User.create({ + name : 'Aaron' + , email : 'aaron@learnboost.com' + }, function (err, newCreator) { + should.strictEqual(err, null); + + post._creator = newCreator._id; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator', {email: 0}) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('Aaron'); + should.not.exist(post._creator.email); + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and fetching many': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans') + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].email.should.equal('fan1@learnboost.com'); + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].email.should.equal('fan2@learnboost.com'); + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].email.should.equal('fan1@learnboost.com'); + + }); + }); + }); + }); + }); + }, + + 'test an error in array reference population propagates': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts + '2') + , User = db.model('RefUser', users + '2'); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + // mock an error + var origFind = User.find; + User.find = function () { + var args = Array.prototype.map.call(arguments, function (arg) { + return 'function' == typeof arg ? function () { + arg(new Error('woot 2')); + } : arg; + }); + return origFind.apply(this, args); + }; + + BlogPost + .find({ $or: [{ _id: post1._id }, { _id: post2._id }] }) + .populate('fans') + .run(function (err, blogposts) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.equal('woot 2'); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references with fields selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, function (err, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 'name') + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].isInit('email').should.be.false; + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].isInit('email').should.be.false; + should.strictEqual(blogposts[0].fans[1].email, undefined); + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].isInit('email').should.be.false; + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].isInit('email').should.be.false; + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and filtering': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', '', { gender: 'female', _id: { $in: [fan2] }}) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(1); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + + blogposts[1].fans.length.should.equal(1); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].email.should.equal('fan2@learnboost.com'); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', false, { gender: 'female' }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + should.strictEqual(blogposts[0].fans.length, 2); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[0].fans[1].gender.should.equal('female'); + blogposts[0].fans[1].name.should.equal('Fan 3'); + blogposts[0].fans[1].email.should.equal('fan3@learnboost.com'); + + should.strictEqual(blogposts[1].fans.length, 2); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[1].fans[1].gender.should.equal('female'); + blogposts[1].fans[1].name.should.equal('Fan 2'); + blogposts[1].fans[1].email.should.equal('fan2@learnboost.com'); + }); + + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and multi-filtering': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + , age : 25 + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', undefined, { _id: fan3 }) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(1); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 3'); + blogposts[0].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[0].fans[0].age, 25); + + blogposts[1].fans.length.should.equal(1); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[1].fans[0].age, 25); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 0, { gender: 'female' }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + blogposts[0].fans.length.should.equal(2); + blogposts[0].fans[0].gender.should.equal('female'); + blogposts[0].fans[0].name.should.equal('Fan 2'); + blogposts[0].fans[0].email.should.equal('fan2@learnboost.com'); + blogposts[0].fans[1].gender.should.equal('female'); + blogposts[0].fans[1].name.should.equal('Fan 3'); + blogposts[0].fans[1].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[0].fans[1].age, 25); + + blogposts[1].fans.length.should.equal(2); + blogposts[1].fans[0].gender.should.equal('female'); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + should.equal(blogposts[1].fans[0].age, 25); + blogposts[1].fans[1].gender.should.equal('female'); + blogposts[1].fans[1].name.should.equal('Fan 2'); + blogposts[1].fans[1].email.should.equal('fan2@learnboost.com'); + }); + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of references and multi-filtering with field selection': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, function (err, fan1) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan2) { + should.strictEqual(err, null); + + User.create({ + name : 'Fan 3' + , email : 'fan3@learnboost.com' + , gender : 'female' + , age : 25 + }, function (err, fan3) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2, fan3] + }, function (err, post1) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan3, fan2, fan1] + }, function (err, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', 'name email', { gender: 'female', age: 25 }) + .run(function (err, blogposts) { + db.close(); + should.strictEqual(err, null); + + should.strictEqual(blogposts[0].fans.length, 1); + blogposts[0].fans[0].name.should.equal('Fan 3'); + blogposts[0].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[0].fans[0].isInit('email').should.be.true; + blogposts[0].fans[0].isInit('gender').should.be.false; + blogposts[0].fans[0].isInit('age').should.be.false; + + should.strictEqual(blogposts[1].fans.length, 1); + blogposts[1].fans[0].name.should.equal('Fan 3'); + blogposts[1].fans[0].email.should.equal('fan3@learnboost.com'); + blogposts[1].fans[0].isInit('email').should.be.true; + blogposts[1].fans[0].isInit('gender').should.be.false; + blogposts[1].fans[0].isInit('age').should.be.false; + }); + }); + }); + }); + }); + }); + }, + + 'test populating an array of refs, changing one, and removing one': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + }, { + name : 'Fan 3' + , email : 'fan3@learnboost.com' + }, { + name : 'Fan 4' + , email : 'fan4@learnboost.com' + }, function (err, fan1, fan2, fan3, fan4) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Woot' + , fans : [fan1, fan2] + }, { + title : 'Woot' + , fans : [fan2, fan1] + }, function (err, post1, post2) { + should.strictEqual(err, null); + + BlogPost + .find({ _id: { $in: [post1._id, post2._id ] } }) + .populate('fans', ['name']) + .run(function (err, blogposts) { + should.strictEqual(err, null); + + blogposts[0].fans[0].name.should.equal('Fan 1'); + blogposts[0].fans[0].isInit('email').should.be.false; + blogposts[0].fans[1].name.should.equal('Fan 2'); + blogposts[0].fans[1].isInit('email').should.be.false; + + blogposts[1].fans[0].name.should.equal('Fan 2'); + blogposts[1].fans[0].isInit('email').should.be.false; + blogposts[1].fans[1].name.should.equal('Fan 1'); + blogposts[1].fans[1].isInit('email').should.be.false; + + blogposts[1].fans = [fan3, fan4]; + + blogposts[1].save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(blogposts[1]._id, [], { populate: ['fans'] }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans[0].name.should.equal('Fan 3'); + post.fans[1].name.should.equal('Fan 4'); + + post.fans.splice(0, 1); + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('fans') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + post.fans.length.should.equal(1); + post.fans[0].name.should.equal('Fan 4'); + }); + }); + }); + }); + }); + }); + }); + }, + + 'test populating subdocuments': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ name: 'User 1' }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ name: 'User 2' }, function (err, user2) { + should.strictEqual(err, null); + + BlogPost.create({ + title: 'Woot' + , _creator: user1._id + , comments: [ + { _creator: user1._id, content: 'Woot woot' } + , { _creator: user2._id, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .populate('comments._creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post._creator.name.should.equal('User 1'); + post.comments[0]._creator.name.should.equal('User 1'); + post.comments[1]._creator.name.should.equal('User 2'); + }); + }); + }); + }); + }, + + 'test populating subdocuments partially': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'User 1' + , email : 'user1@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'User 2' + , email : 'user2@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: user1, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', ['email']) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.comments[0]._creator.email.should.equal('user1@learnboost.com'); + post.comments[0]._creator.isInit('name').should.be.false; + post.comments[1]._creator.email.should.equal('user2@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + }); + }); + }); + }); + }, + + 'test populating subdocuments partially with conditions': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'User 1' + , email : 'user1@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'User 2' + , email : 'user2@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: user1, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', {'email': 1}, { name: /User/ }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.comments[0]._creator.email.should.equal('user1@learnboost.com'); + post.comments[0]._creator.isInit('name').should.be.false; + post.comments[1]._creator.email.should.equal('user2@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + }); + }); + }); + }); + }, + + 'populating subdocs with invalid/missing subproperties': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ + name : 'T-100' + , email : 'terminator100@learnboost.com' + }, function (err, user1) { + should.strictEqual(err, null); + + User.create({ + name : 'T-1000' + , email : 'terminator1000@learnboost.com' + }, function (err, user2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , comments: [ + { _creator: null, content: 'Woot woot' } + , { _creator: user2, content: 'Wha wha' } + ] + }, function (err, post) { + should.strictEqual(err, null); + + // invalid subprop + BlogPost + .findById(post._id) + .populate('comments._idontexist', 'email') + .run(function (err, post) { + should.strictEqual(err, null); + should.exist(post); + post.comments.length.should.equal(2); + should.strictEqual(post.comments[0]._creator, null); + post.comments[1]._creator.toString().should.equal(user2.id); + + // subprop is null in a doc + BlogPost + .findById(post._id) + .populate('comments._creator', 'email') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + should.exist(post); + post.comments.length.should.equal(2); + should.strictEqual(post.comments[0]._creator, null); + should.strictEqual(post.comments[0].content, 'Woot woot'); + post.comments[1]._creator.email.should.equal('terminator1000@learnboost.com'); + post.comments[1]._creator.isInit('name').should.be.false; + post.comments[1].content.should.equal('Wha wha'); + }); + }); + }); + }); + }); + }, + + // gh-481 + 'test populating subdocuments partially with empty array': function (beforeExit) { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , worked = false; + + var post = BlogPost.create({ + title: 'Woot' + , comments: [] // EMPTY ARRAY + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('comments._creator', ['email']) + .run(function (err, returned) { + db.close(); + worked = true; + should.strictEqual(err, null); + returned.id.should.equal(post.id); + }); + }); + + beforeExit(function () { + worked.should.be.true; + }); + }, + + 'populating subdocuments with array including nulls': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + var user = new User({ name: 'hans zimmer' }); + user.save(function (err) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , fans: [] + }, function (err, post) { + should.strictEqual(err, null); + + // shove some uncasted vals + BlogPost.collection.update({ _id: post._id }, { $set: { fans: [null, undefined, user.id, null] } }, function (err) { + should.strictEqual(err, undefined); + + BlogPost + .findById(post._id) + .populate('fans', ['name']) + .run(function (err, returned) { + db.close(); + should.strictEqual(err, null); + returned.id.should.equal(post.id); + returned.fans.length.should.equal(1); + }); + }) + }); + }); + }, + + 'populating more than one array at a time': function () { + var db = start() + , User = db.model('RefUser', users) + , M = db.model('PopMultiSubDocs', new Schema({ + users: [{ type: ObjectId, ref: 'RefUser' }] + , fans: [{ type: ObjectId, ref: 'RefUser' }] + , comments: [Comment] + })) + + User.create({ + email : 'fan1@learnboost.com' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, { + name: 'Fan 3' + }, function (err, fan1, fan2, fan3) { + should.strictEqual(err, null); + + M.create({ + users: [fan3] + , fans: [fan1] + , comments: [ + { _creator: fan1, content: 'bejeah!' } + , { _creator: fan2, content: 'chickfila' } + ] + }, { + users: [fan1] + , fans: [fan2] + , comments: [ + { _creator: fan3, content: 'hello' } + , { _creator: fan1, content: 'world' } + ] + }, function (err, post1, post2) { + should.strictEqual(err, null); + + M.where('_id').in([post1, post2]) + .populate('fans', 'name', { gender: 'female' }) + .populate('users', 'name', { gender: 'male' }) + .populate('comments._creator', ['email'], { name: null }) + .run(function (err, posts) { + db.close(); + should.strictEqual(err, null); + + should.exist(posts); + posts.length.should.equal(2); + var p1 = posts[0]; + var p2 = posts[1]; + should.strictEqual(p1.fans.length, 0); + should.strictEqual(p2.fans.length, 1); + p2.fans[0].name.should.equal('Fan 2'); + p2.fans[0].isInit('email').should.be.false; + p2.fans[0].isInit('gender').should.be.false; + p1.comments.length.should.equal(2); + p2.comments.length.should.equal(2); + should.exist(p1.comments[0]._creator.email); + should.not.exist(p2.comments[0]._creator); + p1.comments[0]._creator.email.should.equal('fan1@learnboost.com'); + p2.comments[1]._creator.email.should.equal('fan1@learnboost.com'); + p1.comments[0]._creator.isInit('name').should.be.false; + p2.comments[1]._creator.isInit('name').should.be.false; + p1.comments[0].content.should.equal('bejeah!'); + p2.comments[1].content.should.equal('world'); + should.not.exist(p1.comments[1]._creator); + should.not.exist(p2.comments[0]._creator); + p1.comments[1].content.should.equal('chickfila'); + p2.comments[0].content.should.equal('hello'); + }); + }); + }); + }, + + 'populating multiple children of a sub-array at a time': function () { + var db = start() + , User = db.model('RefUser', users) + , BlogPost = db.model('RefBlogPost', posts) + , Inner = new Schema({ + user: { type: ObjectId, ref: 'RefUser' } + , post: { type: ObjectId, ref: 'RefBlogPost' } + }) + , I = db.model('PopMultiChildrenOfSubDocInner', Inner) + var M = db.model('PopMultiChildrenOfSubDoc', new Schema({ + kids: [Inner] + })) + + User.create({ + name : 'Fan 1' + , email : 'fan1@learnboost.com' + , gender : 'male' + }, { + name : 'Fan 2' + , email : 'fan2@learnboost.com' + , gender : 'female' + }, function (err, fan1, fan2) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'woot' + }, { + title : 'yay' + }, function (err, post1, post2) { + should.strictEqual(err, null); + M.create({ + kids: [ + { user: fan1, post: post1, y: 5 } + , { user: fan2, post: post2, y: 8 } + ] + , x: 4 + }, function (err, m1) { + should.strictEqual(err, null); + + M.findById(m1) + .populate('kids.user', ["name"]) + .populate('kids.post', ["title"], { title: "woot" }) + .run(function (err, o) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(o.kids.length, 2); + var k1 = o.kids[0]; + var k2 = o.kids[1]; + should.strictEqual(true, !k2.post); + should.strictEqual(k1.user.name, "Fan 1"); + should.strictEqual(k1.user.email, undefined); + should.strictEqual(k1.post.title, "woot"); + should.strictEqual(k2.user.name, "Fan 2"); + }); + }); + }); + }); + }, + + 'passing sort options to the populate method': function () { + var db = start() + , P = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users); + + User.create({ name: 'aaron', age: 10 }, { name: 'fan2', age: 8 }, { name: 'someone else', age: 3 }, + function (err, fan1, fan2, fan3) { + should.strictEqual(err, null); + + P.create({ fans: [fan2, fan3, fan1] }, function (err, post) { + should.strictEqual(err, null); + + P.findById(post) + .populate('fans', null, null, { sort: 'name' }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans.length.should.equal(3); + post.fans[0].name.should.equal('aaron'); + post.fans[1].name.should.equal('fan2'); + post.fans[2].name.should.equal('someone else'); + + P.findById(post) + .populate('fans', 'name', null, { sort: [['name', -1]] }) + .run(function (err, post) { + should.strictEqual(err, null); + + post.fans.length.should.equal(3); + post.fans[2].name.should.equal('aaron'); + should.strictEqual(undefined, post.fans[2].age) + post.fans[1].name.should.equal('fan2'); + should.strictEqual(undefined, post.fans[1].age) + post.fans[0].name.should.equal('someone else'); + should.strictEqual(undefined, post.fans[0].age) + + P.findById(post) + .populate('fans', 'age', { age: { $gt: 3 }}, { sort: [['name', 'desc']] }) + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + post.fans.length.should.equal(2); + post.fans[1].age.valueOf().should.equal(10); + post.fans[0].age.valueOf().should.equal(8); + }); + }); + }); + }); + }); + }, + + 'refs should cast to ObjectId from hexstrings': function () { + var BP = mongoose.model('RefBlogPost', BlogPost); + var bp = new BP; + bp._creator = new DocObjectId().toString(); + bp._creator.should.be.an.instanceof(DocObjectId); + bp.set('_creator', new DocObjectId().toString()); + bp._creator.should.be.an.instanceof(DocObjectId); + }, + + 'populate should work on String _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: String + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: String, ref: 'UserWithStringId' } + , body: String + }) + + var User = db.model('UserWithStringId', UserSchema, random()) + var Note = db.model('NoteWithStringId', NoteSchema, random()) + + var alice = new User({_id: 'alice', name: "Alice"}) + + alice.save(function (err) { + should.strictEqual(err, null); + + var note = new Note({author: 'alice', body: "Buy Milk"}); + note.save(function (err) { + should.strictEqual(err, null); + + Note.findById(note.id).populate('author').run(function (err, note) { + db.close(); + should.strictEqual(err, null); + note.body.should.equal('Buy Milk'); + should.exist(note.author); + note.author.name.should.equal('Alice'); + }); + }); + }) + }, + + 'populate should work on Number _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: Number + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: Number, ref: 'UserWithNumberId' } + , body: String + }) + + var User = db.model('UserWithNumberId', UserSchema, random()) + var Note = db.model('NoteWithNumberId', NoteSchema, random()) + + var alice = new User({_id: 2359, name: "Alice"}) + + alice.save(function (err) { + should.strictEqual(err, null); + + var note = new Note({author: 2359, body: "Buy Milk"}); + note.save(function (err) { + should.strictEqual(err, null); + + Note.findById(note.id).populate('author').run(function (err, note) { + db.close(); + should.strictEqual(err, null); + note.body.should.equal('Buy Milk'); + should.exist(note.author); + note.author.name.should.equal('Alice'); + }); + }); + }) + }, + + // gh-577 + 'required works on ref fields': function () { + var db = start(); + + var userSchema = new Schema({ + email: {type: String, required: true} + }); + var User = db.model('ObjectIdRefRequiredField', userSchema, random()); + + var numSchema = new Schema({ _id: Number, val: Number }); + var Num = db.model('NumberRefRequired', numSchema, random()); + + var strSchema = new Schema({ _id: String, val: String }); + var Str = db.model('StringRefRequired', strSchema, random()); + + var commentSchema = new Schema({ + user: {type: ObjectId, ref: 'ObjectIdRefRequiredField', required: true} + , num: {type: Number, ref: 'NumberRefRequired', required: true} + , str: {type: String, ref: 'StringRefRequired', required: true} + , text: String + }); + var Comment = db.model('CommentWithRequiredField', commentSchema); + + var pending = 3; + + var string = new Str({ _id: 'my string', val: 'hello' }); + var number = new Num({ _id: 1995, val: 234 }); + var user = new User({ email: 'test' }); + + string.save(next); + number.save(next); + user.save(next); + + function next (err) { + should.strictEqual(null, err); + if (--pending) return; + + var comment = new Comment({ + text: 'test' + }); + + comment.save(function (err) { + should.equal('Validation failed', err && err.message); + err.errors.should.have.property('num'); + err.errors.should.have.property('str'); + err.errors.should.have.property('user'); + err.errors.num.type.should.equal('required'); + err.errors.str.type.should.equal('required'); + err.errors.user.type.should.equal('required'); + + comment.user = user; + comment.num = 1995; + comment.str = 'my string'; + + comment.save(function (err, comment) { + should.strictEqual(null, err); + + Comment + .findById(comment.id) + .populate('user') + .populate('num') + .populate('str') + .run(function (err, comment) { + should.strictEqual(err, null); + + comment.set({text: 'test2'}); + + comment.save(function (err, comment) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + } + }, + + 'populate works with schemas with both id and _id defined': function () { + var db =start() + , S1 = new Schema({ id: String }) + , S2 = new Schema({ things: [{ type: ObjectId, ref: '_idAndid' }]}) + + var M1 = db.model('_idAndid', S1); + var M2 = db.model('populateWorksWith_idAndidSchemas', S2); + + M1.create( + { id: "The Tiger That Isn't" } + , { id: "Users Guide To The Universe" } + , function (err, a, b) { + should.strictEqual(null, err); + + var m2 = new M2({ things: [a, b]}); + m2.save(function (err) { + should.strictEqual(null, err); + M2.findById(m2).populate('things').run(function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.things.length.should.equal(2); + doc.things[0].id.should.equal("The Tiger That Isn't"); + doc.things[1].id.should.equal("Users Guide To The Universe"); + }) + }); + }) + }, + + // gh-602 + 'Update works with populated arrays': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + var user1 = new User({ name: 'aphex' }); + var user2 = new User({ name: 'twin' }); + + User.create({name:'aphex'},{name:'twin'}, function (err, u1, u2) { + should.strictEqual(err, null); + + var post = BlogPost.create({ + title: 'Woot' + , fans: [] + }, function (err, post) { + should.strictEqual(err, null); + + var update = { fans: [u1, u2] }; + BlogPost.update({ _id: post }, update, function (err) { + should.strictEqual(err, null); + + // the original update doc should not be modified + ;('fans' in update).should.be.true; + ;('$set' in update).should.be.false; + update.fans[0].should.be.instanceof(mongoose.Document); + update.fans[1].should.be.instanceof(mongoose.Document); + + BlogPost.findById(post, function (err, post) { + db.close(); + should.strictEqual(err, null); + post.fans.length.should.equal(2); + post.fans[0].should.be.instanceof(DocObjectId); + post.fans[1].should.be.instanceof(DocObjectId); + }); + }); + }); + }); + }, + + // gh-675 + 'toJSON should also be called for refs': function () { + var db = start() + , BlogPost = db.model('RefBlogPost', posts) + , User = db.model('RefUser', users) + + User.prototype._toJSON = User.prototype.toJSON; + User.prototype.toJSON = function() { + var res = this._toJSON(); + res.was_in_to_json = true; + return res; + } + + BlogPost.prototype._toJSON = BlogPost.prototype.toJSON; + BlogPost.prototype.toJSON = function() { + var res = this._toJSON(); + res.was_in_to_json = true; + return res; + } + + User.create({ + name : 'Jerem' + , email : 'jerem@jolicloud.com' + }, function (err, creator) { + should.strictEqual(err, null); + + BlogPost.create({ + title : 'Ping Pong' + , _creator : creator + }, function (err, post) { + should.strictEqual(err, null); + + BlogPost + .findById(post._id) + .populate('_creator') + .run(function (err, post) { + db.close(); + should.strictEqual(err, null); + + var json = post.toJSON(); + json.was_in_to_json.should.equal(true); + json._creator.was_in_to_json.should.equal(true); + }); + }); + }); + }, + + // gh-686 + 'populate should work on Buffer _ids': function () { + var db = start(); + + var UserSchema = new Schema({ + _id: Buffer + , name: String + }) + + var NoteSchema = new Schema({ + author: { type: Buffer, ref: 'UserWithBufferId' } + , body: String + }) + + var User = db.model('UserWithBufferId', UserSchema, random()) + var Note = db.model('NoteWithBufferId', NoteSchema, random()) + + var alice = new User({_id: new mongoose.Types.Buffer('YWxpY2U=', 'base64'), name: "Alice"}) + + alice.save(function (err) { + should.strictEqual(err, null); + + var note = new Note({author: 'alice', body: "Buy Milk"}); + note.save(function (err) { + should.strictEqual(err, null); + + Note.findById(note.id).populate('author').run(function (err, note) { + db.close(); + should.strictEqual(err, null); + note.body.should.equal('Buy Milk'); + should.exist(note.author); + note.author.name.should.equal('Alice'); + }); + }); + }) + } +};
\ No newline at end of file diff --git a/node_modules/mongoose/test/model.stream.test.js b/node_modules/mongoose/test/model.stream.test.js new file mode 100644 index 0000000..a010b0e --- /dev/null +++ b/node_modules/mongoose/test/model.stream.test.js @@ -0,0 +1,232 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , utils = require('../lib/utils') + , random = utils.random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ObjectId = Schema.ObjectId + , MongooseBuffer = mongoose.Types.Buffer + , DocumentObjectId = mongoose.Types.ObjectId + , fs = require('fs') + +var names = ('Aaden Aaron Adrian Aditya Agustin Jim Bob Jonah Frank Sally Lucy').split(' '); + +/** + * Setup. + */ + +var Person = new Schema({ + name: String +}); + +mongoose.model('PersonForStream', Person); +var collection = 'personforstream_' + random(); + +;(function setup () { + var db = start() + , P = db.model('PersonForStream', collection) + + var people = names.map(function (name) { + return { name: name }; + }); + + P.create(people, function (err) { + should.strictEqual(null, err); + db.close(); + assignExports(); + }); +})() + +function assignExports () { var o = { + + 'cursor stream': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , i = 0 + , closed = 0 + , paused = 0 + , resumed = 0 + , err + + var stream = P.find({}).stream(); + + stream.on('data', function (doc) { + should.strictEqual(true, !! doc.name); + should.strictEqual(true, !! doc._id); + + if (paused > 0 && 0 === resumed) { + err = new Error('data emitted during pause'); + return done(); + } + + if (++i === 3) { + stream.paused.should.be.false; + stream.pause(); + stream.paused.should.be.true; + paused++; + + setTimeout(function () { + stream.paused.should.be.true; + resumed++; + stream.resume(); + stream.paused.should.be.false; + }, 20); + } + }); + + stream.on('error', function (er) { + err = er; + done(); + }); + + stream.on('close', function () { + closed++; + done(); + }); + + function done () { + db.close(); + should.strictEqual(undefined, err); + should.equal(i, names.length); + closed.should.equal(1); + paused.should.equal(1); + resumed.should.equal(1); + stream._cursor.isClosed().should.be.true; + } + } + +, 'immediately destroying a stream prevents the query from executing': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , i = 0 + + var stream = P.where('name', 'Jonah').select('name').findOne().stream(); + + stream.on('data', function () { + i++; + }) + stream.on('close', done); + stream.on('error', done); + + stream.destroy(); + + function done (err) { + should.strictEqual(undefined, err); + i.should.equal(0); + process.nextTick(function () { + db.close(); + should.strictEqual(null, stream._fields); + }) + } + } + +, 'destroying a stream stops it': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , finished = 0 + , i = 0 + + var stream = P.where('name').$exists().limit(10).only('_id').stream(); + + should.strictEqual(null, stream._destroyed); + stream.readable.should.be.true; + + stream.on('data', function (doc) { + should.strictEqual(undefined, doc.name); + if (++i === 5) { + stream.destroy(); + stream.readable.should.be.false; + } + }); + + stream.on('close', done); + stream.on('error', done); + + function done (err) { + ++finished; + setTimeout(function () { + db.close(); + should.strictEqual(undefined, err); + i.should.equal(5); + finished.should.equal(1); + stream._destroyed.should.equal(true); + stream.readable.should.be.false; + stream._cursor.isClosed().should.be.true; + }, 150) + } + } + +, 'cursor stream errors': function () { + var db = start({ server: { auto_reconnect: false }}) + , P = db.model('PersonForStream', collection) + , finished = 0 + , closed = 0 + , i = 0 + + var stream = P.find().batchSize(5).stream(); + + stream.on('data', function (doc) { + if (++i === 5) { + db.close(); + } + }); + + stream.on('close', function () { + closed++; + }); + + stream.on('error', done); + + function done (err) { + ++finished; + setTimeout(function () { + should.equal('no open connections', err.message); + i.should.equal(5); + closed.should.equal(1); + finished.should.equal(1); + stream._destroyed.should.equal(true); + stream.readable.should.be.false; + stream._cursor.isClosed().should.be.true; + }, 150) + } + } + +, 'cursor stream pipe': function () { + var db = start() + , P = db.model('PersonForStream', collection) + , filename = '/tmp/_mongoose_stream_out.txt' + , out = fs.createWriteStream(filename) + + var stream = P.find().sort('name', 1).limit(20).stream(); + stream.pipe(out); + + stream.on('error', done); + stream.on('close', done); + + function done (err) { + db.close(); + should.strictEqual(undefined, err); + var contents = fs.readFileSync(filename, 'utf8'); + ;/Aaden/.test(contents).should.be.true; + ;/Aaron/.test(contents).should.be.true; + ;/Adrian/.test(contents).should.be.true; + ;/Aditya/.test(contents).should.be.true; + ;/Agustin/.test(contents).should.be.true; + fs.unlink(filename); + } + } +} + +// end exports + +utils.merge(exports, o); + +} diff --git a/node_modules/mongoose/test/model.test.js b/node_modules/mongoose/test/model.test.js new file mode 100644 index 0000000..22c7433 --- /dev/null +++ b/node_modules/mongoose/test/model.test.js @@ -0,0 +1,4682 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + , ObjectId = Schema.ObjectId + , DocumentObjectId = mongoose.Types.ObjectId + , DocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = mongoose.Types.Embedded + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , MongooseError = mongoose.Error; + +/** + * Setup. + */ + +var Comments = new Schema(); + +Comments.add({ + title : String + , date : Date + , body : String + , comments : [Comments] +}); + +var BlogPost = new Schema({ + title : String + , author : String + , slug : String + , date : Date + , meta : { + date : Date + , visitors : Number + } + , published : Boolean + , mixed : {} + , numbers : [Number] + , owners : [ObjectId] + , comments : [Comments] +}); + +BlogPost.virtual('titleWithAuthor') + .get(function () { + return this.get('title') + ' by ' + this.get('author'); + }) + .set(function (val) { + var split = val.split(' by '); + this.set('title', split[0]); + this.set('author', split[1]); + }); + +BlogPost.method('cool', function(){ + return this; +}); + +BlogPost.static('woot', function(){ + return this; +}); + +mongoose.model('BlogPost', BlogPost); + +var collection = 'blogposts_' + random(); + +module.exports = { + + 'test a model isNew flag when instantiating': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.isNew.should.be.true; + db.close(); + }, + + 'modified getter should not throw': function () { + var db = start(); + var BlogPost = db.model('BlogPost', collection); + var post = new BlogPost; + db.close(); + + var threw = false; + try { + post.modified; + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + 'test presence of model schema': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.schema.should.be.an.instanceof(Schema); + BlogPost.prototype.schema.should.be.an.instanceof(Schema); + db.close(); + }, + + 'test a model default structure when instantiated': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.isNew.should.be.true; + post.db.model('BlogPost').modelName.should.equal('BlogPost'); + post.constructor.modelName.should.equal('BlogPost'); + + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.equal(undefined, post.get('published')); + + post.get('numbers').should.be.an.instanceof(MongooseArray); + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }, + + 'mongoose.model returns the model at creation': function () { + var Named = mongoose.model('Named', new Schema({ name: String })); + var n1 = new Named(); + should.equal(n1.name, null); + var n2 = new Named({ name: 'Peter Bjorn' }); + should.equal(n2.name, 'Peter Bjorn'); + + var schema = new Schema({ number: Number }); + var Numbered = mongoose.model('Numbered', schema, collection); + var n3 = new Numbered({ number: 1234 }); + n3.number.valueOf().should.equal(1234); + }, + + 'mongoose.model!init emits init event on schema': function () { + var db = start() + , schema = new Schema({ name: String }) + , model + + schema.on('init', function (model_) { + model = model_; + }); + + var Named = db.model('EmitInitOnSchema', schema); + db.close(); + model.should.equal(Named); + }, + + 'collection name can be specified through schema': function () { + var schema = new Schema({ name: String }, { collection: 'users1' }); + var Named = mongoose.model('CollectionNamedInSchema1', schema); + Named.prototype.collection.name.should.equal('users1'); + + var db = start(); + var users2schema = new Schema({ name: String }, { collection: 'users2' }); + var Named2 = db.model('CollectionNamedInSchema2', users2schema); + db.close(); + Named2.prototype.collection.name.should.equal('users2'); + }, + + 'test default Array type casts to Mixed': function () { + var db = start() + , DefaultArraySchema = new Schema({ + num1: Array + , num2: [] + }) + + mongoose.model('DefaultArraySchema', DefaultArraySchema); + var DefaultArray = db.model('DefaultArraySchema', collection); + var arr = new DefaultArray(); + + arr.get('num1').should.have.length(0); + arr.get('num2').should.have.length(0); + + var threw1 = false + , threw2 = false; + + try { + arr.num1.push({ x: 1 }) + arr.num1.push(9) + arr.num1.push("woah") + } catch (err) { + threw1 = true; + } + + threw1.should.equal(false); + + try { + arr.num2.push({ x: 1 }) + arr.num2.push(9) + arr.num2.push("woah") + } catch (err) { + threw2 = true; + } + + threw2.should.equal(false); + + db.close(); + + }, + + 'test a model default structure that has a nested Array when instantiated': function () { + var db = start() + , NestedSchema = new Schema({ + nested: { + array: [Number] + } + }); + mongoose.model('NestedNumbers', NestedSchema); + var NestedNumbers = db.model('NestedNumbers', collection); + + var nested = new NestedNumbers(); + nested.get('nested.array').should.be.an.instanceof(MongooseArray); + db.close(); + }, + + 'test a model that defaults an Array attribute to a default non-empty array': function () { + var db = start() + , DefaultArraySchema = new Schema({ + arr: {type: Array, cast: String, default: ['a', 'b', 'c']} + }); + mongoose.model('DefaultArray', DefaultArraySchema); + var DefaultArray = db.model('DefaultArray', collection); + var arr = new DefaultArray(); + arr.get('arr').should.have.length(3); + arr.get('arr')[0].should.equal('a'); + arr.get('arr')[1].should.equal('b'); + arr.get('arr')[2].should.equal('c'); + db.close(); + }, + + 'test a model that defaults an Array attribute to a default single member array': function () { + var db = start() + , DefaultOneCardArraySchema = new Schema({ + arr: {type: Array, cast: String, default: ['a']} + }); + mongoose.model('DefaultOneCardArray', DefaultOneCardArraySchema); + var DefaultOneCardArray = db.model('DefaultOneCardArray', collection); + var arr = new DefaultOneCardArray(); + arr.get('arr').should.have.length(1); + arr.get('arr')[0].should.equal('a'); + db.close(); + }, + + 'test a model that defaults an Array attributes to an empty array': function () { + var db = start() + , DefaultZeroCardArraySchema = new Schema({ + arr: {type: Array, cast: String, default: []} + }); + mongoose.model('DefaultZeroCardArray', DefaultZeroCardArraySchema); + var DefaultZeroCardArray = db.model('DefaultZeroCardArray', collection); + var arr = new DefaultZeroCardArray(); + arr.get('arr').should.have.length(0); + arr.arr.should.have.length(0); + db.close(); + }, + + 'test that arrays auto-default to the empty array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.should.have.length(0); + db.close(); + }, + + 'test instantiating a model with a hash that maps to at least 1 null value': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: null + }); + should.strictEqual(null, post.title); + db.close(); + }, + + 'saving a model with a null value should perpetuate that null value to the db': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: null + }); + should.strictEqual(null, post.title); + post.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(found.title, null); + }); + }); + }, + + 'test instantiating a model with a hash that maps to at least 1 undefined value': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + title: undefined + }); + should.strictEqual(undefined, post.title); + post.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(found.title, undefined); + }); + }); + }, + + 'test a model structure when saved': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , pending = 2; + + function done () { + if (!--pending) db.close(); + } + + var post = new BlogPost(); + post.on('save', function (post) { + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + should.equal(undefined, post.get('published')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + done(); + }); + + post.save(function(err, post){ + should.strictEqual(err, null); + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.equal(undefined, post.get('title')); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + should.equal(undefined, post.get('published')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + done(); + }); + }, + + 'test a model structures when created': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({ title: 'hi there'}, function (err, post) { + should.strictEqual(err, null); + post.get('_id').should.be.an.instanceof(DocumentObjectId); + + should.strictEqual(post.get('title'), 'hi there'); + should.equal(undefined, post.get('slug')); + should.equal(undefined, post.get('date')); + + post.get('meta').should.be.a('object'); + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.strictEqual(undefined, post.get('published')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }); + }, + + 'test a model structure when initd': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + , meta : { + date : new Date + , visitors : 5 + } + , published : true + , owners : [new DocumentObjectId, new DocumentObjectId] + , comments : [ + { title: 'Test', date: new Date, body: 'Test' } + , { title: 'Super', date: new Date, body: 'Cool' } + ] + }); + + post.get('title').should.eql('Test'); + post.get('slug').should.eql('test'); + post.get('date').should.be.an.instanceof(Date); + post.get('meta').should.be.a('object'); + post.get('meta').date.should.be.an.instanceof(Date); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + post.get('published').should.be.true; + + post.title.should.eql('Test'); + post.slug.should.eql('test'); + post.date.should.be.an.instanceof(Date); + post.meta.should.be.a('object'); + post.meta.date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.published.should.be.true; + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('owners')[0].should.be.an.instanceof(DocumentObjectId); + post.get('owners')[1].should.be.an.instanceof(DocumentObjectId); + + post.owners.should.be.an.instanceof(MongooseArray); + post.owners[0].should.be.an.instanceof(DocumentObjectId); + post.owners[1].should.be.an.instanceof(DocumentObjectId); + + post.get('comments').should.be.an.instanceof(DocumentArray); + post.get('comments')[0].should.be.an.instanceof(EmbeddedDocument); + post.get('comments')[1].should.be.an.instanceof(EmbeddedDocument); + + post.comments.should.be.an.instanceof(DocumentArray); + post.comments[0].should.be.an.instanceof(EmbeddedDocument); + post.comments[1].should.be.an.instanceof(EmbeddedDocument); + + db.close(); + }, + + 'test a model structure when partially initd': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.get('title').should.eql('Test'); + post.get('slug').should.eql('test'); + post.get('date').should.be.an.instanceof(Date); + post.get('meta').should.be.a('object'); + + post.get('meta').should.eql({}); + should.equal(undefined, post.get('meta.date')); + should.equal(undefined, post.get('meta.visitors')); + + should.equal(undefined, post.get('published')); + + post.get('owners').should.be.an.instanceof(MongooseArray); + post.get('comments').should.be.an.instanceof(DocumentArray); + db.close(); + }, + + 'test initializing with a nested hash': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + meta: { + date : new Date + , visitors : 5 + } + }); + + post.get('meta.visitors').valueOf().should.equal(5); + db.close(); + }, + + 'test isNew on embedded documents after initing': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.get('comments')[0].isNew.should.be.false; + db.close(); + }, + + 'test isNew on embedded documents after saving': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }) + post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] }); + post.get('comments')[0].isNew.should.be.true; + post.get('comments')[0].comments[0].isNew.should.be.true; + post.invalidate('title'); // force error + post.save(function (err) { + post.isNew.should.be.true; + post.get('comments')[0].isNew.should.be.true; + post.get('comments')[0].comments[0].isNew.should.be.true; + post.save(function (err) { + db.close(); + should.strictEqual(null, err); + post.isNew.should.be.false; + post.get('comments')[0].isNew.should.be.false; + post.get('comments')[0].comments[0].isNew.should.be.false; + }); + }); + }, + + 'test isNew on embedded documents added after saving': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }) + post.save(function (err) { + post.isNew.should.be.false; + post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] }); + post.get('comments')[0].isNew.should.be.true; + post.get('comments')[0].comments[0].isNew.should.be.true; + post.save(function (err) { + should.strictEqual(null, err); + post.isNew.should.be.false; + post.get('comments')[0].isNew.should.be.false; + post.get('comments')[0].comments[0].isNew.should.be.false; + db.close(); + }); + }); + }, + + 'modified states are reset after save runs': function () { + var db = start() + , B = db.model('BlogPost', collection) + , pending = 2; + + var b = new B; + + b.numbers.push(3); + b.save(function (err) { + should.strictEqual(null, err); + --pending || find(); + }); + + b.numbers.push(3); + b.save(function (err) { + should.strictEqual(null, err); + --pending || find(); + }); + + function find () { + B.findById(b, function (err, b) { + db.close(); + should.strictEqual(null, err); + b.numbers.length.should.equal(2); + }); + } + }, + + 'modified states in emb-doc are reset after save runs': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ title: 'hocus pocus' }); + post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] }); + post.save(function(err){ + db.close(); + should.strictEqual(null, err); + var mFlag = post.comments[0].isModified('title'); + mFlag.should.equal(false); + post.isModified('title').should.equal(false); + }); + + }, + + 'test isModified when modifying keys': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.isModified('title').should.be.false; + post.set('title', 'test'); + post.isModified('title').should.be.true; + + post.isModified('date').should.be.false; + post.set('date', new Date(post.date + 1)); + post.isModified('date').should.be.true; + + post.isModified('meta.date').should.be.false; + db.close(); + }, + + 'setting a key identically to its current value should not dirty the key': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , date : new Date + }); + + post.isModified('title').should.be.false; + post.set('title', 'Test'); + post.isModified('title').should.be.false; + db.close(); + }, + + 'test isModified and isDirectModified on a DocumentArray': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.isModified('comments.0.title').should.be.false; + post.get('comments')[0].set('title', 'Woot'); + post.isModified('comments').should.be.true; + post.isDirectModified('comments').should.be.false; + post.isModified('comments.0.title').should.be.true; + post.isDirectModified('comments.0.title').should.be.true; + + db.close(); + }, + + 'test isModified on a DocumentArray with accessors': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.isModified('comments.0.body').should.be.false; + post.get('comments')[0].body = 'Woot'; + post.isModified('comments').should.be.true; + post.isDirectModified('comments').should.be.false; + post.isModified('comments.0.body').should.be.true; + post.isDirectModified('comments.0.body').should.be.true; + + db.close(); + }, + + 'test isModified on a MongooseArray with atomics methods': function(){ + // COMPLETEME + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + + post.isModified('owners').should.be.false; + post.get('owners').$push(new DocumentObjectId); + post.isModified('owners').should.be.true; + + db.close(); + }, + + 'test isModified on a MongooseArray with native methods': function(){ + // COMPLETEME + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost() + + post.isModified('owners').should.be.false; + post.get('owners').push(new DocumentObjectId); + + db.close(); + }, + + 'test isModified on a Mongoose Document - Modifying an existing record' : function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + + var doc = { + title : 'Test' + , slug : 'test' + , date : new Date + , meta : { + date : new Date + , visitors : 5 + } + , published : true + , mixed : { x: [ { y: [1,'yes', 2] } ] } + , numbers : [] + , owners : [new DocumentObjectId, new DocumentObjectId] + , comments : [ + { title: 'Test', date: new Date, body: 'Test' } + , { title: 'Super', date: new Date, body: 'Cool' } + ] + }; + + BlogPost.create(doc, function (err, post) { + BlogPost.findById(post.id, function (err, postRead) { + db.close(); + should.strictEqual(null, err); + //set the same data again back to the document. + //expected result, nothing should be set to modified + postRead.isModified('comments').should.be.false; + postRead.isNew.should.be.false; + postRead.set(postRead.toObject()); + + postRead.isModified('title').should.be.false; + postRead.isModified('slug').should.be.false; + postRead.isModified('date').should.be.false; + postRead.isModified('meta.date').should.be.false; + postRead.isModified('meta.visitors').should.be.false; + postRead.isModified('published').should.be.false; + postRead.isModified('mixed').should.be.false; + postRead.isModified('numbers').should.be.false; + postRead.isModified('owners').should.be.false; + postRead.isModified('comments').should.be.false; + postRead.comments[2] = { title: 'index' }; + postRead.comments = postRead.comments; + postRead.isModified('comments').should.be.true; + }); + }); + }, + + // gh-662 + 'test nested structure created by merging': function() { + var db = start(); + + var MergedSchema = new Schema({ + a: { + foo: String + } + }); + + MergedSchema.add({ + a: { + b: { + bar: String + } + } + }); + + mongoose.model('Merged', MergedSchema); + var Merged = db.model('Merged', 'merged_' + Math.random()); + + var merged = new Merged({ + a: { + foo: 'baz' + , b: { + bar: 'qux' + } + } + }); + + merged.save(function(err) { + should.strictEqual(null, err); + Merged.findById(merged.id, function(err, found) { + db.close(); + should.strictEqual(null, err); + found.a.foo.should.eql('baz'); + found.a.b.bar.should.eql('qux'); + }); + }); + }, + + // gh-714 + 'modified nested objects which contain MongoseNumbers should not cause a RangeError on save': function () { + var db =start() + + var schema = new Schema({ + nested: { + num: Number + } + }); + + var M = db.model('NestedObjectWithMongooseNumber', schema); + var m = new M; + m.nested = null; + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + should.strictEqual(null, err); + m.nested.num = 5; + m.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + // gh-714 pt 2 + 'no RangeError on remove() of a doc with Number _id': function () { + var db = start() + + var MySchema = new Schema({ + _id: { type: Number }, + name: String + }); + + var MyModel = db.model('MyModel', MySchema, 'numberrangeerror'+random()); + + var instance = new MyModel({ + name: 'test' + , _id: 35 + }); + + instance.save(function (err) { + should.strictEqual(null, err); + + MyModel.findById(35, function (err, doc) { + should.strictEqual(null, err); + + doc.remove(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + // GH-342 + 'over-writing a number should persist to the db': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ + meta: { + date : new Date + , visitors : 10 + } + }); + + post.save( function (err) { + should.strictEqual(null, err); + post.set('meta.visitors', 20); + post.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(post.id, function (err, found) { + should.strictEqual(null, err); + found.get('meta.visitors').valueOf().should.equal(20); + db.close(); + }); + }); + }); + }, + + 'test defining a new method': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.cool().should.eql(post); + db.close(); + }, + + 'test defining a static': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.woot().should.eql(BlogPost); + db.close(); + }, + + 'test casting error': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost(); + + try { + post.init({ + date: 'Test' + }); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + try { + post.set('title', 'Test'); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test nested casting error': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost(); + + try { + post.init({ + meta: { + date: 'Test' + } + }); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + try { + post.set('meta.date', 'Test'); + } catch(e){ + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test casting error in subdocuments': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost() + post.init({ + title : 'Test' + , slug : 'test' + , comments : [ { title: 'Test', date: new Date, body: 'Test' } ] + }); + + post.get('comments')[0].set('date', 'invalid'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test casting error when adding a subdocument': function(){ + var db = start() + , BlogPost = db.model('BlogPost', collection) + , threw = false; + + var post = new BlogPost() + + try { + post.get('comments').push({ + date: 'Bad date' + }); + } catch (e) { + threw = true; + } + + threw.should.be.false; + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(CastError); + db.close(); + }); + }, + + 'test validation': function(){ + function dovalidate (val) { + this.asyncScope.should.equal('correct'); + return true; + } + + function dovalidateAsync (val, callback) { + this.scope.should.equal('correct'); + process.nextTick(function () { + callback(true); + }); + } + + mongoose.model('TestValidation', new Schema({ + simple: { type: String, required: true } + , scope: { type: String, validate: [dovalidate, 'scope failed'], required: true } + , asyncScope: { type: String, validate: [dovalidateAsync, 'async scope failed'], required: true } + })); + + var db = start() + , TestValidation = db.model('TestValidation'); + + var post = new TestValidation(); + post.set('simple', ''); + post.set('scope', 'correct'); + post.set('asyncScope', 'correct'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('simple', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation with custom message': function () { + function validate (val) { + return val === 'abc'; + } + mongoose.model('TestValidationMessage', new Schema({ + simple: { type: String, validate: [validate, 'must be abc'] } + })); + + var db = start() + , TestValidationMessage = db.model('TestValidationMessage'); + + var post = new TestValidationMessage(); + post.set('simple', ''); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.simple.should.be.an.instanceof(ValidatorError); + err.errors.simple.message.should.equal('Validator "must be abc" failed for path simple'); + post.errors.simple.message.should.equal('Validator "must be abc" failed for path simple'); + + post.set('simple', 'abc'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + // GH-272 + 'test validation with Model.schema.path introspection': function () { + var db = start(); + var IntrospectionValidationSchema = new Schema({ + name: String + }); + var IntrospectionValidation = db.model('IntrospectionValidation', IntrospectionValidationSchema, 'introspections_' + random()); + IntrospectionValidation.schema.path('name').validate(function (value) { + return value.length < 2; + }, 'Name cannot be greater than 1 character'); + var doc = new IntrospectionValidation({name: 'hi'}); + doc.save( function (err) { + err.errors.name.message.should.equal("Validator \"Name cannot be greater than 1 character\" failed for path name"); + err.name.should.equal("ValidationError"); + err.message.should.equal("Validation failed"); + db.close(); + }); + }, + + 'test required validation for undefined values': function () { + mongoose.model('TestUndefinedValidation', new Schema({ + simple: { type: String, required: true } + })); + + var db = start() + , TestUndefinedValidation = db.model('TestUndefinedValidation'); + + var post = new TestUndefinedValidation(); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('simple', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + // GH-319 + 'save callback should only execute once regardless of number of failed validations': function () { + var db = start() + + var D = db.model('CallbackFiresOnceValidation', new Schema({ + username: { type: String, validate: /^[a-z]{6}$/i } + , email: { type: String, validate: /^[a-z]{6}$/i } + , password: { type: String, validate: /^[a-z]{6}$/i } + })); + + var post = new D({ + username: "nope" + , email: "too" + , password: "short" + }); + + var timesCalled = 0; + + post.save(function (err) { + db.close(); + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + (++timesCalled).should.eql(1); + + (Object.keys(err.errors).length).should.eql(3); + err.errors.password.should.be.an.instanceof(ValidatorError); + err.errors.email.should.be.an.instanceof(ValidatorError); + err.errors.username.should.be.an.instanceof(ValidatorError); + err.errors.password.message.should.eql('Validator failed for path password'); + err.errors.email.message.should.eql('Validator failed for path email'); + err.errors.username.message.should.eql('Validator failed for path username'); + + (Object.keys(post.errors).length).should.eql(3); + post.errors.password.should.be.an.instanceof(ValidatorError); + post.errors.email.should.be.an.instanceof(ValidatorError); + post.errors.username.should.be.an.instanceof(ValidatorError); + post.errors.password.message.should.eql('Validator failed for path password'); + post.errors.email.message.should.eql('Validator failed for path email'); + post.errors.username.message.should.eql('Validator failed for path username'); + + }); + }, + + 'test validation on a query result': function () { + mongoose.model('TestValidationOnResult', new Schema({ + resultv: { type: String, required: true } + })); + + var db = start() + , TestV = db.model('TestValidationOnResult'); + + var post = new TestV; + + post.validate(function (err) { + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.resultv = 'yeah'; + post.save(function (err) { + should.strictEqual(err, null); + TestV.findOne({ _id: post.id }, function (err, found) { + should.strictEqual(err, null); + found.resultv.should.eql('yeah'); + found.save(function(err){ + should.strictEqual(err, null); + db.close(); + }) + }); + }); + }) + }, + + 'test required validation for previously existing null values': function () { + mongoose.model('TestPreviousNullValidation', new Schema({ + previous: { type: String, required: true } + , a: String + })); + + var db = start() + , TestP = db.model('TestPreviousNullValidation') + + TestP.collection.insert({ a: null, previous: null}, {}, function (err, f) { + should.strictEqual(err, null); + + TestP.findOne({_id: f[0]._id}, function (err, found) { + should.strictEqual(err, null); + found.isNew.should.be.false; + should.strictEqual(found.get('previous'), null); + + found.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + found.set('previous', 'yoyo'); + found.save(function (err) { + should.strictEqual(err, null); + db.close(); + }); + }) + }) + }); + }, + + 'test nested validation': function(){ + mongoose.model('TestNestedValidation', new Schema({ + nested: { + required: { type: String, required: true } + } + })); + + var db = start() + , TestNestedValidation = db.model('TestNestedValidation'); + + var post = new TestNestedValidation(); + post.set('nested.required', null); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + post.set('nested.required', 'here'); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation in subdocuments': function(){ + var Subdocs = new Schema({ + required: { type: String, required: true } + }); + + mongoose.model('TestSubdocumentsValidation', new Schema({ + items: [Subdocs] + })); + + var db = start() + , TestSubdocumentsValidation = db.model('TestSubdocumentsValidation'); + + var post = new TestSubdocumentsValidation(); + + post.get('items').push({ required: '' }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.required.should.be.an.instanceof(ValidatorError); + err.errors.required.message.should.eql('Validator "required" failed for path required'); + + post.get('items')[0].set('required', true); + post.save(function(err){ + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test async validation': function(){ + var executed = false; + + function validator(v, fn){ + setTimeout(function () { + executed = true; + fn(v !== 'test'); + }, 50); + }; + mongoose.model('TestAsyncValidation', new Schema({ + async: { type: String, validate: [validator, 'async validator'] } + })); + + var db = start() + , TestAsyncValidation = db.model('TestAsyncValidation'); + + var post = new TestAsyncValidation(); + post.set('async', 'test'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.async.should.be.an.instanceof(ValidatorError); + err.errors.async.message.should.eql('Validator "async validator" failed for path async'); + executed.should.be.true; + executed = false; + + post.set('async', 'woot'); + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test nested async validation': function(){ + var executed = false; + + function validator(v, fn){ + setTimeout(function () { + executed = true; + fn(v !== 'test'); + }, 50); + }; + + mongoose.model('TestNestedAsyncValidation', new Schema({ + nested: { + async: { type: String, validate: [validator, 'async validator'] } + } + })); + + var db = start() + , TestNestedAsyncValidation = db.model('TestNestedAsyncValidation'); + + var post = new TestNestedAsyncValidation(); + post.set('nested.async', 'test'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.set('nested.async', 'woot'); + post.validate(function(err){ + executed.should.be.true; + should.equal(err, null); + executed = false; + + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }) + + }); + }, + + 'test async validation in subdocuments': function(){ + var executed = false; + + function validator (v, fn) { + setTimeout(function(){ + executed = true; + fn(v !== ''); + }, 50); + }; + + var Subdocs = new Schema({ + required: { type: String, validate: [validator, 'async in subdocs'] } + }); + + mongoose.model('TestSubdocumentsAsyncValidation', new Schema({ + items: [Subdocs] + })); + + var db = start() + , Test = db.model('TestSubdocumentsAsyncValidation'); + + var post = new Test(); + + post.get('items').push({ required: '' }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + executed.should.be.true; + executed = false; + + post.get('items')[0].set({ required: 'here' }); + post.save(function(err){ + executed.should.be.true; + should.strictEqual(err, null); + db.close(); + }); + }); + }, + + 'test validation without saving': function(){ + + mongoose.model('TestCallingValidation', new Schema({ + item: { type: String, required: true } + })); + + var db = start() + , TestCallingValidation = db.model('TestCallingValidation'); + + var post = new TestCallingValidation; + + post.schema.path('item').isRequired.should.be.true; + + should.strictEqual(post.isNew, true); + + post.validate(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + should.strictEqual(post.isNew, true); + + post.item = 'yo'; + post.validate(function(err){ + should.equal(err, null); + should.strictEqual(post.isNew, true); + db.close(); + }); + }); + + }, + + 'test setting required to false': function () { + function validator () { + return true; + } + + mongoose.model('TestRequiredFalse', new Schema({ + result: { type: String, validate: [validator, 'chump validator'], required: false } + })); + + var db = start() + , TestV = db.model('TestRequiredFalse'); + + var post = new TestV; + + post.schema.path('result').isRequired.should.be.false; + + db.close(); + }, + + 'test defaults application': function(){ + var now = Date.now(); + + mongoose.model('TestDefaults', new Schema({ + date: { type: Date, default: now } + })); + + var db = start() + , TestDefaults = db.model('TestDefaults'); + + var post = new TestDefaults(); + post.get('date').should.be.an.instanceof(Date); + (+post.get('date')).should.eql(now); + db.close(); + }, + + 'test "type" is allowed as a key': function(){ + mongoose.model('TestTypeDefaults', new Schema({ + type: { type: String, default: 'YES!' } + })); + + var db = start() + , TestDefaults = db.model('TestTypeDefaults'); + + var post = new TestDefaults(); + post.get('type').should.be.a('string'); + post.get('type').should.eql('YES!'); + + // GH-402 + var TestDefaults2 = db.model('TestTypeDefaults2', new Schema({ + x: { y: { type: { type: String }, owner: String } } + })); + + var post = new TestDefaults2; + post.x.y.type = "#402"; + post.x.y.owner= "me"; + post.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + + }, + + 'test nested defaults application': function(){ + var now = Date.now(); + + mongoose.model('TestNestedDefaults', new Schema({ + nested: { + date: { type: Date, default: now } + } + })); + + var db = start() + , TestDefaults = db.model('TestNestedDefaults'); + + var post = new TestDefaults(); + post.get('nested.date').should.be.an.instanceof(Date); + (+post.get('nested.date')).should.eql(now); + db.close(); + }, + + 'test defaults application in subdocuments': function(){ + var now = Date.now(); + + var Items = new Schema({ + date: { type: Date, default: now } + }); + + mongoose.model('TestSubdocumentsDefaults', new Schema({ + items: [Items] + })); + + var db = start() + , TestSubdocumentsDefaults = db.model('TestSubdocumentsDefaults'); + + var post = new TestSubdocumentsDefaults(); + post.get('items').push({}); + post.get('items')[0].get('date').should.be.an.instanceof(Date); + (+post.get('items')[0].get('date')).should.eql(now); + db.close(); + }, + + // TODO: adapt this text to handle a getIndexes callback that's not unique to + // the mongodb-native driver. + 'test that indexes are ensured when the model is compiled': function(){ + var Indexed = new Schema({ + name : { type: String, index: true } + , last : String + , email : String + }); + + Indexed.index({ last: 1, email: 1 }, { unique: true }); + + var db = start() + , IndexedModel = db.model('IndexedModel', Indexed, 'indexedmodel' + random()) + , assertions = 0; + + IndexedModel.on('index', function(){ + IndexedModel.collection.getIndexes(function(err, indexes){ + should.strictEqual(err, null); + + for (var i in indexes) + indexes[i].forEach(function(index){ + if (index[0] == 'name') + assertions++; + if (index[0] == 'last') + assertions++; + if (index[0] == 'email') + assertions++; + }); + + assertions.should.eql(3); + db.close(); + }); + }); + }, + + 'test indexes on embedded documents': function () { + var BlogPosts = new Schema({ + _id : { type: ObjectId, index: true } + , title : { type: String, index: true } + , desc : String + }); + + var User = new Schema({ + name : { type: String, index: true } + , blogposts : [BlogPosts] + }); + + var db = start() + , UserModel = db.model('DeepIndexedModel', User, 'deepindexedmodel' + random()) + , assertions = 0; + + UserModel.on('index', function () { + UserModel.collection.getIndexes(function (err, indexes) { + should.strictEqual(err, null); + + for (var i in indexes) + indexes[i].forEach(function(index){ + if (index[0] == 'name') + assertions++; + if (index[0] == 'blogposts._id') + assertions++; + if (index[0] == 'blogposts.title') + assertions++; + }); + + assertions.should.eql(3); + db.close(); + }); + }); + }, + + 'compound indexes on embedded documents should be created': function () { + var BlogPosts = new Schema({ + title : String + , desc : String + }); + + BlogPosts.index({ title: 1, desc: 1 }); + + var User = new Schema({ + name : { type: String, index: true } + , blogposts : [BlogPosts] + }); + + var db = start() + , UserModel = db.model('DeepCompoundIndexModel', User, 'deepcompoundindexmodel' + random()) + , found = 0; + + UserModel.on('index', function () { + UserModel.collection.getIndexes(function (err, indexes) { + should.strictEqual(err, null); + + for (var index in indexes) { + switch (index) { + case 'name_1': + case 'blogposts.title_1_blogposts.desc_1': + ++found; + break; + } + } + + db.close(); + found.should.eql(2); + }); + }); + }, + + 'test getters with same name on embedded documents not clashing': function() { + var Post = new Schema({ + title : String + , author : { name : String } + , subject : { name : String } + }); + + mongoose.model('PostWithClashGetters', Post); + + var db = start() + , PostModel = db.model('PostWithClashGetters', 'postwithclash' + random()); + + var post = new PostModel({ + title: 'Test' + , author: { name: 'A' } + , subject: { name: 'B' } + }); + + post.author.name.should.eql('A'); + post.subject.name.should.eql('B'); + post.author.name.should.eql('A'); + + db.close(); + }, + + 'test post save middleware': function () { + var schema = new Schema({ + title: String + }); + + var called = 0; + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(0); + called++; + }); + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(1); + called++; + }); + + schema.post('save', function (obj) { + obj.title.should.eql('Little Green Running Hood'); + called.should.equal(2); + db.close(); + }); + + var db = start() + , TestMiddleware = db.model('TestPostSaveMiddleware', schema); + + var test = new TestMiddleware({ title: 'Little Green Running Hood'}); + + test.save(function(err){ + should.strictEqual(err, null); + }); + }, + + 'test middleware': function () { + var schema = new Schema({ + title: String + }); + + var called = 0; + + schema.pre('init', function (next) { + called++; + next(); + }); + + schema.pre('save', function (next) { + called++; + next(new Error('Error 101')); + }); + + schema.pre('remove', function (next) { + called++; + next(); + }); + + mongoose.model('TestMiddleware', schema); + + var db = start() + , TestMiddleware = db.model('TestMiddleware'); + + var test = new TestMiddleware(); + + test.init({ + title: 'Test' + }); + + called.should.eql(1); + + test.save(function(err){ + err.should.be.an.instanceof(Error); + err.message.should.eql('Error 101'); + called.should.eql(2); + + test.remove(function(err){ + should.strictEqual(err, null); + called.should.eql(3); + db.close(); + }); + }); + }, + + 'test post init middleware': function () { + var schema = new Schema({ + title: String + }); + + var preinit = 0 + , postinit = 0 + + schema.pre('init', function (next) { + ++preinit; + next(); + }); + + schema.post('init', function () { + ++postinit; + }); + + mongoose.model('TestPostInitMiddleware', schema); + + var db = start() + , Test = db.model('TestPostInitMiddleware'); + + var test = new Test({ title: "banana" }); + + test.save(function(err){ + should.strictEqual(err, null); + + Test.findById(test._id, function (err, test) { + should.strictEqual(err, null); + preinit.should.eql(1); + postinit.should.eql(1); + test.remove(function(err){ + db.close(); + }); + }); + }); + }, + + 'test update doc casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.set('title', '1'); + + var id = post.get('_id'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.update({ title: 1, _id: id }, { title: 2 }, function (err) { + should.strictEqual(err, null); + + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + should.strictEqual(err, null); + + doc.get('title').should.eql('2'); + db.close(); + }); + }); + }); + }, + + 'test $push casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.get('numbers').push('3'); + post.get('numbers')[0].should.equal(3); + db.close(); + }, + + 'test $pull casting': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.get('numbers').push(1, 2, 3, 4); + post.save( function (err) { + BlogPost.findById( post.get('_id'), function (err, found) { + found.get('numbers').length.should.equal(4); + found.get('numbers').$pull('3'); + found.save( function (err) { + BlogPost.findById( found.get('_id'), function (err, found2) { + found2.get('numbers').length.should.equal(3); + db.close(); + }); + }); + }); + }); + }, + + 'test updating numbers atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , totalDocs = 4 + , saveQueue = []; + + var post = new BlogPost(); + post.set('meta.visitors', 5); + + post.save(function(err){ + if (err) throw err; + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('meta.visitors').increment(); + doc.get('meta.visitors').valueOf().should.be.equal(6); + save(doc); + }); + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == 4) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + if (err) throw err; + doc.get('meta.visitors').valueOf().should.be.equal(9); + db.close(); + }); + }; + }); + }, + + 'test incrementing a number atomically with an arbitrary value': function () { + var db = start() + , BlogPost = db.model('BlogPost'); + + var post = new BlogPost(); + + post.meta.visitors = 0; + + post.save(function (err) { + should.strictEqual(err, null); + + post.meta.visitors.increment(50); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (+doc.meta.visitors).should.eql(50); + db.close(); + }); + }); + }); + }, + + // GH-203 + 'test changing a number non-atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.meta.visitors = 5; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.meta.visitors -= 2; + + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (+doc.meta.visitors).should.eql(3); + db.close(); + }); + }); + }); + }); + }, + + 'test saving subdocuments atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , totalDocs = 4 + , saveQueue = []; + + var post = new BlogPost(); + + post.save(function(err){ + if (err) throw err; + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '1' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '2' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '3' }); + save(doc); + }); + + BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('comments').push({ title: '4' }, { title: '5' }); + save(doc); + }); + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == 4) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('comments').length.should.eql(5); + + doc.get('comments').some(function(comment){ + return comment.get('title') == '1'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '2'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '3'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '4'; + }).should.be.true; + + doc.get('comments').some(function(comment){ + return comment.get('title') == '5'; + }).should.be.true; + + db.close(); + }); + }; + }); + }, + + // GH-310 + 'test setting a subdocument atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + + BlogPost.create({ + comments: [{ title: 'first-title', body: 'first-body'}] + }, function (err, blog) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, agent1blog) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, agent2blog) { + should.strictEqual(null, err); + agent1blog.get('comments')[0].title = 'second-title'; + agent1blog.save( function (err) { + should.strictEqual(null, err); + agent2blog.get('comments')[0].body = 'second-body'; + agent2blog.save( function (err) { + should.strictEqual(null, err); + BlogPost.findById(blog.id, function (err, foundBlog) { + should.strictEqual(null, err); + db.close(); + var comment = foundBlog.get('comments')[0]; + comment.title.should.eql('second-title'); + comment.body.should.eql('second-body'); + }); + }); + }); + }); + }); + }); + }, + + 'test doubly nested array saving and loading': function(){ + var Inner = new Schema({ + arr: [Number] + }); + + var Outer = new Schema({ + inner: [Inner] + }); + mongoose.model('Outer', Outer); + + var db = start(); + var Outer = db.model('Outer', 'arr_test_' + random()); + + var outer = new Outer(); + outer.inner.push({}); + outer.save(function(err) { + should.strictEqual(err, null); + outer.get('_id').should.be.an.instanceof(DocumentObjectId); + + Outer.findById(outer.get('_id'), function(err, found) { + should.strictEqual(err, null); + should.equal(1, found.inner.length); + found.inner[0].arr.push(5); + found.save(function(err) { + should.strictEqual(err, null); + found.get('_id').should.be.an.instanceof(DocumentObjectId); + Outer.findById(found.get('_id'), function(err, found2) { + db.close(); + should.strictEqual(err, null); + should.equal(1, found2.inner.length); + should.equal(1, found2.inner[0].arr.length); + should.equal(5, found2.inner[0].arr[0]); + }); + }); + }); + }); + }, + + 'test updating multiple Number $pushes as a single $pushAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({}, function (err, t) { + t.nested.nums.push(1); + t.nested.nums.push(2); + + t.nested.nums.should.have.length(2); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(2); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(2); + db.close(); + }); + }); + }); + }, + + 'test updating at least a single $push and $pushAll as a single $pushAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({}, function (err, t) { + t.nested.nums.push(1); + t.nested.nums.$pushAll([2, 3]); + + t.nested.nums.should.have.length(3); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(3); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(3); + db.close(); + }); + }); + }); + }, + + 'test activePaths should be updated for nested modifieds': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pull(2); + + t._activePaths.stateOf('nested.nums').should.equal('modify'); + db.close(); + + }); + }, + + '$pull should affect what you see in an array before a save': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + + t.nested.nums.should.have.length(4); + + db.close(); + }); + }, + + '$pullAll should affect what you see in an array before a save': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pullAll([1, 2, 3]); + + t.nested.nums.should.have.length(2); + + db.close(); + }); + }, + + 'test updating multiple Number $pulls as a single $pullAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pull(2); + + t.nested.nums.should.have.length(3); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(3); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(3); + db.close(); + }); + }); + }); + }, + + 'having both a pull and pullAll should default to pullAll': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('NestedPushes', schema); + var Temp = db.model('NestedPushes', collection); + + Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) { + t.nested.nums.$pull(1); + t.nested.nums.$pullAll([2, 3]); + + t.nested.nums.should.have.length(2); + + t.save( function (err) { + should.strictEqual(null, err); + t.nested.nums.should.have.length(2); + Temp.findById(t._id, function (err, found) { + found.nested.nums.should.have.length(2); + db.close(); + }); + }); + }); + }, + + '$shift': function () { + var db = start() + , schema = new Schema({ + nested: { + nums: [Number] + } + }); + + mongoose.model('TestingShift', schema); + var Temp = db.model('TestingShift', collection); + + Temp.create({ nested: { nums: [1,2,3] }}, function (err, t) { + should.strictEqual(null, err); + + Temp.findById(t._id, function (err, found) { + should.strictEqual(null, err); + found.nested.nums.should.have.length(3); + found.nested.nums.$pop(); + found.nested.nums.should.have.length(2); + found.nested.nums[0].should.eql(1); + found.nested.nums[1].should.eql(2); + + found.save(function (err) { + should.strictEqual(null, err); + Temp.findById(t._id, function (err, found) { + should.strictEqual(null, err); + found.nested.nums.should.have.length(2); + found.nested.nums[0].should.eql(1); + found.nested.nums[1].should.eql(2); + found.nested.nums.$shift(); + found.nested.nums.should.have.length(1); + found.nested.nums[0].should.eql(2); + + found.save(function (err) { + should.strictEqual(null, err); + Temp.findById(t._id, function (err, found) { + db.close(); + should.strictEqual(null, err); + found.nested.nums.should.have.length(1); + found.nested.nums[0].should.eql(2); + }); + }); + }); + }); + }); + }); + }, + + 'test saving embedded arrays of Numbers atomically': function () { + var db = start() + , TempSchema = new Schema({ + nums: [Number] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('Temp', TempSchema); + var Temp = db.model('Temp', collection); + + var t = new Temp(); + + t.save(function(err){ + if (err) throw err; + + Temp.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('nums').push(1); + save(doc); + }); + + Temp.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('nums').push(2, 3); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + Temp.findOne({ _id: t.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('nums').length.should.eql(3); + + doc.get('nums').some(function(num){ + return num.valueOf() == '1'; + }).should.be.true; + + doc.get('nums').some(function(num){ + return num.valueOf() == '2'; + }).should.be.true; + + doc.get('nums').some(function(num){ + return num.valueOf() == '3'; + }).should.be.true; + + + db.close(); + }); + }; + }); + }, + + 'test saving embedded arrays of Strings atomically': function () { + var db = start() + , StrListSchema = new Schema({ + strings: [String] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('StrList', StrListSchema); + var StrList = db.model('StrList'); + + var t = new StrList(); + + t.save(function(err){ + if (err) throw err; + + StrList.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('strings').push('a'); + save(doc); + }); + + StrList.findOne({ _id: t.get('_id') }, function(err, doc){ + if (err) throw err; + doc.get('strings').push('b', 'c'); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + if (err) throw err; + --totalDocs || complete(); + }); + }); + }; + + function complete () { + StrList.findOne({ _id: t.get('_id') }, function (err, doc) { + if (err) throw err; + + doc.get('strings').length.should.eql(3); + + doc.get('strings').some(function(str){ + return str == 'a'; + }).should.be.true; + + doc.get('strings').some(function(str){ + return str == 'b'; + }).should.be.true; + + doc.get('strings').some(function(str){ + return str == 'c'; + }).should.be.true; + + db.close(); + }); + }; + }); + }, + + 'test saving embedded arrays of Buffers atomically': function () { + var db = start() + , BufListSchema = new Schema({ + buffers: [Buffer] + }) + , totalDocs = 2 + , saveQueue = []; + + mongoose.model('BufList', BufListSchema); + var BufList = db.model('BufList'); + + var t = new BufList(); + + t.save(function(err){ + should.strictEqual(null, err); + + BufList.findOne({ _id: t.get('_id') }, function(err, doc){ + should.strictEqual(null, err); + doc.get('buffers').push(new Buffer([140])); + save(doc); + }); + + BufList.findOne({ _id: t.get('_id') }, function(err, doc){ + should.strictEqual(null, err); + doc.get('buffers').push(new Buffer([141]), new Buffer([142])); + save(doc); + }); + + + function save(doc) { + saveQueue.push(doc); + if (saveQueue.length == totalDocs) + saveQueue.forEach(function (doc) { + doc.save(function (err) { + should.strictEqual(null, err); + --totalDocs || complete(); + }); + }); + }; + + function complete () { + BufList.findOne({ _id: t.get('_id') }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + + doc.get('buffers').length.should.eql(3); + + doc.get('buffers').some(function(buf){ + return buf[0] == 140; + }).should.be.true; + + doc.get('buffers').some(function(buf){ + return buf[0] == 141; + }).should.be.true; + + doc.get('buffers').some(function(buf){ + return buf[0] == 142; + }).should.be.true; + + }); + }; + }); + }, + + // GH-255 + 'test updating an embedded document in an embedded array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({comments: [{title: 'woot'}]}, function (err, post) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, found) { + should.strictEqual(err, null); + found.comments[0].title.should.equal('woot'); + found.comments[0].title = 'notwoot'; + found.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(found._id, function (err, updated) { + db.close(); + should.strictEqual(err, null); + updated.comments[0].title.should.equal('notwoot'); + }); + }); + }); + }); + }, + + // GH-334 + 'test updating an embedded array document to an Object value': function () { + var db = start() + , SubSchema = new Schema({ + name : String , + subObj : { subName : String } + }); + var GH334Schema = new Schema ({ name : String , arrData : [ SubSchema] }); + + mongoose.model('GH334' , GH334Schema); + var AModel = db.model('GH334'); + var instance = new AModel(); + + instance.set( { name : 'name-value' , arrData : [ { name : 'arrName1' , subObj : { subName : 'subName1' } } ] }); + instance.save(function(err) { + AModel.findById(instance.id, function(err, doc) { + doc.arrData[0].set('subObj' , { subName : 'modified subName' }); + doc.save(function(err) { + should.strictEqual(null, err); + AModel.findById(instance.id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.arrData[0].subObj.subName.should.eql('modified subName'); + }); + }); + }); + }); + }, + + // GH-267 + 'saving an embedded document twice should not push that doc onto the parent doc twice': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.comments.push({title: 'woot'}); + post.save( function (err) { + should.strictEqual(err, null); + post.comments.should.have.length(1); + BlogPost.findById(post.id, function (err, found) { + should.strictEqual(err, null); + found.comments.should.have.length(1); + post.save( function (err) { + should.strictEqual(err, null); + post.comments.should.have.length(1); + BlogPost.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(err, null); + found.comments.should.have.length(1); + }); + }); + }); + }); + }, + + 'test filtering an embedded array by the id shortcut function': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + var subdoc1 = post.comments[0]; + var subdoc2 = post.comments[1]; + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + // test with an objectid + doc.comments.id(subdoc1.get('_id')).title.should.eql('woot'); + + // test with a string + var id = DocumentObjectId.toString(subdoc2._id); + doc.comments.id(id).title.should.eql('aaaa'); + + db.close(); + }); + }); + }, + + 'test filtering an embedded array by the id with cast error': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + should.strictEqual(doc.comments.id(null), null); + + db.close(); + }); + }); + }, + + 'test filtering an embedded array by the id shortcut with no match': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + should.strictEqual(doc.comments.id(new DocumentObjectId), null); + + db.close(); + }); + }); + }, + + 'test for removing a subdocument atomically': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'hahaha'; + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments[0].remove(); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.should.have.length(1); + doc.comments[0].title.should.eql('aaaa'); + + db.close(); + }); + }); + }); + }); + }, + + 'test for single pull embedded doc' : function() { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'hahaha'; + post.comments.push({ title: 'woot' }); + post.comments.push({ title: 'aaaa' }); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.pull(doc.comments[0]); + doc.comments.pull(doc.comments[0]); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.comments.should.have.length(0); + db.close(); + }); + }); + }); + }); + }, + + 'try saving mixed data': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , count = 3; + + // string + var post = new BlogPost(); + post.mixed = 'woot'; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err) { + should.strictEqual(err, null); + + --count || db.close(); + }); + }); + + // array + var post2 = new BlogPost(); + post2.mixed = { name: "mr bungle", arr: [] }; + post2.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post2._id, function (err, doc){ + should.strictEqual(err, null); + + Array.isArray(doc.mixed.arr).should.be.true; + + doc.mixed = [{foo: 'bar'}]; + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(doc._id, function (err, doc){ + should.strictEqual(err, null); + + Array.isArray(doc.mixed).should.be.true; + doc.mixed.push({ hello: 'world' }); + doc.mixed.push([ 'foo', 'bar' ]); + doc.commit('mixed'); + + doc.save(function (err, doc) { + should.strictEqual(err, null); + + BlogPost.findById(post2._id, function (err, doc) { + should.strictEqual(err, null); + + doc.mixed[0].should.eql({ foo: 'bar' }); + doc.mixed[1].should.eql({ hello: 'world' }); + doc.mixed[2].should.eql(['foo','bar']); + --count || db.close(); + }); + }); + }); + + // date + var post3 = new BlogPost(); + post3.mixed = new Date; + post3.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post3._id, function (err, doc) { + should.strictEqual(err, null); + + doc.mixed.should.be.an.instanceof(Date); + --count || db.close(); + }); + }); + }); + + }); + }); + + }, + + // GH-200 + 'try populating mixed data from the constructor': function () { + var db = start() + , BlogPost = db.model('BlogPost'); + + var post = new BlogPost({ + mixed: { + type: 'test' + , github: 'rules' + , nested: { + number: 3 + } + } + }); + + post.mixed.type.should.eql('test'); + post.mixed.github.should.eql('rules'); + post.mixed.nested.number.should.eql(3); + + db.close(); + }, + + 'test that we instantiate MongooseNumber in arrays': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.push(1, '2', 3); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + (~doc.numbers.indexOf(1)).should.not.eql(0); + (~doc.numbers.indexOf(2)).should.not.eql(0); + (~doc.numbers.indexOf(3)).should.not.eql(0); + + db.close(); + }); + }); + }, + + 'test removing from an array atomically using MongooseArray#remove': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.numbers.push(1, 2, 3); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.remove('1'); + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.should.have.length(2); + doc.numbers.remove('2', '3'); + + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + doc.numbers.should.have.length(0); + db.close(); + }); + }); + }); + }); + }); + }); + }, + + 'test getting a virtual property via get(...)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost({ + title: 'Letters from Earth' + , author: 'Mark Twain' + }); + + post.get('titleWithAuthor').should.equal('Letters from Earth by Mark Twain'); + + db.close(); + }, + + 'test setting a virtual property': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain') + post.get('title').should.equal('Huckleberry Finn'); + post.get('author').should.equal('Mark Twain'); + + db.close(); + }, + + 'test getting a virtual property via shortcut getter': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost({ + title: 'Letters from Earth' + , author: 'Mark Twain' + }); + + post.titleWithAuthor.should.equal('Letters from Earth by Mark Twain'); + + db.close(); + }, + + // gh-685 + 'getters should not be triggered at construction': function () { + var db = start() + , called = false + + db.close(); + + var schema = new mongoose.Schema({ + number: { + type:Number + , set: function(x){return x/2} + , get: function(x){ + called = true; + return x*2; + } + } + }); + + var A = mongoose.model('gettersShouldNotBeTriggeredAtConstruction', schema); + + var a = new A({ number: 100 }); + called.should.be.false; + var num = a.number; + called.should.be.true; + num.valueOf().should.equal(100); + a.getValue('number').valueOf().should.equal(50); + + called = false; + var b = new A; + b.init({ number: 50 }); + called.should.be.false; + num = b.number; + called.should.be.true; + num.valueOf().should.equal(100); + b.getValue('number').valueOf().should.equal(50); + }, + + 'saving a doc with a set virtual property should persist the real properties but not the virtual property': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain') + post.get('title').should.equal('Huckleberry Finn'); + post.get('author').should.equal('Mark Twain'); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post.get('_id'), function (err, found) { + should.strictEqual(err, null); + + found.get('title').should.equal('Huckleberry Finn'); + found.get('author').should.equal('Mark Twain'); + found.toObject().should.not.have.property('titleWithAuthor'); + db.close(); + }); + }); + }, + + 'test setting a pseudo-nested virtual property': function () { + var db = start() + , PersonSchema = new Schema({ + name: { + first: String + , last: String + } + }); + + PersonSchema.virtual('name.full') + .get( function () { + return this.get('name.first') + ' ' + this.get('name.last'); + }) + .set( function (fullName) { + var split = fullName.split(' '); + this.set('name.first', split[0]); + this.set('name.last', split[1]); + }); + + mongoose.model('Person', PersonSchema); + + var Person = db.model('Person') + , person = new Person({ + name: { + first: 'Michael' + , last: 'Sorrentino' + } + }); + + person.get('name.full').should.equal('Michael Sorrentino'); + person.set('name.full', 'The Situation'); + person.get('name.first').should.equal('The'); + person.get('name.last').should.equal('Situation'); + + person.name.full.should.equal('The Situation'); + person.name.full = 'Michael Sorrentino'; + person.name.first.should.equal('Michael'); + person.name.last.should.equal('Sorrentino'); + + db.close(); + }, + + 'test removing all documents from a collection via Model.remove': function () { + var db = start() + , collection = 'blogposts_' + random() + , BlogPost = db.model('BlogPost', collection) + , post = new BlogPost(); + + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.find({}, function (err, found) { + should.strictEqual(err, null); + + found.length.should.equal(1); + + BlogPost.remove({}, function (err) { + should.strictEqual(!!err, false); + + BlogPost.find({}, function (err, found2) { + should.strictEqual(err, null); + + found2.should.have.length(0); + db.close(); + }); + }); + }); + }); + }, + + // GH-190 + 'test shorcut getter for a type defined with { type: Native }': function () { + var schema = new Schema({ + date: { type: Date } + }); + + mongoose.model('ShortcutGetterObject', schema); + + var db = start() + , ShortcutGetter = db.model('ShortcutGetterObject', 'shortcut' + random()) + , post = new ShortcutGetter(); + + post.set('date', Date.now()); + post.date.should.be.an.instanceof(Date); + + db.close(); + }, + + 'test shortcut getter for a nested path': function () { + var schema = new Schema({ + first: { + second: [Number] + } + }); + mongoose.model('ShortcutGetterNested', schema); + + var db = start() + , ShortcutGetterNested = db.model('ShortcutGetterNested', collection) + , doc = new ShortcutGetterNested(); + + doc.first.should.be.a('object'); + doc.first.second.should.be.an.instanceof(MongooseArray); + + db.close(); + }, + + // GH-195 + 'test that save on an unaltered model doesn\'t clear the document': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.title = 'woot'; + post.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, doc) { + should.strictEqual(err, null); + + // we deliberately make no alterations + doc.save(function (err) { + should.strictEqual(err, null); + + BlogPost.findById(doc._id, function (err, doc) { + should.strictEqual(err, null); + + doc.title.should.eql('woot'); + db.close(); + }); + }); + }); + }); + }, + + 'test that safe mode is the default and it works': function () { + var Human = new Schema({ + name : String + , email : { type: String, unique: true } + }); + + mongoose.model('SafeHuman', Human, true); + + var db = start() + , Human = db.model('SafeHuman', 'safehuman' + random()); + + var me = new Human({ + name : 'Guillermo Rauch' + , email : 'rauchg@gmail.com' + }); + + me.save(function (err) { + should.strictEqual(err, null); + + Human.findById(me._id, function (err, doc){ + should.strictEqual(err, null); + doc.email.should.eql('rauchg@gmail.com'); + + var copycat = new Human({ + name : 'Lionel Messi' + , email : 'rauchg@gmail.com' + }); + + copycat.save(function (err) { + /duplicate/.test(err.message).should.be.true; + err.should.be.an.instanceof(Error); + db.close(); + }); + }); + }); + }, + + 'test that safe mode can be turned off': function () { + var Human = new Schema({ + name : String + , email : { type: String, unique: true } + }); + + // turn it off + Human.set('safe', false); + + mongoose.model('UnsafeHuman', Human, true); + + var db = start() + , Human = db.model('UnsafeHuman', 'unsafehuman' + random()); + + var me = new Human({ + name : 'Guillermo Rauch' + , email : 'rauchg@gmail.com' + }); + + me.save(function (err) { + should.strictEqual(err, null); + + Human.findById(me._id, function (err, doc){ + should.strictEqual(err, null); + doc.email.should.eql('rauchg@gmail.com'); + + var copycat = new Human({ + name : 'Lionel Messi' + , email : 'rauchg@gmail.com' + }); + + copycat.save(function (err) { + should.strictEqual(err, null); + db.close(); + }); + }); + }); + }, + + 'passing null in pre hook works': function () { + var db = start(); + var schema = new Schema({ name: String }); + + schema.pre('save', function (next) { + next(null); // <<----- + }); + + var S = db.model('S', schema, collection); + var s = new S({name: 'zupa'}); + + s.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + + }, + + 'pre hooks called on all sub levels': function () { + var db = start(); + + var grandSchema = new Schema({ name : String }); + grandSchema.pre('save', function (next) { + this.name = 'grand'; + next(); + }); + + var childSchema = new Schema({ name : String, grand : [grandSchema]}); + childSchema.pre('save', function (next) { + this.name = 'child'; + next(); + }); + + var schema = new Schema({ name: String, child : [childSchema] }); + + schema.pre('save', function (next) { + this.name = 'parent'; + next(); + }); + + var S = db.model('presave_hook', schema, 'presave_hook'); + var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]}); + + s.save(function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.name.should.eql('parent'); + doc.child[0].name.should.eql('child'); + doc.child[0].grand[0].name.should.eql('grand'); + }); + }, + + 'pre hooks error on all sub levels': function () { + var db = start(); + + var grandSchema = new Schema({ name : String }); + grandSchema.pre('save', function (next) { + next(new Error('Error 101')); + }); + + var childSchema = new Schema({ name : String, grand : [grandSchema]}); + childSchema.pre('save', function (next) { + this.name = 'child'; + next(); + }); + + var schema = new Schema({ name: String, child : [childSchema] }); + schema.pre('save', function (next) { + this.name = 'parent'; + next(); + }); + + var S = db.model('presave_hook_error', schema, 'presave_hook_error'); + var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]}); + + s.save(function (err, doc) { + db.close(); + err.should.be.an.instanceof(Error); + err.message.should.eql('Error 101'); + }); + }, + + 'test post hooks': function () { + var schema = new Schema({ + title: String + }) + , save = false + , remove = false + , init = false + , post = undefined; + + schema.post('save', function (arg) { + arg.id.should.equal(post.id) + save = true; + }); + + schema.post('init', function () { + init = true; + }); + + schema.post('remove', function (arg) { + arg.id.should.eql(post.id) + remove = true; + }); + + mongoose.model('PostHookTest', schema); + + var db = start() + , BlogPost = db.model('PostHookTest'); + + post = new BlogPost(); + + post.save(function (err) { + process.nextTick(function () { + should.strictEqual(err, null); + save.should.be.true; + BlogPost.findById(post._id, function (err, doc) { + process.nextTick(function () { + should.strictEqual(err, null); + init.should.be.true; + + doc.remove(function (err) { + process.nextTick(function () { + should.strictEqual(err, null); + remove.should.be.true; + db.close(); + }); + }); + }); + }); + }); + }); + }, + + 'test count querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable count as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable count as promise'}); + query.exec(function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }, + + 'test update querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable update as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.update({title: 'interoperable update as promise'}, {title: 'interoperable update as promise delta'}); + query.exec(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable update as promise delta'}, function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }); + }, + + 'test findOne querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable findOne as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.findOne({title: 'interoperable findOne as promise'}); + query.exec(function (err, found) { + should.strictEqual(err, null); + found.id.should.eql(created.id); + db.close(); + }); + }); + }, + + 'test find querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable find as promise'} + , {title: 'interoperable find as promise'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.find({title: 'interoperable find as promise'}); + query.exec(function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(2); + found[0]._id.id.should.eql(createdOne._id.id); + found[1]._id.id.should.eql(createdTwo._id.id); + db.close(); + }); + }); + }, + + 'test remove querying via #run (aka #exec)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable remove as promise'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.remove({title: 'interoperable remove as promise'}); + query.exec(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable remove as promise'}, function (err, count) { + db.close(); + count.should.equal(0); + }); + }); + }); + }, + + 'test changing query at the last minute via #run(op, callback)': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable ad-hoc as promise'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable ad-hoc as promise'}); + query.exec('findOne', function (err, found) { + should.strictEqual(err, null); + found.id; + found._id.should.eql(created._id); + db.close(); + }); + }); + }, + + 'test count querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable count as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable count as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }, + + 'test update querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable update as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.update({title: 'interoperable update as promise 2'}, {title: 'interoperable update as promise delta 2'}); + var promise = query.run(); + promise.addBack(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable update as promise delta 2'}, function (err, count) { + should.strictEqual(err, null); + count.should.equal(1); + db.close(); + }); + }); + }); + }, + + 'test findOne querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable findOne as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.findOne({title: 'interoperable findOne as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found.id.should.eql(created.id); + db.close(); + }); + }); + }, + + 'test find querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable find as promise 2'} + , {title: 'interoperable find as promise 2'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.find({title: 'interoperable find as promise 2'}); + var promise = query.run(); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found.length.should.equal(2); + found[0].id; + found[1].id; + found[0]._id.should.eql(createdOne._id); + found[1]._id.should.eql(createdTwo._id); + db.close(); + }); + }); + }, + + 'test remove querying via #run (aka #exec) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create( + {title: 'interoperable remove as promise 2'} + , function (err, createdOne, createdTwo) { + should.strictEqual(err, null); + var query = BlogPost.remove({title: 'interoperable remove as promise 2'}); + var promise = query.exec(); + promise.addBack(function (err) { + should.strictEqual(err, null); + BlogPost.count({title: 'interoperable remove as promise 2'}, function (err, count) { + count.should.equal(0); + db.close(); + }); + }); + }); + }, + + 'test changing query at the last minute via #run(op) with promise': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create({title: 'interoperable ad-hoc as promise 2'}, function (err, created) { + should.strictEqual(err, null); + var query = BlogPost.count({title: 'interoperable ad-hoc as promise 2'}); + var promise = query.exec('findOne'); + promise.addBack(function (err, found) { + should.strictEqual(err, null); + found._id.id.should.eql(created._id.id); + db.close(); + }); + }); + }, + + 'test nested obj literal getter/setters': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var date = new Date; + + var meta = { + date: date + , visitors: 5 + }; + + var post = new BlogPost() + post.init({ + meta: meta + }); + + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.date.should.be.an.instanceof(Date); + + var threw = false; + var getter1; + var getter2; + var strmet; + try { + strmet = JSON.stringify(meta); + getter1 = JSON.stringify(post.get('meta')); + getter2 = JSON.stringify(post.meta); + } catch (err) { + threw = true; + } + + threw.should.be.false; + getter1 = JSON.parse(getter1); + getter2 = JSON.parse(getter2); + getter1.visitors.should.eql(getter2.visitors); + getter1.date.should.eql(getter2.date); + + post.meta.date = new Date - 1000; + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + + post.meta.visitors = 2; + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + + var newmeta = { + date: date - 2000 + , visitors: 234 + }; + + post.set(newmeta, 'meta'); + + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + (+post.meta.date).should.eql(date - 2000); + (+post.get('meta').date).should.eql(date - 2000); + (+post.meta.visitors).should.eql(234); + (+post.get('meta').visitors).should.eql(234); + + // set object directly + post.meta = { + date: date - 3000 + , visitors: 4815162342 + }; + + post.meta.date.should.be.an.instanceof(Date); + post.get('meta').date.should.be.an.instanceof(Date); + post.meta.visitors.should.be.an.instanceof(MongooseNumber); + post.get('meta').visitors.should.be.an.instanceof(MongooseNumber); + (+post.meta.date).should.eql(date - 3000); + (+post.get('meta').date).should.eql(date - 3000); + (+post.meta.visitors).should.eql(4815162342); + (+post.get('meta').visitors).should.eql(4815162342); + + db.close(); + }, + + 'nested object property access works when root initd with null': function () { + var db = start() + + var schema = new Schema({ + nest: { + st: String + } + }); + + mongoose.model('NestedStringA', schema); + var T = db.model('NestedStringA', collection); + + var t = new T({ nest: null }); + + should.strictEqual(t.nest.st, null); + t.nest = { st: "jsconf rules" }; + t.nest.toObject().should.eql({ st: "jsconf rules" }); + t.nest.st.should.eql("jsconf rules"); + + t.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + + }, + + 'nested object property access works when root initd with undefined': function () { + var db = start() + + var schema = new Schema({ + nest: { + st: String + } + }); + + mongoose.model('NestedStringB', schema); + var T = db.model('NestedStringB', collection); + + var t = new T({ nest: undefined }); + + should.strictEqual(t.nest.st, undefined); + t.nest = { st: "jsconf rules" }; + t.nest.toObject().should.eql({ st: "jsconf rules" }); + t.nest.st.should.eql("jsconf rules"); + + t.save(function (err) { + should.strictEqual(err, null); + db.close(); + }) + }, + + 're-saving object with pre-existing null nested object': function(){ + var db = start() + + var schema = new Schema({ + nest: { + st: String + , yep: String + } + }); + + mongoose.model('NestedStringC', schema); + var T = db.model('NestedStringC', collection); + + var t = new T({ nest: null }); + + t.save(function (err) { + should.strictEqual(err, null); + + t.nest = { st: "jsconf rules", yep: "it does" }; + t.save(function (err) { + should.strictEqual(err, null); + + T.findById(t.id, function (err, t) { + should.strictEqual(err, null); + t.nest.st.should.eql("jsconf rules"); + t.nest.yep.should.eql("it does"); + + t.nest = null; + t.save(function (err) { + should.strictEqual(err, null); + should.strictEqual(t._doc.nest, null); + db.close(); + }); + }); + }); + }); + }, + + 'pushing to a nested array of Mixed works on existing doc': function () { + var db = start(); + + mongoose.model('MySchema', new Schema({ + nested: { + arrays: [] + } + })); + + var DooDad = db.model('MySchema') + , doodad = new DooDad({ nested: { arrays: [] } }) + , date = 1234567890; + + doodad.nested.arrays.push(["+10", "yup", date]); + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad.nested.arrays.toObject().should.eql([['+10','yup',date]]); + + doodad.nested.arrays.push(["another", 1]); + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad + .nested + .arrays + .toObject() + .should.eql([['+10','yup',date], ["another", 1]]); + + db.close(); + }); + }); + }) + }); + + }, + + 'directly setting nested props works when property is named "type"': function () { + var db = start(); + + function def () { + return [{ x: 1 }, { x: 2 }, { x:3 }] + } + + mongoose.model('MySchema2', new Schema({ + nested: { + type: { type: String, default: 'yep' } + , array: { + type: Array, default: def + } + } + })); + + var DooDad = db.model('MySchema2', collection) + , doodad = new DooDad() + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + + doodad.nested.type.should.eql("yep"); + doodad.nested.array.toObject().should.eql([{x:1},{x:2},{x:3}]); + + doodad.nested.type = "nope"; + doodad.nested.array = ["some", "new", "stuff"]; + + doodad.save(function (err) { + should.strictEqual(err, null); + + DooDad.findById(doodad._id, function (err, doodad) { + should.strictEqual(err, null); + db.close(); + + doodad.nested.type.should.eql("nope"); + + doodad + .nested + .array + .toObject() + .should.eql(["some", "new", "stuff"]); + + }); + }); + }) + }); + }, + + 'system.profile should be a default model': function () { + var Profile = mongoose.model('system.profile'); + Profile.schema.paths.ts.should.be.a('object'); + Profile.schema.paths.info.should.be.a('object'); + Profile.schema.paths.millis.should.be.a('object'); + + Profile.schema.paths.op.should.be.a('object'); + Profile.schema.paths.ns.should.be.a('object'); + Profile.schema.paths.query.should.be.a('object'); + Profile.schema.paths.updateobj.should.be.a('object'); + Profile.schema.paths.ntoreturn.should.be.a('object'); + Profile.schema.paths.nreturned.should.be.a('object'); + Profile.schema.paths.nscanned.should.be.a('object'); + Profile.schema.paths.responseLength.should.be.a('object'); + Profile.schema.paths.client.should.be.a('object'); + Profile.schema.paths.user.should.be.a('object'); + Profile.schema.paths.idhack.should.be.a('object'); + Profile.schema.paths.scanAndOrder.should.be.a('object'); + Profile.schema.paths.keyUpdates.should.be.a('object'); + should.strictEqual(undefined, Profile.schema.paths._id); + should.strictEqual(undefined, Profile.schema.virtuals.id); + + var db = start(); + Profile = db.model('system.profile'); + db.close(); + Profile.schema.paths.ts.should.be.a('object'); + Profile.schema.paths.info.should.be.a('object'); + Profile.schema.paths.millis.should.be.a('object'); + Profile.schema.paths.op.should.be.a('object'); + Profile.schema.paths.ns.should.be.a('object'); + Profile.schema.paths.query.should.be.a('object'); + Profile.schema.paths.updateobj.should.be.a('object'); + Profile.schema.paths.ntoreturn.should.be.a('object'); + Profile.schema.paths.nreturned.should.be.a('object'); + Profile.schema.paths.nscanned.should.be.a('object'); + Profile.schema.paths.responseLength.should.be.a('object'); + Profile.schema.paths.client.should.be.a('object'); + Profile.schema.paths.user.should.be.a('object'); + Profile.schema.paths.idhack.should.be.a('object'); + Profile.schema.paths.scanAndOrder.should.be.a('object'); + Profile.schema.paths.keyUpdates.should.be.a('object'); + should.strictEqual(undefined, Profile.schema.paths._id); + should.strictEqual(undefined, Profile.schema.virtuals.id); + + // can override the default + db = start(); + // reset Mongoose state + delete db.base.modelSchemas['system.profile'] + delete db.base.models['system.profile'] + delete db.models['system.profile']; + db.close(); + // test + var over = db.model('system.profile', new Schema({ name: String })); + over.schema.paths.name.should.be.a('object'); + should.strictEqual(undefined, over.schema.paths.ts); + // reset + delete db.base.modelSchemas['system.profile'] + delete db.base.models['system.profile'] + delete db.models['system.profile']; + }, + + 'setting profiling levels': function () { + var db = start(); + db.setProfiling(3, function (err) { + err.message.should.eql('Invalid profiling level: 3'); + db.setProfiling('fail', function (err) { + err.message.should.eql('Invalid profiling level: fail'); + db.setProfiling(2, function (err, doc) { + should.strictEqual(err, null); + db.setProfiling(1, 50, function (err, doc) { + should.strictEqual(err, null); + doc.was.should.eql(2); + db.setProfiling(0, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.was.should.eql(1); + doc.slowms.should.eql(50); + }); + }); + }); + }); + }); + }, + + 'test post hooks on embedded documents': function(){ + var save = false, + init = false, + remove = false; + + var EmbeddedSchema = new Schema({ + title : String + }); + + var ParentSchema = new Schema({ + embeds : [EmbeddedSchema] + }); + + EmbeddedSchema.post('save', function(next){ + save = true; + }); + + // Don't know how to test those on a embedded document. + //EmbeddedSchema.post('init', function () { + //init = true; + //}); + + //EmbeddedSchema.post('remove', function () { + //remove = true; + //}); + + mongoose.model('Parent', ParentSchema); + + var db = start(), + Parent = db.model('Parent'); + + var parent = new Parent(); + + parent.embeds.push({title: 'Testing post hooks for embedded docs'}); + + parent.save(function(err){ + db.close(); + should.strictEqual(err, null); + save.should.be.true; + }); + }, + + 'console.log shows helpful values': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var date = new Date(1305730951086); + var id0 = new DocumentObjectId('4dd3e169dbfb13b4570000b9'); + var id1 = new DocumentObjectId('4dd3e169dbfb13b4570000b6'); + var id2 = new DocumentObjectId('4dd3e169dbfb13b4570000b7'); + var id3 = new DocumentObjectId('4dd3e169dbfb13b4570000b8'); + + var post = new BlogPost({ + title: 'Test' + , _id: id0 + , date: date + , numbers: [5,6,7] + , owners: [id1] + , meta: { visitors: 45 } + , comments: [ + { _id: id2, title: 'my comment', date: date, body: 'this is a comment' }, + { _id: id3, title: 'the next thang', date: date, body: 'this is a comment too!' }] + }); + + db.close(); + + var a = '{ meta: { visitors: 45 },\n numbers: [ 5, 6, 7 ],\n owners: [ 4dd3e169dbfb13b4570000b6 ],\n comments: \n [{ _id: 4dd3e169dbfb13b4570000b7,\n comments: [],\n body: \'this is a comment\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'my comment\' }\n { _id: 4dd3e169dbfb13b4570000b8,\n comments: [],\n body: \'this is a comment too!\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'the next thang\' }],\n _id: 4dd3e169dbfb13b4570000b9,\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'Test\' }' + + var out = post.inspect(); + ;/meta: { visitors: 45 }/.test(out).should.be.true; + ;/numbers: \[ 5, 6, 7 \]/.test(out).should.be.true; + ;/date: Wed, 18 May 2011 15:02:31 GMT/.test(out).should.be.true; + ;/activePaths:/.test(out).should.be.false; + ;/_atomics:/.test(out).should.be.false; + }, + + 'path can be used as pathname': function () { + var db = start() + , P = db.model('pathnametest', new Schema({ path: String })) + db.close(); + + var threw = false; + try { + new P({ path: 'i should not throw' }); + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + 'when mongo is down, save callback should fire with err if auto_reconnect is disabled': function () { + var db = start({ server: { auto_reconnect: false }}); + var T = db.model('Thing', new Schema({ type: String })); + db.on('open', function () { + var t = new T({ type: "monster" }); + + var worked = false; + t.save(function (err) { + worked = true; + err.message.should.eql('no open connections'); + }); + + process.nextTick(function () { + db.close(); + }); + + setTimeout(function () { + worked.should.be.true; + }, 500); + }); + }, + + //'when mongo is down, auto_reconnect should kick in and db operation should succeed': function () { + //var db = start(); + //var T = db.model('Thing', new Schema({ type: String })); + //db.on('open', function () { + //var t = new T({ type: "monster" }); + + //var worked = false; + //t.save(function (err) { + //should.strictEqual(err, null); + //worked = true; + //}); + + //process.nextTick(function () { + //db.close(); + //}); + + //setTimeout(function () { + //worked.should.be.true; + //}, 500); + //}); + //}, + + 'subdocuments with changed values should persist the values': function () { + var db = start() + var Subdoc = new Schema({ name: String, mixed: Schema.Types.Mixed }); + var T = db.model('SubDocMixed', new Schema({ subs: [Subdoc] })); + + var t = new T({ subs: [{ name: "Hubot", mixed: { w: 1, x: 2 }}] }); + t.subs[0].name.should.equal("Hubot"); + t.subs[0].mixed.w.should.equal(1); + t.subs[0].mixed.x.should.equal(2); + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + should.strictEqual(null, err); + t.subs[0].name.should.equal("Hubot"); + t.subs[0].mixed.w.should.equal(1); + t.subs[0].mixed.x.should.equal(2); + + var sub = t.subs[0]; + sub.name = "Hubot1"; + sub.name.should.equal("Hubot1"); + sub.isModified('name').should.be.true; + t.modified.should.be.true; + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + should.strictEqual(null, err); + should.strictEqual(t.subs[0].name, "Hubot1"); + + var sub = t.subs[0]; + sub.mixed.w = 5; + sub.mixed.w.should.equal(5); + sub.isModified('mixed').should.be.false; + sub.commit('mixed'); + sub.isModified('mixed').should.be.true; + sub.modified.should.be.true; + t.modified.should.be.true; + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(t.subs[0].mixed.w, 5); + }) + }) + }); + }); + }) + }) + }, + + 'RegExps can be saved': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost({ mixed: { rgx: /^asdf$/ } }); + post.mixed.rgx.should.be.instanceof(RegExp); + post.mixed.rgx.source.should.eql('^asdf$'); + post.save(function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, post) { + db.close(); + should.strictEqual(err, null); + post.mixed.rgx.should.be.instanceof(RegExp); + post.mixed.rgx.source.should.eql('^asdf$'); + }); + }); + }, + + 'null defaults are allowed': function () { + var db = start(); + var T = db.model('NullDefault', new Schema({ name: { type: String, default: null }}), collection); + var t = new T(); + + should.strictEqual(null, t.name); + + t.save(function (err) { + should.strictEqual(null, err); + + T.findById(t._id, function (err, t) { + db.close(); + should.strictEqual(null, err); + should.strictEqual(null, t.name); + }); + }); + }, + + // GH-365, GH-390, GH-422 + 'test that setters are used on embedded documents': function () { + var db = start(); + + function setLat (val) { + return parseInt(val); + } + + var tick = 0; + function uptick () { + return ++tick; + } + + var Location = new Schema({ + lat: { type: Number, default: 0, set: setLat} + , long: { type: Number, set: uptick } + }); + + var Deal = new Schema({ + title: String + , locations: [Location] + }); + + Location = db.model('Location', Location, 'locations_' + random()); + Deal = db.model('Deal', Deal, 'deals_' + random()); + + var location = new Location({lat: 1.2, long: 10}); + location.lat.valueOf().should.equal(1); + location.long.valueOf().should.equal(1); + + var deal = new Deal({title: "My deal", locations: [{lat: 1.2, long: 33}]}); + deal.locations[0].lat.valueOf().should.equal(1); + deal.locations[0].long.valueOf().should.equal(2); + + deal.save(function (err) { + should.strictEqual(err, null); + Deal.findById(deal._id, function (err, deal) { + db.close(); + should.strictEqual(err, null); + deal.locations[0].lat.valueOf().should.equal(1); + // GH-422 + deal.locations[0].long.valueOf().should.equal(2); + }); + }); + }, + + // GH-289 + 'test that pre-init middleware has access to the true ObjectId when used with querying': function () { + var db = start() + , PreInitSchema = new Schema({}) + , preId; + PreInitSchema.pre('init', function (next) { + preId = this._id; + next(); + }); + var PreInit = db.model('PreInit', PreInitSchema, 'pre_inits' + random()); + + var doc = new PreInit(); + doc.save( function (err) { + should.strictEqual(err, null); + PreInit.findById(doc._id, function (err, found) { + db.close(); + should.strictEqual(err, null); + should.strictEqual(undefined, preId); + }); + }); + }, + + // Demonstration showing why GH-261 is a misunderstanding + 'a single instantiated document should be able to update its embedded documents more than once': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + var post = new BlogPost(); + post.comments.push({title: 'one'}); + post.save(function (err) { + should.strictEqual(err, null); + post.comments[0].title.should.eql('one'); + post.comments[0].title = 'two'; + post.comments[0].title.should.eql('two'); + post.save( function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, found) { + should.strictEqual(err, null); + db.close(); + should.strictEqual(err, null); + found.comments[0].title.should.eql('two'); + }); + }); + }); + }, + + 'defining a method on a Schema corresponding to an EmbeddedDocument should generate an instance method for the ED': function () { + var db = start(); + var ChildSchema = new Schema({ name: String }); + ChildSchema.method('talk', function () { + return 'gaga'; + }); + + var ParentSchema = new Schema({ + children: [ChildSchema] + }); + + var ChildA = db.model('ChildA', ChildSchema, 'children_' + random()); + var ParentA = db.model('ParentA', ParentSchema, 'parents_' + random()); + + var c = new ChildA; + c.talk.should.be.a('function'); + + var p = new ParentA(); + p.children.push({}); + p.children[0].talk.should.be.a('function'); + db.close(); + }, + + 'Model#save should throw errors if a callback is not passed': function () { + var db = start(); + + var DefaultErrSchema = new Schema({}); + DefaultErrSchema.pre('save', function (next) { + try { + next(new Error); + } catch (error) { + // throws b/c nothing is listening to the error event + db.close(); + error.should.be.instanceof(Error); + } + }); + var DefaultErr = db.model('DefaultErr1', DefaultErrSchema, 'default_err_' + random()); + new DefaultErr().save(); + }, + + 'Model#save should emit an error on its db if a callback is not passed to it': function () { + var db = start(); + + db.on('error', function (err) { + db.close(); + err.should.be.an.instanceof(Error); + }); + + var DefaultErrSchema = new Schema({}); + DefaultErrSchema.pre('save', function (next) { + next(new Error); + }); + var DefaultErr = db.model('DefaultErr2', DefaultErrSchema, 'default_err_' + random()); + new DefaultErr().save(); + }, + + // once hooks-js merges our fix this will pass + 'calling next() after a thrown error should not work': function () { + var db = start(); + + var s = new Schema({}); + s.methods.funky = function () { + should.strictEqual(false, true, 'reached unreachable code'); + } + + s.pre('funky', function (next) { + db.close(); + try { + next(new Error); + } catch (error) { + error.should.be.instanceof(Error); + next(); + // throws b/c nothing is listening to the error event + } + }); + var Kaboom = db.model('wowNext2xAndThrow', s, 'next2xAndThrow' + random()); + new Kaboom().funky(); + }, + + 'ensureIndex error should emit on the db': function () { + var db = start(); + + db.on('error', function (err) { + /^E11000 duplicate key error index:/.test(err.message).should.equal(true); + db.close(); + }); + + var schema = new Schema({ name: { type: String } }) + , Test = db.model('IndexError', schema, "x"+random()); + + Test.create({ name: 'hi' }, { name: 'hi' }, function (err) { + should.strictEqual(err, null); + Test.schema.index({ name: 1 }, { unique: true }); + Test.init(); + }); + }, + + 'backward compatibility with conflicted data in the db': function () { + var db = start(); + var M = db.model('backwardDataConflict', new Schema({ namey: { first: String, last: String }})); + var m = new M({ namey: "[object Object]" }); + m.namey = { first: 'GI', last: 'Joe' };// <-- should overwrite the string + m.save(function (err) { + db.close(); + should.strictEqual(err, null); + should.strictEqual('GI', m.namey.first); + should.strictEqual('Joe', m.namey.last); + }); + }, + + '#push should work on EmbeddedDocuments more than 2 levels deep': function () { + var db = start() + , Post = db.model('BlogPost', collection) + , Comment = db.model('CommentNesting', Comments, collection); + + var p =new Post({ title: "comment nesting" }); + var c1 =new Comment({ title: "c1" }); + var c2 =new Comment({ title: "c2" }); + var c3 =new Comment({ title: "c3" }); + + p.comments.push(c1); + c1.comments.push(c2); + c2.comments.push(c3); + + p.save(function (err) { + should.strictEqual(err, null); + + Post.findById(p._id, function (err, p) { + should.strictEqual(err, null); + + var c4=new Comment({ title: "c4" }); + p.comments[0].comments[0].comments[0].comments.push(c4); + p.save(function (err) { + should.strictEqual(err, null); + + Post.findById(p._id, function (err, p) { + db.close(); + should.strictEqual(err, null); + p.comments[0].comments[0].comments[0].comments[0].title.should.equal('c4'); + }); + }); + }); + }) + + }, + + 'test standalone invalidate': function() { + var db = start() + , InvalidateSchema = null + , Post = null + , post = null; + + InvalidateSchema = new Schema({ + prop: { type: String }, + }); + + mongoose.model('InvalidateSchema', InvalidateSchema); + + Post = db.model('InvalidateSchema'); + post = new Post(); + post.set({baz: 'val'}); + post.invalidate('baz', 'reason'); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + + err.errors.baz.should.be.an.instanceof(ValidatorError); + err.errors.baz.message.should.equal('Validator "reason" failed for path baz'); + err.errors.baz.type.should.equal('reason'); + err.errors.baz.path.should.equal('baz'); + + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test simple validation middleware': function() { + var db = start() + , ValidationMiddlewareSchema = null + , Post = null + , post = null; + + ValidationMiddlewareSchema = new Schema({ + baz: { type: String } + }); + + ValidationMiddlewareSchema.pre('validate', function(next) { + if (this.get('baz') == 'bad') { + this.invalidate('baz', 'bad'); + } + next(); + }); + + mongoose.model('ValidationMiddleware', ValidationMiddlewareSchema); + + Post = db.model('ValidationMiddleware'); + post = new Post(); + post.set({baz: 'bad'}); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.baz.type.should.equal('bad'); + err.errors.baz.path.should.equal('baz'); + + post.set('baz', 'good'); + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test async validation middleware': function() { + var db = start() + , AsyncValidationMiddlewareSchema = null + , Post = null + , post = null; + + AsyncValidationMiddlewareSchema = new Schema({ + prop: { type: String } + }); + + AsyncValidationMiddlewareSchema.pre('validate', true, function(next, done) { + var self = this; + setTimeout(function() { + if (self.get('prop') == 'bad') { + self.invalidate('prop', 'bad'); + } + done(); + }, 50); + next(); + }); + + mongoose.model('AsyncValidationMiddleware', AsyncValidationMiddlewareSchema); + + Post = db.model('AsyncValidationMiddleware'); + post = new Post(); + post.set({prop: 'bad'}); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + err.errors.prop.type.should.equal('bad'); + err.errors.prop.path.should.equal('prop'); + + post.set('prop', 'good'); + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'test complex validation middleware': function() { + var db = start() + , ComplexValidationMiddlewareSchema = null + , Post = null + , post = null + , abc = function(v) { + return v === 'abc'; + }; + + ComplexValidationMiddlewareSchema = new Schema({ + baz: { type: String }, + abc: { type: String, validate: [abc, 'must be abc'] }, + test: { type: String, validate: [/test/, 'must also be abc'] }, + required: { type: String, required: true } + }); + + ComplexValidationMiddlewareSchema.pre('validate', true, function(next, done) { + var self = this; + setTimeout(function() { + if (self.get('baz') == 'bad') { + self.invalidate('baz', 'bad'); + } + done(); + }, 50); + next(); + }); + + mongoose.model('ComplexValidationMiddleware', ComplexValidationMiddlewareSchema); + + Post = db.model('ComplexValidationMiddleware'); + post = new Post(); + post.set({ + baz: 'bad', + abc: 'not abc', + test: 'fail' + }); + + post.save(function(err){ + err.should.be.an.instanceof(MongooseError); + err.should.be.an.instanceof(ValidationError); + Object.keys(err.errors).length.should.equal(4); + err.errors.baz.should.be.an.instanceof(ValidatorError); + err.errors.baz.type.should.equal('bad'); + err.errors.baz.path.should.equal('baz'); + err.errors.abc.should.be.an.instanceof(ValidatorError); + err.errors.abc.type.should.equal('must be abc'); + err.errors.abc.path.should.equal('abc'); + err.errors.test.should.be.an.instanceof(ValidatorError); + err.errors.test.type.should.equal('must also be abc'); + err.errors.test.path.should.equal('test'); + err.errors.required.should.be.an.instanceof(ValidatorError); + err.errors.required.type.should.equal('required'); + err.errors.required.path.should.equal('required'); + + post.set({ + baz: 'good', + abc: 'abc', + test: 'test', + required: 'here' + }); + + post.save(function(err){ + db.close(); + should.strictEqual(err, null); + }); + }); + }, + + 'Model.create can accept an array': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create([{ title: 'hi'}, { title: 'bye'}], function (err, post1, post2) { + db.close(); + should.strictEqual(err, null); + post1.get('_id').should.be.an.instanceof(DocumentObjectId); + post2.get('_id').should.be.an.instanceof(DocumentObjectId); + + post1.title.should.equal('hi'); + post2.title.should.equal('bye'); + }); + }, + + 'Model.create when passed no docs still fires callback': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create(function (err, a) { + db.close(); + should.strictEqual(err, null); + should.not.exist(a); + }); + }, + + 'Model.create fires callback when an empty array is passed': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection); + + BlogPost.create([], function (err, a) { + db.close(); + should.strictEqual(err, null); + should.not.exist(a); + }); + }, + + 'enhanced date casting test (datejs - gh-502)': function () { + var db = start() + + Date.prototype.toObject = function() { + return { + millisecond: 86 + , second: 42 + , minute: 47 + , hour: 17 + , day: 13 + , week: 50 + , month: 11 + , year: 2011 + }; + }; + + var S = new Schema({ + name: String + , description: String + , sabreId: String + , data: { + lastPrice: Number + , comm: String + , curr: String + , rateName: String + } + , created: { type: Date, default: Date.now } + , valid: { type: Boolean, default: true } + }); + + var M = db.model('gh502', S); + + var m = new M; + m.save(function (err) { + should.strictEqual(err, null); + M.findById(m._id, function (err, m) { + should.strictEqual(err, null); + m.save(function (err) { + should.strictEqual(err, null); + M.remove(function (err) { + delete Date.prototype.toObject; + db.close(); + }); + }); + }); + }); + }, + + 'should not persist non-schema props': function () { + var db = start() + , B = db.model('BlogPost', collection) + + var b = new B; + b.whateveriwant = 10; + b.save(function (err) { + should.strictEqual(null, err); + B.collection.findOne({ _id: b._id }, function (err, doc) { + db.close(); + should.strictEqual(null, err); + ;('whateveriwant' in doc).should.be.false; + }); + }); + }, + + 'should not throw range error when using Number _id and saving existing doc': function () { + var db =start(); + + var T = new Schema({ _id: Number, a: String }); + + var D = db.model('Testing691', T, 'asdf' + random()); + + var d = new D({ _id: 1 }); + d.save(function (err) { + should.strictEqual(null, err); + + D.findById(d._id, function (err, d) { + should.strictEqual(null, err); + + d.a = 'yo'; + d.save(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }, + + 'number of affected docs should be returned when saving': function () { + var db = start() + var schema = new Schema({ name: String }); + var S = db.model('AffectedDocsAreReturned', schema); + var s = new S({ name: 'aaron' }); + s.save(function (err, doc, affected) { + should.strictEqual(null, err); + affected.should.equal(1); + s.name = 'heckmanananananana'; + s.save(function (err, doc, affected) { + db.close(); + should.strictEqual(null, err); + affected.should.equal(1); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/model.update.test.js b/node_modules/mongoose/test/model.update.test.js new file mode 100644 index 0000000..8e795d5 --- /dev/null +++ b/node_modules/mongoose/test/model.update.test.js @@ -0,0 +1,526 @@ + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Query = require('../lib/query') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , CastError = SchemaType.CastError + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + , ObjectId = Schema.ObjectId + , DocumentObjectId = mongoose.Types.ObjectId + , DocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = mongoose.Types.Embedded + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , MongooseError = mongoose.Error; + +/** + * Setup. + */ + +var Comments = new Schema(); + +Comments.add({ + title : String + , date : Date + , body : String + , comments : [Comments] +}); + +var BlogPost = new Schema({ + title : String + , author : String + , slug : String + , date : Date + , meta : { + date : Date + , visitors : Number + } + , published : Boolean + , mixed : {} + , numbers : [Number] + , owners : [ObjectId] + , comments : [Comments] +}); + +BlogPost.virtual('titleWithAuthor') + .get(function () { + return this.get('title') + ' by ' + this.get('author'); + }) + .set(function (val) { + var split = val.split(' by '); + this.set('title', split[0]); + this.set('author', split[1]); + }); + +BlogPost.method('cool', function(){ + return this; +}); + +BlogPost.static('woot', function(){ + return this; +}); + +mongoose.model('BlogPost', BlogPost); + +var collection = 'blogposts_' + random(); + +var strictSchema = new Schema({ name: String }, { strict: true }); +mongoose.model('UpdateStrictSchema', strictSchema); + +module.exports = { + + 'test updating documents': function () { + var db = start() + , BlogPost = db.model('BlogPost', collection) + , title = 'Tobi ' + random() + , author = 'Brian ' + random() + , newTitle = 'Woot ' + random() + , id0 = new DocumentObjectId + , id1 = new DocumentObjectId + + var post = new BlogPost(); + post.set('title', title); + post.author = author; + post.meta.visitors = 0; + post.date = new Date; + post.published = true; + post.mixed = { x: 'ex' }; + post.numbers = [4,5,6,7]; + post.owners = [id0, id1]; + post.comments = [{ body: 'been there' }, { body: 'done that' }]; + + post.save(function (err) { + should.strictEqual(err, null); + BlogPost.findById(post._id, function (err, cf) { + should.strictEqual(err, null); + cf.title.should.equal(title); + cf.author.should.equal(author); + cf.meta.visitors.valueOf().should.eql(0); + cf.date.should.eql(post.date); + cf.published.should.be.true; + cf.mixed.x.should.equal('ex'); + cf.numbers.toObject().should.eql([4,5,6,7]); + cf.owners.length.should.equal(2); + cf.owners[0].toString().should.equal(id0.toString()); + cf.owners[1].toString().should.equal(id1.toString()); + cf.comments.length.should.equal(2); + cf.comments[0].body.should.eql('been there'); + cf.comments[1].body.should.eql('done that'); + should.exist(cf.comments[0]._id); + should.exist(cf.comments[1]._id); + cf.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + cf.comments[1]._id.should.be.an.instanceof(DocumentObjectId); + + var update = { + title: newTitle // becomes $set + , $inc: { 'meta.visitors': 2 } + , $set: { date: new Date } + , published: false // becomes $set + , 'mixed': { x: 'ECKS', y: 'why' } // $set + , $pullAll: { 'numbers': [4, 6] } + , $pull: { 'owners': id0 } + , 'comments.1.body': 8 // $set + } + + BlogPost.update({ title: title }, update, function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, up) { + should.strictEqual(err, null); + up.title.should.equal(newTitle); + up.author.should.equal(author); + up.meta.visitors.valueOf().should.equal(2); + up.date.toString().should.equal(update.$set.date.toString()); + up.published.should.eql(false); + up.mixed.x.should.equal('ECKS'); + up.mixed.y.should.equal('why'); + up.numbers.toObject().should.eql([5,7]); + up.owners.length.should.equal(1); + up.owners[0].toString().should.eql(id1.toString()); + up.comments[0].body.should.equal('been there'); + up.comments[1].body.should.equal('8'); + should.exist(up.comments[0]._id); + should.exist(up.comments[1]._id); + up.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + up.comments[1]._id.should.be.an.instanceof(DocumentObjectId); + + var update2 = { + 'comments.body': 'fail' + } + + BlogPost.update({ _id: post._id }, update2, function (err) { + should.strictEqual(!!err, true); + ;/^can't append to array using string field name \[body\]/.test(err.message).should.be.true; + BlogPost.findById(post, function (err, p) { + should.strictEqual(null, err); + + var update3 = { + $pull: 'fail' + } + + BlogPost.update({ _id: post._id }, update3, function (err) { + should.strictEqual(!!err, true); + ;/Invalid atomic update value/.test(err.message).should.be.true; + + var update4 = { + $inc: { idontexist: 1 } + } + + // should not overwrite doc when no valid paths are submitted + BlogPost.update({ _id: post._id }, update4, function (err) { + should.strictEqual(err, null); + + BlogPost.findById(post._id, function (err, up) { + should.strictEqual(err, null); + + up.title.should.equal(newTitle); + up.author.should.equal(author); + up.meta.visitors.valueOf().should.equal(2); + up.date.toString().should.equal(update.$set.date.toString()); + up.published.should.eql(false); + up.mixed.x.should.equal('ECKS'); + up.mixed.y.should.equal('why'); + up.numbers.toObject().should.eql([5,7]); + up.owners.length.should.equal(1); + up.owners[0].toString().should.eql(id1.toString()); + up.comments[0].body.should.equal('been there'); + up.comments[1].body.should.equal('8'); + // non-schema data was still stored in mongodb + should.strictEqual(1, up._doc.idontexist); + + update5(post); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + function update5 (post) { + var update = { + comments: [{ body: 'worked great' }] + , $set: {'numbers.1': 100} + , $inc: { idontexist: 1 } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(err, null); + + // get the underlying doc + BlogPost.collection.findOne({ _id: post._id }, function (err, doc) { + should.strictEqual(err, null); + + var up = new BlogPost; + up.init(doc); + up.comments.length.should.equal(1); + up.comments[0].body.should.equal('worked great'); + should.strictEqual(true, !! doc.comments[0]._id); + up.meta.visitors.valueOf().should.equal(2); + up.mixed.x.should.equal('ECKS'); + up.numbers.toObject().should.eql([5,100]); + up.numbers[1].valueOf().should.eql(100); + + doc.idontexist.should.equal(2); + doc.numbers[1].should.eql(100); + + update6(post); + }); + }); + } + + function update6 (post) { + var update = { + $pushAll: { comments: [{ body: 'i am number 2' }, { body: 'i am number 3' }] } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(3); + ret.comments[1].body.should.equal('i am number 2'); + should.strictEqual(true, !! ret.comments[1]._id); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[2].body.should.equal('i am number 3'); + should.strictEqual(true, !! ret.comments[2]._id); + ret.comments[2]._id.should.be.an.instanceof(DocumentObjectId) + + update7(post); + }) + }); + } + + // gh-542 + function update7 (post) { + var update = { + $pull: { comments: { body: 'i am number 2' } } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[1].body.should.equal('i am number 3'); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + + update8(post); + }) + }); + } + + // gh-479 + function update8 (post) { + function a () {}; + a.prototype.toString = function () { return 'MongoDB++' } + var crazy = new a; + + var update = { + $addToSet: { 'comments.$.comments': { body: 'The Ring Of Power' } } + , $set: { 'comments.$.title': crazy } + } + + BlogPost.update({ _id: post._id, 'comments.body': 'worked great' }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[0].title.should.equal('MongoDB++'); + should.strictEqual(true, !! ret.comments[0].comments); + ret.comments[0].comments.length.should.equal(1); + should.strictEqual(ret.comments[0].comments[0].body, 'The Ring Of Power'); + ret.comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[0].comments[0]._id.should.be.an.instanceof(DocumentObjectId) + ret.comments[1].body.should.equal('i am number 3'); + should.strictEqual(undefined, ret.comments[1].title); + ret.comments[1]._id.should.be.an.instanceof(DocumentObjectId) + + update9(post); + }) + }); + } + + // gh-479 + function update9 (post) { + var update = { + $inc: { 'comments.$.newprop': '1' } + , $set: { date: (new Date).getTime() } // check for single val casting + } + + BlogPost.update({ _id: post._id, 'comments.body': 'worked great' }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret._doc.comments[0]._doc.newprop.should.equal(1); + should.strictEqual(undefined, ret._doc.comments[1]._doc.newprop); + ret.date.should.be.an.instanceof(Date); + ret.date.toString().should.equal(update.$set.date.toString()); + + update10(post, ret); + }) + }); + } + + // gh-545 + function update10 (post, last) { + var owner = last.owners[0]; + + var update = { + $addToSet: { 'owners': owner } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(1); + ret.owners[0].toString().should.eql(owner.toString()); + + update11(post, ret); + }) + }); + } + + // gh-545 + function update11 (post, last) { + var owner = last.owners[0] + , newowner = new DocumentObjectId + + var update = { + $addToSet: { 'owners': { $each: [owner, newowner] }} + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(2); + ret.owners[0].toString().should.eql(owner.toString()); + ret.owners[1].toString().should.eql(newowner.toString()); + + update12(post, newowner); + }) + }); + } + + // gh-574 + function update12 (post, newowner) { + var update = { + $pop: { 'owners': -1 } + , $unset: { title: 1 } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.owners.length.should.equal(1); + ret.owners[0].toString().should.eql(newowner.toString()); + should.strictEqual(undefined, ret.title); + + update13(post, ret); + }) + }); + } + + function update13 (post, ret) { + var update = { + $set: { + 'comments.0.comments.0.date': '11/5/2011' + , 'comments.1.body': 9000 + } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + ret.comments[0].body.should.equal('worked great'); + ret.comments[1].body.should.equal('9000'); + ret.comments[0].comments[0].date.toString().should.equal(new Date('11/5/2011').toString()) + ret.comments[1].comments.length.should.equal(0); + + update14(post, ret); + }) + }); + } + + // gh-542 + function update14 (post, ret) { + var update = { + $pull: { comments: { _id: ret.comments[0].id } } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(1); + ret.comments[0].body.should.equal('9000'); + update15(post, ret); + }) + }); + } + + function update15 (post, ret) { + var update = { + $pull: { comments: { body: { $in: [ret.comments[0].body] }} } + } + + BlogPost.update({ _id: post._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(0); + update16(post, ret); + }) + }); + } + + function update16 (post, ret) { + ret.comments.$pushAll([{body: 'hi'}, {body:'there'}]); + ret.save(function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + should.strictEqual(null, err); + ret.comments.length.should.equal(2); + + var update = { + $pull: { comments: { body: { $nin: ['there'] }} } + } + + BlogPost.update({ _id: ret._id }, update, function (err) { + should.strictEqual(null, err); + BlogPost.findById(post, function (err, ret) { + db.close(); + should.strictEqual(null, err); + ret.comments.length.should.equal(1); + }) + }); + }) + }); + } + }, + + // gh-699 + 'Model._castUpdate should honor strict schemas': function () { + var db = start(); + var S = db.model('UpdateStrictSchema'); + db.close(); + + var doc = S.find()._castUpdate({ ignore: true }); + Object.keys(doc.$set).length.should.equal(0); + }, + + 'Model.update should honor strict schemas': function () { + var db = start(); + var S = db.model('UpdateStrictSchema'); + var s = new S({ name: 'orange crush' }); + + s.save(function (err) { + should.strictEqual(null, err); + + S.update({ _id: s._id }, { ignore: true }, function (err, affected) { + should.strictEqual(null, err); + affected.should.equal(1); + + S.findById(s._id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + should.not.exist(doc.ignore); + should.not.exist(doc._doc.ignore); + }); + }); + }); + }, + + 'model.update passes number of affected documents': function () { + var db = start() + , B = db.model('BlogPost', 'wwwwowowo'+random()) + + B.create({ title: 'one'},{title:'two'},{title:'three'}, function (err) { + should.strictEqual(null, err); + B.update({}, { title: 'newtitle' }, { multi: true }, function (err, affected) { + db.close(); + should.strictEqual(null, err); + affected.should.equal(3); + }); + }); + } + +} diff --git a/node_modules/mongoose/test/namedscope.test.js b/node_modules/mongoose/test/namedscope.test.js new file mode 100644 index 0000000..0160404 --- /dev/null +++ b/node_modules/mongoose/test/namedscope.test.js @@ -0,0 +1,253 @@ +//Query.prototype.where(criteria, callback) +//Query.prototype.where(path, val, callback) +// +//UserNS.namedScope({ +// twenties: Query.where('age').gte(20).lt(30) +// , male: Query.where('gender', 'male') +// , lastLogin: Query.where('lastLogin').get(+new Date - (24 * 3600 * 1000)) +//}); +// +//UserNS.find(twenties, male, active, function (err, found) { +//}); +// +//// twenties.male OR twenties.active +//UserNS.twenties.male.OR.twenties.active.find(callback); +//UserNS.find(twenties.male, twenties.active, function (err, found) { +//}); +// +//UserNS.find(olderThan(20).male, olderThan(30).active, function (err, found) { +//}); +//UserNS.twenties.male.active.remove(callback); + +/** + * Test dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , _24hours = 24 * 3600 * 1000; + +/** + * Setup. + */ + +var UserNSSchema = new Schema({ + age: Number + , gender: String + , lastLogin: Date +}); + +UserNSSchema.namedScope('olderThan', function (age) { + return this.where('age').gt(age); +}); + +UserNSSchema.namedScope('youngerThan', function (age) { + return this.where('age').lt(age); +}); + +UserNSSchema.namedScope('twenties').olderThan(19).youngerThan(30); + +UserNSSchema.namedScope('male').where('gender', 'male'); + +UserNSSchema.namedScope('female').where('gender', 'female'); + +UserNSSchema.namedScope('active', function () { + return this.where('lastLogin').gte(+new Date - _24hours) +}); + +mongoose.model('UserNS', UserNSSchema); + +// TODO Add in tests for using named scopes where findOne, update, remove +module.exports = { + 'basic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, _) { + should.strictEqual(err, null); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'dynamic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21} + , {age: 22} + , {age: 19} + , function (err, _) { + should.strictEqual(err, null); + UserNS.olderThan(20).find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'named scopes built on top of dynamic named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21} + , {age: 22} + , {age: 19} + , function (err, _) { + should.strictEqual(err, null); + UserNS.twenties.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(2); + }); + } + ); + }, + 'chaining named scopes should work, for find': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600} + , {age: 45, gender: 'male', lastLogin: +new Date} + , {age: 50, gender: 'female', lastLogin: +new Date} + , function (err, _, match, _) { + should.strictEqual(err, null); + UserNS.olderThan(40).active.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + found[0]._id.should.eql(match._id); + }); + } + ); + }, + + 'basic named scopes should work, for remove': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, _) { + UserNS.male.remove( function (err) { + should.strictEqual(!!err, false); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(0); + }); + }); + } + ); + }, + + // TODO multi-updates + 'basic named scopes should work, for update': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {gender: 'male'} + , {gender: 'male'} + , {gender: 'female'} + , function (err, male1, male2, female1) { + should.strictEqual(err, null); + UserNS.male.update({gender: 'female'}, function (err) { + should.strictEqual(err, null); + UserNS.female.find( function (err, found) { + should.strictEqual(err, null); + found.should.have.length(2); + UserNS.male.find( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.should.have.length(1); + }); + }); + }); + } + ); + }, + + 'chained named scopes should work, for findOne': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 100, gender: 'male'} + , function (err, maleCentenarian) { + should.strictEqual(err, null); + UserNS.male.olderThan(99).findOne( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(maleCentenarian._id); + }); + } + ); + }, + + 'hybrid use of chained named scopes and ad hoc querying should work': function () { + var db = start() + , UserNS = db.model('UserNS', 'users_' + random()); + UserNS.create( + {age: 100, gender: 'female'} + , function (err, femaleCentenarian) { + should.strictEqual(null, err); + UserNS.female.where('age').gt(99).findOne( function (err, found) { + db.close(); + should.strictEqual(err, null); + found.id; + found._id.should.eql(femaleCentenarian._id); + }); + } + ); + }, +// 'using chained named scopes in a find': function () { +// var db = start() +// , UserNS = db.model('UserNS', 'users_' + random()); +// UserNS.create({age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600}, function (err, _) { +// should.strictEqual(err, null); +// UserNS.create({age: 45, gender: 'male', lastLogin: +new Date}, function (err, match) { +// should.strictEqual(err, null); +// UserNS.create({age: 50, gender: 'female', lastLogin: +new Date}, function (err, _) { +// should.strictEqual(err, null); +// UserNS.find(olderThan(40).active.male, function (err, found) { +// db.close(); +// should.strictEqual(err, null); +// found.should.have.length(1); +// found[0]._id.should.eql(match._id); +// }); +// }); +// }); +// }); +// }, +// 'using multiple chained named scopes in a find to do an OR': function () { +// var db = start() +// , UserNS = db.model('UserNS', collection); +// var db = start() +// , UserNS = db.model('UserNS', collection); +// UserNS.create( +// {age: 21, gender: 'male', lastLogin: (+new Date) - _24hours - 3600} +// , {age: 45, gender: 'male', lastLogin: +new Date} +// , {age: 50, gender: 'female', lastLogin: +new Date} +// , {age: 35, gender: 'female', lastLogin: +new Date} +// , function (err, a, b, c, d) { +// should.strictEqual(err, null); +// UserNS.find(twenties.active.male, thirties.active.female, function (err, found) { +// db.close(); +// should.strictEqual(err, null); +// found.should.have.length(2); +// }); +// } +// ); +// }, +}; diff --git a/node_modules/mongoose/test/promise.test.js b/node_modules/mongoose/test/promise.test.js new file mode 100644 index 0000000..6d23a55 --- /dev/null +++ b/node_modules/mongoose/test/promise.test.js @@ -0,0 +1,167 @@ + +/** + * Module dependencies. + */ + +var should = require('should') + , Promise = require('../lib/promise'); + +/** + * Test. + */ + +module.exports = { + + 'test that events fire right away after complete()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.on('complete', function (a, b) { + a.should.eql('1'); + b.should.eql('2'); + called++; + }); + + promise.complete('1', '2'); + + promise.on('complete', function (a, b) { + a.should.eql('1'); + b.should.eql('2'); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test that events fire right away after error()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.on('err', function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + promise.error(new Error('booyah')); + + promise.on('err', function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test errback+callback from constructor': function (beforeExit) { + var promise = new Promise(function (err) { + err.should.be.an.instanceof(Error); + called++; + }) + , called = 0; + + promise.error(new Error('dawg')); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test errback+callback after complete()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.complete('woot'); + + promise.addBack(function (err, data){ + data.should.eql('woot'); + called++; + }); + + promise.addBack(function (err, data){ + should.strictEqual(err, null); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test errback+callback after error()ing': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.error(new Error('woot')); + + promise.addBack(function (err){ + err.should.be.an.instanceof(Error); + called++; + }); + + promise.addBack(function (err){ + err.should.be.an.instanceof(Error); + called++; + }); + + beforeExit(function () { + called.should.eql(2); + }); + }, + + 'test addCallback shortcut': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.addCallback(function (woot) { + should.strictEqual(woot, undefined); + called++; + }); + + promise.complete(); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test addErrback shortcut': function (beforeExit) { + var promise = new Promise() + , called = 0; + + promise.addErrback(function (err) { + err.should.be.an.instanceof(Error); + called++; + }); + + promise.error(new Error); + + beforeExit(function () { + called.should.eql(1); + }); + }, + + 'test return value of #on()': function () { + var promise = new Promise() + promise.on('jump', function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addCallback()': function () { + var promise = new Promise() + promise.addCallback(function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addErrback()': function () { + var promise = new Promise() + promise.addErrback(function(){}).should.be.an.instanceof(Promise); + }, + + 'test return value of #addBack()': function () { + var promise = new Promise() + promise.addBack(function(){}).should.be.an.instanceof(Promise); + } + +}; diff --git a/node_modules/mongoose/test/query.test.js b/node_modules/mongoose/test/query.test.js new file mode 100644 index 0000000..427d6cc --- /dev/null +++ b/node_modules/mongoose/test/query.test.js @@ -0,0 +1,890 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , Query = require('../lib/query') + , mongoose = start.mongoose + , DocumentObjectId = mongoose.Types.ObjectId + , Schema = mongoose.Schema + , should = require('should') + +var Comment = new Schema({ + text: String +}); + +var Product = new Schema({ + tags: {} // mixed + , array: Array + , ids: [Schema.ObjectId] + , strings: [String] + , numbers: [Number] + , comments: [Comment] +}); + +mongoose.model('Product', Product); +mongoose.model('Comment', Comment); + +/** + * Test. + */ + +module.exports = { + 'test query.fields({a: 1, b: 1, c: 1})': function () { + var query = new Query(); + query.fields({a: 1, b: 1, c: 1}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields({only: "a b c"})': function () { + var query = new Query(); + query.fields({only: "a b c"}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields({only: ["a", "b", "c"]})': function () { + var query = new Query(); + query.fields({only: ['a', 'b', 'c']}); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields("a b c")': function () { + var query = new Query(); + query.fields("a b c"); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + 'test query.fields("a", "b", "c")': function () { + var query = new Query(); + query.fields('a', 'b', 'c'); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.fields(['a', 'b', 'c'])": function () { + var query = new Query(); + query.fields(['a', 'b', 'c']); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "Query#fields should not over-ride fields set in prior calls to Query#fields": function () { + var query = new Query(); + query.fields('a'); + query._fields.should.eql({a: 1}); + query.fields('b'); + query._fields.should.eql({a: 1, b: 1}); + }, +// "Query#fields should be able to over-ride fields set in prior calls to Query#fields if you specify override": function () { +// var query = new Query(); +// query.fields('a'); +// query._fields.should.eql({a: 1}); +// query.override.fields('b'); +// query._fields.should.eql({b: 1}); +// } + + "test query.only('a b c')": function () { + var query = new Query(); + query.only("a b c"); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.only('a', 'b', 'c')": function () { + var query = new Query(); + query.only('a', 'b', 'c'); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "test query.only('a', 'b', 'c')": function () { + var query = new Query(); + query.only(['a', 'b', 'c']); + query._fields.should.eql({a: 1, b: 1, c: 1}); + }, + "Query#only should not over-ride fields set in prior calls to Query#only": function () { + var query = new Query(); + query.only('a'); + query._fields.should.eql({a: 1}); + query.only('b'); + query._fields.should.eql({a: 1, b: 1}); + }, + + "test query.exclude('a b c')": function () { + var query = new Query(); + query.exclude("a b c"); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "test query.exclude('a', 'b', 'c')": function () { + var query = new Query(); + query.exclude('a', 'b', 'c'); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "test query.exclude('a', 'b', 'c')": function () { + var query = new Query(); + query.exclude(['a', 'b', 'c']); + query._fields.should.eql({a: 0, b: 0, c: 0}); + }, + "Query#exclude should not over-ride fields set in prior calls to Query#exclude": function () { + var query = new Query(); + query.exclude('a'); + query._fields.should.eql({a: 0}); + query.exclude('b'); + query._fields.should.eql({a: 0, b: 0}); + }, + + 'test setting a condition via where': function () { + var query = new Query(); + query.where('name', 'guillermo'); + query._conditions.should.eql({name: 'guillermo'}); + }, + + 'setting a value via equals': function () { + var query = new Query(); + query.where('name').equals('guillermo'); + query._conditions.should.eql({name: 'guillermo'}); + }, + + 'test Query#gte where 2 arguments': function () { + var query = new Query(); + query.gte('age', 18); + query._conditions.should.eql({age: {$gte: 18}}); + }, + + 'test Query#gt where 2 arguments': function () { + var query = new Query(); + query.gt('age', 17); + query._conditions.should.eql({age: {$gt: 17}}); + }, + + 'test Query#lte where 2 arguments': function () { + var query = new Query(); + query.lte('age', 65); + query._conditions.should.eql({age: {$lte: 65}}); + }, + + 'test Query#lt where 2 arguments': function () { + var query = new Query(); + query.lt('age', 66); + query._conditions.should.eql({age: {$lt: 66}}); + }, + + 'test Query#gte where 1 argument': function () { + var query = new Query(); + query.where("age").gte(18); + query._conditions.should.eql({age: {$gte: 18}}); + }, + + 'test Query#gt where 1 argument': function () { + var query = new Query(); + query.where("age").gt(17); + query._conditions.should.eql({age: {$gt: 17}}); + }, + + 'test Query#lte where 1 argument': function () { + var query = new Query(); + query.where("age").lte(65); + query._conditions.should.eql({age: {$lte: 65}}); + }, + + 'test Query#lt where 1 argument': function () { + var query = new Query(); + query.where("age").lt(66); + query._conditions.should.eql({age: {$lt: 66}}); + }, + + 'test combined Query#lt and Query#gt': function () { + var query = new Query(); + query.where("age").lt(66).gt(17); + query._conditions.should.eql({age: {$lt: 66, $gt: 17}}); + }, + + 'test Query#lt on one path and Query#gt on another path on the same query': function () { + var query = new Query(); + query + .where("age").lt(66) + .where("height").gt(5); + query._conditions.should.eql({age: {$lt: 66}, height: {$gt: 5}}); + }, + + 'test Query#ne where 2 arguments': function () { + var query = new Query(); + query.ne('age', 21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#gte where 1 argument': function () { + var query = new Query(); + query.where("age").ne(21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#ne alias Query#notEqualTo': function () { + var query = new Query(); + query.where('age').notEqualTo(21); + query._conditions.should.eql({age: {$ne: 21}}); + + query = new Query(); + query.notEqualTo('age', 21); + query._conditions.should.eql({age: {$ne: 21}}); + }, + + 'test Query#in where 2 arguments': function () { + var query = new Query(); + query.in('age', [21, 25, 30]); + query._conditions.should.eql({age: {$in: [21, 25, 30]}}); + }, + + 'test Query#in where 1 argument': function () { + var query = new Query(); + query.where("age").in([21, 25, 30]); + query._conditions.should.eql({age: {$in: [21, 25, 30]}}); + }, + + 'test Query#in where a non-array value not via where': function () { + var query = new Query(); + query.in('age', 21); + query._conditions.should.eql({age: {$in: 21}}); + }, + + 'test Query#in where a non-array value via where': function () { + var query = new Query(); + query.where('age').in(21); + query._conditions.should.eql({age: {$in: 21}}); + }, + + 'test Query#nin where 2 arguments': function () { + var query = new Query(); + query.nin('age', [21, 25, 30]); + query._conditions.should.eql({age: {$nin: [21, 25, 30]}}); + }, + + 'test Query#nin where 1 argument': function () { + var query = new Query(); + query.where("age").nin([21, 25, 30]); + query._conditions.should.eql({age: {$nin: [21, 25, 30]}}); + }, + + 'test Query#nin where a non-array value not via where': function () { + var query = new Query(); + query.nin('age', 21); + query._conditions.should.eql({age: {$nin: 21}}); + }, + + 'test Query#nin where a non-array value via where': function () { + var query = new Query(); + query.where('age').nin(21); + query._conditions.should.eql({age: {$nin: 21}}); + }, + + 'test Query#mod not via where, where [a, b] param': function () { + var query = new Query(); + query.mod('age', [5, 2]); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod not via where, where a and b params': function () { + var query = new Query(); + query.mod('age', 5, 2); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod via where, where [a, b] param': function () { + var query = new Query(); + query.where("age").mod([5, 2]); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#mod via where, where a and b params': function () { + var query = new Query(); + query.where("age").mod(5, 2); + query._conditions.should.eql({age: {$mod: [5, 2]}}); + }, + + 'test Query#near via where, where [lat, long] param': function () { + var query = new Query(); + query.where('checkin').near([40, -72]); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near via where, where lat and long params': function () { + var query = new Query(); + query.where('checkin').near(40, -72); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near not via where, where [lat, long] param': function () { + var query = new Query(); + query.near('checkin', [40, -72]); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#near not via where, where lat and long params': function () { + var query = new Query(); + query.near('checkin', 40, -72); + query._conditions.should.eql({checkin: {$near: [40, -72]}}); + }, + + 'test Query#maxDistance via where': function () { + var query = new Query(); + query.where('checkin').near([40, -72]).maxDistance(1); + query._conditions.should.eql({checkin: {$near: [40, -72], $maxDistance: 1}}); + query = new Query(); + query.where('checkin').near([40, -72]).$maxDistance(1); + query._conditions.should.eql({checkin: {$near: [40, -72], $maxDistance: 1}}); + }, + + 'test Query#wherein.box not via where': function () { + var query = new Query(); + query.wherein.box('gps', {ll: [5, 25], ur: [10, 30]}); + query._conditions.should.eql({gps: {$within: {$box: [[5, 25], [10, 30]]}}}); + }, + + 'test Query#wherein.box via where': function () { + var query = new Query(); + query.where('gps').wherein.box({ll: [5, 25], ur: [10, 30]}); + query._conditions.should.eql({gps: {$within: {$box: [[5, 25], [10, 30]]}}}); + }, + + 'test Query#wherein.center not via where': function () { + var query = new Query(); + query.wherein.center('gps', {center: [5, 25], radius: 5}); + query._conditions.should.eql({gps: {$within: {$center: [[5, 25], 5]}}}); + }, + + 'test Query#wherein.center not via where': function () { + var query = new Query(); + query.where('gps').wherein.center({center: [5, 25], radius: 5}); + query._conditions.should.eql({gps: {$within: {$center: [[5, 25], 5]}}}); + }, + + 'test Query#exists where 0 arguments via where': function () { + var query = new Query(); + query.where("username").exists(); + query._conditions.should.eql({username: {$exists: true}}); + }, + + 'test Query#exists where 1 argument via where': function () { + var query = new Query(); + query.where("username").exists(false); + query._conditions.should.eql({username: {$exists: false}}); + }, + + 'test Query#exists where 1 argument not via where': function () { + var query = new Query(); + query.exists('username'); + query._conditions.should.eql({username: {$exists: true}}); + }, + + 'test Query#exists where 1 argument not via where': function () { + var query = new Query(); + query.exists("username", false); + query._conditions.should.eql({username: {$exists: false}}); + }, + + // TODO $not + + 'test Query#all via where': function () { + var query = new Query(); + query.where('pets').all(['dog', 'cat', 'ferret']); + query._conditions.should.eql({pets: {$all: ['dog', 'cat', 'ferret']}}); + }, + + 'test Query#all not via where': function () { + var query = new Query(); + query.all('pets', ['dog', 'cat', 'ferret']); + query._conditions.should.eql({pets: {$all: ['dog', 'cat', 'ferret']}}); + }, + + 'test strict array equivalence condition via Query#find': function () { + var query = new Query(); + query.find({'pets': ['dog', 'cat', 'ferret']}); + query._conditions.should.eql({pets: ['dog', 'cat', 'ferret']}); + }, + + '#find with no args should not throw': function () { + var threw = false; + var q = new Query(); + + try { + q.find(); + } catch (err) { + threw = true; + } + + threw.should.be.false; + }, + + // TODO Check key.index queries + + 'test Query#size via where': function () { + var query = new Query(); + query.where('collection').size(5); + query._conditions.should.eql({collection: {$size: 5}}); + }, + + 'test Query#size not via where': function () { + var query = new Query(); + query.size('collection', 5); + query._conditions.should.eql({collection: {$size: 5}}); + }, + + 'test Query#slice via where, where just positive limit param': function () { + var query = new Query(); + query.where('collection').slice(5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice via where, where just negative limit param': function () { + var query = new Query(); + query.where('collection').slice(-5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice via where, where [skip, limit] param': function () { + var query = new Query(); + query.where('collection').slice([14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where skip and limit params': function () { + var query = new Query(); + query.where('collection').slice(14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where just positive limit param': function () { + var query = new Query(); + query.where('collection').slice(5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice via where, where just negative limit param': function () { + var query = new Query(); + query.where('collection').slice(-5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice via where, where the [skip, limit] param': function () { + var query = new Query(); + query.where('collection').slice([14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice via where, where the skip and limit params': function () { + var query = new Query(); + query.where('collection').slice(14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + + 'test Query#slice not via where, where just positive limit param': function () { + var query = new Query(); + query.slice('collection', 5); + query._fields.should.eql({collection: {$slice: 5}}); + }, + + 'test Query#slice not via where, where just negative limit param': function () { + var query = new Query(); + query.slice('collection', -5); + query._fields.should.eql({collection: {$slice: -5}}); + }, + + 'test Query#slice not via where, where [skip, limit] param': function () { + var query = new Query(); + query.slice('collection', [14, 10]); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#slice not via where, where skip and limit params': function () { + var query = new Query(); + query.slice('collection', 14, 10); // Return the 15th through 25th + query._fields.should.eql({collection: {$slice: [14, 10]}}); + }, + + 'test Query#elemMatch not via where': function () { + var query = new Query(); + query.elemMatch('comments', {author: 'bnoguchi', votes: {$gte: 5}}); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch not via where, where block notation': function () { + var query = new Query(); + query.elemMatch('comments', function (elem) { + elem.where('author', 'bnoguchi') + elem.where('votes').gte(5); + }); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch via where': function () { + var query = new Query(); + query.where('comments').elemMatch({author: 'bnoguchi', votes: {$gte: 5}}); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + 'test Query#elemMatch via where, where block notation': function () { + var query = new Query(); + query.where('comments').elemMatch(function (elem) { + elem.where('author', 'bnoguchi') + elem.where('votes').gte(5); + }); + query._conditions.should.eql({comments: {$elemMatch: {author: 'bnoguchi', votes: {$gte: 5}}}}); + }, + + + 'test Query#$where where a function arg': function () { + var query = new Query(); + function filter () { + return this.lastName === this.firstName; + } + query.$where(filter); + query._conditions.should.eql({$where: filter}); + }, + + 'test Query#where where a javascript string arg': function () { + var query = new Query(); + query.$where('this.lastName === this.firstName'); + query._conditions.should.eql({$where: 'this.lastName === this.firstName'}); + }, + + 'test Query#limit': function () { + var query = new Query(); + query.limit(5); + query.options.limit.should.equal(5); + }, + + 'test Query#skip': function () { + var query = new Query(); + query.skip(9); + query.options.skip.should.equal(9); + }, + + 'test Query#sort': function () { + var query = new Query(); + query.sort('a', 1, 'c', -1, 'b', 1); + query.options.sort.should.eql([['a', 1], ['c', -1], ['b', 1]]); + }, + + 'test Query#asc and Query#desc': function () { + var query = new Query(); + query.asc('a', 'z').desc('c', 'v').asc('b'); + query.options.sort.should.eql([['a', 1], ['z', 1], ['c', -1], ['v', -1], ['b', 1]]); + }, + + 'Query#or': function () { + var query = new Query; + query.find({ $or: [{x:1},{x:2}] }); + query._conditions.$or.length.should.equal(2); + query.$or([{y:"We're under attack"}, {z:47}]); + query._conditions.$or.length.should.equal(4); + query._conditions.$or[3].z.should.equal(47); + query.or({z:"phew"}); + query._conditions.$or.length.should.equal(5); + query._conditions.$or[3].z.should.equal(47); + query._conditions.$or[4].z.should.equal("phew"); + }, + + 'test running an empty Query should not throw': function () { + var query = new Query(); + var threw = false; + + try { + query.exec(); + } catch (err) { + threw = true; + } + + threw.should.eql(false); + }, + + 'test casting an array set to mixed type works': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var params = { _id: new DocumentObjectId, tags: { $in: [ 4, 8, 15, 16 ] }}; + + query.cast(Product, params); + + params.tags.$in.should.eql([4,8,15,16]); + db.close(); + }, + + //'throwing inside a query callback should not execute the callback again': function () { + //var query = new Query(); + //var db = start(); + //var Product = db.model('Product'); + + //var threw = false; + //Product.find({}, function (err) { + //if (!threw) { + //db.close(); + //threw = true; + //throw new Error("Double callback"); + //} + + //should.strictEqual(err, null, 'Double callback detected'); + //}); + //}, + + 'Query#find $ne should not cast single value to array for schematype of Array': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var id = new DocumentObjectId; + var castedComment = { _id: id, text: 'hello there' }; + var comment = new Comment(castedComment); + + var params = { + array: { $ne: 5 } + , ids: { $ne: id } + , comments: { $ne: comment } + , strings: { $ne: 'Hi there' } + , numbers: { $ne: 10000 } + }; + + query.cast(Product, params); + params.array.$ne.should.equal(5); + params.ids.$ne.should.eql(id); + params.comments.$ne._id.toHexString(); + params.comments.$ne.should.eql(castedComment); + params.strings.$ne.should.eql('Hi there'); + params.numbers.$ne.should.eql(10000); + + params.array.$ne = [5]; + params.ids.$ne = [id]; + params.comments.$ne = [comment]; + params.strings.$ne = ['Hi there']; + params.numbers.$ne = [10000]; + query.cast(Product, params); + params.array.$ne.should.be.instanceof(Array); + params.array.$ne[0].should.eql(5); + params.ids.$ne.should.be.instanceof(Array); + params.ids.$ne[0].toString().should.eql(id.toString()); + params.comments.$ne.should.be.instanceof(Array); + params.comments.$ne[0].should.eql(castedComment); + params.strings.$ne.should.be.instanceof(Array); + params.strings.$ne[0].should.eql('Hi there'); + params.numbers.$ne.should.be.instanceof(Array); + params.numbers.$ne[0].should.eql(10000); + }, + + 'Querying a subdocument array with $ne: null should not throw': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var params = { + comments: { $ne: null } + }; + + query.cast(Product, params); + should.strictEqual(params.comments.$ne, null); + }, + + 'Query#find should not cast single value to array for schematype of Array': function () { + var query = new Query(); + var db = start(); + var Product = db.model('Product'); + var Comment = db.model('Comment'); + db.close(); + + var id = new DocumentObjectId; + var castedComment = { _id: id, text: 'hello there' }; + var comment = new Comment(castedComment); + + var params = { + array: 5 + , ids: id + , comments: comment + , strings: 'Hi there' + , numbers: 10000 + }; + + query.cast(Product, params); + params.array.should.equal(5); + params.ids.should.eql(id); + params.comments._id.toHexString(); + params.comments.should.eql(castedComment); + params.strings.should.eql('Hi there'); + params.numbers.should.eql(10000); + + params.array = [5]; + params.ids = [id]; + params.comments = [comment]; + params.strings = ['Hi there']; + params.numbers = [10000]; + query.cast(Product, params); + params.array.should.be.instanceof(Array); + params.array[0].should.eql(5); + params.ids.should.be.instanceof(Array); + params.ids[0].toString().should.eql(id.toString()); + params.comments.should.be.instanceof(Array); + params.comments[0].should.eql(castedComment); + params.strings.should.be.instanceof(Array); + params.strings[0].should.eql('Hi there'); + params.numbers.should.be.instanceof(Array); + params.numbers[0].should.eql(10000); + }, + + 'distinct Query op should be "distinct"': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + new Query().bind(Product, 'distinct').distinct('blah', function(){ + db.close(); + }).op.should.equal('distinct'); + }, + + 'Query without a callback works': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + new Query().bind(Product, 'count').count(); + setTimeout(db.close.bind(db), 1000); + }, + + '#findOne should set the op when callback is passed': function () { + var db = start(); + var query = new Query(); + var Product = db.model('Product'); + var q = new Query().bind(Product, 'distinct'); + q.op.should.equal('distinct'); + q.findOne(); + q.op.should.equal('findOne'); + db.close(); + }, + + 'querying/updating with model instance containing embedded docs should work (#454)': function () { + var db = start(); + var Product = db.model('Product'); + + var proddoc = { comments: [{ text: 'hello' }] }; + var prod2doc = { comments: [{ text: 'goodbye' }] }; + + var prod = new Product(proddoc); + var prod2 = new Product(prod2doc); + + prod.save(function (err) { + should.strictEqual(err, null); + + Product.findOne(prod, function (err, product) { + should.strictEqual(err, null); + product.comments.length.should.equal(1); + product.comments[0].text.should.equal('hello'); + + Product.update(product, prod2doc, function (err) { + should.strictEqual(err, null); + + Product.collection.findOne({ _id: product._id }, function (err, doc) { + db.close(); + should.strictEqual(err, null); + doc.comments.length.should.equal(1); + // ensure hidden private props were not saved to db + doc.comments[0].should.not.have.ownProperty('parentArr'); + doc.comments[0].text.should.equal('goodbye'); + }); + }); + }); + }); + }, + + 'optionsForExecute should retain key order': function () { + // this is important for query hints + var hint = { x: 1, y: 1, z: 1 }; + var a = JSON.stringify({ hint: hint, safe: true}); + + var q = new Query; + q.hint(hint); + + var options = q._optionsForExec({ options: { safe: true } }); + + a.should.equal(JSON.stringify(options)); + }, + + // Advanced Query options + + 'test Query#maxscan': function () { + var query = new Query(); + query.maxscan(100); + query.options.maxscan.should.equal(100); + }, + + 'test Query#slaveOk': function () { + var query = new Query(); + query.slaveOk(); + query.options.slaveOk.should.be.true; + + var query = new Query(); + query.slaveOk(true); + query.options.slaveOk.should.be.true; + + var query = new Query(); + query.slaveOk(false); + query.options.slaveOk.should.be.false; + }, + + 'test Query#tailable': function () { + var query = new Query(); + query.tailable(); + query.options.tailable.should.be.true; + + var query = new Query(); + query.tailable(true); + query.options.tailable.should.be.true; + + var query = new Query(); + query.tailable(false); + query.options.tailable.should.be.false; + }, + + 'Query#comment': function () { + var query = new Query; + 'function'.should.equal(typeof query.comment); + query.comment('Lowpass is more fun').should.equal(query); + query.options.comment.should.equal('Lowpass is more fun'); + }, + + 'test Query#hint': function () { + var query = new Query(); + query.hint('indexAttributeA', 1, 'indexAttributeB', -1); + query.options.hint.should.eql({'indexAttributeA': 1, 'indexAttributeB': -1}); + + var query2 = new Query(); + query2.hint({'indexAttributeA': 1, 'indexAttributeB': -1}); + query2.options.hint.should.eql({'indexAttributeA': 1, 'indexAttributeB': -1}); + + var query3 = new Query(); + query3.hint('indexAttributeA'); + query3.options.hint.should.eql({}); + }, + + 'test Query#snapshot': function () { + var query = new Query(); + query.snapshot(true); + query.options.snapshot.should.be.true; + }, + + 'test Query#batchSize': function () { + var query = new Query(); + query.batchSize(10); + query.options.batchSize.should.equal(10); + }, + + 'empty updates are not run': function () { + var q = new Query; + ;(!!q._castUpdate({})).should.be.false; + } + + // TODO +// 'test Query#min': function () { +// var query = new Query(); +// query.min(10); +// query.options.min.should.equal(10); +// }, +// + //TODO +// 'test Query#max': function () { +// var query = new Query(); +// query.max(100); +// query.options.max.should.equal(100); +// }, + + // TODO +// 'test Query#explain': function () { +// } +}; diff --git a/node_modules/mongoose/test/schema.onthefly.test.js b/node_modules/mongoose/test/schema.onthefly.test.js new file mode 100644 index 0000000..d038d78 --- /dev/null +++ b/node_modules/mongoose/test/schema.onthefly.test.js @@ -0,0 +1,105 @@ +var start = require('./common') + , should = require('should') + , mongoose = start.mongoose + , random = require('../lib/utils').random + , Schema = mongoose.Schema + , ObjectId = Schema.ObjectId; + +/** + * Setup. + */ + +var DecoratedSchema = new Schema({ + title : String +}); + +mongoose.model('Decorated', DecoratedSchema); + +var collection = 'decorated_' + random(); + +module.exports = { + 'setting on the fly schemas should cache the type schema and cast values appropriately': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated(); + post.set('adhoc', '9', Number); + post.get('adhoc').valueOf().should.eql(9); + db.close(); + }, + + 'on the fly schemas should be local to the particular document': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var postOne = new Decorated(); + postOne.set('adhoc', '9', Number); + postOne._path('adhoc').should.not.equal(undefined); + + var postTwo = new Decorated(); + postTwo._path('title').should.not.equal(undefined); + should.strictEqual(undefined, postTwo._path('adhoc')); + db.close(); + }, + + 'querying a document that had an on the fly schema should work': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated({title: 'AD HOC'}); + // Interpret adhoc as a Number + post.set('adhoc', '9', Number); + post.get('adhoc').valueOf().should.eql(9); + post.save( function (err) { + should.strictEqual(null, err); + Decorated.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(null, err); + found.get('adhoc').should.eql(9); + // Interpret adhoc as a String instead of a Number now + found.get('adhoc', String).should.eql('9'); + found.get('adhoc').should.eql('9'); + }); + }); + }, + + 'on the fly Embedded Array schemas should cast properly': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated(); + post.set('moderators', [{name: 'alex trebek'}], [new Schema({name: String})]); + post.get('moderators')[0].name.should.eql('alex trebek'); + db.close(); + }, + + 'on the fly Embedded Array schemas should get from a fresh queried document properly': function () { + var db = start() + , Decorated = db.model('Decorated', collection); + + var post = new Decorated() + , ModeratorSchema = new Schema({name: String, ranking: Number}); + post.set('moderators', [{name: 'alex trebek', ranking: '1'}], [ModeratorSchema]); + post.get('moderators')[0].name.should.eql('alex trebek'); + post.save( function (err) { + should.strictEqual(null, err); + Decorated.findById(post.id, function (err, found) { + db.close(); + should.strictEqual(null, err); + var rankingPreCast = found.get('moderators')[0].ranking; + rankingPreCast.should.eql(1); + should.strictEqual(undefined, rankingPreCast.increment); + var rankingPostCast = found.get('moderators', [ModeratorSchema])[0].ranking; + rankingPostCast.valueOf().should.equal(1); + rankingPostCast.increment.should.not.equal(undefined); + + var NewModeratorSchema = new Schema({ name: String, ranking: String}); + rankingPostCast = found.get('moderators', [NewModeratorSchema])[0].ranking; + rankingPostCast.should.equal('1'); + }); + }); + }, + 'should support on the fly nested documents': function () { + // TODO + } +}; diff --git a/node_modules/mongoose/test/schema.test.js b/node_modules/mongoose/test/schema.test.js new file mode 100644 index 0000000..93a3545 --- /dev/null +++ b/node_modules/mongoose/test/schema.test.js @@ -0,0 +1,978 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , should = require('should') + , Schema = mongoose.Schema + , Document = mongoose.Document + , SchemaType = mongoose.SchemaType + , VirtualType = mongoose.VirtualType + , ObjectId = Schema.ObjectId + , ValidatorError = SchemaType.ValidatorError + , CastError = SchemaType.CastError + , SchemaTypes = Schema.Types + , DocumentObjectId = mongoose.Types.ObjectId + , Mixed = SchemaTypes.Mixed + , MongooseNumber = mongoose.Types.Number + , MongooseArray = mongoose.Types.Array + , vm = require('vm') + +/** + * Test Document constructor. + */ + +function TestDocument () { + Document.apply(this, arguments); +}; + +/** + * Inherits from Document. + */ + +TestDocument.prototype.__proto__ = Document.prototype; + +/** + * Set a dummy schema to simulate compilation. + */ + +TestDocument.prototype.schema = new Schema({ + test : String +}); + +/** + * Test. + */ + +module.exports = { + + 'test different schema types support': function(){ + var Checkin = new Schema({ + date : Date + , location : { + lat: Number + , lng: Number + } + }); + + var Ferret = new Schema({ + name : String + , owner : ObjectId + , fur : String + , color : { type: String } + , age : Number + , checkins : [Checkin] + , friends : [ObjectId] + , likes : Array + , alive : Boolean + , extra : Mixed + }); + + Ferret.path('name').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('owner').should.be.an.instanceof(SchemaTypes.ObjectId); + Ferret.path('fur').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('color').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('age').should.be.an.instanceof(SchemaTypes.Number); + Ferret.path('checkins').should.be.an.instanceof(SchemaTypes.DocumentArray); + Ferret.path('friends').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('likes').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('alive').should.be.an.instanceof(SchemaTypes.Boolean); + Ferret.path('extra').should.be.an.instanceof(SchemaTypes.Mixed); + + should.strictEqual(Ferret.path('unexistent'), undefined); + + Checkin.path('date').should.be.an.instanceof(SchemaTypes.Date); + }, + + 'dot notation support for accessing paths': function(){ + var Racoon = new Schema({ + name : { type: String, enum: ['Edwald', 'Tobi'] } + , age : Number + }); + + var Person = new Schema({ + name : String + , raccoons : [Racoon] + , location : { + city : String + , state : String + } + }); + + Person.path('name').should.be.an.instanceof(SchemaTypes.String); + Person.path('raccoons').should.be.an.instanceof(SchemaTypes.DocumentArray); + Person.path('location.city').should.be.an.instanceof(SchemaTypes.String); + Person.path('location.state').should.be.an.instanceof(SchemaTypes.String); + + should.strictEqual(Person.path('location.unexistent'), undefined); + }, + + 'nested paths more than 2 levels deep': function () { + var Nested = new Schema({ + first: { + second: { + third: String + } + } + }); + Nested.path('first.second.third').should.be.an.instanceof(SchemaTypes.String); + }, + + 'test default definition': function(){ + var Test = new Schema({ + simple : { type: String, default: 'a' } + , array : { type: Array, default: [1,2,3,4,5] } + , arrayX : { type: Array, default: 9 } + , arrayFn : { type: Array, default: function () { return [8] } } + , callback : { type: Number, default: function(){ + this.a.should.eql('b'); + return '3'; + }} + }); + + Test.path('simple').defaultValue.should.eql('a'); + Test.path('callback').defaultValue.should.be.a('function'); + + Test.path('simple').getDefault().should.eql('a'); + (+Test.path('callback').getDefault({ a: 'b' })).should.eql(3); + Test.path('array').defaultValue.should.be.a('function'); + Test.path('array').getDefault(new TestDocument)[3].should.eql(4); + Test.path('arrayX').getDefault(new TestDocument)[0].should.eql(9); + Test.path('arrayFn').defaultValue.should.be.a('function'); + Test.path('arrayFn').getDefault(new TestDocument).should.be.an.instanceof(MongooseArray); + }, + + 'test Mixed defaults can be empty arrays': function () { + var Test = new Schema({ + mixed1 : { type: Mixed, default: [] } + , mixed2 : { type: Mixed, default: Array } + }); + + Test.path('mixed1').getDefault().should.be.an.instanceof(Array); + Test.path('mixed1').getDefault().length.should.be.eql(0); + Test.path('mixed2').getDefault().should.be.an.instanceof(Array); + Test.path('mixed2').getDefault().length.should.be.eql(0); + }, + + 'test string required validation': function(){ + var Test = new Schema({ + simple: String + }); + + Test.path('simple').required(true); + Test.path('simple').validators.should.have.length(1); + + Test.path('simple').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('woot', function(err){ + should.strictEqual(err, null); + }); + }, + + 'test string enum validation': function(){ + var Test = new Schema({ + complex: { type: String, enum: ['a', 'b', undefined, 'c', null] } + }); + + Test.path('complex').should.be.an.instanceof(SchemaTypes.String); + Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null]); + Test.path('complex').validators.should.have.length(1); + + Test.path('complex').enum('d', 'e'); + + Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null, 'd', 'e']); + + Test.path('complex').doValidate('x', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('complex').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('complex').doValidate(null, function(err){ + should.strictEqual(null, err); + }); + + Test.path('complex').doValidate('da', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test string regular expression validation': function(){ + var Test = new Schema({ + simple: { type: String, match: /[a-z]/ } + }); + + Test.path('simple').validators.should.have.length(1); + + Test.path('simple').doValidate('az', function(err){ + should.strictEqual(err, null); + }); + + Test.path('simple').match(/[0-9]/); + Test.path('simple').validators.should.have.length(2); + + Test.path('simple').doValidate('12', function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Test.path('simple').doValidate('a12', function(err){ + should.strictEqual(err, null); + }); + }, + + 'test string casting': function(){ + var Tobi = new Schema({ + nickname: String + }); + + function Test(){}; + Test.prototype.toString = function(){ + return 'woot'; + }; + + // test Number -> String cast + Tobi.path('nickname').cast(0).should.be.a('string'); + Tobi.path('nickname').cast(0).should.eql('0'); + + // test any object that implements toString + Tobi.path('nickname').cast(new Test()).should.be.a('string'); + Tobi.path('nickname').cast(new Test()).should.eql('woot'); + }, + + 'test number minimums and maximums validation': function(){ + var Tobi = new Schema({ + friends: { type: Number, max: 15, min: 5 } + }); + + Tobi.path('friends').validators.should.have.length(2); + + Tobi.path('friends').doValidate(10, function(err){ + should.strictEqual(err, null); + }); + + Tobi.path('friends').doValidate(100, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Tobi.path('friends').doValidate(1, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + // null is allowed + Tobi.path('friends').doValidate(null, function(err){ + should.strictEqual(err, null); + }); + }, + + 'test number required validation': function(){ + var Edwald = new Schema({ + friends: { type: Number, required: true } + }); + + Edwald.path('friends').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Edwald.path('friends').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Edwald.path('friends').doValidate(0, function(err){ + should.strictEqual(err, null); + }); + }, + + 'test number casting': function(){ + var Tobi = new Schema({ + age: Number + }); + + // test String -> Number cast + Tobi.path('age').cast('0').should.be.an.instanceof(MongooseNumber); + (+Tobi.path('age').cast('0')).should.eql(0); + + Tobi.path('age').cast(0).should.be.an.instanceof(MongooseNumber); + (+Tobi.path('age').cast(0)).should.eql(0); + }, + + 'test date required validation': function(){ + var Loki = new Schema({ + birth_date: { type: Date, required: true } + }); + + Loki.path('birth_date').doValidate(null, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('birth_date').doValidate(undefined, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('birth_date').doValidate(new Date(), function (err) { + should.strictEqual(err, null); + }); + }, + + 'test date casting': function(){ + var Loki = new Schema({ + birth_date: { type: Date } + }); + + Loki.path('birth_date').cast(1294525628301).should.be.an.instanceof(Date); + Loki.path('birth_date').cast('8/24/2000').should.be.an.instanceof(Date); + Loki.path('birth_date').cast(new Date).should.be.an.instanceof(Date); + }, + + 'test object id required validator': function(){ + var Loki = new Schema({ + owner: { type: ObjectId, required: true } + }); + + Loki.path('owner').doValidate(new DocumentObjectId(), function(err){ + should.strictEqual(err, null); + }); + + Loki.path('owner').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('owner').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test object id casting': function(){ + var Loki = new Schema({ + owner: { type: ObjectId } + }); + + var doc = new TestDocument() + , id = doc._id.toString(); + + Loki.path('owner').cast('4c54f3453e688c000000001a') + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(new DocumentObjectId()) + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(doc) + .should.be.an.instanceof(DocumentObjectId); + + Loki.path('owner').cast(doc).toString().should.eql(id); + }, + + 'test array required validation': function(){ + var Loki = new Schema({ + likes: { type: Array, required: true } + }); + + Loki.path('likes').doValidate(null, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('likes').doValidate(undefined, function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + + Loki.path('likes').doValidate([], function (err) { + err.should.be.an.instanceof(ValidatorError); + }); + }, + + 'test array casting': function(){ + var Loki = new Schema({ + oids : [ObjectId] + , dates : [Date] + , numbers : [Number] + , strings : [String] + , buffers : [Buffer] + , nocast : [] + , mixed : [Mixed] + }); + + var oids = Loki.path('oids').cast(['4c54f3453e688c000000001a', new DocumentObjectId]); + + oids[0].should.be.an.instanceof(DocumentObjectId); + oids[1].should.be.an.instanceof(DocumentObjectId); + + var dates = Loki.path('dates').cast(['8/24/2010', 1294541504958]); + + dates[0].should.be.an.instanceof(Date); + dates[1].should.be.an.instanceof(Date); + + var numbers = Loki.path('numbers').cast([152, '31']); + + numbers[0].should.be.a('number'); + numbers[1].should.be.a('number'); + + var strings = Loki.path('strings').cast(['test', 123]); + + strings[0].should.be.a('string'); + strings[0].should.eql('test'); + + strings[1].should.be.a('string'); + strings[1].should.eql('123'); + + var buffers = Loki.path('buffers').cast(['\0\0\0', new Buffer("abc")]); + + buffers[0].should.be.an.instanceof(Buffer); + buffers[1].should.be.an.instanceof(Buffer); + + var nocasts = Loki.path('nocast').cast(['test', 123]); + + nocasts[0].should.be.a('string'); + nocasts[0].should.eql('test'); + + nocasts[1].should.be.a('number'); + nocasts[1].should.eql(123); + + var mixed = Loki.path('mixed').cast(['test', 123, '123', {}, new Date, new DocumentObjectId]); + + mixed[0].should.be.a('string'); + mixed[1].should.be.a('number'); + mixed[2].should.be.a('string'); + mixed[3].should.be.a('object'); + mixed[4].should.be.an.instanceof(Date); + mixed[5].should.be.an.instanceof(DocumentObjectId); + }, + + 'test boolean required validator': function(){ + var Animal = new Schema({ + isFerret: { type: Boolean, required: true } + }); + + Animal.path('isFerret').doValidate(null, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Animal.path('isFerret').doValidate(undefined, function(err){ + err.should.be.an.instanceof(ValidatorError); + }); + + Animal.path('isFerret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + Animal.path('isFerret').doValidate(false, function(err){ + should.strictEqual(err, null); + }); + }, + + 'test boolean casting': function(){ + var Animal = new Schema({ + isFerret: { type: Boolean, required: true } + }); + + should.strictEqual(Animal.path('isFerret').cast(null), null); + Animal.path('isFerret').cast(undefined).should.be.false; + Animal.path('isFerret').cast(false).should.be.false; + Animal.path('isFerret').cast(0).should.be.false; + Animal.path('isFerret').cast('0').should.be.false; + Animal.path('isFerret').cast({}).should.be.true; + Animal.path('isFerret').cast(true).should.be.true; + Animal.path('isFerret').cast(1).should.be.true; + Animal.path('isFerret').cast('1').should.be.true; + }, + + 'test async multiple validators': function(beforeExit){ + var executed = 0; + + function validator (value, fn) { + setTimeout(function(){ + executed++; + fn(value === true); + }, 50); + }; + + var Animal = new Schema({ + ferret: { + type: Boolean, + validate: [ + { + 'validator': validator, + 'msg': 'validator1' + }, + { + 'validator': validator, + 'msg': 'validator2' + }, + ], + } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + beforeExit(function(){ + executed.should.eql(2); + }); + }, + + 'test async validators': function(beforeExit){ + var executed = 0; + + function validator (value, fn) { + setTimeout(function(){ + executed++; + fn(value === true); + }, 50); + }; + + var Animal = new Schema({ + ferret: { type: Boolean, validate: validator } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }); + + Animal.path('ferret').doValidate(false, function(err){ + err.should.be.an.instanceof(Error); + }); + + beforeExit(function(){ + executed.should.eql(2); + }); + }, + + 'test async validators scope': function(beforeExit){ + var executed = false; + + function validator (value, fn) { + this.a.should.eql('b'); + + setTimeout(function(){ + executed = true; + fn(true); + }, 50); + }; + + var Animal = new Schema({ + ferret: { type: Boolean, validate: validator } + }); + + Animal.path('ferret').doValidate(true, function(err){ + should.strictEqual(err, null); + }, { a: 'b' }); + + beforeExit(function(){ + executed.should.be.true; + }); + }, + + 'test declaring new methods': function(){ + var a = new Schema(); + a.method('test', function(){}); + a.method({ + a: function(){} + , b: function(){} + }); + + Object.keys(a.methods).should.have.length(3); + }, + + 'test declaring new statics': function(){ + var a = new Schema(); + a.static('test', function(){}); + a.static({ + a: function(){} + , b: function(){} + , c: function(){} + }); + + Object.keys(a.statics).should.have.length(4); + }, + + 'test setter(s)': function(){ + function lowercase (v) { + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, set: lowercase } + }); + + Tobi.path('name').applySetters('WOOT').should.eql('woot'); + Tobi.path('name').setters.should.have.length(1); + + Tobi.path('name').set(function(v){ + return v + 'WOOT'; + }); + + Tobi.path('name').applySetters('WOOT').should.eql('wootwoot'); + Tobi.path('name').setters.should.have.length(2); + }, + + 'test setters scope': function(){ + function lowercase (v, self) { + this.a.should.eql('b'); + self.path.should.eql('name'); + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, set: lowercase } + }); + + Tobi.path('name').applySetters('WHAT', { a: 'b' }).should.eql('what'); + }, + + 'test string built-in setter `lowercase`': function () { + var Tobi = new Schema({ + name: { type: String, lowercase: true } + }); + + Tobi.path('name').applySetters('WHAT').should.eql('what'); + }, + + 'test string built-in setter `uppercase`': function () { + var Tobi = new Schema({ + name: { type: String, uppercase: true } + }); + + Tobi.path('name').applySetters('what').should.eql('WHAT'); + }, + + 'test string built-in setter `trim`': function () { + var Tobi = new Schema({ + name: { type: String, uppercase: true, trim: true } + }); + + Tobi.path('name').applySetters(' what ').should.eql('WHAT'); + }, + + 'test getter(s)': function(){ + function woot (v) { + return v + ' woot'; + }; + + var Tobi = new Schema({ + name: { type: String, get: woot } + }); + + Tobi.path('name').getters.should.have.length(1); + Tobi.path('name').applyGetters('test').should.eql('test woot'); + }, + + 'test getters scope': function(){ + function woot (v, self) { + this.a.should.eql('b'); + self.path.should.eql('name'); + return v.toLowerCase(); + }; + + var Tobi = new Schema({ + name: { type: String, get: woot } + }); + + Tobi.path('name').applyGetters('YEP', { a: 'b' }).should.eql('yep'); + }, + + 'test setters casting': function(){ + function last (v) { + v.should.be.a('string'); + v.should.eql('0'); + return 'last'; + }; + + function first (v) { + return 0; + }; + + var Tobi = new Schema({ + name: { type: String, set: last } + }); + + Tobi.path('name').set(first); + Tobi.path('name').applySetters('woot').should.eql('last'); + }, + + 'test getters casting': function(){ + function last (v) { + v.should.be.a('string'); + v.should.eql('0'); + return 'last'; + }; + + function first (v) { + return 0; + }; + + var Tobi = new Schema({ + name: { type: String, get: last } + }); + + Tobi.path('name').get(first); + Tobi.path('name').applyGetters('woot').should.eql('last'); + }, + + 'test hooks registration': function(){ + var Tobi = new Schema(); + + Tobi.pre('save', function(){}); + Tobi.callQueue.should.have.length(1); + + Tobi.post('save', function(){}); + Tobi.callQueue.should.have.length(2); + + Tobi.pre('save', function(){}); + Tobi.callQueue.should.have.length(3); + }, + + 'test applying setters when none have been defined': function(){ + var Tobi = new Schema({ + name: String + }); + + Tobi.path('name').applySetters('woot').should.eql('woot'); + }, + + 'test applying getters when none have been defined': function(){ + var Tobi = new Schema({ + name: String + }); + + Tobi.path('name').applyGetters('woot').should.eql('woot'); + }, + + 'test defining an index': function(){ + var Tobi = new Schema({ + name: { type: String, index: true } + }); + + Tobi.path('name')._index.should.be.true; + Tobi.path('name').index({ unique: true }); + Tobi.path('name')._index.should.eql({ unique: true }); + Tobi.path('name').unique(false); + Tobi.path('name')._index.should.eql({ unique: false}); + + var T1 = new Schema({ + name: { type: String, sparse: true } + }); + T1.path('name')._index.should.eql({ sparse: true }); + + var T2 = new Schema({ + name: { type: String, unique: true } + }); + T2.path('name')._index.should.eql({ unique: true }); + + var T3 = new Schema({ + name: { type: String, sparse: true, unique: true } + }); + T3.path('name')._index.should.eql({ sparse: true, unique: true }); + + var T4 = new Schema({ + name: { type: String, unique: true, sparse: true } + }); + var i = T4.path('name')._index; + i.unique.should.be.true; + i.sparse.should.be.true; + + var T5 = new Schema({ + name: { type: String, index: { sparse: true, unique: true } } + }); + var i = T5.path('name')._index; + i.unique.should.be.true; + i.sparse.should.be.true; + }, + + 'test defining compound indexes': function(){ + var Tobi = new Schema({ + name: { type: String, index: true } + , last: { type: Number, sparse: true } + }); + + Tobi.index({ firstname: 1, last: 1 }, { unique: true }); + + Tobi.indexes.should.eql([ + [{ name: 1 }, {}] + , [{ last: 1 }, { sparse: true }] + , [{ firstname: 1, last: 1}, {unique: true}] + ]); + }, + + 'test plugins': function (beforeExit) { + var Tobi = new Schema() + , called = false; + + Tobi.plugin(function(schema){ + schema.should.equal(Tobi); + called = true; + }); + + beforeExit(function () { + called.should.be.true; + }); + }, + + 'test that default options are set': function () { + var Tobi = new Schema(); + + Tobi.options.should.be.a('object'); + Tobi.options.safe.should.be.true; + }, + + 'test setting options': function () { + var Tobi = new Schema({}, { collection: 'users' }); + + Tobi.set('a', 'b'); + Tobi.set('safe', false); + Tobi.options.collection.should.eql('users'); + + Tobi.options.a.should.eql('b'); + Tobi.options.safe.should.be.false; + }, + + 'test declaring virtual attributes': function () { + var Contact = new Schema({ + firstName: String + , lastName: String + }); + Contact.virtual('fullName') + .get( function () { + return this.get('firstName') + ' ' + this.get('lastName'); + }).set(function (fullName) { + var split = fullName.split(' '); + this.set('firstName', split[0]); + this.set('lastName', split[1]); + }); + + Contact.virtualpath('fullName').should.be.an.instanceof(VirtualType); + }, + + 'test GH-298 - The default creation of a virtual `id` should be muted when someone defines their own `id` attribute': function () { + new Schema({ id: String }); + }, + + 'allow disabling the auto .id virtual': function () { + var schema = new Schema({ name: String }, { noVirtualId: true }); + should.strictEqual(undefined, schema.virtuals.id); + }, + + 'selected option': function () { + var s = new Schema({ thought: { type: String, select: false }}); + s.path('thought').selected.should.be.false; + + var a = new Schema({ thought: { type: String, select: true }}); + a.path('thought').selected.should.be.true; + }, + + 'schema creation works with objects from other contexts': function () { + var str = 'code = {' + + ' name: String' + + ', arr1: Array ' + + ', arr2: { type: [] }' + + ', date: Date ' + + ', num: { type: Number }' + + ', bool: Boolean' + + ', nest: { sub: { type: {}, required: true }}' + + '}'; + + var script = vm.createScript(str, 'testSchema.vm'); + var sandbox = { code: null }; + script.runInNewContext(sandbox); + + var Ferret = new Schema(sandbox.code); + Ferret.path('nest.sub').should.be.an.instanceof(SchemaTypes.Mixed); + Ferret.path('name').should.be.an.instanceof(SchemaTypes.String); + Ferret.path('arr1').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('arr2').should.be.an.instanceof(SchemaTypes.Array); + Ferret.path('date').should.be.an.instanceof(SchemaTypes.Date); + Ferret.path('num').should.be.an.instanceof(SchemaTypes.Number); + Ferret.path('bool').should.be.an.instanceof(SchemaTypes.Boolean); + }, + + 'schema string casts undefined to "undefined"': function () { + var db= require('./common')(); + var schema = new Schema({ arr: [String] }); + var M = db.model('castingStringArrayWithUndefined', schema); + M.find({ arr: { $in: [undefined] }}, function (err) { + db.close(); + should.equal(err && err.message, 'Cast to string failed for value "undefined"'); + }) + }, + + 'array of object literal missing a `type` is interpreted as Mixed': function () { + var s = new Schema({ + arr: [ + { something: { type: String } } + ] + }); + }, + + 'helpful schema debugging msg': function () { + var err; + try { + new Schema({ name: { first: null } }) + } catch (e) { + err = e; + } + err.message.should.equal('Invalid value for schema path `name.first`') + try { + new Schema({ age: undefined }) + } catch (e) { + err = e; + } + err.message.should.equal('Invalid value for schema path `age`') + }, + + 'add() does not polute existing paths': function () { + var o = { name: String } + var s = new Schema(o); + s.add({ age: Number }, 'name.'); + ;('age' in o.name).should.be.false; + }, + + // gh-700 + 'nested schemas should throw': function () { + var a = new Schema({ title: String }) + , err + + try { + new Schema({ blah: Boolean, a: a }); + } catch (err_) { + err = err_; + } + + should.exist(err); + ;/Did you try nesting Schemas/.test(err.message).should.be.true; + }, + + 'non-function etters throw': function () { + var schema = new Schema({ fun: String }); + var g, s; + + try { + schema.path('fun').get(true); + } catch (err_) { + g = err_; + } + + should.exist(g); + g.message.should.equal('A getter must be a function.'); + + try { + schema.path('fun').set(4); + } catch (err_) { + s = err_; + } + + should.exist(s); + s.message.should.equal('A setter must be a function.'); + } + +}; diff --git a/node_modules/mongoose/test/shard.test.js b/node_modules/mongoose/test/shard.test.js new file mode 100644 index 0000000..2df04c3 --- /dev/null +++ b/node_modules/mongoose/test/shard.test.js @@ -0,0 +1,216 @@ + +var start = require('./common') + , should = require('should') + , random = require('../lib/utils').random + , mongoose = start.mongoose + , Mongoose = mongoose.Mongoose + , Schema = mongoose.Schema; + +var uri = process.env.MONGOOSE_SHARD_TEST_URI; + +if (!uri) { + console.log('\033[30m', '\n', 'You\'re not testing shards!' + , '\n', 'Please set the MONGOOSE_SHARD_TEST_URI env variable.', '\n' + , 'e.g: `mongodb://localhost:27017/database', '\n' + , 'Sharding must already be enabled on your database' + , '\033[39m'); + + exports.r = function expressoHack(){} + return; +} + +var schema = new Schema({ + name: String + , age: Number + , likes: [String] +}, { shardkey: { name: 1, age: 1 }}); + +var collection = 'shardperson_' + random(); +mongoose.model('ShardPerson', schema, collection); + +var db = start({ uri: uri }); +db.on('open', function () { + + // set up a sharded test collection + var P = db.model('ShardPerson', collection); + + var cmd = {}; + cmd.shardcollection = db.name + '.' + collection; + cmd.key = P.schema.options.shardkey; + + P.db.db.executeDbAdminCommand(cmd,function (err, res) { + db.close(); + + if (err) throw err; + + if (!(res && res.documents && res.documents[0] && res.documents[0].ok)) { + throw new Error('could not shard test collection ' + collection); + } + + // assign exports to tell expresso to begin + Object.keys(tests).forEach(function (test) { + exports[test] = tests[test]; + }); + + }); +}); + +var tests = { + + 'can read and write to a shard': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + P.create({ name: 'ryu', age: 25, likes: ['street fighting']}, function (err, ryu) { + should.strictEqual(null, err); + P.findById(ryu._id, function (err, doc) { + db.close(); + should.strictEqual(null, err); + doc.id.should.equal(ryu.id); + }); + }); + }, + + 'save() and remove() works with shard keys transparently': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + var zangief = new P({ name: 'Zangief', age: 33 }); + zangief.save(function (err) { + should.strictEqual(null, err); + + zangief._shardval.name.should.equal('Zangief'); + zangief._shardval.age.should.equal(33); + + P.findById(zangief._id, function (err, zang) { + should.strictEqual(null, err); + + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + + zang.likes = ['spinning', 'laughing']; + zang.save(function (err) { + should.strictEqual(null, err); + + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + + zang.likes.addToSet('winning'); + zang.save(function (err) { + should.strictEqual(null, err); + zang._shardval.name.should.equal('Zangief'); + zang._shardval.age.should.equal(33); + zang.remove(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }, + + 'inserting to a sharded collection without the full shard key fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + var pending = 6; + + P.create({ name: 'ryu', likes: ['street fighting']}, function (err, ryu) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ likes: ['street fighting']}, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ name: 'ryu' }, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ age: 49 }, function (err, ryu) { should.exist(err); + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + P.create({ likes: ['street fighting'], age: 8 }, function (err, ryu) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + var p = new P; + p.save(function (err) { + --pending || db.close(); + should.exist(err); + err.message.should.equal('tried to insert object with no valid shard key'); + }); + + }, + + 'updating a sharded collection without the full shard key fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + + P.create({ name: 'ken', age: 27 }, function (err, ken) { + should.strictEqual(null, err); + + P.update({ _id: ken._id }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + "right object doesn't have full shard key".should.equal(err.message); + + P.update({ _id: ken._id, name: 'ken' }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + + P.update({ _id: ken._id, age: 27 }, { likes: ['kicking', 'punching'] }, function (err) { + should.exist(err); + + P.update({ age: 27 }, { likes: ['kicking', 'punching'] }, function (err) { + db.close(); + should.exist(err); + }); + }); + }); + }); + }); + }, + + 'updating shard key values fails': function () { + var db = start({ uri: uri }) + var P = db.model('ShardPerson', collection); + P.create({ name: 'chun li', age: 19, likes: ['street fighting']}, function (err, chunli) { + should.strictEqual(null, err); + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + chunli.age = 20; + chunli.save(function (err) { + /^Can't modify shard key's value field/.test(err.message).should.be.true; + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + P.findById(chunli._id, function (err, chunli) { + should.strictEqual(null, err); + + chunli._shardval.name.should.equal('chun li'); + chunli._shardval.age.should.equal(19); + + chunli.name='chuuuun liiiii'; + chunli.save(function (err) { + db.close(); + /^Can't modify shard key's value field/.test(err.message).should.be.true; + }); + }); + }); + }); + } +} diff --git a/node_modules/mongoose/test/types.array.test.js b/node_modules/mongoose/test/types.array.test.js new file mode 100644 index 0000000..8a37399 --- /dev/null +++ b/node_modules/mongoose/test/types.array.test.js @@ -0,0 +1,551 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = require('./common').mongoose + , Schema = mongoose.Schema + , random = require('../lib/utils').random + , MongooseArray = mongoose.Types.Array; + +var User = new Schema({ + name: String + , pets: [Schema.ObjectId] +}); + +mongoose.model('User', User); + +var Pet = new Schema({ + name: String +}); + +mongoose.model('Pet', Pet); + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose array behaves and quacks like an array': function(){ + var a = new MongooseArray; + + a.should.be.an.instanceof(Array); + a.should.be.an.instanceof(MongooseArray); + Array.isArray(a).should.be.true; + ;(a._atomics.constructor).should.eql(Object); + + }, + + 'doAtomics does not throw': function () { + var b = new MongooseArray([12,3,4,5]).filter(Boolean); + var threw = false; + + try { + b.doAtomics + } catch (_) { + threw = true; + } + + threw.should.be.false; + + var a = new MongooseArray([67,8]).filter(Boolean); + try { + a.push(3,4); + } catch (_) { + console.error(_); + threw = true; + } + + threw.should.be.false; + + }, + + 'test indexOf()': function(){ + var db = start() + , User = db.model('User', 'users_' + random()) + , Pet = db.model('Pet', 'pets' + random()); + + var tj = new User({ name: 'tj' }) + , tobi = new Pet({ name: 'tobi' }) + , loki = new Pet({ name: 'loki' }) + , jane = new Pet({ name: 'jane' }) + , pets = []; + + tj.pets.push(tobi); + tj.pets.push(loki); + tj.pets.push(jane); + + var pending = 3; + + ;[tobi, loki, jane].forEach(function(pet){ + pet.save(function(){ + --pending || done(); + }); + }); + + function done() { + Pet.find({}, function(err, pets){ + tj.save(function(err){ + User.findOne({ name: 'tj' }, function(err, user){ + db.close(); + should.equal(null, err, 'error in callback'); + user.pets.should.have.length(3); + user.pets.indexOf(tobi.id).should.equal(0); + user.pets.indexOf(loki.id).should.equal(1); + user.pets.indexOf(jane.id).should.equal(2); + user.pets.indexOf(tobi._id).should.equal(0); + user.pets.indexOf(loki._id).should.equal(1); + user.pets.indexOf(jane._id).should.equal(2); + }); + }); + }); + } + }, + + 'test #splice() with numbers': function () { + var collection = 'splicetest-number' + random(); + var db = start() + , schema = new Schema({ numbers: Array }) + , A = db.model('splicetestNumber', schema, collection); + + var a = new A({ numbers: [4,5,6,7] }); + a.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + var removed = doc.numbers.splice(1, 1); + removed.should.eql([5]); + doc.numbers.toObject().should.eql([4,6,7]); + doc.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + doc.numbers.toObject().should.eql([4,6,7]); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + 'test #splice() on embedded docs': function () { + var collection = 'splicetest-embeddeddocs' + random(); + var db = start() + , schema = new Schema({ types: [new Schema({ type: String }) ]}) + , A = db.model('splicetestEmbeddedDoc', schema, collection); + + var a = new A({ types: [{type:'bird'},{type:'boy'},{type:'frog'},{type:'cloud'}] }); + a.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + + var removed = doc.types.splice(1, 1); + removed.length.should.eql(1); + removed[0].type.should.eql('boy'); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('bird'); + obj[1].type.should.eql('frog'); + obj[2].type.should.eql('cloud'); + + doc.save(function (err) { + should.equal(null, err, 'could not save splice test'); + A.findById(a._id, function (err, doc) { + should.equal(null, err, 'error finding splice doc'); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('bird'); + obj[1].type.should.eql('frog'); + obj[2].type.should.eql('cloud'); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + '#unshift': function () { + var db = start() + , schema = new Schema({ + types: [new Schema({ type: String })] + , nums: [Number] + , strs: [String] + }) + , A = db.model('unshift', schema, 'unshift'+random()); + + var a = new A({ + types: [{type:'bird'},{type:'boy'},{type:'frog'},{type:'cloud'}] + , nums: [1,2,3] + , strs: 'one two three'.split(' ') + }); + + a.save(function (err) { + should.equal(null, err); + A.findById(a._id, function (err, doc) { + should.equal(null, err); + + var tlen = doc.types.unshift({type:'tree'}); + var nlen = doc.nums.unshift(0); + var slen = doc.strs.unshift('zero'); + + tlen.should.equal(5); + nlen.should.equal(4); + slen.should.equal(4); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('tree'); + obj[1].type.should.eql('bird'); + obj[2].type.should.eql('boy'); + obj[3].type.should.eql('frog'); + obj[4].type.should.eql('cloud'); + + obj = doc.nums.toObject(); + obj[0].valueOf().should.equal(0); + obj[1].valueOf().should.equal(1); + obj[2].valueOf().should.equal(2); + obj[3].valueOf().should.equal(3); + + obj = doc.strs.toObject(); + obj[0].should.equal('zero'); + obj[1].should.equal('one'); + obj[2].should.equal('two'); + obj[3].should.equal('three'); + + doc.save(function (err) { + should.equal(null, err); + A.findById(a._id, function (err, doc) { + should.equal(null, err); + + var obj = doc.types.toObject(); + obj[0].type.should.eql('tree'); + obj[1].type.should.eql('bird'); + obj[2].type.should.eql('boy'); + obj[3].type.should.eql('frog'); + obj[4].type.should.eql('cloud'); + + obj = doc.nums.toObject(); + obj[0].valueOf().should.equal(0); + obj[1].valueOf().should.equal(1); + obj[2].valueOf().should.equal(2); + obj[3].valueOf().should.equal(3); + + obj = doc.strs.toObject(); + obj[0].should.equal('zero'); + obj[1].should.equal('one'); + obj[2].should.equal('two'); + obj[3].should.equal('three'); + + A.collection.drop(function (err) { + db.close(); + should.strictEqual(err, null); + }); + }); + }); + }); + }); + }, + + '#addToSet': function () { + var db = start() + , e = new Schema({ name: String, arr: [] }) + , schema = new Schema({ + num: [Number] + , str: [String] + , doc: [e] + , date: [Date] + , id: [Schema.ObjectId] + }); + + var M = db.model('testAddToSet', schema); + var m = new M; + + m.num.push(1,2,3); + m.str.push('one','two','tres'); + m.doc.push({ name: 'Dubstep', arr: [1] }, { name: 'Polka', arr: [{ x: 3 }]}); + + var d1 = new Date; + var d2 = new Date( +d1 + 60000); + var d3 = new Date( +d1 + 30000); + var d4 = new Date( +d1 + 20000); + var d5 = new Date( +d1 + 90000); + var d6 = new Date( +d1 + 10000); + m.date.push(d1, d2); + + var id1 = new mongoose.Types.ObjectId; + var id2 = new mongoose.Types.ObjectId; + var id3 = new mongoose.Types.ObjectId; + var id4 = new mongoose.Types.ObjectId; + var id5 = new mongoose.Types.ObjectId; + var id6 = new mongoose.Types.ObjectId; + + m.id.push(id1, id2); + + m.num.addToSet(3,4,5); + m.num.length.should.equal(5); + m.str.$addToSet('four', 'five', 'two'); + m.str.length.should.equal(5); + m.id.addToSet(id2, id3); + m.id.length.should.equal(3); + m.doc.$addToSet(m.doc[0]); + m.doc.length.should.equal(2); + m.doc.$addToSet({ name: 'Waltz', arr: [1] }, m.doc[0]); + m.doc.length.should.equal(3); + m.date.length.should.equal(2); + m.date.$addToSet(d1); + m.date.length.should.equal(2); + m.date.addToSet(d3); + m.date.length.should.equal(3); + + m.save(function (err) { + should.strictEqual(null, err); + M.findById(m, function (err, m) { + should.strictEqual(null, err); + + m.num.length.should.equal(5); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + + m.str.length.should.equal(5); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + + m.id.length.should.equal(3); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + + m.date.length.should.equal(3); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + + m.doc.length.should.equal(3); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + + // test single $addToSet + m.num.addToSet(3,4,5,6); + m.num.length.should.equal(6); + m.str.$addToSet('four', 'five', 'two', 'six'); + m.str.length.should.equal(6); + m.id.addToSet(id2, id3, id4); + m.id.length.should.equal(4); + + m.date.$addToSet(d1, d3, d4); + m.date.length.should.equal(4); + + m.doc.$addToSet(m.doc[0], { name: '8bit' }); + m.doc.length.should.equal(4); + + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + should.strictEqual(null, err); + + m.num.length.should.equal(6); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + (~m.num.indexOf(6)).should.be.ok; + + m.str.length.should.equal(6); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + (~m.str.indexOf('six')).should.be.ok; + + m.id.length.should.equal(4); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + (~m.id.indexOf(id4)).should.be.ok; + + m.date.length.should.equal(4); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + (~m.date.indexOf(d4.toString())).should.be.ok; + + m.doc.length.should.equal(4); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + m.doc.some(function(v){return v.name === '8bit'}).should.be.ok + + // test multiple $addToSet + m.num.addToSet(7,8); + m.num.length.should.equal(8); + m.str.$addToSet('seven', 'eight'); + m.str.length.should.equal(8); + m.id.addToSet(id5, id6); + m.id.length.should.equal(6); + + m.date.$addToSet(d5, d6); + m.date.length.should.equal(6); + + m.doc.$addToSet(m.doc[1], { name: 'BigBeat' }, { name: 'Funk' }); + m.doc.length.should.equal(6); + + m.save(function (err) { + should.strictEqual(null, err); + + M.findById(m, function (err, m) { + db.close(); + should.strictEqual(null, err); + + m.num.length.should.equal(8); + (~m.num.indexOf(1)).should.be.ok; + (~m.num.indexOf(2)).should.be.ok; + (~m.num.indexOf(3)).should.be.ok; + (~m.num.indexOf(4)).should.be.ok; + (~m.num.indexOf(5)).should.be.ok; + (~m.num.indexOf(6)).should.be.ok; + (~m.num.indexOf(7)).should.be.ok; + (~m.num.indexOf(8)).should.be.ok; + + m.str.length.should.equal(8); + (~m.str.indexOf('one')).should.be.ok; + (~m.str.indexOf('two')).should.be.ok; + (~m.str.indexOf('tres')).should.be.ok; + (~m.str.indexOf('four')).should.be.ok; + (~m.str.indexOf('five')).should.be.ok; + (~m.str.indexOf('six')).should.be.ok; + (~m.str.indexOf('seven')).should.be.ok; + (~m.str.indexOf('eight')).should.be.ok; + + m.id.length.should.equal(6); + (~m.id.indexOf(id1)).should.be.ok; + (~m.id.indexOf(id2)).should.be.ok; + (~m.id.indexOf(id3)).should.be.ok; + (~m.id.indexOf(id4)).should.be.ok; + (~m.id.indexOf(id5)).should.be.ok; + (~m.id.indexOf(id6)).should.be.ok; + + m.date.length.should.equal(6); + (~m.date.indexOf(d1.toString())).should.be.ok; + (~m.date.indexOf(d2.toString())).should.be.ok; + (~m.date.indexOf(d3.toString())).should.be.ok; + (~m.date.indexOf(d4.toString())).should.be.ok; + (~m.date.indexOf(d5.toString())).should.be.ok; + (~m.date.indexOf(d6.toString())).should.be.ok; + + m.doc.length.should.equal(6); + m.doc.some(function(v){return v.name === 'Waltz'}).should.be.ok + m.doc.some(function(v){return v.name === 'Dubstep'}).should.be.ok + m.doc.some(function(v){return v.name === 'Polka'}).should.be.ok + m.doc.some(function(v){return v.name === '8bit'}).should.be.ok + m.doc.some(function(v){return v.name === 'BigBeat'}).should.be.ok + m.doc.some(function(v){return v.name === 'Funk'}).should.be.ok + }); + }); + }); + }); + }); + }); + }, + + 'setting doc array should adjust path positions': function () { + var db = start(); + + var D = db.model('subDocPositions', new Schema({ + em1: [new Schema({ name: String })] + })); + + var d = new D({ + em1: [ + { name: 'pos0' } + , { name: 'pos1' } + , { name: 'pos2' } + ] + }); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + should.strictEqual(null, err); + + var n = d.em1.slice(); + n[2].name = 'position two'; + var x = []; + x[1] = n[2]; + x[2] = n[1]; + d.em1 = x.filter(Boolean); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }, + + 'paths with similar names should be saved': function () { + var db = start(); + + var D = db.model('similarPathNames', new Schema({ + account: { + role: String + , roles: [String] + } + , em: [new Schema({ name: String })] + })); + + var d = new D({ + account: { role: 'teacher', roles: ['teacher', 'admin'] } + , em: [{ name: 'bob' }] + }); + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + should.strictEqual(null, err); + + d.account.role = 'president'; + d.account.roles = ['president', 'janitor']; + d.em[0].name = 'memorable'; + d.em = [{ name: 'frida' }]; + + d.save(function (err) { + should.strictEqual(null, err); + D.findById(d, function (err, d) { + db.close(); + should.strictEqual(null, err); + d.account.role.should.equal('president'); + d.account.roles.length.should.equal(2); + d.account.roles[0].should.equal('president'); + d.account.roles[1].should.equal('janitor'); + d.em.length.should.equal(1); + d.em[0].name.should.equal('frida'); + }); + }); + }); + }); + } +}; diff --git a/node_modules/mongoose/test/types.buffer.test.js b/node_modules/mongoose/test/types.buffer.test.js new file mode 100644 index 0000000..b9c8d5d --- /dev/null +++ b/node_modules/mongoose/test/types.buffer.test.js @@ -0,0 +1,350 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , should = require('should') + , mongoose = require('./common').mongoose + , Schema = mongoose.Schema + , random = require('../lib/utils').random + , MongooseBuffer = mongoose.Types.Buffer; + +// can't index Buffer fields yet + +function valid (v) { + return !v || v.length > 10; +} + +var subBuf = new Schema({ + name: String + , buf: { type: Buffer, validate: [valid, 'valid failed'], required: true } +}); + +var UserBuffer = new Schema({ + name: String + , serial: Buffer + , array: [Buffer] + , required: { type: Buffer, required: true, index: true } + , sub: [subBuf] +}); + +// Dont put indexed models on the default connection, it +// breaks index.test.js tests on a "pure" default conn. +// mongoose.model('UserBuffer', UserBuffer); + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose buffer behaves and quacks like an buffer': function(){ + var a = new MongooseBuffer; + + a.should.be.an.instanceof(Buffer); + a.should.be.an.instanceof(MongooseBuffer); + Buffer.isBuffer(a).should.be.true; + + var a = new MongooseBuffer([195, 188, 98, 101, 114]); + var b = new MongooseBuffer("buffer shtuffs are neat"); + var c = new MongooseBuffer('aGVsbG8gd29ybGQ=', 'base64'); + + a.toString('utf8').should.equal('über'); + b.toString('utf8').should.equal('buffer shtuffs are neat'); + c.toString('utf8').should.equal('hello world'); + }, + + 'buffer validation': function () { + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var t = new User({ + name: 'test validation' + }); + + t.validate(function (err) { + err.message.should.eql('Validation failed'); + err.errors.required.type.should.equal('required'); + t.required = 20; + t.save(function (err) { + err.name.should.eql('CastError'); + err.type.should.eql('buffer'); + err.value.should.equal(20); + err.message.should.eql('Cast to buffer failed for value "20"'); + t.required = new Buffer("hello"); + + t.sub.push({ name: 'Friday Friday' }); + t.save(function (err) { + err.message.should.eql('Validation failed'); + err.errors.buf.type.should.equal('required'); + t.sub[0].buf = new Buffer("well well"); + t.save(function (err) { + err.message.should.eql('Validation failed'); + err.errors.buf.type.should.equal('valid failed'); + + t.sub[0].buf = new Buffer("well well well"); + t.validate(function (err) { + db.close(); + should.strictEqual(null, err); + }); + }); + }); + }); + }); + }) + }, + + 'buffer storage': function(){ + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var sampleBuffer = new Buffer([123, 223, 23, 42, 11]); + + var tj = new User({ + name: 'tj' + , serial: sampleBuffer + , required: new Buffer(sampleBuffer) + }); + + tj.save(function (err) { + should.equal(null, err); + User.find({}, function (err, users) { + db.close(); + should.equal(null, err); + users.should.have.length(1); + var user = users[0]; + var base64 = sampleBuffer.toString('base64'); + should.equal(base64, + user.serial.toString('base64'), 'buffer mismatch'); + should.equal(base64, + user.required.toString('base64'), 'buffer mismatch'); + }); + }); + }); + }, + + 'test write markModified': function(){ + var db = start() + , User = db.model('UserBuffer', UserBuffer, 'usersbuffer_' + random()); + + User.on('index', function () { + var sampleBuffer = new Buffer([123, 223, 23, 42, 11]); + + var tj = new User({ + name: 'tj' + , serial: sampleBuffer + , required: sampleBuffer + }); + + tj.save(function (err) { + should.equal(null, err); + + tj.serial.write('aa', 1, 'ascii'); + tj.isModified('serial').should.be.true; + + tj.save(function (err) { + should.equal(null, err); + + User.findById(tj._id, function (err, user) { + db.close(); + should.equal(null, err); + + var expectedBuffer = new Buffer([123, 97, 97, 42, 11]); + + should.equal(expectedBuffer.toString('base64'), + user.serial.toString('base64'), 'buffer mismatch'); + + tj.isModified('required').should.be.false; + tj.serial.copy(tj.required, 1); + tj.isModified('required').should.be.true; + should.equal('e3thYSo=', tj.required.toString('base64')); + + // buffer method tests + var fns = { + 'writeUInt8': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt8(0x3, 0, 'big'); + tj.isModified('required').should.be.true; + } + , 'writeUInt16': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16(0xbeef, 0, 'little'); + tj.isModified('required').should.be.true; + } + , 'writeUInt16LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16LE(0xbeef, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt16BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt16BE(0xbeef, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt32': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32(0xfeedface, 0, 'little'); + tj.isModified('required').should.be.true; + } + , 'writeUInt32LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32LE(0xfeedface, 0); + tj.isModified('required').should.be.true; + } + , 'writeUInt32BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeUInt32BE(0xfeedface, 0); + tj.isModified('required').should.be.true; + } + , 'writeInt8': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt8(-5, 0, 'big'); + tj.isModified('required').should.be.true; + } + , 'writeInt16': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16(0x0023, 2, 'little'); + tj.isModified('required').should.be.true; + tj.required[2].should.eql(0x23); + tj.required[3].should.eql(0x00); + } + , 'writeInt16LE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16LE(0x0023, 2); + tj.isModified('required').should.be.true; + tj.required[2].should.eql(0x23); + tj.required[3].should.eql(0x00); + } + , 'writeInt16BE': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt16BE(0x0023, 2); + tj.isModified('required').should.be.true; + } + , 'writeInt32': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32(0x23, 0, 'big'); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x00); + tj.required[2].should.eql(0x00); + tj.required[3].should.eql(0x23); + tj.required = new Buffer(8); + } + , 'writeInt32LE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32LE(0x23, 0); + tj.isModified('required').should.be.true; + } + , 'writeInt32BE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeInt32BE(0x23, 0); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x00); + tj.required[2].should.eql(0x00); + tj.required[3].should.eql(0x23); + } + , 'writeFloat': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloat(2.225073858507201e-308, 0, 'big'); + tj.isModified('required').should.be.true; + tj.required[0].should.eql(0x00); + tj.required[1].should.eql(0x0f); + tj.required[2].should.eql(0xff); + tj.required[3].should.eql(0xff); + tj.required[4].should.eql(0xff); + tj.required[5].should.eql(0xff); + tj.required[6].should.eql(0xff); + tj.required[7].should.eql(0xff); + } + , 'writeFloatLE': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloatLE(2.225073858507201e-308, 0); + tj.isModified('required').should.be.true; + } + , 'writeFloatBE': function () { + tj.required = new Buffer(16); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeFloatBE(2.225073858507201e-308, 0); + tj.isModified('required').should.be.true; + } + , 'writeDoubleLE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeDoubleLE(0xdeadbeefcafebabe, 0); + tj.isModified('required').should.be.true; + } + , 'writeDoubleBE': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.writeDoubleBE(0xdeadbeefcafebabe, 0); + tj.isModified('required').should.be.true; + } + , 'fill': function () { + tj.required = new Buffer(8); + reset(tj); + tj.isModified('required').should.be.false; + tj.required.fill(0); + tj.isModified('required').should.be.true; + for (var i = 0; i < tj.required.length; i++) { + tj.required[i].should.eql(0); + } + } + , 'set': function () { + reset(tj); + tj.isModified('required').should.be.false; + tj.required.set(0, 1); + tj.isModified('required').should.be.true; + } + }; + + var keys = Object.keys(fns) + , i = keys.length + , key + + while (i--) { + key = keys[i]; + if (Buffer.prototype[key]) { + fns[key](); + } + } + }); + }); + }); + }); + + function reset (model) { + // internal + model._activePaths.clear('modify'); + model.schema.requiredPaths.forEach(function (path) { + model._activePaths.require(path); + }); + } + } +}; diff --git a/node_modules/mongoose/test/types.document.test.js b/node_modules/mongoose/test/types.document.test.js new file mode 100644 index 0000000..a422403 --- /dev/null +++ b/node_modules/mongoose/test/types.document.test.js @@ -0,0 +1,197 @@ + +/** + * Module dependencies. + */ + +var should = require('should') + , start = require('./common') + , mongoose = start.mongoose + , EmbeddedDocument = require('../lib/types/embedded') + , DocumentArray = require('../lib/types/documentarray') + , Schema = mongoose.Schema + , SchemaType = mongoose.SchemaType + , ValidatorError = SchemaType.ValidatorError + , ValidationError = mongoose.Document.ValidationError + +/** + * Setup. + */ + +function Subdocument () { + EmbeddedDocument.call(this, {}, new DocumentArray); +}; + +/** + * Inherits from EmbeddedDocument. + */ + +Subdocument.prototype.__proto__ = EmbeddedDocument.prototype; + +/** + * Set schema. + */ + +Subdocument.prototype.schema = new Schema({ + test: { type: String, required: true } + , work: { type: String, validate: /^good/ } +}); + +/** + * Schema. + */ + +var RatingSchema = new Schema({ + stars: Number +}); + +var MovieSchema = new Schema({ + title: String + , ratings: [RatingSchema] +}); + +mongoose.model('Movie', MovieSchema); + +/** + * Test. + */ + +module.exports = { + + 'test that save fires errors': function(){ + var a = new Subdocument(); + a.set('test', ''); + a.set('work', 'nope'); + + a.save(function(err){ + err.should.be.an.instanceof(ValidationError); + err.toString().should.eql('ValidationError: Validator "required" failed for path test, Validator failed for path work'); + }); + }, + + 'test that save fires with null if no errors': function(){ + var a = new Subdocument(); + a.set('test', 'cool'); + a.set('work', 'goods'); + + a.save(function(err){ + should.strictEqual(err, null); + }); + }, + + 'objects can be passed to #set': function () { + var a = new Subdocument(); + a.set({ test: 'paradiddle', work: 'good flam'}); + a.test.should.eql('paradiddle'); + a.work.should.eql('good flam'); + }, + + 'Subdocuments can be passed to #set': function () { + var a = new Subdocument(); + a.set({ test: 'paradiddle', work: 'good flam'}); + a.test.should.eql('paradiddle'); + a.work.should.eql('good flam'); + var b = new Subdocument(); + b.set(a); + b.test.should.eql('paradiddle'); + b.work.should.eql('good flam'); + }, + + 'cached _ids': function () { + var db = start(); + var Movie = db.model('Movie'); + db.close(); + var m = new Movie; + + should.equal(m.id, m.__id); + var old = m.id; + m._id = new mongoose.Types.ObjectId; + should.equal(m.id, m.__id); + should.strictEqual(true, old !== m.__id); + + var m2= new Movie; + delete m2._doc._id; + m2.init({ _id: new mongoose.Types.ObjectId }); + should.equal(m2.id, m2.__id); + should.strictEqual(true, m.__id !== m2.__id); + should.strictEqual(true, m.id !== m2.id); + should.strictEqual(true, m.__id !== m2.__id); + } + + /* + 'Subdocument#remove': function (beforeExit) { + var db = start(); + var Movie = db.model('Movie'); + + var super8 = new Movie({ title: 'Super 8' }); + + var id1 = '4e3d5fc7da5d7eb635063c96'; + var id2 = '4e3d5fc7da5d7eb635063c97'; + var id3 = '4e3d5fc7da5d7eb635063c98'; + var id4 = '4e3d5fc7da5d7eb635063c99'; + + super8.ratings.push({ stars: 9, _id: id1 }); + super8.ratings.push({ stars: 8, _id: id2 }); + super8.ratings.push({ stars: 7, _id: id3 }); + super8.ratings.push({ stars: 6, _id: id4 }); + console.error('pre save', super8); + + super8.save(function (err) { + should.strictEqual(err, null); + + super8.title.should.eql('Super 8'); + super8.ratings.id(id1).stars.valueOf().should.eql(9); + super8.ratings.id(id2).stars.valueOf().should.eql(8); + super8.ratings.id(id3).stars.valueOf().should.eql(7); + super8.ratings.id(id4).stars.valueOf().should.eql(6); + + super8.ratings.id(id1).stars = 5; + super8.ratings.id(id2).remove(); + super8.ratings.id(id3).stars = 4; + super8.ratings.id(id4).stars = 3; + + super8.save(function (err) { + should.strictEqual(err, null); + + Movie.findById(super8._id, function (err, movie) { + should.strictEqual(err, null); + + movie.title.should.eql('Super 8'); + movie.ratings.length.should.equal(3); + movie.ratings.id(id1).stars.valueOf().should.eql(5); + movie.ratings.id(id3).stars.valueOf().should.eql(4); + movie.ratings.id(id4).stars.valueOf().should.eql(3); + + console.error('after save + findbyId', movie); + + movie.ratings.id(id1).stars = 2; + movie.ratings.id(id3).remove(); + movie.ratings.id(id4).stars = 1; + + console.error('after modified', movie); + + movie.save(function (err) { + should.strictEqual(err, null); + + Movie.findById(super8._id, function (err, movie) { + db.close(); + should.strictEqual(err, null); + movie.ratings.length.should.equal(2); + movie.ratings.id(id1).stars.valueOf().should.eql(2); + movie.ratings.id(id4).stars.valueOf().should.eql(1); + console.error('after resave + findById', movie); + }); + }); + }); + }); + }); + + beforeExit(function () { + var db = start(); + Movie.remove({}, function (err) { + db.close(); + }); + }); + } + */ + +}; diff --git a/node_modules/mongoose/test/types.documentarray.test.js b/node_modules/mongoose/test/types.documentarray.test.js new file mode 100644 index 0000000..ff8b381 --- /dev/null +++ b/node_modules/mongoose/test/types.documentarray.test.js @@ -0,0 +1,132 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , MongooseArray = mongoose.Types.Array + , MongooseDocumentArray = mongoose.Types.DocumentArray + , EmbeddedDocument = require('../lib/types/embedded') + , DocumentArray = require('../lib/types/documentarray') + , Schema = mongoose.Schema + +/** + * Setup. + */ + +function TestDoc (schema) { + var Subdocument = function () { + EmbeddedDocument.call(this, {}, new DocumentArray); + }; + + /** + * Inherits from EmbeddedDocument. + */ + + Subdocument.prototype.__proto__ = EmbeddedDocument.prototype; + + /** + * Set schema. + */ + + var SubSchema = new Schema({ + title: { type: String } + }); + + Subdocument.prototype.schema = schema || SubSchema; + + return Subdocument; +} + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose array behaves and quacks like an array': function(){ + var a = new MongooseDocumentArray(); + + a.should.be.an.instanceof(Array); + a.should.be.an.instanceof(MongooseArray); + a.should.be.an.instanceof(MongooseDocumentArray); + Array.isArray(a).should.be.true; + a._atomics.constructor.name.should.equal('Object'); + 'object'.should.eql(typeof a); + + var b = new MongooseArray([1,2,3,4]); + 'object'.should.eql(typeof b); + Object.keys(b.toObject()).length.should.equal(4); + }, + + '#id': function () { + var Subdocument = TestDoc(); + + var sub1 = new Subdocument(); + sub1.title = 'Hello again to all my friends'; + var id = sub1.id; + + var a = new MongooseDocumentArray([sub1]); + a.id(id).title.should.equal('Hello again to all my friends'); + a.id(sub1._id).title.should.equal('Hello again to all my friends'); + + // test with custom string _id + var Custom = new Schema({ + title: { type: String } + , _id: { type: String, required: true } + }); + + var Subdocument = TestDoc(Custom); + + var sub2 = new Subdocument(); + sub2.title = 'together we can play some rock-n-roll'; + sub2._id = 'a25'; + var id2 = sub2.id; + + var a = new MongooseDocumentArray([sub2]); + a.id(id2).title.should.equal('together we can play some rock-n-roll'); + a.id(sub2._id).title.should.equal('together we can play some rock-n-roll'); + + // test with custom number _id + var CustNumber = new Schema({ + title: { type: String } + , _id: { type: Number, required: true } + }); + + var Subdocument = TestDoc(CustNumber); + + var sub3 = new Subdocument(); + sub3.title = 'rock-n-roll'; + sub3._id = 1995; + var id3 = sub3.id; + + var a = new MongooseDocumentArray([sub3]); + a.id(id3).title.should.equal('rock-n-roll'); + a.id(sub3._id).title.should.equal('rock-n-roll'); + }, + + 'inspect works with bad data': function () { + var threw = false; + var a = new MongooseDocumentArray([null]); + try { + a.inspect(); + } catch (err) { + threw = true; + console.error(err.stack); + } + threw.should.be.false; + }, + + 'toObject works with bad data': function () { + var threw = false; + var a = new MongooseDocumentArray([null]); + try { + a.toObject(); + } catch (err) { + threw = true; + console.error(err.stack); + } + threw.should.be.false; + } + +}; diff --git a/node_modules/mongoose/test/types.number.test.js b/node_modules/mongoose/test/types.number.test.js new file mode 100644 index 0000000..68ebe5e --- /dev/null +++ b/node_modules/mongoose/test/types.number.test.js @@ -0,0 +1,37 @@ + +/** + * Module dependencies. + */ + +var mongoose = require('./common').mongoose + , MongooseNumber = mongoose.Types.Number + , SchemaNumber = mongoose.Schema.Types.Number + , should = require('should') + +/** + * Test. + */ + +module.exports = { + + 'test that a mongoose number behaves and quacks like a number': function(){ + var a = new MongooseNumber(5); + + a.should.be.an.instanceof(Number); + a.should.be.an.instanceof(MongooseNumber); + a.toString().should.eql('5'); + + (a._atomics.constructor).should.eql(Object); + }, + + 'an empty string casts to null': function () { + var n = new SchemaNumber(); + should.strictEqual(n.cast(''), null); + }, + + 'a null number should castForQuery to null': function () { + var n = new SchemaNumber(); + should.strictEqual(n.castForQuery(null), null); + } + +}; diff --git a/node_modules/mongoose/test/utils.test.js b/node_modules/mongoose/test/utils.test.js new file mode 100644 index 0000000..b275578 --- /dev/null +++ b/node_modules/mongoose/test/utils.test.js @@ -0,0 +1,196 @@ + +/** + * Module dependencies. + */ + +var start = require('./common') + , mongoose = start.mongoose + , Schema = mongoose.Schema + , utils = require('../lib/utils') + , StateMachine = require('../lib/statemachine') + , ObjectId = require('../lib/types/objectid') + , MongooseBuffer = require('../lib/types/buffer') + +/** + * Setup. + */ + +var ActiveRoster = StateMachine.ctor('require', 'init', 'modify'); + +/** + * Test. + */ + +module.exports = { + + 'should detect a path as required if it has been required': function () { + var ar = new ActiveRoster(); + ar.require('hello'); + ar.stateOf('hello').should.equal('require'); + }, + + 'should detect a path as inited if it has been inited': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.stateOf('hello').should.equal('init'); + }, + + 'should detect a path as modified': function () { + var ar = new ActiveRoster(); + ar.modify('hello'); + ar.stateOf('hello').should.equal('modify'); + }, + + 'should remove a path from an old state upon a state change': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('hello'); + ar.states.init.should.not.have.property('hello'); + ar.states.modify.should.have.property('hello'); + }, + + 'forEach should be able to iterate through the paths belonging to one state': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach('init', function (path) { + ['hello', 'goodbye'].should.contain(path); + }); + }, + + 'forEach should be able to iterate through the paths in the union of two or more states': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach('modify', 'require', function (path) { + ['world', 'foo'].should.contain(path); + }); + }, + + 'forEach should iterate through all paths that have any state if given no state arguments': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.init('goodbye'); + ar.modify('world'); + ar.require('foo'); + ar.forEach(function (path) { + ['hello', 'goodbye', 'world', 'foo'].should.contain(path); + }); + }, + + 'should be able to detect if at least one path exists in a set of states': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.some('init').should.be.true; + ar.some('modify').should.be.true; + ar.some('require').should.be.false; + ar.some('init', 'modify').should.be.true; + ar.some('init', 'require').should.be.true; + ar.some('modify', 'require').should.be.true; + }, + + 'should be able to `map` over the set of paths in a given state': function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.require('iAmTheWalrus'); + var suffixedPaths = ar.map('init', 'modify', function (path) { + return path + '-suffix'; + }); + suffixedPaths.should.eql(['hello-suffix', 'world-suffix']); + }, + + "should `map` over all states' paths if no states are specified in a `map` invocation": function () { + var ar = new ActiveRoster(); + ar.init('hello'); + ar.modify('world'); + ar.require('iAmTheWalrus'); + var suffixedPaths = ar.map(function (path) { + return path + '-suffix'; + }); + suffixedPaths.should.eql(['iAmTheWalrus-suffix', 'hello-suffix', 'world-suffix']); + }, + + 'test utils.options': function () { + var o = { a: 1, b: 2, c: 3, 0: 'zero1' }; + var defaults = { b: 10, d: 20, 0: 'zero2' }; + var result = utils.options(defaults, o); + result.a.should.equal(1); + result.b.should.equal(2); + result.c.should.equal(3); + result.d.should.equal(20); + o.d.should.equal(result.d); + result['0'].should.equal('zero1'); + + var result2 = utils.options(defaults); + result2.b.should.equal(10); + result2.d.should.equal(20); + result2['0'].should.equal('zero2'); + + // same properties/vals + defaults.should.eql(result2); + + // same object + defaults.should.not.equal(result2); + }, + + 'test deepEquals on ObjectIds': function () { + var s = (new ObjectId).toString(); + + var a = new ObjectId(s) + , b = new ObjectId(s); + + utils.deepEqual(a, b).should.be.true; + utils.deepEqual(a, a).should.be.true; + utils.deepEqual(a, new ObjectId).should.be.false; + }, + + 'deepEquals on MongooseDocumentArray works': function () { + var db = start() + , A = new Schema({ a: String }) + , M = db.model('deepEqualsOnMongooseDocArray', new Schema({ + a1: [A] + , a2: [A] + })); + + db.close(); + + var m1 = new M({ + a1: [{a: 'Hi'}, {a: 'Bye'}] + }); + + m1.a2 = m1.a1; + utils.deepEqual(m1.a1, m1.a2).should.be.true; + + var m2 = new M; + m2.init(m1.toObject()); + + utils.deepEqual(m1.a1, m2.a1).should.be.true; + + m2.set(m1.toObject()); + utils.deepEqual(m1.a1, m2.a1).should.be.true; + }, + + // gh-688 + 'deepEquals with MongooseBuffer': function () { + var str = "this is the day"; + var a = new MongooseBuffer(str); + var b = new MongooseBuffer(str); + var c = new Buffer(str); + var d = new Buffer("this is the way"); + var e = new Buffer("other length"); + + utils.deepEqual(a, b).should.be.true; + utils.deepEqual(a, c).should.be.true; + utils.deepEqual(a, d).should.be.false; + utils.deepEqual(a, e).should.be.false; + utils.deepEqual(a, []).should.be.false; + utils.deepEqual([], a).should.be.false; + } + +}; diff --git a/node_modules/mongoose/test/zzlast.test.js b/node_modules/mongoose/test/zzlast.test.js new file mode 100644 index 0000000..706b7f1 --- /dev/null +++ b/node_modules/mongoose/test/zzlast.test.js @@ -0,0 +1,11 @@ + +var should = require('should'); +var gleak = require('gleak')(); +gleak.whitelist.push('currentObjectStored', 'reg_exp'); // node-mongodb-native + +module.exports.globals = function (beforeExit) { + beforeExit(function () { + var leaks = gleak.detect(); + should.strictEqual(leaks.length, 0, 'detected global var leaks: ' + leaks.join(', ')); + }) +} |
