summaryrefslogtreecommitdiff
path: root/node_modules/mongoose/test/model.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/mongoose/test/model.test.js')
-rw-r--r--node_modules/mongoose/test/model.test.js4682
1 files changed, 4682 insertions, 0 deletions
diff --git a/node_modules/mongoose/test/model.test.js b/node_modules/mongoose/test/model.test.js
new file mode 100644
index 0000000..22c7433
--- /dev/null
+++ b/node_modules/mongoose/test/model.test.js
@@ -0,0 +1,4682 @@
+
+/**
+ * Test dependencies.
+ */
+
+var start = require('./common')
+ , should = require('should')
+ , mongoose = start.mongoose
+ , random = require('../lib/utils').random
+ , Query = require('../lib/query')
+ , Schema = mongoose.Schema
+ , SchemaType = mongoose.SchemaType
+ , CastError = SchemaType.CastError
+ , ValidatorError = SchemaType.ValidatorError
+ , ValidationError = mongoose.Document.ValidationError
+ , ObjectId = Schema.ObjectId
+ , DocumentObjectId = mongoose.Types.ObjectId
+ , DocumentArray = mongoose.Types.DocumentArray
+ , EmbeddedDocument = mongoose.Types.Embedded
+ , MongooseNumber = mongoose.Types.Number
+ , MongooseArray = mongoose.Types.Array
+ , MongooseError = mongoose.Error;
+
+/**
+ * Setup.
+ */
+
+var Comments = new Schema();
+
+Comments.add({
+ title : String
+ , date : Date
+ , body : String
+ , comments : [Comments]
+});
+
+var BlogPost = new Schema({
+ title : String
+ , author : String
+ , slug : String
+ , date : Date
+ , meta : {
+ date : Date
+ , visitors : Number
+ }
+ , published : Boolean
+ , mixed : {}
+ , numbers : [Number]
+ , owners : [ObjectId]
+ , comments : [Comments]
+});
+
+BlogPost.virtual('titleWithAuthor')
+ .get(function () {
+ return this.get('title') + ' by ' + this.get('author');
+ })
+ .set(function (val) {
+ var split = val.split(' by ');
+ this.set('title', split[0]);
+ this.set('author', split[1]);
+ });
+
+BlogPost.method('cool', function(){
+ return this;
+});
+
+BlogPost.static('woot', function(){
+ return this;
+});
+
+mongoose.model('BlogPost', BlogPost);
+
+var collection = 'blogposts_' + random();
+
+module.exports = {
+
+ 'test a model isNew flag when instantiating': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.isNew.should.be.true;
+ db.close();
+ },
+
+ 'modified getter should not throw': function () {
+ var db = start();
+ var BlogPost = db.model('BlogPost', collection);
+ var post = new BlogPost;
+ db.close();
+
+ var threw = false;
+ try {
+ post.modified;
+ } catch (err) {
+ threw = true;
+ }
+
+ threw.should.be.false;
+ },
+
+ 'test presence of model schema': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.schema.should.be.an.instanceof(Schema);
+ BlogPost.prototype.schema.should.be.an.instanceof(Schema);
+ db.close();
+ },
+
+ 'test a model default structure when instantiated': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.isNew.should.be.true;
+ post.db.model('BlogPost').modelName.should.equal('BlogPost');
+ post.constructor.modelName.should.equal('BlogPost');
+
+ post.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ should.equal(undefined, post.get('title'));
+ should.equal(undefined, post.get('slug'));
+ should.equal(undefined, post.get('date'));
+
+ post.get('meta').should.be.a('object');
+ post.get('meta').should.eql({});
+ should.equal(undefined, post.get('meta.date'));
+ should.equal(undefined, post.get('meta.visitors'));
+
+ should.equal(undefined, post.get('published'));
+
+ post.get('numbers').should.be.an.instanceof(MongooseArray);
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ db.close();
+ },
+
+ 'mongoose.model returns the model at creation': function () {
+ var Named = mongoose.model('Named', new Schema({ name: String }));
+ var n1 = new Named();
+ should.equal(n1.name, null);
+ var n2 = new Named({ name: 'Peter Bjorn' });
+ should.equal(n2.name, 'Peter Bjorn');
+
+ var schema = new Schema({ number: Number });
+ var Numbered = mongoose.model('Numbered', schema, collection);
+ var n3 = new Numbered({ number: 1234 });
+ n3.number.valueOf().should.equal(1234);
+ },
+
+ 'mongoose.model!init emits init event on schema': function () {
+ var db = start()
+ , schema = new Schema({ name: String })
+ , model
+
+ schema.on('init', function (model_) {
+ model = model_;
+ });
+
+ var Named = db.model('EmitInitOnSchema', schema);
+ db.close();
+ model.should.equal(Named);
+ },
+
+ 'collection name can be specified through schema': function () {
+ var schema = new Schema({ name: String }, { collection: 'users1' });
+ var Named = mongoose.model('CollectionNamedInSchema1', schema);
+ Named.prototype.collection.name.should.equal('users1');
+
+ var db = start();
+ var users2schema = new Schema({ name: String }, { collection: 'users2' });
+ var Named2 = db.model('CollectionNamedInSchema2', users2schema);
+ db.close();
+ Named2.prototype.collection.name.should.equal('users2');
+ },
+
+ 'test default Array type casts to Mixed': function () {
+ var db = start()
+ , DefaultArraySchema = new Schema({
+ num1: Array
+ , num2: []
+ })
+
+ mongoose.model('DefaultArraySchema', DefaultArraySchema);
+ var DefaultArray = db.model('DefaultArraySchema', collection);
+ var arr = new DefaultArray();
+
+ arr.get('num1').should.have.length(0);
+ arr.get('num2').should.have.length(0);
+
+ var threw1 = false
+ , threw2 = false;
+
+ try {
+ arr.num1.push({ x: 1 })
+ arr.num1.push(9)
+ arr.num1.push("woah")
+ } catch (err) {
+ threw1 = true;
+ }
+
+ threw1.should.equal(false);
+
+ try {
+ arr.num2.push({ x: 1 })
+ arr.num2.push(9)
+ arr.num2.push("woah")
+ } catch (err) {
+ threw2 = true;
+ }
+
+ threw2.should.equal(false);
+
+ db.close();
+
+ },
+
+ 'test a model default structure that has a nested Array when instantiated': function () {
+ var db = start()
+ , NestedSchema = new Schema({
+ nested: {
+ array: [Number]
+ }
+ });
+ mongoose.model('NestedNumbers', NestedSchema);
+ var NestedNumbers = db.model('NestedNumbers', collection);
+
+ var nested = new NestedNumbers();
+ nested.get('nested.array').should.be.an.instanceof(MongooseArray);
+ db.close();
+ },
+
+ 'test a model that defaults an Array attribute to a default non-empty array': function () {
+ var db = start()
+ , DefaultArraySchema = new Schema({
+ arr: {type: Array, cast: String, default: ['a', 'b', 'c']}
+ });
+ mongoose.model('DefaultArray', DefaultArraySchema);
+ var DefaultArray = db.model('DefaultArray', collection);
+ var arr = new DefaultArray();
+ arr.get('arr').should.have.length(3);
+ arr.get('arr')[0].should.equal('a');
+ arr.get('arr')[1].should.equal('b');
+ arr.get('arr')[2].should.equal('c');
+ db.close();
+ },
+
+ 'test a model that defaults an Array attribute to a default single member array': function () {
+ var db = start()
+ , DefaultOneCardArraySchema = new Schema({
+ arr: {type: Array, cast: String, default: ['a']}
+ });
+ mongoose.model('DefaultOneCardArray', DefaultOneCardArraySchema);
+ var DefaultOneCardArray = db.model('DefaultOneCardArray', collection);
+ var arr = new DefaultOneCardArray();
+ arr.get('arr').should.have.length(1);
+ arr.get('arr')[0].should.equal('a');
+ db.close();
+ },
+
+ 'test a model that defaults an Array attributes to an empty array': function () {
+ var db = start()
+ , DefaultZeroCardArraySchema = new Schema({
+ arr: {type: Array, cast: String, default: []}
+ });
+ mongoose.model('DefaultZeroCardArray', DefaultZeroCardArraySchema);
+ var DefaultZeroCardArray = db.model('DefaultZeroCardArray', collection);
+ var arr = new DefaultZeroCardArray();
+ arr.get('arr').should.have.length(0);
+ arr.arr.should.have.length(0);
+ db.close();
+ },
+
+ 'test that arrays auto-default to the empty array': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.numbers.should.have.length(0);
+ db.close();
+ },
+
+ 'test instantiating a model with a hash that maps to at least 1 null value': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({
+ title: null
+ });
+ should.strictEqual(null, post.title);
+ db.close();
+ },
+
+ 'saving a model with a null value should perpetuate that null value to the db': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({
+ title: null
+ });
+ should.strictEqual(null, post.title);
+ post.save( function (err) {
+ should.strictEqual(err, null);
+ BlogPost.findById(post.id, function (err, found) {
+ db.close();
+ should.strictEqual(err, null);
+ should.strictEqual(found.title, null);
+ });
+ });
+ },
+
+ 'test instantiating a model with a hash that maps to at least 1 undefined value': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({
+ title: undefined
+ });
+ should.strictEqual(undefined, post.title);
+ post.save( function (err) {
+ should.strictEqual(null, err);
+ BlogPost.findById(post.id, function (err, found) {
+ db.close();
+ should.strictEqual(err, null);
+ should.strictEqual(found.title, undefined);
+ });
+ });
+ },
+
+ 'test a model structure when saved': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , pending = 2;
+
+ function done () {
+ if (!--pending) db.close();
+ }
+
+ var post = new BlogPost();
+ post.on('save', function (post) {
+ post.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ should.equal(undefined, post.get('title'));
+ should.equal(undefined, post.get('slug'));
+ should.equal(undefined, post.get('date'));
+ should.equal(undefined, post.get('published'));
+
+ post.get('meta').should.be.a('object');
+ post.get('meta').should.eql({});
+ should.equal(undefined, post.get('meta.date'));
+ should.equal(undefined, post.get('meta.visitors'));
+
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ done();
+ });
+
+ post.save(function(err, post){
+ should.strictEqual(err, null);
+ post.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ should.equal(undefined, post.get('title'));
+ should.equal(undefined, post.get('slug'));
+ should.equal(undefined, post.get('date'));
+ should.equal(undefined, post.get('published'));
+
+ post.get('meta').should.be.a('object');
+ post.get('meta').should.eql({});
+ should.equal(undefined, post.get('meta.date'));
+ should.equal(undefined, post.get('meta.visitors'));
+
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ done();
+ });
+ },
+
+ 'test a model structures when created': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({ title: 'hi there'}, function (err, post) {
+ should.strictEqual(err, null);
+ post.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ should.strictEqual(post.get('title'), 'hi there');
+ should.equal(undefined, post.get('slug'));
+ should.equal(undefined, post.get('date'));
+
+ post.get('meta').should.be.a('object');
+ post.get('meta').should.eql({});
+ should.equal(undefined, post.get('meta.date'));
+ should.equal(undefined, post.get('meta.visitors'));
+
+ should.strictEqual(undefined, post.get('published'));
+
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ db.close();
+ });
+ },
+
+ 'test a model structure when initd': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , date : new Date
+ , meta : {
+ date : new Date
+ , visitors : 5
+ }
+ , published : true
+ , owners : [new DocumentObjectId, new DocumentObjectId]
+ , comments : [
+ { title: 'Test', date: new Date, body: 'Test' }
+ , { title: 'Super', date: new Date, body: 'Cool' }
+ ]
+ });
+
+ post.get('title').should.eql('Test');
+ post.get('slug').should.eql('test');
+ post.get('date').should.be.an.instanceof(Date);
+ post.get('meta').should.be.a('object');
+ post.get('meta').date.should.be.an.instanceof(Date);
+ post.get('meta').visitors.should.be.an.instanceof(MongooseNumber);
+ post.get('published').should.be.true;
+
+ post.title.should.eql('Test');
+ post.slug.should.eql('test');
+ post.date.should.be.an.instanceof(Date);
+ post.meta.should.be.a('object');
+ post.meta.date.should.be.an.instanceof(Date);
+ post.meta.visitors.should.be.an.instanceof(MongooseNumber);
+ post.published.should.be.true;
+
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('owners')[0].should.be.an.instanceof(DocumentObjectId);
+ post.get('owners')[1].should.be.an.instanceof(DocumentObjectId);
+
+ post.owners.should.be.an.instanceof(MongooseArray);
+ post.owners[0].should.be.an.instanceof(DocumentObjectId);
+ post.owners[1].should.be.an.instanceof(DocumentObjectId);
+
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ post.get('comments')[0].should.be.an.instanceof(EmbeddedDocument);
+ post.get('comments')[1].should.be.an.instanceof(EmbeddedDocument);
+
+ post.comments.should.be.an.instanceof(DocumentArray);
+ post.comments[0].should.be.an.instanceof(EmbeddedDocument);
+ post.comments[1].should.be.an.instanceof(EmbeddedDocument);
+
+ db.close();
+ },
+
+ 'test a model structure when partially initd': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , date : new Date
+ });
+
+ post.get('title').should.eql('Test');
+ post.get('slug').should.eql('test');
+ post.get('date').should.be.an.instanceof(Date);
+ post.get('meta').should.be.a('object');
+
+ post.get('meta').should.eql({});
+ should.equal(undefined, post.get('meta.date'));
+ should.equal(undefined, post.get('meta.visitors'));
+
+ should.equal(undefined, post.get('published'));
+
+ post.get('owners').should.be.an.instanceof(MongooseArray);
+ post.get('comments').should.be.an.instanceof(DocumentArray);
+ db.close();
+ },
+
+ 'test initializing with a nested hash': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({
+ meta: {
+ date : new Date
+ , visitors : 5
+ }
+ });
+
+ post.get('meta.visitors').valueOf().should.equal(5);
+ db.close();
+ },
+
+ 'test isNew on embedded documents after initing': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , comments : [ { title: 'Test', date: new Date, body: 'Test' } ]
+ });
+
+ post.get('comments')[0].isNew.should.be.false;
+ db.close();
+ },
+
+ 'test isNew on embedded documents after saving': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({ title: 'hocus pocus' })
+ post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] });
+ post.get('comments')[0].isNew.should.be.true;
+ post.get('comments')[0].comments[0].isNew.should.be.true;
+ post.invalidate('title'); // force error
+ post.save(function (err) {
+ post.isNew.should.be.true;
+ post.get('comments')[0].isNew.should.be.true;
+ post.get('comments')[0].comments[0].isNew.should.be.true;
+ post.save(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ post.isNew.should.be.false;
+ post.get('comments')[0].isNew.should.be.false;
+ post.get('comments')[0].comments[0].isNew.should.be.false;
+ });
+ });
+ },
+
+ 'test isNew on embedded documents added after saving': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({ title: 'hocus pocus' })
+ post.save(function (err) {
+ post.isNew.should.be.false;
+ post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] });
+ post.get('comments')[0].isNew.should.be.true;
+ post.get('comments')[0].comments[0].isNew.should.be.true;
+ post.save(function (err) {
+ should.strictEqual(null, err);
+ post.isNew.should.be.false;
+ post.get('comments')[0].isNew.should.be.false;
+ post.get('comments')[0].comments[0].isNew.should.be.false;
+ db.close();
+ });
+ });
+ },
+
+ 'modified states are reset after save runs': function () {
+ var db = start()
+ , B = db.model('BlogPost', collection)
+ , pending = 2;
+
+ var b = new B;
+
+ b.numbers.push(3);
+ b.save(function (err) {
+ should.strictEqual(null, err);
+ --pending || find();
+ });
+
+ b.numbers.push(3);
+ b.save(function (err) {
+ should.strictEqual(null, err);
+ --pending || find();
+ });
+
+ function find () {
+ B.findById(b, function (err, b) {
+ db.close();
+ should.strictEqual(null, err);
+ b.numbers.length.should.equal(2);
+ });
+ }
+ },
+
+ 'modified states in emb-doc are reset after save runs': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({ title: 'hocus pocus' });
+ post.comments.push({ title: 'Humpty Dumpty', comments: [{title: 'nested'}] });
+ post.save(function(err){
+ db.close();
+ should.strictEqual(null, err);
+ var mFlag = post.comments[0].isModified('title');
+ mFlag.should.equal(false);
+ post.isModified('title').should.equal(false);
+ });
+
+ },
+
+ 'test isModified when modifying keys': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , date : new Date
+ });
+
+ post.isModified('title').should.be.false;
+ post.set('title', 'test');
+ post.isModified('title').should.be.true;
+
+ post.isModified('date').should.be.false;
+ post.set('date', new Date(post.date + 1));
+ post.isModified('date').should.be.true;
+
+ post.isModified('meta.date').should.be.false;
+ db.close();
+ },
+
+ 'setting a key identically to its current value should not dirty the key': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , date : new Date
+ });
+
+ post.isModified('title').should.be.false;
+ post.set('title', 'Test');
+ post.isModified('title').should.be.false;
+ db.close();
+ },
+
+ 'test isModified and isDirectModified on a DocumentArray': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , comments : [ { title: 'Test', date: new Date, body: 'Test' } ]
+ });
+
+ post.isModified('comments.0.title').should.be.false;
+ post.get('comments')[0].set('title', 'Woot');
+ post.isModified('comments').should.be.true;
+ post.isDirectModified('comments').should.be.false;
+ post.isModified('comments.0.title').should.be.true;
+ post.isDirectModified('comments.0.title').should.be.true;
+
+ db.close();
+ },
+
+ 'test isModified on a DocumentArray with accessors': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , comments : [ { title: 'Test', date: new Date, body: 'Test' } ]
+ });
+
+ post.isModified('comments.0.body').should.be.false;
+ post.get('comments')[0].body = 'Woot';
+ post.isModified('comments').should.be.true;
+ post.isDirectModified('comments').should.be.false;
+ post.isModified('comments.0.body').should.be.true;
+ post.isDirectModified('comments.0.body').should.be.true;
+
+ db.close();
+ },
+
+ 'test isModified on a MongooseArray with atomics methods': function(){
+ // COMPLETEME
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+
+ post.isModified('owners').should.be.false;
+ post.get('owners').$push(new DocumentObjectId);
+ post.isModified('owners').should.be.true;
+
+ db.close();
+ },
+
+ 'test isModified on a MongooseArray with native methods': function(){
+ // COMPLETEME
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost()
+
+ post.isModified('owners').should.be.false;
+ post.get('owners').push(new DocumentObjectId);
+
+ db.close();
+ },
+
+ 'test isModified on a Mongoose Document - Modifying an existing record' : function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+
+ var doc = {
+ title : 'Test'
+ , slug : 'test'
+ , date : new Date
+ , meta : {
+ date : new Date
+ , visitors : 5
+ }
+ , published : true
+ , mixed : { x: [ { y: [1,'yes', 2] } ] }
+ , numbers : []
+ , owners : [new DocumentObjectId, new DocumentObjectId]
+ , comments : [
+ { title: 'Test', date: new Date, body: 'Test' }
+ , { title: 'Super', date: new Date, body: 'Cool' }
+ ]
+ };
+
+ BlogPost.create(doc, function (err, post) {
+ BlogPost.findById(post.id, function (err, postRead) {
+ db.close();
+ should.strictEqual(null, err);
+ //set the same data again back to the document.
+ //expected result, nothing should be set to modified
+ postRead.isModified('comments').should.be.false;
+ postRead.isNew.should.be.false;
+ postRead.set(postRead.toObject());
+
+ postRead.isModified('title').should.be.false;
+ postRead.isModified('slug').should.be.false;
+ postRead.isModified('date').should.be.false;
+ postRead.isModified('meta.date').should.be.false;
+ postRead.isModified('meta.visitors').should.be.false;
+ postRead.isModified('published').should.be.false;
+ postRead.isModified('mixed').should.be.false;
+ postRead.isModified('numbers').should.be.false;
+ postRead.isModified('owners').should.be.false;
+ postRead.isModified('comments').should.be.false;
+ postRead.comments[2] = { title: 'index' };
+ postRead.comments = postRead.comments;
+ postRead.isModified('comments').should.be.true;
+ });
+ });
+ },
+
+ // gh-662
+ 'test nested structure created by merging': function() {
+ var db = start();
+
+ var MergedSchema = new Schema({
+ a: {
+ foo: String
+ }
+ });
+
+ MergedSchema.add({
+ a: {
+ b: {
+ bar: String
+ }
+ }
+ });
+
+ mongoose.model('Merged', MergedSchema);
+ var Merged = db.model('Merged', 'merged_' + Math.random());
+
+ var merged = new Merged({
+ a: {
+ foo: 'baz'
+ , b: {
+ bar: 'qux'
+ }
+ }
+ });
+
+ merged.save(function(err) {
+ should.strictEqual(null, err);
+ Merged.findById(merged.id, function(err, found) {
+ db.close();
+ should.strictEqual(null, err);
+ found.a.foo.should.eql('baz');
+ found.a.b.bar.should.eql('qux');
+ });
+ });
+ },
+
+ // gh-714
+ 'modified nested objects which contain MongoseNumbers should not cause a RangeError on save': function () {
+ var db =start()
+
+ var schema = new Schema({
+ nested: {
+ num: Number
+ }
+ });
+
+ var M = db.model('NestedObjectWithMongooseNumber', schema);
+ var m = new M;
+ m.nested = null;
+ m.save(function (err) {
+ should.strictEqual(null, err);
+
+ M.findById(m, function (err, m) {
+ should.strictEqual(null, err);
+ m.nested.num = 5;
+ m.save(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ });
+ });
+ });
+ },
+
+ // gh-714 pt 2
+ 'no RangeError on remove() of a doc with Number _id': function () {
+ var db = start()
+
+ var MySchema = new Schema({
+ _id: { type: Number },
+ name: String
+ });
+
+ var MyModel = db.model('MyModel', MySchema, 'numberrangeerror'+random());
+
+ var instance = new MyModel({
+ name: 'test'
+ , _id: 35
+ });
+
+ instance.save(function (err) {
+ should.strictEqual(null, err);
+
+ MyModel.findById(35, function (err, doc) {
+ should.strictEqual(null, err);
+
+ doc.remove(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ });
+ });
+ });
+ },
+
+ // GH-342
+ 'over-writing a number should persist to the db': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({
+ meta: {
+ date : new Date
+ , visitors : 10
+ }
+ });
+
+ post.save( function (err) {
+ should.strictEqual(null, err);
+ post.set('meta.visitors', 20);
+ post.save( function (err) {
+ should.strictEqual(null, err);
+ BlogPost.findById(post.id, function (err, found) {
+ should.strictEqual(null, err);
+ found.get('meta.visitors').valueOf().should.equal(20);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test defining a new method': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.cool().should.eql(post);
+ db.close();
+ },
+
+ 'test defining a static': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.woot().should.eql(BlogPost);
+ db.close();
+ },
+
+ 'test casting error': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , threw = false;
+
+ var post = new BlogPost();
+
+ try {
+ post.init({
+ date: 'Test'
+ });
+ } catch(e){
+ threw = true;
+ }
+
+ threw.should.be.false;
+
+ try {
+ post.set('title', 'Test');
+ } catch(e){
+ threw = true;
+ }
+
+ threw.should.be.false;
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(CastError);
+ db.close();
+ });
+ },
+
+ 'test nested casting error': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , threw = false;
+
+ var post = new BlogPost();
+
+ try {
+ post.init({
+ meta: {
+ date: 'Test'
+ }
+ });
+ } catch(e){
+ threw = true;
+ }
+
+ threw.should.be.false;
+
+ try {
+ post.set('meta.date', 'Test');
+ } catch(e){
+ threw = true;
+ }
+
+ threw.should.be.false;
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(CastError);
+ db.close();
+ });
+ },
+
+ 'test casting error in subdocuments': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , threw = false;
+
+ var post = new BlogPost()
+ post.init({
+ title : 'Test'
+ , slug : 'test'
+ , comments : [ { title: 'Test', date: new Date, body: 'Test' } ]
+ });
+
+ post.get('comments')[0].set('date', 'invalid');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(CastError);
+ db.close();
+ });
+ },
+
+ 'test casting error when adding a subdocument': function(){
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , threw = false;
+
+ var post = new BlogPost()
+
+ try {
+ post.get('comments').push({
+ date: 'Bad date'
+ });
+ } catch (e) {
+ threw = true;
+ }
+
+ threw.should.be.false;
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(CastError);
+ db.close();
+ });
+ },
+
+ 'test validation': function(){
+ function dovalidate (val) {
+ this.asyncScope.should.equal('correct');
+ return true;
+ }
+
+ function dovalidateAsync (val, callback) {
+ this.scope.should.equal('correct');
+ process.nextTick(function () {
+ callback(true);
+ });
+ }
+
+ mongoose.model('TestValidation', new Schema({
+ simple: { type: String, required: true }
+ , scope: { type: String, validate: [dovalidate, 'scope failed'], required: true }
+ , asyncScope: { type: String, validate: [dovalidateAsync, 'async scope failed'], required: true }
+ }));
+
+ var db = start()
+ , TestValidation = db.model('TestValidation');
+
+ var post = new TestValidation();
+ post.set('simple', '');
+ post.set('scope', 'correct');
+ post.set('asyncScope', 'correct');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ post.set('simple', 'here');
+ post.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ 'test validation with custom message': function () {
+ function validate (val) {
+ return val === 'abc';
+ }
+ mongoose.model('TestValidationMessage', new Schema({
+ simple: { type: String, validate: [validate, 'must be abc'] }
+ }));
+
+ var db = start()
+ , TestValidationMessage = db.model('TestValidationMessage');
+
+ var post = new TestValidationMessage();
+ post.set('simple', '');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ err.errors.simple.should.be.an.instanceof(ValidatorError);
+ err.errors.simple.message.should.equal('Validator "must be abc" failed for path simple');
+ post.errors.simple.message.should.equal('Validator "must be abc" failed for path simple');
+
+ post.set('simple', 'abc');
+ post.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ // GH-272
+ 'test validation with Model.schema.path introspection': function () {
+ var db = start();
+ var IntrospectionValidationSchema = new Schema({
+ name: String
+ });
+ var IntrospectionValidation = db.model('IntrospectionValidation', IntrospectionValidationSchema, 'introspections_' + random());
+ IntrospectionValidation.schema.path('name').validate(function (value) {
+ return value.length < 2;
+ }, 'Name cannot be greater than 1 character');
+ var doc = new IntrospectionValidation({name: 'hi'});
+ doc.save( function (err) {
+ err.errors.name.message.should.equal("Validator \"Name cannot be greater than 1 character\" failed for path name");
+ err.name.should.equal("ValidationError");
+ err.message.should.equal("Validation failed");
+ db.close();
+ });
+ },
+
+ 'test required validation for undefined values': function () {
+ mongoose.model('TestUndefinedValidation', new Schema({
+ simple: { type: String, required: true }
+ }));
+
+ var db = start()
+ , TestUndefinedValidation = db.model('TestUndefinedValidation');
+
+ var post = new TestUndefinedValidation();
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ post.set('simple', 'here');
+ post.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ // GH-319
+ 'save callback should only execute once regardless of number of failed validations': function () {
+ var db = start()
+
+ var D = db.model('CallbackFiresOnceValidation', new Schema({
+ username: { type: String, validate: /^[a-z]{6}$/i }
+ , email: { type: String, validate: /^[a-z]{6}$/i }
+ , password: { type: String, validate: /^[a-z]{6}$/i }
+ }));
+
+ var post = new D({
+ username: "nope"
+ , email: "too"
+ , password: "short"
+ });
+
+ var timesCalled = 0;
+
+ post.save(function (err) {
+ db.close();
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ (++timesCalled).should.eql(1);
+
+ (Object.keys(err.errors).length).should.eql(3);
+ err.errors.password.should.be.an.instanceof(ValidatorError);
+ err.errors.email.should.be.an.instanceof(ValidatorError);
+ err.errors.username.should.be.an.instanceof(ValidatorError);
+ err.errors.password.message.should.eql('Validator failed for path password');
+ err.errors.email.message.should.eql('Validator failed for path email');
+ err.errors.username.message.should.eql('Validator failed for path username');
+
+ (Object.keys(post.errors).length).should.eql(3);
+ post.errors.password.should.be.an.instanceof(ValidatorError);
+ post.errors.email.should.be.an.instanceof(ValidatorError);
+ post.errors.username.should.be.an.instanceof(ValidatorError);
+ post.errors.password.message.should.eql('Validator failed for path password');
+ post.errors.email.message.should.eql('Validator failed for path email');
+ post.errors.username.message.should.eql('Validator failed for path username');
+
+ });
+ },
+
+ 'test validation on a query result': function () {
+ mongoose.model('TestValidationOnResult', new Schema({
+ resultv: { type: String, required: true }
+ }));
+
+ var db = start()
+ , TestV = db.model('TestValidationOnResult');
+
+ var post = new TestV;
+
+ post.validate(function (err) {
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ post.resultv = 'yeah';
+ post.save(function (err) {
+ should.strictEqual(err, null);
+ TestV.findOne({ _id: post.id }, function (err, found) {
+ should.strictEqual(err, null);
+ found.resultv.should.eql('yeah');
+ found.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ })
+ });
+ });
+ })
+ },
+
+ 'test required validation for previously existing null values': function () {
+ mongoose.model('TestPreviousNullValidation', new Schema({
+ previous: { type: String, required: true }
+ , a: String
+ }));
+
+ var db = start()
+ , TestP = db.model('TestPreviousNullValidation')
+
+ TestP.collection.insert({ a: null, previous: null}, {}, function (err, f) {
+ should.strictEqual(err, null);
+
+ TestP.findOne({_id: f[0]._id}, function (err, found) {
+ should.strictEqual(err, null);
+ found.isNew.should.be.false;
+ should.strictEqual(found.get('previous'), null);
+
+ found.validate(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ found.set('previous', 'yoyo');
+ found.save(function (err) {
+ should.strictEqual(err, null);
+ db.close();
+ });
+ })
+ })
+ });
+ },
+
+ 'test nested validation': function(){
+ mongoose.model('TestNestedValidation', new Schema({
+ nested: {
+ required: { type: String, required: true }
+ }
+ }));
+
+ var db = start()
+ , TestNestedValidation = db.model('TestNestedValidation');
+
+ var post = new TestNestedValidation();
+ post.set('nested.required', null);
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ post.set('nested.required', 'here');
+ post.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ 'test validation in subdocuments': function(){
+ var Subdocs = new Schema({
+ required: { type: String, required: true }
+ });
+
+ mongoose.model('TestSubdocumentsValidation', new Schema({
+ items: [Subdocs]
+ }));
+
+ var db = start()
+ , TestSubdocumentsValidation = db.model('TestSubdocumentsValidation');
+
+ var post = new TestSubdocumentsValidation();
+
+ post.get('items').push({ required: '' });
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ err.errors.required.should.be.an.instanceof(ValidatorError);
+ err.errors.required.message.should.eql('Validator "required" failed for path required');
+
+ post.get('items')[0].set('required', true);
+ post.save(function(err){
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ 'test async validation': function(){
+ var executed = false;
+
+ function validator(v, fn){
+ setTimeout(function () {
+ executed = true;
+ fn(v !== 'test');
+ }, 50);
+ };
+ mongoose.model('TestAsyncValidation', new Schema({
+ async: { type: String, validate: [validator, 'async validator'] }
+ }));
+
+ var db = start()
+ , TestAsyncValidation = db.model('TestAsyncValidation');
+
+ var post = new TestAsyncValidation();
+ post.set('async', 'test');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ err.errors.async.should.be.an.instanceof(ValidatorError);
+ err.errors.async.message.should.eql('Validator "async validator" failed for path async');
+ executed.should.be.true;
+ executed = false;
+
+ post.set('async', 'woot');
+ post.save(function(err){
+ executed.should.be.true;
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ 'test nested async validation': function(){
+ var executed = false;
+
+ function validator(v, fn){
+ setTimeout(function () {
+ executed = true;
+ fn(v !== 'test');
+ }, 50);
+ };
+
+ mongoose.model('TestNestedAsyncValidation', new Schema({
+ nested: {
+ async: { type: String, validate: [validator, 'async validator'] }
+ }
+ }));
+
+ var db = start()
+ , TestNestedAsyncValidation = db.model('TestNestedAsyncValidation');
+
+ var post = new TestNestedAsyncValidation();
+ post.set('nested.async', 'test');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ executed.should.be.true;
+ executed = false;
+
+ post.validate(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ executed.should.be.true;
+ executed = false;
+
+ post.set('nested.async', 'woot');
+ post.validate(function(err){
+ executed.should.be.true;
+ should.equal(err, null);
+ executed = false;
+
+ post.save(function(err){
+ executed.should.be.true;
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ })
+
+ });
+ },
+
+ 'test async validation in subdocuments': function(){
+ var executed = false;
+
+ function validator (v, fn) {
+ setTimeout(function(){
+ executed = true;
+ fn(v !== '');
+ }, 50);
+ };
+
+ var Subdocs = new Schema({
+ required: { type: String, validate: [validator, 'async in subdocs'] }
+ });
+
+ mongoose.model('TestSubdocumentsAsyncValidation', new Schema({
+ items: [Subdocs]
+ }));
+
+ var db = start()
+ , Test = db.model('TestSubdocumentsAsyncValidation');
+
+ var post = new Test();
+
+ post.get('items').push({ required: '' });
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ executed.should.be.true;
+ executed = false;
+
+ post.get('items')[0].set({ required: 'here' });
+ post.save(function(err){
+ executed.should.be.true;
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ },
+
+ 'test validation without saving': function(){
+
+ mongoose.model('TestCallingValidation', new Schema({
+ item: { type: String, required: true }
+ }));
+
+ var db = start()
+ , TestCallingValidation = db.model('TestCallingValidation');
+
+ var post = new TestCallingValidation;
+
+ post.schema.path('item').isRequired.should.be.true;
+
+ should.strictEqual(post.isNew, true);
+
+ post.validate(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ should.strictEqual(post.isNew, true);
+
+ post.item = 'yo';
+ post.validate(function(err){
+ should.equal(err, null);
+ should.strictEqual(post.isNew, true);
+ db.close();
+ });
+ });
+
+ },
+
+ 'test setting required to false': function () {
+ function validator () {
+ return true;
+ }
+
+ mongoose.model('TestRequiredFalse', new Schema({
+ result: { type: String, validate: [validator, 'chump validator'], required: false }
+ }));
+
+ var db = start()
+ , TestV = db.model('TestRequiredFalse');
+
+ var post = new TestV;
+
+ post.schema.path('result').isRequired.should.be.false;
+
+ db.close();
+ },
+
+ 'test defaults application': function(){
+ var now = Date.now();
+
+ mongoose.model('TestDefaults', new Schema({
+ date: { type: Date, default: now }
+ }));
+
+ var db = start()
+ , TestDefaults = db.model('TestDefaults');
+
+ var post = new TestDefaults();
+ post.get('date').should.be.an.instanceof(Date);
+ (+post.get('date')).should.eql(now);
+ db.close();
+ },
+
+ 'test "type" is allowed as a key': function(){
+ mongoose.model('TestTypeDefaults', new Schema({
+ type: { type: String, default: 'YES!' }
+ }));
+
+ var db = start()
+ , TestDefaults = db.model('TestTypeDefaults');
+
+ var post = new TestDefaults();
+ post.get('type').should.be.a('string');
+ post.get('type').should.eql('YES!');
+
+ // GH-402
+ var TestDefaults2 = db.model('TestTypeDefaults2', new Schema({
+ x: { y: { type: { type: String }, owner: String } }
+ }));
+
+ var post = new TestDefaults2;
+ post.x.y.type = "#402";
+ post.x.y.owner= "me";
+ post.save(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ });
+
+ },
+
+ 'test nested defaults application': function(){
+ var now = Date.now();
+
+ mongoose.model('TestNestedDefaults', new Schema({
+ nested: {
+ date: { type: Date, default: now }
+ }
+ }));
+
+ var db = start()
+ , TestDefaults = db.model('TestNestedDefaults');
+
+ var post = new TestDefaults();
+ post.get('nested.date').should.be.an.instanceof(Date);
+ (+post.get('nested.date')).should.eql(now);
+ db.close();
+ },
+
+ 'test defaults application in subdocuments': function(){
+ var now = Date.now();
+
+ var Items = new Schema({
+ date: { type: Date, default: now }
+ });
+
+ mongoose.model('TestSubdocumentsDefaults', new Schema({
+ items: [Items]
+ }));
+
+ var db = start()
+ , TestSubdocumentsDefaults = db.model('TestSubdocumentsDefaults');
+
+ var post = new TestSubdocumentsDefaults();
+ post.get('items').push({});
+ post.get('items')[0].get('date').should.be.an.instanceof(Date);
+ (+post.get('items')[0].get('date')).should.eql(now);
+ db.close();
+ },
+
+ // TODO: adapt this text to handle a getIndexes callback that's not unique to
+ // the mongodb-native driver.
+ 'test that indexes are ensured when the model is compiled': function(){
+ var Indexed = new Schema({
+ name : { type: String, index: true }
+ , last : String
+ , email : String
+ });
+
+ Indexed.index({ last: 1, email: 1 }, { unique: true });
+
+ var db = start()
+ , IndexedModel = db.model('IndexedModel', Indexed, 'indexedmodel' + random())
+ , assertions = 0;
+
+ IndexedModel.on('index', function(){
+ IndexedModel.collection.getIndexes(function(err, indexes){
+ should.strictEqual(err, null);
+
+ for (var i in indexes)
+ indexes[i].forEach(function(index){
+ if (index[0] == 'name')
+ assertions++;
+ if (index[0] == 'last')
+ assertions++;
+ if (index[0] == 'email')
+ assertions++;
+ });
+
+ assertions.should.eql(3);
+ db.close();
+ });
+ });
+ },
+
+ 'test indexes on embedded documents': function () {
+ var BlogPosts = new Schema({
+ _id : { type: ObjectId, index: true }
+ , title : { type: String, index: true }
+ , desc : String
+ });
+
+ var User = new Schema({
+ name : { type: String, index: true }
+ , blogposts : [BlogPosts]
+ });
+
+ var db = start()
+ , UserModel = db.model('DeepIndexedModel', User, 'deepindexedmodel' + random())
+ , assertions = 0;
+
+ UserModel.on('index', function () {
+ UserModel.collection.getIndexes(function (err, indexes) {
+ should.strictEqual(err, null);
+
+ for (var i in indexes)
+ indexes[i].forEach(function(index){
+ if (index[0] == 'name')
+ assertions++;
+ if (index[0] == 'blogposts._id')
+ assertions++;
+ if (index[0] == 'blogposts.title')
+ assertions++;
+ });
+
+ assertions.should.eql(3);
+ db.close();
+ });
+ });
+ },
+
+ 'compound indexes on embedded documents should be created': function () {
+ var BlogPosts = new Schema({
+ title : String
+ , desc : String
+ });
+
+ BlogPosts.index({ title: 1, desc: 1 });
+
+ var User = new Schema({
+ name : { type: String, index: true }
+ , blogposts : [BlogPosts]
+ });
+
+ var db = start()
+ , UserModel = db.model('DeepCompoundIndexModel', User, 'deepcompoundindexmodel' + random())
+ , found = 0;
+
+ UserModel.on('index', function () {
+ UserModel.collection.getIndexes(function (err, indexes) {
+ should.strictEqual(err, null);
+
+ for (var index in indexes) {
+ switch (index) {
+ case 'name_1':
+ case 'blogposts.title_1_blogposts.desc_1':
+ ++found;
+ break;
+ }
+ }
+
+ db.close();
+ found.should.eql(2);
+ });
+ });
+ },
+
+ 'test getters with same name on embedded documents not clashing': function() {
+ var Post = new Schema({
+ title : String
+ , author : { name : String }
+ , subject : { name : String }
+ });
+
+ mongoose.model('PostWithClashGetters', Post);
+
+ var db = start()
+ , PostModel = db.model('PostWithClashGetters', 'postwithclash' + random());
+
+ var post = new PostModel({
+ title: 'Test'
+ , author: { name: 'A' }
+ , subject: { name: 'B' }
+ });
+
+ post.author.name.should.eql('A');
+ post.subject.name.should.eql('B');
+ post.author.name.should.eql('A');
+
+ db.close();
+ },
+
+ 'test post save middleware': function () {
+ var schema = new Schema({
+ title: String
+ });
+
+ var called = 0;
+
+ schema.post('save', function (obj) {
+ obj.title.should.eql('Little Green Running Hood');
+ called.should.equal(0);
+ called++;
+ });
+
+ schema.post('save', function (obj) {
+ obj.title.should.eql('Little Green Running Hood');
+ called.should.equal(1);
+ called++;
+ });
+
+ schema.post('save', function (obj) {
+ obj.title.should.eql('Little Green Running Hood');
+ called.should.equal(2);
+ db.close();
+ });
+
+ var db = start()
+ , TestMiddleware = db.model('TestPostSaveMiddleware', schema);
+
+ var test = new TestMiddleware({ title: 'Little Green Running Hood'});
+
+ test.save(function(err){
+ should.strictEqual(err, null);
+ });
+ },
+
+ 'test middleware': function () {
+ var schema = new Schema({
+ title: String
+ });
+
+ var called = 0;
+
+ schema.pre('init', function (next) {
+ called++;
+ next();
+ });
+
+ schema.pre('save', function (next) {
+ called++;
+ next(new Error('Error 101'));
+ });
+
+ schema.pre('remove', function (next) {
+ called++;
+ next();
+ });
+
+ mongoose.model('TestMiddleware', schema);
+
+ var db = start()
+ , TestMiddleware = db.model('TestMiddleware');
+
+ var test = new TestMiddleware();
+
+ test.init({
+ title: 'Test'
+ });
+
+ called.should.eql(1);
+
+ test.save(function(err){
+ err.should.be.an.instanceof(Error);
+ err.message.should.eql('Error 101');
+ called.should.eql(2);
+
+ test.remove(function(err){
+ should.strictEqual(err, null);
+ called.should.eql(3);
+ db.close();
+ });
+ });
+ },
+
+ 'test post init middleware': function () {
+ var schema = new Schema({
+ title: String
+ });
+
+ var preinit = 0
+ , postinit = 0
+
+ schema.pre('init', function (next) {
+ ++preinit;
+ next();
+ });
+
+ schema.post('init', function () {
+ ++postinit;
+ });
+
+ mongoose.model('TestPostInitMiddleware', schema);
+
+ var db = start()
+ , Test = db.model('TestPostInitMiddleware');
+
+ var test = new Test({ title: "banana" });
+
+ test.save(function(err){
+ should.strictEqual(err, null);
+
+ Test.findById(test._id, function (err, test) {
+ should.strictEqual(err, null);
+ preinit.should.eql(1);
+ postinit.should.eql(1);
+ test.remove(function(err){
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test update doc casting': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.set('title', '1');
+
+ var id = post.get('_id');
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.update({ title: 1, _id: id }, { title: 2 }, function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.get('title').should.eql('2');
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test $push casting': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.get('numbers').push('3');
+ post.get('numbers')[0].should.equal(3);
+ db.close();
+ },
+
+ 'test $pull casting': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.get('numbers').push(1, 2, 3, 4);
+ post.save( function (err) {
+ BlogPost.findById( post.get('_id'), function (err, found) {
+ found.get('numbers').length.should.equal(4);
+ found.get('numbers').$pull('3');
+ found.save( function (err) {
+ BlogPost.findById( found.get('_id'), function (err, found2) {
+ found2.get('numbers').length.should.equal(3);
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'test updating numbers atomically': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , totalDocs = 4
+ , saveQueue = [];
+
+ var post = new BlogPost();
+ post.set('meta.visitors', 5);
+
+ post.save(function(err){
+ if (err) throw err;
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('meta.visitors').increment();
+ doc.get('meta.visitors').valueOf().should.be.equal(6);
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('meta.visitors').increment();
+ doc.get('meta.visitors').valueOf().should.be.equal(6);
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('meta.visitors').increment();
+ doc.get('meta.visitors').valueOf().should.be.equal(6);
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('meta.visitors').increment();
+ doc.get('meta.visitors').valueOf().should.be.equal(6);
+ save(doc);
+ });
+
+ function save(doc) {
+ saveQueue.push(doc);
+ if (saveQueue.length == 4)
+ saveQueue.forEach(function (doc) {
+ doc.save(function (err) {
+ if (err) throw err;
+ --totalDocs || complete();
+ });
+ });
+ };
+
+ function complete () {
+ BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) {
+ if (err) throw err;
+ doc.get('meta.visitors').valueOf().should.be.equal(9);
+ db.close();
+ });
+ };
+ });
+ },
+
+ 'test incrementing a number atomically with an arbitrary value': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost');
+
+ var post = new BlogPost();
+
+ post.meta.visitors = 0;
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ post.meta.visitors.increment(50);
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ (+doc.meta.visitors).should.eql(50);
+ db.close();
+ });
+ });
+ });
+ },
+
+ // GH-203
+ 'test changing a number non-atomically': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+
+ post.meta.visitors = 5;
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.meta.visitors -= 2;
+
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ (+doc.meta.visitors).should.eql(3);
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'test saving subdocuments atomically': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , totalDocs = 4
+ , saveQueue = [];
+
+ var post = new BlogPost();
+
+ post.save(function(err){
+ if (err) throw err;
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('comments').push({ title: '1' });
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('comments').push({ title: '2' });
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('comments').push({ title: '3' });
+ save(doc);
+ });
+
+ BlogPost.findOne({ _id: post.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('comments').push({ title: '4' }, { title: '5' });
+ save(doc);
+ });
+
+ function save(doc) {
+ saveQueue.push(doc);
+ if (saveQueue.length == 4)
+ saveQueue.forEach(function (doc) {
+ doc.save(function (err) {
+ if (err) throw err;
+ --totalDocs || complete();
+ });
+ });
+ };
+
+ function complete () {
+ BlogPost.findOne({ _id: post.get('_id') }, function (err, doc) {
+ if (err) throw err;
+
+ doc.get('comments').length.should.eql(5);
+
+ doc.get('comments').some(function(comment){
+ return comment.get('title') == '1';
+ }).should.be.true;
+
+ doc.get('comments').some(function(comment){
+ return comment.get('title') == '2';
+ }).should.be.true;
+
+ doc.get('comments').some(function(comment){
+ return comment.get('title') == '3';
+ }).should.be.true;
+
+ doc.get('comments').some(function(comment){
+ return comment.get('title') == '4';
+ }).should.be.true;
+
+ doc.get('comments').some(function(comment){
+ return comment.get('title') == '5';
+ }).should.be.true;
+
+ db.close();
+ });
+ };
+ });
+ },
+
+ // GH-310
+ 'test setting a subdocument atomically': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+
+ BlogPost.create({
+ comments: [{ title: 'first-title', body: 'first-body'}]
+ }, function (err, blog) {
+ should.strictEqual(null, err);
+ BlogPost.findById(blog.id, function (err, agent1blog) {
+ should.strictEqual(null, err);
+ BlogPost.findById(blog.id, function (err, agent2blog) {
+ should.strictEqual(null, err);
+ agent1blog.get('comments')[0].title = 'second-title';
+ agent1blog.save( function (err) {
+ should.strictEqual(null, err);
+ agent2blog.get('comments')[0].body = 'second-body';
+ agent2blog.save( function (err) {
+ should.strictEqual(null, err);
+ BlogPost.findById(blog.id, function (err, foundBlog) {
+ should.strictEqual(null, err);
+ db.close();
+ var comment = foundBlog.get('comments')[0];
+ comment.title.should.eql('second-title');
+ comment.body.should.eql('second-body');
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test doubly nested array saving and loading': function(){
+ var Inner = new Schema({
+ arr: [Number]
+ });
+
+ var Outer = new Schema({
+ inner: [Inner]
+ });
+ mongoose.model('Outer', Outer);
+
+ var db = start();
+ var Outer = db.model('Outer', 'arr_test_' + random());
+
+ var outer = new Outer();
+ outer.inner.push({});
+ outer.save(function(err) {
+ should.strictEqual(err, null);
+ outer.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ Outer.findById(outer.get('_id'), function(err, found) {
+ should.strictEqual(err, null);
+ should.equal(1, found.inner.length);
+ found.inner[0].arr.push(5);
+ found.save(function(err) {
+ should.strictEqual(err, null);
+ found.get('_id').should.be.an.instanceof(DocumentObjectId);
+ Outer.findById(found.get('_id'), function(err, found2) {
+ db.close();
+ should.strictEqual(err, null);
+ should.equal(1, found2.inner.length);
+ should.equal(1, found2.inner[0].arr.length);
+ should.equal(5, found2.inner[0].arr[0]);
+ });
+ });
+ });
+ });
+ },
+
+ 'test updating multiple Number $pushes as a single $pushAll': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({}, function (err, t) {
+ t.nested.nums.push(1);
+ t.nested.nums.push(2);
+
+ t.nested.nums.should.have.length(2);
+
+ t.save( function (err) {
+ should.strictEqual(null, err);
+ t.nested.nums.should.have.length(2);
+ Temp.findById(t._id, function (err, found) {
+ found.nested.nums.should.have.length(2);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test updating at least a single $push and $pushAll as a single $pushAll': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({}, function (err, t) {
+ t.nested.nums.push(1);
+ t.nested.nums.$pushAll([2, 3]);
+
+ t.nested.nums.should.have.length(3);
+
+ t.save( function (err) {
+ should.strictEqual(null, err);
+ t.nested.nums.should.have.length(3);
+ Temp.findById(t._id, function (err, found) {
+ found.nested.nums.should.have.length(3);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test activePaths should be updated for nested modifieds': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) {
+ t.nested.nums.$pull(1);
+ t.nested.nums.$pull(2);
+
+ t._activePaths.stateOf('nested.nums').should.equal('modify');
+ db.close();
+
+ });
+ },
+
+ '$pull should affect what you see in an array before a save': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) {
+ t.nested.nums.$pull(1);
+
+ t.nested.nums.should.have.length(4);
+
+ db.close();
+ });
+ },
+
+ '$pullAll should affect what you see in an array before a save': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) {
+ t.nested.nums.$pullAll([1, 2, 3]);
+
+ t.nested.nums.should.have.length(2);
+
+ db.close();
+ });
+ },
+
+ 'test updating multiple Number $pulls as a single $pullAll': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) {
+ t.nested.nums.$pull(1);
+ t.nested.nums.$pull(2);
+
+ t.nested.nums.should.have.length(3);
+
+ t.save( function (err) {
+ should.strictEqual(null, err);
+ t.nested.nums.should.have.length(3);
+ Temp.findById(t._id, function (err, found) {
+ found.nested.nums.should.have.length(3);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'having both a pull and pullAll should default to pullAll': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('NestedPushes', schema);
+ var Temp = db.model('NestedPushes', collection);
+
+ Temp.create({nested: {nums: [1, 2, 3, 4, 5]}}, function (err, t) {
+ t.nested.nums.$pull(1);
+ t.nested.nums.$pullAll([2, 3]);
+
+ t.nested.nums.should.have.length(2);
+
+ t.save( function (err) {
+ should.strictEqual(null, err);
+ t.nested.nums.should.have.length(2);
+ Temp.findById(t._id, function (err, found) {
+ found.nested.nums.should.have.length(2);
+ db.close();
+ });
+ });
+ });
+ },
+
+ '$shift': function () {
+ var db = start()
+ , schema = new Schema({
+ nested: {
+ nums: [Number]
+ }
+ });
+
+ mongoose.model('TestingShift', schema);
+ var Temp = db.model('TestingShift', collection);
+
+ Temp.create({ nested: { nums: [1,2,3] }}, function (err, t) {
+ should.strictEqual(null, err);
+
+ Temp.findById(t._id, function (err, found) {
+ should.strictEqual(null, err);
+ found.nested.nums.should.have.length(3);
+ found.nested.nums.$pop();
+ found.nested.nums.should.have.length(2);
+ found.nested.nums[0].should.eql(1);
+ found.nested.nums[1].should.eql(2);
+
+ found.save(function (err) {
+ should.strictEqual(null, err);
+ Temp.findById(t._id, function (err, found) {
+ should.strictEqual(null, err);
+ found.nested.nums.should.have.length(2);
+ found.nested.nums[0].should.eql(1);
+ found.nested.nums[1].should.eql(2);
+ found.nested.nums.$shift();
+ found.nested.nums.should.have.length(1);
+ found.nested.nums[0].should.eql(2);
+
+ found.save(function (err) {
+ should.strictEqual(null, err);
+ Temp.findById(t._id, function (err, found) {
+ db.close();
+ should.strictEqual(null, err);
+ found.nested.nums.should.have.length(1);
+ found.nested.nums[0].should.eql(2);
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test saving embedded arrays of Numbers atomically': function () {
+ var db = start()
+ , TempSchema = new Schema({
+ nums: [Number]
+ })
+ , totalDocs = 2
+ , saveQueue = [];
+
+ mongoose.model('Temp', TempSchema);
+ var Temp = db.model('Temp', collection);
+
+ var t = new Temp();
+
+ t.save(function(err){
+ if (err) throw err;
+
+ Temp.findOne({ _id: t.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('nums').push(1);
+ save(doc);
+ });
+
+ Temp.findOne({ _id: t.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('nums').push(2, 3);
+ save(doc);
+ });
+
+
+ function save(doc) {
+ saveQueue.push(doc);
+ if (saveQueue.length == totalDocs)
+ saveQueue.forEach(function (doc) {
+ doc.save(function (err) {
+ if (err) throw err;
+ --totalDocs || complete();
+ });
+ });
+ };
+
+ function complete () {
+ Temp.findOne({ _id: t.get('_id') }, function (err, doc) {
+ if (err) throw err;
+
+ doc.get('nums').length.should.eql(3);
+
+ doc.get('nums').some(function(num){
+ return num.valueOf() == '1';
+ }).should.be.true;
+
+ doc.get('nums').some(function(num){
+ return num.valueOf() == '2';
+ }).should.be.true;
+
+ doc.get('nums').some(function(num){
+ return num.valueOf() == '3';
+ }).should.be.true;
+
+
+ db.close();
+ });
+ };
+ });
+ },
+
+ 'test saving embedded arrays of Strings atomically': function () {
+ var db = start()
+ , StrListSchema = new Schema({
+ strings: [String]
+ })
+ , totalDocs = 2
+ , saveQueue = [];
+
+ mongoose.model('StrList', StrListSchema);
+ var StrList = db.model('StrList');
+
+ var t = new StrList();
+
+ t.save(function(err){
+ if (err) throw err;
+
+ StrList.findOne({ _id: t.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('strings').push('a');
+ save(doc);
+ });
+
+ StrList.findOne({ _id: t.get('_id') }, function(err, doc){
+ if (err) throw err;
+ doc.get('strings').push('b', 'c');
+ save(doc);
+ });
+
+
+ function save(doc) {
+ saveQueue.push(doc);
+ if (saveQueue.length == totalDocs)
+ saveQueue.forEach(function (doc) {
+ doc.save(function (err) {
+ if (err) throw err;
+ --totalDocs || complete();
+ });
+ });
+ };
+
+ function complete () {
+ StrList.findOne({ _id: t.get('_id') }, function (err, doc) {
+ if (err) throw err;
+
+ doc.get('strings').length.should.eql(3);
+
+ doc.get('strings').some(function(str){
+ return str == 'a';
+ }).should.be.true;
+
+ doc.get('strings').some(function(str){
+ return str == 'b';
+ }).should.be.true;
+
+ doc.get('strings').some(function(str){
+ return str == 'c';
+ }).should.be.true;
+
+ db.close();
+ });
+ };
+ });
+ },
+
+ 'test saving embedded arrays of Buffers atomically': function () {
+ var db = start()
+ , BufListSchema = new Schema({
+ buffers: [Buffer]
+ })
+ , totalDocs = 2
+ , saveQueue = [];
+
+ mongoose.model('BufList', BufListSchema);
+ var BufList = db.model('BufList');
+
+ var t = new BufList();
+
+ t.save(function(err){
+ should.strictEqual(null, err);
+
+ BufList.findOne({ _id: t.get('_id') }, function(err, doc){
+ should.strictEqual(null, err);
+ doc.get('buffers').push(new Buffer([140]));
+ save(doc);
+ });
+
+ BufList.findOne({ _id: t.get('_id') }, function(err, doc){
+ should.strictEqual(null, err);
+ doc.get('buffers').push(new Buffer([141]), new Buffer([142]));
+ save(doc);
+ });
+
+
+ function save(doc) {
+ saveQueue.push(doc);
+ if (saveQueue.length == totalDocs)
+ saveQueue.forEach(function (doc) {
+ doc.save(function (err) {
+ should.strictEqual(null, err);
+ --totalDocs || complete();
+ });
+ });
+ };
+
+ function complete () {
+ BufList.findOne({ _id: t.get('_id') }, function (err, doc) {
+ db.close();
+ should.strictEqual(null, err);
+
+ doc.get('buffers').length.should.eql(3);
+
+ doc.get('buffers').some(function(buf){
+ return buf[0] == 140;
+ }).should.be.true;
+
+ doc.get('buffers').some(function(buf){
+ return buf[0] == 141;
+ }).should.be.true;
+
+ doc.get('buffers').some(function(buf){
+ return buf[0] == 142;
+ }).should.be.true;
+
+ });
+ };
+ });
+ },
+
+ // GH-255
+ 'test updating an embedded document in an embedded array': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({comments: [{title: 'woot'}]}, function (err, post) {
+ should.strictEqual(err, null);
+ BlogPost.findById(post._id, function (err, found) {
+ should.strictEqual(err, null);
+ found.comments[0].title.should.equal('woot');
+ found.comments[0].title = 'notwoot';
+ found.save( function (err) {
+ should.strictEqual(err, null);
+ BlogPost.findById(found._id, function (err, updated) {
+ db.close();
+ should.strictEqual(err, null);
+ updated.comments[0].title.should.equal('notwoot');
+ });
+ });
+ });
+ });
+ },
+
+ // GH-334
+ 'test updating an embedded array document to an Object value': function () {
+ var db = start()
+ , SubSchema = new Schema({
+ name : String ,
+ subObj : { subName : String }
+ });
+ var GH334Schema = new Schema ({ name : String , arrData : [ SubSchema] });
+
+ mongoose.model('GH334' , GH334Schema);
+ var AModel = db.model('GH334');
+ var instance = new AModel();
+
+ instance.set( { name : 'name-value' , arrData : [ { name : 'arrName1' , subObj : { subName : 'subName1' } } ] });
+ instance.save(function(err) {
+ AModel.findById(instance.id, function(err, doc) {
+ doc.arrData[0].set('subObj' , { subName : 'modified subName' });
+ doc.save(function(err) {
+ should.strictEqual(null, err);
+ AModel.findById(instance.id, function (err, doc) {
+ db.close();
+ should.strictEqual(null, err);
+ doc.arrData[0].subObj.subName.should.eql('modified subName');
+ });
+ });
+ });
+ });
+ },
+
+ // GH-267
+ 'saving an embedded document twice should not push that doc onto the parent doc twice': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.comments.push({title: 'woot'});
+ post.save( function (err) {
+ should.strictEqual(err, null);
+ post.comments.should.have.length(1);
+ BlogPost.findById(post.id, function (err, found) {
+ should.strictEqual(err, null);
+ found.comments.should.have.length(1);
+ post.save( function (err) {
+ should.strictEqual(err, null);
+ post.comments.should.have.length(1);
+ BlogPost.findById(post.id, function (err, found) {
+ db.close();
+ should.strictEqual(err, null);
+ found.comments.should.have.length(1);
+ });
+ });
+ });
+ });
+ },
+
+ 'test filtering an embedded array by the id shortcut function': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+
+ post.comments.push({ title: 'woot' });
+ post.comments.push({ title: 'aaaa' });
+
+ var subdoc1 = post.comments[0];
+ var subdoc2 = post.comments[1];
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ // test with an objectid
+ doc.comments.id(subdoc1.get('_id')).title.should.eql('woot');
+
+ // test with a string
+ var id = DocumentObjectId.toString(subdoc2._id);
+ doc.comments.id(id).title.should.eql('aaaa');
+
+ db.close();
+ });
+ });
+ },
+
+ 'test filtering an embedded array by the id with cast error': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+ should.strictEqual(doc.comments.id(null), null);
+
+ db.close();
+ });
+ });
+ },
+
+ 'test filtering an embedded array by the id shortcut with no match': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+ should.strictEqual(doc.comments.id(new DocumentObjectId), null);
+
+ db.close();
+ });
+ });
+ },
+
+ 'test for removing a subdocument atomically': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.title = 'hahaha';
+ post.comments.push({ title: 'woot' });
+ post.comments.push({ title: 'aaaa' });
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.comments[0].remove();
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.comments.should.have.length(1);
+ doc.comments[0].title.should.eql('aaaa');
+
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'test for single pull embedded doc' : function() {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.title = 'hahaha';
+ post.comments.push({ title: 'woot' });
+ post.comments.push({ title: 'aaaa' });
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.comments.pull(doc.comments[0]);
+ doc.comments.pull(doc.comments[0]);
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.comments.should.have.length(0);
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'try saving mixed data': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , count = 3;
+
+ // string
+ var post = new BlogPost();
+ post.mixed = 'woot';
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err) {
+ should.strictEqual(err, null);
+
+ --count || db.close();
+ });
+ });
+
+ // array
+ var post2 = new BlogPost();
+ post2.mixed = { name: "mr bungle", arr: [] };
+ post2.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post2._id, function (err, doc){
+ should.strictEqual(err, null);
+
+ Array.isArray(doc.mixed.arr).should.be.true;
+
+ doc.mixed = [{foo: 'bar'}];
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(doc._id, function (err, doc){
+ should.strictEqual(err, null);
+
+ Array.isArray(doc.mixed).should.be.true;
+ doc.mixed.push({ hello: 'world' });
+ doc.mixed.push([ 'foo', 'bar' ]);
+ doc.commit('mixed');
+
+ doc.save(function (err, doc) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post2._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.mixed[0].should.eql({ foo: 'bar' });
+ doc.mixed[1].should.eql({ hello: 'world' });
+ doc.mixed[2].should.eql(['foo','bar']);
+ --count || db.close();
+ });
+ });
+ });
+
+ // date
+ var post3 = new BlogPost();
+ post3.mixed = new Date;
+ post3.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post3._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.mixed.should.be.an.instanceof(Date);
+ --count || db.close();
+ });
+ });
+ });
+
+ });
+ });
+
+ },
+
+ // GH-200
+ 'try populating mixed data from the constructor': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost');
+
+ var post = new BlogPost({
+ mixed: {
+ type: 'test'
+ , github: 'rules'
+ , nested: {
+ number: 3
+ }
+ }
+ });
+
+ post.mixed.type.should.eql('test');
+ post.mixed.github.should.eql('rules');
+ post.mixed.nested.number.should.eql(3);
+
+ db.close();
+ },
+
+ 'test that we instantiate MongooseNumber in arrays': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.numbers.push(1, '2', 3);
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ (~doc.numbers.indexOf(1)).should.not.eql(0);
+ (~doc.numbers.indexOf(2)).should.not.eql(0);
+ (~doc.numbers.indexOf(3)).should.not.eql(0);
+
+ db.close();
+ });
+ });
+ },
+
+ 'test removing from an array atomically using MongooseArray#remove': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.numbers.push(1, 2, 3);
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.numbers.remove('1');
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.numbers.should.have.length(2);
+ doc.numbers.remove('2', '3');
+
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.numbers.should.have.length(0);
+ db.close();
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test getting a virtual property via get(...)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost({
+ title: 'Letters from Earth'
+ , author: 'Mark Twain'
+ });
+
+ post.get('titleWithAuthor').should.equal('Letters from Earth by Mark Twain');
+
+ db.close();
+ },
+
+ 'test setting a virtual property': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain')
+ post.get('title').should.equal('Huckleberry Finn');
+ post.get('author').should.equal('Mark Twain');
+
+ db.close();
+ },
+
+ 'test getting a virtual property via shortcut getter': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost({
+ title: 'Letters from Earth'
+ , author: 'Mark Twain'
+ });
+
+ post.titleWithAuthor.should.equal('Letters from Earth by Mark Twain');
+
+ db.close();
+ },
+
+ // gh-685
+ 'getters should not be triggered at construction': function () {
+ var db = start()
+ , called = false
+
+ db.close();
+
+ var schema = new mongoose.Schema({
+ number: {
+ type:Number
+ , set: function(x){return x/2}
+ , get: function(x){
+ called = true;
+ return x*2;
+ }
+ }
+ });
+
+ var A = mongoose.model('gettersShouldNotBeTriggeredAtConstruction', schema);
+
+ var a = new A({ number: 100 });
+ called.should.be.false;
+ var num = a.number;
+ called.should.be.true;
+ num.valueOf().should.equal(100);
+ a.getValue('number').valueOf().should.equal(50);
+
+ called = false;
+ var b = new A;
+ b.init({ number: 50 });
+ called.should.be.false;
+ num = b.number;
+ called.should.be.true;
+ num.valueOf().should.equal(100);
+ b.getValue('number').valueOf().should.equal(50);
+ },
+
+ 'saving a doc with a set virtual property should persist the real properties but not the virtual property': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.set('titleWithAuthor', 'Huckleberry Finn by Mark Twain')
+ post.get('title').should.equal('Huckleberry Finn');
+ post.get('author').should.equal('Mark Twain');
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post.get('_id'), function (err, found) {
+ should.strictEqual(err, null);
+
+ found.get('title').should.equal('Huckleberry Finn');
+ found.get('author').should.equal('Mark Twain');
+ found.toObject().should.not.have.property('titleWithAuthor');
+ db.close();
+ });
+ });
+ },
+
+ 'test setting a pseudo-nested virtual property': function () {
+ var db = start()
+ , PersonSchema = new Schema({
+ name: {
+ first: String
+ , last: String
+ }
+ });
+
+ PersonSchema.virtual('name.full')
+ .get( function () {
+ return this.get('name.first') + ' ' + this.get('name.last');
+ })
+ .set( function (fullName) {
+ var split = fullName.split(' ');
+ this.set('name.first', split[0]);
+ this.set('name.last', split[1]);
+ });
+
+ mongoose.model('Person', PersonSchema);
+
+ var Person = db.model('Person')
+ , person = new Person({
+ name: {
+ first: 'Michael'
+ , last: 'Sorrentino'
+ }
+ });
+
+ person.get('name.full').should.equal('Michael Sorrentino');
+ person.set('name.full', 'The Situation');
+ person.get('name.first').should.equal('The');
+ person.get('name.last').should.equal('Situation');
+
+ person.name.full.should.equal('The Situation');
+ person.name.full = 'Michael Sorrentino';
+ person.name.first.should.equal('Michael');
+ person.name.last.should.equal('Sorrentino');
+
+ db.close();
+ },
+
+ 'test removing all documents from a collection via Model.remove': function () {
+ var db = start()
+ , collection = 'blogposts_' + random()
+ , BlogPost = db.model('BlogPost', collection)
+ , post = new BlogPost();
+
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.find({}, function (err, found) {
+ should.strictEqual(err, null);
+
+ found.length.should.equal(1);
+
+ BlogPost.remove({}, function (err) {
+ should.strictEqual(!!err, false);
+
+ BlogPost.find({}, function (err, found2) {
+ should.strictEqual(err, null);
+
+ found2.should.have.length(0);
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ // GH-190
+ 'test shorcut getter for a type defined with { type: Native }': function () {
+ var schema = new Schema({
+ date: { type: Date }
+ });
+
+ mongoose.model('ShortcutGetterObject', schema);
+
+ var db = start()
+ , ShortcutGetter = db.model('ShortcutGetterObject', 'shortcut' + random())
+ , post = new ShortcutGetter();
+
+ post.set('date', Date.now());
+ post.date.should.be.an.instanceof(Date);
+
+ db.close();
+ },
+
+ 'test shortcut getter for a nested path': function () {
+ var schema = new Schema({
+ first: {
+ second: [Number]
+ }
+ });
+ mongoose.model('ShortcutGetterNested', schema);
+
+ var db = start()
+ , ShortcutGetterNested = db.model('ShortcutGetterNested', collection)
+ , doc = new ShortcutGetterNested();
+
+ doc.first.should.be.a('object');
+ doc.first.second.should.be.an.instanceof(MongooseArray);
+
+ db.close();
+ },
+
+ // GH-195
+ 'test that save on an unaltered model doesn\'t clear the document': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.title = 'woot';
+ post.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(post._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ // we deliberately make no alterations
+ doc.save(function (err) {
+ should.strictEqual(err, null);
+
+ BlogPost.findById(doc._id, function (err, doc) {
+ should.strictEqual(err, null);
+
+ doc.title.should.eql('woot');
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'test that safe mode is the default and it works': function () {
+ var Human = new Schema({
+ name : String
+ , email : { type: String, unique: true }
+ });
+
+ mongoose.model('SafeHuman', Human, true);
+
+ var db = start()
+ , Human = db.model('SafeHuman', 'safehuman' + random());
+
+ var me = new Human({
+ name : 'Guillermo Rauch'
+ , email : 'rauchg@gmail.com'
+ });
+
+ me.save(function (err) {
+ should.strictEqual(err, null);
+
+ Human.findById(me._id, function (err, doc){
+ should.strictEqual(err, null);
+ doc.email.should.eql('rauchg@gmail.com');
+
+ var copycat = new Human({
+ name : 'Lionel Messi'
+ , email : 'rauchg@gmail.com'
+ });
+
+ copycat.save(function (err) {
+ /duplicate/.test(err.message).should.be.true;
+ err.should.be.an.instanceof(Error);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test that safe mode can be turned off': function () {
+ var Human = new Schema({
+ name : String
+ , email : { type: String, unique: true }
+ });
+
+ // turn it off
+ Human.set('safe', false);
+
+ mongoose.model('UnsafeHuman', Human, true);
+
+ var db = start()
+ , Human = db.model('UnsafeHuman', 'unsafehuman' + random());
+
+ var me = new Human({
+ name : 'Guillermo Rauch'
+ , email : 'rauchg@gmail.com'
+ });
+
+ me.save(function (err) {
+ should.strictEqual(err, null);
+
+ Human.findById(me._id, function (err, doc){
+ should.strictEqual(err, null);
+ doc.email.should.eql('rauchg@gmail.com');
+
+ var copycat = new Human({
+ name : 'Lionel Messi'
+ , email : 'rauchg@gmail.com'
+ });
+
+ copycat.save(function (err) {
+ should.strictEqual(err, null);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'passing null in pre hook works': function () {
+ var db = start();
+ var schema = new Schema({ name: String });
+
+ schema.pre('save', function (next) {
+ next(null); // <<-----
+ });
+
+ var S = db.model('S', schema, collection);
+ var s = new S({name: 'zupa'});
+
+ s.save(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ });
+
+ },
+
+ 'pre hooks called on all sub levels': function () {
+ var db = start();
+
+ var grandSchema = new Schema({ name : String });
+ grandSchema.pre('save', function (next) {
+ this.name = 'grand';
+ next();
+ });
+
+ var childSchema = new Schema({ name : String, grand : [grandSchema]});
+ childSchema.pre('save', function (next) {
+ this.name = 'child';
+ next();
+ });
+
+ var schema = new Schema({ name: String, child : [childSchema] });
+
+ schema.pre('save', function (next) {
+ this.name = 'parent';
+ next();
+ });
+
+ var S = db.model('presave_hook', schema, 'presave_hook');
+ var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]});
+
+ s.save(function (err, doc) {
+ db.close();
+ should.strictEqual(null, err);
+ doc.name.should.eql('parent');
+ doc.child[0].name.should.eql('child');
+ doc.child[0].grand[0].name.should.eql('grand');
+ });
+ },
+
+ 'pre hooks error on all sub levels': function () {
+ var db = start();
+
+ var grandSchema = new Schema({ name : String });
+ grandSchema.pre('save', function (next) {
+ next(new Error('Error 101'));
+ });
+
+ var childSchema = new Schema({ name : String, grand : [grandSchema]});
+ childSchema.pre('save', function (next) {
+ this.name = 'child';
+ next();
+ });
+
+ var schema = new Schema({ name: String, child : [childSchema] });
+ schema.pre('save', function (next) {
+ this.name = 'parent';
+ next();
+ });
+
+ var S = db.model('presave_hook_error', schema, 'presave_hook_error');
+ var s = new S({ name : 'a' , child : [ { name : 'b', grand : [{ name : 'c'}] } ]});
+
+ s.save(function (err, doc) {
+ db.close();
+ err.should.be.an.instanceof(Error);
+ err.message.should.eql('Error 101');
+ });
+ },
+
+ 'test post hooks': function () {
+ var schema = new Schema({
+ title: String
+ })
+ , save = false
+ , remove = false
+ , init = false
+ , post = undefined;
+
+ schema.post('save', function (arg) {
+ arg.id.should.equal(post.id)
+ save = true;
+ });
+
+ schema.post('init', function () {
+ init = true;
+ });
+
+ schema.post('remove', function (arg) {
+ arg.id.should.eql(post.id)
+ remove = true;
+ });
+
+ mongoose.model('PostHookTest', schema);
+
+ var db = start()
+ , BlogPost = db.model('PostHookTest');
+
+ post = new BlogPost();
+
+ post.save(function (err) {
+ process.nextTick(function () {
+ should.strictEqual(err, null);
+ save.should.be.true;
+ BlogPost.findById(post._id, function (err, doc) {
+ process.nextTick(function () {
+ should.strictEqual(err, null);
+ init.should.be.true;
+
+ doc.remove(function (err) {
+ process.nextTick(function () {
+ should.strictEqual(err, null);
+ remove.should.be.true;
+ db.close();
+ });
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test count querying via #run (aka #exec)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable count as promise'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.count({title: 'interoperable count as promise'});
+ query.exec(function (err, count) {
+ should.strictEqual(err, null);
+ count.should.equal(1);
+ db.close();
+ });
+ });
+ },
+
+ 'test update querying via #run (aka #exec)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable update as promise'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.update({title: 'interoperable update as promise'}, {title: 'interoperable update as promise delta'});
+ query.exec(function (err) {
+ should.strictEqual(err, null);
+ BlogPost.count({title: 'interoperable update as promise delta'}, function (err, count) {
+ should.strictEqual(err, null);
+ count.should.equal(1);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test findOne querying via #run (aka #exec)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable findOne as promise'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.findOne({title: 'interoperable findOne as promise'});
+ query.exec(function (err, found) {
+ should.strictEqual(err, null);
+ found.id.should.eql(created.id);
+ db.close();
+ });
+ });
+ },
+
+ 'test find querying via #run (aka #exec)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create(
+ {title: 'interoperable find as promise'}
+ , {title: 'interoperable find as promise'}
+ , function (err, createdOne, createdTwo) {
+ should.strictEqual(err, null);
+ var query = BlogPost.find({title: 'interoperable find as promise'});
+ query.exec(function (err, found) {
+ should.strictEqual(err, null);
+ found.length.should.equal(2);
+ found[0]._id.id.should.eql(createdOne._id.id);
+ found[1]._id.id.should.eql(createdTwo._id.id);
+ db.close();
+ });
+ });
+ },
+
+ 'test remove querying via #run (aka #exec)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create(
+ {title: 'interoperable remove as promise'}
+ , function (err, createdOne, createdTwo) {
+ should.strictEqual(err, null);
+ var query = BlogPost.remove({title: 'interoperable remove as promise'});
+ query.exec(function (err) {
+ should.strictEqual(err, null);
+ BlogPost.count({title: 'interoperable remove as promise'}, function (err, count) {
+ db.close();
+ count.should.equal(0);
+ });
+ });
+ });
+ },
+
+ 'test changing query at the last minute via #run(op, callback)': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable ad-hoc as promise'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.count({title: 'interoperable ad-hoc as promise'});
+ query.exec('findOne', function (err, found) {
+ should.strictEqual(err, null);
+ found.id;
+ found._id.should.eql(created._id);
+ db.close();
+ });
+ });
+ },
+
+ 'test count querying via #run (aka #exec) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable count as promise 2'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.count({title: 'interoperable count as promise 2'});
+ var promise = query.exec();
+ promise.addBack(function (err, count) {
+ should.strictEqual(err, null);
+ count.should.equal(1);
+ db.close();
+ });
+ });
+ },
+
+ 'test update querying via #run (aka #exec) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable update as promise 2'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.update({title: 'interoperable update as promise 2'}, {title: 'interoperable update as promise delta 2'});
+ var promise = query.run();
+ promise.addBack(function (err) {
+ should.strictEqual(err, null);
+ BlogPost.count({title: 'interoperable update as promise delta 2'}, function (err, count) {
+ should.strictEqual(err, null);
+ count.should.equal(1);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test findOne querying via #run (aka #exec) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable findOne as promise 2'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.findOne({title: 'interoperable findOne as promise 2'});
+ var promise = query.exec();
+ promise.addBack(function (err, found) {
+ should.strictEqual(err, null);
+ found.id.should.eql(created.id);
+ db.close();
+ });
+ });
+ },
+
+ 'test find querying via #run (aka #exec) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create(
+ {title: 'interoperable find as promise 2'}
+ , {title: 'interoperable find as promise 2'}
+ , function (err, createdOne, createdTwo) {
+ should.strictEqual(err, null);
+ var query = BlogPost.find({title: 'interoperable find as promise 2'});
+ var promise = query.run();
+ promise.addBack(function (err, found) {
+ should.strictEqual(err, null);
+ found.length.should.equal(2);
+ found[0].id;
+ found[1].id;
+ found[0]._id.should.eql(createdOne._id);
+ found[1]._id.should.eql(createdTwo._id);
+ db.close();
+ });
+ });
+ },
+
+ 'test remove querying via #run (aka #exec) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create(
+ {title: 'interoperable remove as promise 2'}
+ , function (err, createdOne, createdTwo) {
+ should.strictEqual(err, null);
+ var query = BlogPost.remove({title: 'interoperable remove as promise 2'});
+ var promise = query.exec();
+ promise.addBack(function (err) {
+ should.strictEqual(err, null);
+ BlogPost.count({title: 'interoperable remove as promise 2'}, function (err, count) {
+ count.should.equal(0);
+ db.close();
+ });
+ });
+ });
+ },
+
+ 'test changing query at the last minute via #run(op) with promise': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create({title: 'interoperable ad-hoc as promise 2'}, function (err, created) {
+ should.strictEqual(err, null);
+ var query = BlogPost.count({title: 'interoperable ad-hoc as promise 2'});
+ var promise = query.exec('findOne');
+ promise.addBack(function (err, found) {
+ should.strictEqual(err, null);
+ found._id.id.should.eql(created._id.id);
+ db.close();
+ });
+ });
+ },
+
+ 'test nested obj literal getter/setters': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var date = new Date;
+
+ var meta = {
+ date: date
+ , visitors: 5
+ };
+
+ var post = new BlogPost()
+ post.init({
+ meta: meta
+ });
+
+ post.get('meta').date.should.be.an.instanceof(Date);
+ post.meta.date.should.be.an.instanceof(Date);
+
+ var threw = false;
+ var getter1;
+ var getter2;
+ var strmet;
+ try {
+ strmet = JSON.stringify(meta);
+ getter1 = JSON.stringify(post.get('meta'));
+ getter2 = JSON.stringify(post.meta);
+ } catch (err) {
+ threw = true;
+ }
+
+ threw.should.be.false;
+ getter1 = JSON.parse(getter1);
+ getter2 = JSON.parse(getter2);
+ getter1.visitors.should.eql(getter2.visitors);
+ getter1.date.should.eql(getter2.date);
+
+ post.meta.date = new Date - 1000;
+ post.meta.date.should.be.an.instanceof(Date);
+ post.get('meta').date.should.be.an.instanceof(Date);
+
+ post.meta.visitors = 2;
+ post.get('meta').visitors.should.be.an.instanceof(MongooseNumber);
+ post.meta.visitors.should.be.an.instanceof(MongooseNumber);
+
+ var newmeta = {
+ date: date - 2000
+ , visitors: 234
+ };
+
+ post.set(newmeta, 'meta');
+
+ post.meta.date.should.be.an.instanceof(Date);
+ post.get('meta').date.should.be.an.instanceof(Date);
+ post.meta.visitors.should.be.an.instanceof(MongooseNumber);
+ post.get('meta').visitors.should.be.an.instanceof(MongooseNumber);
+ (+post.meta.date).should.eql(date - 2000);
+ (+post.get('meta').date).should.eql(date - 2000);
+ (+post.meta.visitors).should.eql(234);
+ (+post.get('meta').visitors).should.eql(234);
+
+ // set object directly
+ post.meta = {
+ date: date - 3000
+ , visitors: 4815162342
+ };
+
+ post.meta.date.should.be.an.instanceof(Date);
+ post.get('meta').date.should.be.an.instanceof(Date);
+ post.meta.visitors.should.be.an.instanceof(MongooseNumber);
+ post.get('meta').visitors.should.be.an.instanceof(MongooseNumber);
+ (+post.meta.date).should.eql(date - 3000);
+ (+post.get('meta').date).should.eql(date - 3000);
+ (+post.meta.visitors).should.eql(4815162342);
+ (+post.get('meta').visitors).should.eql(4815162342);
+
+ db.close();
+ },
+
+ 'nested object property access works when root initd with null': function () {
+ var db = start()
+
+ var schema = new Schema({
+ nest: {
+ st: String
+ }
+ });
+
+ mongoose.model('NestedStringA', schema);
+ var T = db.model('NestedStringA', collection);
+
+ var t = new T({ nest: null });
+
+ should.strictEqual(t.nest.st, null);
+ t.nest = { st: "jsconf rules" };
+ t.nest.toObject().should.eql({ st: "jsconf rules" });
+ t.nest.st.should.eql("jsconf rules");
+
+ t.save(function (err) {
+ should.strictEqual(err, null);
+ db.close();
+ })
+
+ },
+
+ 'nested object property access works when root initd with undefined': function () {
+ var db = start()
+
+ var schema = new Schema({
+ nest: {
+ st: String
+ }
+ });
+
+ mongoose.model('NestedStringB', schema);
+ var T = db.model('NestedStringB', collection);
+
+ var t = new T({ nest: undefined });
+
+ should.strictEqual(t.nest.st, undefined);
+ t.nest = { st: "jsconf rules" };
+ t.nest.toObject().should.eql({ st: "jsconf rules" });
+ t.nest.st.should.eql("jsconf rules");
+
+ t.save(function (err) {
+ should.strictEqual(err, null);
+ db.close();
+ })
+ },
+
+ 're-saving object with pre-existing null nested object': function(){
+ var db = start()
+
+ var schema = new Schema({
+ nest: {
+ st: String
+ , yep: String
+ }
+ });
+
+ mongoose.model('NestedStringC', schema);
+ var T = db.model('NestedStringC', collection);
+
+ var t = new T({ nest: null });
+
+ t.save(function (err) {
+ should.strictEqual(err, null);
+
+ t.nest = { st: "jsconf rules", yep: "it does" };
+ t.save(function (err) {
+ should.strictEqual(err, null);
+
+ T.findById(t.id, function (err, t) {
+ should.strictEqual(err, null);
+ t.nest.st.should.eql("jsconf rules");
+ t.nest.yep.should.eql("it does");
+
+ t.nest = null;
+ t.save(function (err) {
+ should.strictEqual(err, null);
+ should.strictEqual(t._doc.nest, null);
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'pushing to a nested array of Mixed works on existing doc': function () {
+ var db = start();
+
+ mongoose.model('MySchema', new Schema({
+ nested: {
+ arrays: []
+ }
+ }));
+
+ var DooDad = db.model('MySchema')
+ , doodad = new DooDad({ nested: { arrays: [] } })
+ , date = 1234567890;
+
+ doodad.nested.arrays.push(["+10", "yup", date]);
+
+ doodad.save(function (err) {
+ should.strictEqual(err, null);
+
+ DooDad.findById(doodad._id, function (err, doodad) {
+ should.strictEqual(err, null);
+
+ doodad.nested.arrays.toObject().should.eql([['+10','yup',date]]);
+
+ doodad.nested.arrays.push(["another", 1]);
+
+ doodad.save(function (err) {
+ should.strictEqual(err, null);
+
+ DooDad.findById(doodad._id, function (err, doodad) {
+ should.strictEqual(err, null);
+
+ doodad
+ .nested
+ .arrays
+ .toObject()
+ .should.eql([['+10','yup',date], ["another", 1]]);
+
+ db.close();
+ });
+ });
+ })
+ });
+
+ },
+
+ 'directly setting nested props works when property is named "type"': function () {
+ var db = start();
+
+ function def () {
+ return [{ x: 1 }, { x: 2 }, { x:3 }]
+ }
+
+ mongoose.model('MySchema2', new Schema({
+ nested: {
+ type: { type: String, default: 'yep' }
+ , array: {
+ type: Array, default: def
+ }
+ }
+ }));
+
+ var DooDad = db.model('MySchema2', collection)
+ , doodad = new DooDad()
+
+ doodad.save(function (err) {
+ should.strictEqual(err, null);
+
+ DooDad.findById(doodad._id, function (err, doodad) {
+ should.strictEqual(err, null);
+
+ doodad.nested.type.should.eql("yep");
+ doodad.nested.array.toObject().should.eql([{x:1},{x:2},{x:3}]);
+
+ doodad.nested.type = "nope";
+ doodad.nested.array = ["some", "new", "stuff"];
+
+ doodad.save(function (err) {
+ should.strictEqual(err, null);
+
+ DooDad.findById(doodad._id, function (err, doodad) {
+ should.strictEqual(err, null);
+ db.close();
+
+ doodad.nested.type.should.eql("nope");
+
+ doodad
+ .nested
+ .array
+ .toObject()
+ .should.eql(["some", "new", "stuff"]);
+
+ });
+ });
+ })
+ });
+ },
+
+ 'system.profile should be a default model': function () {
+ var Profile = mongoose.model('system.profile');
+ Profile.schema.paths.ts.should.be.a('object');
+ Profile.schema.paths.info.should.be.a('object');
+ Profile.schema.paths.millis.should.be.a('object');
+
+ Profile.schema.paths.op.should.be.a('object');
+ Profile.schema.paths.ns.should.be.a('object');
+ Profile.schema.paths.query.should.be.a('object');
+ Profile.schema.paths.updateobj.should.be.a('object');
+ Profile.schema.paths.ntoreturn.should.be.a('object');
+ Profile.schema.paths.nreturned.should.be.a('object');
+ Profile.schema.paths.nscanned.should.be.a('object');
+ Profile.schema.paths.responseLength.should.be.a('object');
+ Profile.schema.paths.client.should.be.a('object');
+ Profile.schema.paths.user.should.be.a('object');
+ Profile.schema.paths.idhack.should.be.a('object');
+ Profile.schema.paths.scanAndOrder.should.be.a('object');
+ Profile.schema.paths.keyUpdates.should.be.a('object');
+ should.strictEqual(undefined, Profile.schema.paths._id);
+ should.strictEqual(undefined, Profile.schema.virtuals.id);
+
+ var db = start();
+ Profile = db.model('system.profile');
+ db.close();
+ Profile.schema.paths.ts.should.be.a('object');
+ Profile.schema.paths.info.should.be.a('object');
+ Profile.schema.paths.millis.should.be.a('object');
+ Profile.schema.paths.op.should.be.a('object');
+ Profile.schema.paths.ns.should.be.a('object');
+ Profile.schema.paths.query.should.be.a('object');
+ Profile.schema.paths.updateobj.should.be.a('object');
+ Profile.schema.paths.ntoreturn.should.be.a('object');
+ Profile.schema.paths.nreturned.should.be.a('object');
+ Profile.schema.paths.nscanned.should.be.a('object');
+ Profile.schema.paths.responseLength.should.be.a('object');
+ Profile.schema.paths.client.should.be.a('object');
+ Profile.schema.paths.user.should.be.a('object');
+ Profile.schema.paths.idhack.should.be.a('object');
+ Profile.schema.paths.scanAndOrder.should.be.a('object');
+ Profile.schema.paths.keyUpdates.should.be.a('object');
+ should.strictEqual(undefined, Profile.schema.paths._id);
+ should.strictEqual(undefined, Profile.schema.virtuals.id);
+
+ // can override the default
+ db = start();
+ // reset Mongoose state
+ delete db.base.modelSchemas['system.profile']
+ delete db.base.models['system.profile']
+ delete db.models['system.profile'];
+ db.close();
+ // test
+ var over = db.model('system.profile', new Schema({ name: String }));
+ over.schema.paths.name.should.be.a('object');
+ should.strictEqual(undefined, over.schema.paths.ts);
+ // reset
+ delete db.base.modelSchemas['system.profile']
+ delete db.base.models['system.profile']
+ delete db.models['system.profile'];
+ },
+
+ 'setting profiling levels': function () {
+ var db = start();
+ db.setProfiling(3, function (err) {
+ err.message.should.eql('Invalid profiling level: 3');
+ db.setProfiling('fail', function (err) {
+ err.message.should.eql('Invalid profiling level: fail');
+ db.setProfiling(2, function (err, doc) {
+ should.strictEqual(err, null);
+ db.setProfiling(1, 50, function (err, doc) {
+ should.strictEqual(err, null);
+ doc.was.should.eql(2);
+ db.setProfiling(0, function (err, doc) {
+ db.close();
+ should.strictEqual(err, null);
+ doc.was.should.eql(1);
+ doc.slowms.should.eql(50);
+ });
+ });
+ });
+ });
+ });
+ },
+
+ 'test post hooks on embedded documents': function(){
+ var save = false,
+ init = false,
+ remove = false;
+
+ var EmbeddedSchema = new Schema({
+ title : String
+ });
+
+ var ParentSchema = new Schema({
+ embeds : [EmbeddedSchema]
+ });
+
+ EmbeddedSchema.post('save', function(next){
+ save = true;
+ });
+
+ // Don't know how to test those on a embedded document.
+ //EmbeddedSchema.post('init', function () {
+ //init = true;
+ //});
+
+ //EmbeddedSchema.post('remove', function () {
+ //remove = true;
+ //});
+
+ mongoose.model('Parent', ParentSchema);
+
+ var db = start(),
+ Parent = db.model('Parent');
+
+ var parent = new Parent();
+
+ parent.embeds.push({title: 'Testing post hooks for embedded docs'});
+
+ parent.save(function(err){
+ db.close();
+ should.strictEqual(err, null);
+ save.should.be.true;
+ });
+ },
+
+ 'console.log shows helpful values': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var date = new Date(1305730951086);
+ var id0 = new DocumentObjectId('4dd3e169dbfb13b4570000b9');
+ var id1 = new DocumentObjectId('4dd3e169dbfb13b4570000b6');
+ var id2 = new DocumentObjectId('4dd3e169dbfb13b4570000b7');
+ var id3 = new DocumentObjectId('4dd3e169dbfb13b4570000b8');
+
+ var post = new BlogPost({
+ title: 'Test'
+ , _id: id0
+ , date: date
+ , numbers: [5,6,7]
+ , owners: [id1]
+ , meta: { visitors: 45 }
+ , comments: [
+ { _id: id2, title: 'my comment', date: date, body: 'this is a comment' },
+ { _id: id3, title: 'the next thang', date: date, body: 'this is a comment too!' }]
+ });
+
+ db.close();
+
+ var a = '{ meta: { visitors: 45 },\n numbers: [ 5, 6, 7 ],\n owners: [ 4dd3e169dbfb13b4570000b6 ],\n comments: \n [{ _id: 4dd3e169dbfb13b4570000b7,\n comments: [],\n body: \'this is a comment\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'my comment\' }\n { _id: 4dd3e169dbfb13b4570000b8,\n comments: [],\n body: \'this is a comment too!\',\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'the next thang\' }],\n _id: 4dd3e169dbfb13b4570000b9,\n date: Wed, 18 May 2011 15:02:31 GMT,\n title: \'Test\' }'
+
+ var out = post.inspect();
+ ;/meta: { visitors: 45 }/.test(out).should.be.true;
+ ;/numbers: \[ 5, 6, 7 \]/.test(out).should.be.true;
+ ;/date: Wed, 18 May 2011 15:02:31 GMT/.test(out).should.be.true;
+ ;/activePaths:/.test(out).should.be.false;
+ ;/_atomics:/.test(out).should.be.false;
+ },
+
+ 'path can be used as pathname': function () {
+ var db = start()
+ , P = db.model('pathnametest', new Schema({ path: String }))
+ db.close();
+
+ var threw = false;
+ try {
+ new P({ path: 'i should not throw' });
+ } catch (err) {
+ threw = true;
+ }
+
+ threw.should.be.false;
+ },
+
+ 'when mongo is down, save callback should fire with err if auto_reconnect is disabled': function () {
+ var db = start({ server: { auto_reconnect: false }});
+ var T = db.model('Thing', new Schema({ type: String }));
+ db.on('open', function () {
+ var t = new T({ type: "monster" });
+
+ var worked = false;
+ t.save(function (err) {
+ worked = true;
+ err.message.should.eql('no open connections');
+ });
+
+ process.nextTick(function () {
+ db.close();
+ });
+
+ setTimeout(function () {
+ worked.should.be.true;
+ }, 500);
+ });
+ },
+
+ //'when mongo is down, auto_reconnect should kick in and db operation should succeed': function () {
+ //var db = start();
+ //var T = db.model('Thing', new Schema({ type: String }));
+ //db.on('open', function () {
+ //var t = new T({ type: "monster" });
+
+ //var worked = false;
+ //t.save(function (err) {
+ //should.strictEqual(err, null);
+ //worked = true;
+ //});
+
+ //process.nextTick(function () {
+ //db.close();
+ //});
+
+ //setTimeout(function () {
+ //worked.should.be.true;
+ //}, 500);
+ //});
+ //},
+
+ 'subdocuments with changed values should persist the values': function () {
+ var db = start()
+ var Subdoc = new Schema({ name: String, mixed: Schema.Types.Mixed });
+ var T = db.model('SubDocMixed', new Schema({ subs: [Subdoc] }));
+
+ var t = new T({ subs: [{ name: "Hubot", mixed: { w: 1, x: 2 }}] });
+ t.subs[0].name.should.equal("Hubot");
+ t.subs[0].mixed.w.should.equal(1);
+ t.subs[0].mixed.x.should.equal(2);
+
+ t.save(function (err) {
+ should.strictEqual(null, err);
+
+ T.findById(t._id, function (err, t) {
+ should.strictEqual(null, err);
+ t.subs[0].name.should.equal("Hubot");
+ t.subs[0].mixed.w.should.equal(1);
+ t.subs[0].mixed.x.should.equal(2);
+
+ var sub = t.subs[0];
+ sub.name = "Hubot1";
+ sub.name.should.equal("Hubot1");
+ sub.isModified('name').should.be.true;
+ t.modified.should.be.true;
+
+ t.save(function (err) {
+ should.strictEqual(null, err);
+
+ T.findById(t._id, function (err, t) {
+ should.strictEqual(null, err);
+ should.strictEqual(t.subs[0].name, "Hubot1");
+
+ var sub = t.subs[0];
+ sub.mixed.w = 5;
+ sub.mixed.w.should.equal(5);
+ sub.isModified('mixed').should.be.false;
+ sub.commit('mixed');
+ sub.isModified('mixed').should.be.true;
+ sub.modified.should.be.true;
+ t.modified.should.be.true;
+
+ t.save(function (err) {
+ should.strictEqual(null, err);
+
+ T.findById(t._id, function (err, t) {
+ db.close();
+ should.strictEqual(null, err);
+ should.strictEqual(t.subs[0].mixed.w, 5);
+ })
+ })
+ });
+ });
+ })
+ })
+ },
+
+ 'RegExps can be saved': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost({ mixed: { rgx: /^asdf$/ } });
+ post.mixed.rgx.should.be.instanceof(RegExp);
+ post.mixed.rgx.source.should.eql('^asdf$');
+ post.save(function (err) {
+ should.strictEqual(err, null);
+ BlogPost.findById(post._id, function (err, post) {
+ db.close();
+ should.strictEqual(err, null);
+ post.mixed.rgx.should.be.instanceof(RegExp);
+ post.mixed.rgx.source.should.eql('^asdf$');
+ });
+ });
+ },
+
+ 'null defaults are allowed': function () {
+ var db = start();
+ var T = db.model('NullDefault', new Schema({ name: { type: String, default: null }}), collection);
+ var t = new T();
+
+ should.strictEqual(null, t.name);
+
+ t.save(function (err) {
+ should.strictEqual(null, err);
+
+ T.findById(t._id, function (err, t) {
+ db.close();
+ should.strictEqual(null, err);
+ should.strictEqual(null, t.name);
+ });
+ });
+ },
+
+ // GH-365, GH-390, GH-422
+ 'test that setters are used on embedded documents': function () {
+ var db = start();
+
+ function setLat (val) {
+ return parseInt(val);
+ }
+
+ var tick = 0;
+ function uptick () {
+ return ++tick;
+ }
+
+ var Location = new Schema({
+ lat: { type: Number, default: 0, set: setLat}
+ , long: { type: Number, set: uptick }
+ });
+
+ var Deal = new Schema({
+ title: String
+ , locations: [Location]
+ });
+
+ Location = db.model('Location', Location, 'locations_' + random());
+ Deal = db.model('Deal', Deal, 'deals_' + random());
+
+ var location = new Location({lat: 1.2, long: 10});
+ location.lat.valueOf().should.equal(1);
+ location.long.valueOf().should.equal(1);
+
+ var deal = new Deal({title: "My deal", locations: [{lat: 1.2, long: 33}]});
+ deal.locations[0].lat.valueOf().should.equal(1);
+ deal.locations[0].long.valueOf().should.equal(2);
+
+ deal.save(function (err) {
+ should.strictEqual(err, null);
+ Deal.findById(deal._id, function (err, deal) {
+ db.close();
+ should.strictEqual(err, null);
+ deal.locations[0].lat.valueOf().should.equal(1);
+ // GH-422
+ deal.locations[0].long.valueOf().should.equal(2);
+ });
+ });
+ },
+
+ // GH-289
+ 'test that pre-init middleware has access to the true ObjectId when used with querying': function () {
+ var db = start()
+ , PreInitSchema = new Schema({})
+ , preId;
+ PreInitSchema.pre('init', function (next) {
+ preId = this._id;
+ next();
+ });
+ var PreInit = db.model('PreInit', PreInitSchema, 'pre_inits' + random());
+
+ var doc = new PreInit();
+ doc.save( function (err) {
+ should.strictEqual(err, null);
+ PreInit.findById(doc._id, function (err, found) {
+ db.close();
+ should.strictEqual(err, null);
+ should.strictEqual(undefined, preId);
+ });
+ });
+ },
+
+ // Demonstration showing why GH-261 is a misunderstanding
+ 'a single instantiated document should be able to update its embedded documents more than once': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ var post = new BlogPost();
+ post.comments.push({title: 'one'});
+ post.save(function (err) {
+ should.strictEqual(err, null);
+ post.comments[0].title.should.eql('one');
+ post.comments[0].title = 'two';
+ post.comments[0].title.should.eql('two');
+ post.save( function (err) {
+ should.strictEqual(err, null);
+ BlogPost.findById(post._id, function (err, found) {
+ should.strictEqual(err, null);
+ db.close();
+ should.strictEqual(err, null);
+ found.comments[0].title.should.eql('two');
+ });
+ });
+ });
+ },
+
+ 'defining a method on a Schema corresponding to an EmbeddedDocument should generate an instance method for the ED': function () {
+ var db = start();
+ var ChildSchema = new Schema({ name: String });
+ ChildSchema.method('talk', function () {
+ return 'gaga';
+ });
+
+ var ParentSchema = new Schema({
+ children: [ChildSchema]
+ });
+
+ var ChildA = db.model('ChildA', ChildSchema, 'children_' + random());
+ var ParentA = db.model('ParentA', ParentSchema, 'parents_' + random());
+
+ var c = new ChildA;
+ c.talk.should.be.a('function');
+
+ var p = new ParentA();
+ p.children.push({});
+ p.children[0].talk.should.be.a('function');
+ db.close();
+ },
+
+ 'Model#save should throw errors if a callback is not passed': function () {
+ var db = start();
+
+ var DefaultErrSchema = new Schema({});
+ DefaultErrSchema.pre('save', function (next) {
+ try {
+ next(new Error);
+ } catch (error) {
+ // throws b/c nothing is listening to the error event
+ db.close();
+ error.should.be.instanceof(Error);
+ }
+ });
+ var DefaultErr = db.model('DefaultErr1', DefaultErrSchema, 'default_err_' + random());
+ new DefaultErr().save();
+ },
+
+ 'Model#save should emit an error on its db if a callback is not passed to it': function () {
+ var db = start();
+
+ db.on('error', function (err) {
+ db.close();
+ err.should.be.an.instanceof(Error);
+ });
+
+ var DefaultErrSchema = new Schema({});
+ DefaultErrSchema.pre('save', function (next) {
+ next(new Error);
+ });
+ var DefaultErr = db.model('DefaultErr2', DefaultErrSchema, 'default_err_' + random());
+ new DefaultErr().save();
+ },
+
+ // once hooks-js merges our fix this will pass
+ 'calling next() after a thrown error should not work': function () {
+ var db = start();
+
+ var s = new Schema({});
+ s.methods.funky = function () {
+ should.strictEqual(false, true, 'reached unreachable code');
+ }
+
+ s.pre('funky', function (next) {
+ db.close();
+ try {
+ next(new Error);
+ } catch (error) {
+ error.should.be.instanceof(Error);
+ next();
+ // throws b/c nothing is listening to the error event
+ }
+ });
+ var Kaboom = db.model('wowNext2xAndThrow', s, 'next2xAndThrow' + random());
+ new Kaboom().funky();
+ },
+
+ 'ensureIndex error should emit on the db': function () {
+ var db = start();
+
+ db.on('error', function (err) {
+ /^E11000 duplicate key error index:/.test(err.message).should.equal(true);
+ db.close();
+ });
+
+ var schema = new Schema({ name: { type: String } })
+ , Test = db.model('IndexError', schema, "x"+random());
+
+ Test.create({ name: 'hi' }, { name: 'hi' }, function (err) {
+ should.strictEqual(err, null);
+ Test.schema.index({ name: 1 }, { unique: true });
+ Test.init();
+ });
+ },
+
+ 'backward compatibility with conflicted data in the db': function () {
+ var db = start();
+ var M = db.model('backwardDataConflict', new Schema({ namey: { first: String, last: String }}));
+ var m = new M({ namey: "[object Object]" });
+ m.namey = { first: 'GI', last: 'Joe' };// <-- should overwrite the string
+ m.save(function (err) {
+ db.close();
+ should.strictEqual(err, null);
+ should.strictEqual('GI', m.namey.first);
+ should.strictEqual('Joe', m.namey.last);
+ });
+ },
+
+ '#push should work on EmbeddedDocuments more than 2 levels deep': function () {
+ var db = start()
+ , Post = db.model('BlogPost', collection)
+ , Comment = db.model('CommentNesting', Comments, collection);
+
+ var p =new Post({ title: "comment nesting" });
+ var c1 =new Comment({ title: "c1" });
+ var c2 =new Comment({ title: "c2" });
+ var c3 =new Comment({ title: "c3" });
+
+ p.comments.push(c1);
+ c1.comments.push(c2);
+ c2.comments.push(c3);
+
+ p.save(function (err) {
+ should.strictEqual(err, null);
+
+ Post.findById(p._id, function (err, p) {
+ should.strictEqual(err, null);
+
+ var c4=new Comment({ title: "c4" });
+ p.comments[0].comments[0].comments[0].comments.push(c4);
+ p.save(function (err) {
+ should.strictEqual(err, null);
+
+ Post.findById(p._id, function (err, p) {
+ db.close();
+ should.strictEqual(err, null);
+ p.comments[0].comments[0].comments[0].comments[0].title.should.equal('c4');
+ });
+ });
+ });
+ })
+
+ },
+
+ 'test standalone invalidate': function() {
+ var db = start()
+ , InvalidateSchema = null
+ , Post = null
+ , post = null;
+
+ InvalidateSchema = new Schema({
+ prop: { type: String },
+ });
+
+ mongoose.model('InvalidateSchema', InvalidateSchema);
+
+ Post = db.model('InvalidateSchema');
+ post = new Post();
+ post.set({baz: 'val'});
+ post.invalidate('baz', 'reason');
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+
+ err.errors.baz.should.be.an.instanceof(ValidatorError);
+ err.errors.baz.message.should.equal('Validator "reason" failed for path baz');
+ err.errors.baz.type.should.equal('reason');
+ err.errors.baz.path.should.equal('baz');
+
+ post.save(function(err){
+ db.close();
+ should.strictEqual(err, null);
+ });
+ });
+ },
+
+ 'test simple validation middleware': function() {
+ var db = start()
+ , ValidationMiddlewareSchema = null
+ , Post = null
+ , post = null;
+
+ ValidationMiddlewareSchema = new Schema({
+ baz: { type: String }
+ });
+
+ ValidationMiddlewareSchema.pre('validate', function(next) {
+ if (this.get('baz') == 'bad') {
+ this.invalidate('baz', 'bad');
+ }
+ next();
+ });
+
+ mongoose.model('ValidationMiddleware', ValidationMiddlewareSchema);
+
+ Post = db.model('ValidationMiddleware');
+ post = new Post();
+ post.set({baz: 'bad'});
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ err.errors.baz.type.should.equal('bad');
+ err.errors.baz.path.should.equal('baz');
+
+ post.set('baz', 'good');
+ post.save(function(err){
+ db.close();
+ should.strictEqual(err, null);
+ });
+ });
+ },
+
+ 'test async validation middleware': function() {
+ var db = start()
+ , AsyncValidationMiddlewareSchema = null
+ , Post = null
+ , post = null;
+
+ AsyncValidationMiddlewareSchema = new Schema({
+ prop: { type: String }
+ });
+
+ AsyncValidationMiddlewareSchema.pre('validate', true, function(next, done) {
+ var self = this;
+ setTimeout(function() {
+ if (self.get('prop') == 'bad') {
+ self.invalidate('prop', 'bad');
+ }
+ done();
+ }, 50);
+ next();
+ });
+
+ mongoose.model('AsyncValidationMiddleware', AsyncValidationMiddlewareSchema);
+
+ Post = db.model('AsyncValidationMiddleware');
+ post = new Post();
+ post.set({prop: 'bad'});
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ err.errors.prop.type.should.equal('bad');
+ err.errors.prop.path.should.equal('prop');
+
+ post.set('prop', 'good');
+ post.save(function(err){
+ db.close();
+ should.strictEqual(err, null);
+ });
+ });
+ },
+
+ 'test complex validation middleware': function() {
+ var db = start()
+ , ComplexValidationMiddlewareSchema = null
+ , Post = null
+ , post = null
+ , abc = function(v) {
+ return v === 'abc';
+ };
+
+ ComplexValidationMiddlewareSchema = new Schema({
+ baz: { type: String },
+ abc: { type: String, validate: [abc, 'must be abc'] },
+ test: { type: String, validate: [/test/, 'must also be abc'] },
+ required: { type: String, required: true }
+ });
+
+ ComplexValidationMiddlewareSchema.pre('validate', true, function(next, done) {
+ var self = this;
+ setTimeout(function() {
+ if (self.get('baz') == 'bad') {
+ self.invalidate('baz', 'bad');
+ }
+ done();
+ }, 50);
+ next();
+ });
+
+ mongoose.model('ComplexValidationMiddleware', ComplexValidationMiddlewareSchema);
+
+ Post = db.model('ComplexValidationMiddleware');
+ post = new Post();
+ post.set({
+ baz: 'bad',
+ abc: 'not abc',
+ test: 'fail'
+ });
+
+ post.save(function(err){
+ err.should.be.an.instanceof(MongooseError);
+ err.should.be.an.instanceof(ValidationError);
+ Object.keys(err.errors).length.should.equal(4);
+ err.errors.baz.should.be.an.instanceof(ValidatorError);
+ err.errors.baz.type.should.equal('bad');
+ err.errors.baz.path.should.equal('baz');
+ err.errors.abc.should.be.an.instanceof(ValidatorError);
+ err.errors.abc.type.should.equal('must be abc');
+ err.errors.abc.path.should.equal('abc');
+ err.errors.test.should.be.an.instanceof(ValidatorError);
+ err.errors.test.type.should.equal('must also be abc');
+ err.errors.test.path.should.equal('test');
+ err.errors.required.should.be.an.instanceof(ValidatorError);
+ err.errors.required.type.should.equal('required');
+ err.errors.required.path.should.equal('required');
+
+ post.set({
+ baz: 'good',
+ abc: 'abc',
+ test: 'test',
+ required: 'here'
+ });
+
+ post.save(function(err){
+ db.close();
+ should.strictEqual(err, null);
+ });
+ });
+ },
+
+ 'Model.create can accept an array': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create([{ title: 'hi'}, { title: 'bye'}], function (err, post1, post2) {
+ db.close();
+ should.strictEqual(err, null);
+ post1.get('_id').should.be.an.instanceof(DocumentObjectId);
+ post2.get('_id').should.be.an.instanceof(DocumentObjectId);
+
+ post1.title.should.equal('hi');
+ post2.title.should.equal('bye');
+ });
+ },
+
+ 'Model.create when passed no docs still fires callback': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create(function (err, a) {
+ db.close();
+ should.strictEqual(err, null);
+ should.not.exist(a);
+ });
+ },
+
+ 'Model.create fires callback when an empty array is passed': function () {
+ var db = start()
+ , BlogPost = db.model('BlogPost', collection);
+
+ BlogPost.create([], function (err, a) {
+ db.close();
+ should.strictEqual(err, null);
+ should.not.exist(a);
+ });
+ },
+
+ 'enhanced date casting test (datejs - gh-502)': function () {
+ var db = start()
+
+ Date.prototype.toObject = function() {
+ return {
+ millisecond: 86
+ , second: 42
+ , minute: 47
+ , hour: 17
+ , day: 13
+ , week: 50
+ , month: 11
+ , year: 2011
+ };
+ };
+
+ var S = new Schema({
+ name: String
+ , description: String
+ , sabreId: String
+ , data: {
+ lastPrice: Number
+ , comm: String
+ , curr: String
+ , rateName: String
+ }
+ , created: { type: Date, default: Date.now }
+ , valid: { type: Boolean, default: true }
+ });
+
+ var M = db.model('gh502', S);
+
+ var m = new M;
+ m.save(function (err) {
+ should.strictEqual(err, null);
+ M.findById(m._id, function (err, m) {
+ should.strictEqual(err, null);
+ m.save(function (err) {
+ should.strictEqual(err, null);
+ M.remove(function (err) {
+ delete Date.prototype.toObject;
+ db.close();
+ });
+ });
+ });
+ });
+ },
+
+ 'should not persist non-schema props': function () {
+ var db = start()
+ , B = db.model('BlogPost', collection)
+
+ var b = new B;
+ b.whateveriwant = 10;
+ b.save(function (err) {
+ should.strictEqual(null, err);
+ B.collection.findOne({ _id: b._id }, function (err, doc) {
+ db.close();
+ should.strictEqual(null, err);
+ ;('whateveriwant' in doc).should.be.false;
+ });
+ });
+ },
+
+ 'should not throw range error when using Number _id and saving existing doc': function () {
+ var db =start();
+
+ var T = new Schema({ _id: Number, a: String });
+
+ var D = db.model('Testing691', T, 'asdf' + random());
+
+ var d = new D({ _id: 1 });
+ d.save(function (err) {
+ should.strictEqual(null, err);
+
+ D.findById(d._id, function (err, d) {
+ should.strictEqual(null, err);
+
+ d.a = 'yo';
+ d.save(function (err) {
+ db.close();
+ should.strictEqual(null, err);
+ });
+ });
+ });
+ },
+
+ 'number of affected docs should be returned when saving': function () {
+ var db = start()
+ var schema = new Schema({ name: String });
+ var S = db.model('AffectedDocsAreReturned', schema);
+ var s = new S({ name: 'aaron' });
+ s.save(function (err, doc, affected) {
+ should.strictEqual(null, err);
+ affected.should.equal(1);
+ s.name = 'heckmanananananana';
+ s.save(function (err, doc, affected) {
+ db.close();
+ should.strictEqual(null, err);
+ affected.should.equal(1);
+ });
+ });
+ }
+};