/** * 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); }); }); } };