mongoose
Version:
Mongoose MongoDB ORM
1,750 lines (1,411 loc) • 120 kB
JavaScript
/**
* 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.Document
, 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);
},
'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 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();
},
// 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');
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
}
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');
var update2 = {
'comments.body': 'fail'
}
BlogPost.update({ _id: post._id }, update2, function (err) {
should.strictEqual(!!err, true);
;/^can't append to array using string field name \[body\]/.test(err.message).should.be.true;
var update3 = {
$pull: 'fail'
}
BlogPost.update({ _id: post._id }, update3, function (err) {
should.strictEqual(!!err, true);
;/Invalid atomic update value/.test(err.message).should.be.true;
var update4 = {
$inc: { idontexist: 1 }
}
// should not overwrite doc when no valid paths are submitted
BlogPost.update({ _id: post._id }, update4, function (err) {
should.strictEqual(err, null);
BlogPost.findById(post._id, function (err, up) {
should.strictEqual(err, null);
up.title.should.equal(newTitle);
up.author.should.equal(author);
up.meta.visitors.valueOf().should.equal(2);
up.date.toString().should.equal(update.$set.date.toString());
up.published.should.eql(false);
up.mixed.x.should.equal('ECKS');
up.mixed.y.should.equal('why');
up.numbers.toObject().should.eql([5,7]);
up.owners.length.should.equal(1);
up.owners[0].toString().should.eql(id1.toString());
up.comments[0].body.should.equal('been there');
up.comments[1].body.should.equal('8');
var update5 = {
comments: [{ body: 'worked great' }]
, $set: {'numbers.1': 100}
, $inc: { idontexist: 1 }
}
BlogPost.update({ _id: post._id }, update5, function (err) {
should.strictEqual(err, null);
// get the underlying doc
BlogPost.collection.findOne({ _id: post._id }, function (err, doc) {
db.close();
should.strictEqual(err, null);
var up = new BlogPost;
up.init(doc);
up.comments.length.should.equal(1);
up.comments[0].body.should.equal('worked great');
up.meta.visitors.valueOf().should.equal(2);
up.mixed.x.should.equal('ECKS');
up.numbers.toObject().should.eql([5,100]);
up.numbers[1].valueOf().should.eql(100);
should.strictEqual(undefined, doc.idontexist);
doc.numbers[1].should.eql(100);
});
});
});
});
});
});
});
});
});
});
},
'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);
}