UNPKG

mongoose

Version:

Mongoose MongoDB ODM

1,743 lines (1,414 loc) 139 kB
/** * 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); }, '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; }); }); }, '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-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 () { mongoose.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 db = start() , CallbackFiresOnceValidation = db.model('CallbackFiresOnceValidation'); var post = new CallbackFiresOnceValidation({ username: "nope" , email: "too" , password: "short" }); var timesCalled = 0; post.save(function (err) { 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'); db.close(); }); }, '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 updating documents': function () { var db = start() , BlogPost = db.model('BlogPost', collection) , title = 'Tobi ' + random() , author = 'Brian ' + random() , newTitle = 'Woot ' + random() , id0 = new DocumentObjectId , id1 = new DocumentObjectId var post = new BlogPost(); post.set('title', title); post.author = author; post.meta.visitors = 0; post.date = new Date; post.published = true; post.mixed = { x: 'ex' }; post.numbers = [4,5,6,7]; post.owners = [id0, id1]; post.comments = [{ body: 'been there' }, { body: 'done that' }]; post.save(function (err) { should.strictEqual(err, null); BlogPost.findById(post._id, function (err, cf) { should.strictEqual(err, null); cf.title.should.equal(title); cf.author.should.equal(author); cf.meta.visitors.valueOf().should.eql(0); cf.date.should.eql(post.date); cf.published.should.be.true; cf.mixed.x.should.equal('ex'); cf.numbers.toObject().should.eql([4,5,6,7]); cf.owners.length.should.equal(2); cf.owners[0].toString().should.equal(id0.toString()); cf.owners[1].toString().should.equal(id1.toString()); cf.comments.length.should.equal(2); cf.comments[0].body.should.eql('been there'); cf.comments[1].body.should.eql('done that'); should.exist(cf.comments[0]._id); should.exist(cf.comments[1]._id); cf.comments[0]._id.should.be.an.instanceof(DocumentObjectId) cf.comments[1]._id.should.be.an.instanceof(DocumentObjectId); var update = { title: newTitle // becomes $set , $inc: { 'meta.visitors': 2 } , $set: { date: new Date } , published: false // becomes $set , 'mixed': { x: 'ECKS', y: 'why' } // $set , $pullAll: { 'numbers': [4, 6] } , $pull: { 'owners': id0 } , 'comments.1.body': 8 // $set } BlogPost.update({ title: title }, update, function (err) { should.strictEqual(err, null); BlogPost.findById(post._id, function (err, up) { should.strictEqual(err, null); up.title.should.equal(newTitle); up.author.should.equal(author); up.meta.visitors.valueOf().should.equal(2); up.date.toString().should.equal(update.$set.date.toString()); up.published.should.eql(false); up.mixed.x.should.equal('ECKS'); up.mixed.y.should.equal('why'); up.numbers.toObject().should.eql([5,7]); up.owners.length.should.equal(1); up.owners[0].toString().should.eql(id1.toString()); up.comments[0].body.should.equal('been there'); up.comments[1].body.should.equal('8'); should.exist(up.comments[0]._id); should.exist(up.comments[1]._id); up.comments[0]._id.should.be.an.instanceof(DocumentObjectId) up.comments[1]._id.should.be.an.instanceof(DocumentObjectId); var update2 = { 'comments.body': 'fail' } BlogPost.update({ _id: post._id }, update2, function (err) { should.strictEqual(!!err, true); ;/^can't append to array us