mongoose
Version:
Mongoose MongoDB ORM
849 lines (665 loc) • 23.6 kB
JavaScript
/**
* Module dependencies.
*/
var mongoose = require('./common').mongoose
, should = require('should')
, Schema = mongoose.Schema
, Document = mongoose.Document
, SchemaType = mongoose.SchemaType
, VirtualType = mongoose.VirtualType
, ObjectId = Schema.ObjectId
, ValidatorError = SchemaType.ValidatorError
, CastError = SchemaType.CastError
, SchemaTypes = Schema.Types
, DocumentObjectId = mongoose.Types.ObjectId
, Mixed = SchemaTypes.Mixed
, MongooseNumber = mongoose.Types.Number
, MongooseArray = mongoose.Types.Array
, vm = require('vm')
/**
* Test Document constructor.
*/
function TestDocument () {
Document.apply(this, arguments);
};
/**
* Inherits from Document.
*/
TestDocument.prototype.__proto__ = Document.prototype;
/**
* Set a dummy schema to simulate compilation.
*/
TestDocument.prototype.schema = new Schema({
test : String
});
/**
* Test.
*/
module.exports = {
'test different schema types support': function(){
var Checkin = new Schema({
date : Date
, location : {
lat: Number
, lng: Number
}
});
var Ferret = new Schema({
name : String
, owner : ObjectId
, fur : String
, color : { type: String }
, age : Number
, checkins : [Checkin]
, friends : [ObjectId]
, likes : Array
, alive : Boolean
, extra : Mixed
});
Ferret.path('name').should.be.an.instanceof(SchemaTypes.String);
Ferret.path('owner').should.be.an.instanceof(SchemaTypes.ObjectId);
Ferret.path('fur').should.be.an.instanceof(SchemaTypes.String);
Ferret.path('color').should.be.an.instanceof(SchemaTypes.String);
Ferret.path('age').should.be.an.instanceof(SchemaTypes.Number);
Ferret.path('checkins').should.be.an.instanceof(SchemaTypes.DocumentArray);
Ferret.path('friends').should.be.an.instanceof(SchemaTypes.Array);
Ferret.path('likes').should.be.an.instanceof(SchemaTypes.Array);
Ferret.path('alive').should.be.an.instanceof(SchemaTypes.Boolean);
Ferret.path('extra').should.be.an.instanceof(SchemaTypes.Mixed);
should.strictEqual(Ferret.path('unexistent'), undefined);
Checkin.path('date').should.be.an.instanceof(SchemaTypes.Date);
},
'dot notation support for accessing paths': function(){
var Racoon = new Schema({
name : { type: String, enum: ['Edwald', 'Tobi'] }
, age : Number
});
var Person = new Schema({
name : String
, raccoons : [Racoon]
, location : {
city : String
, state : String
}
});
Person.path('name').should.be.an.instanceof(SchemaTypes.String);
Person.path('raccoons').should.be.an.instanceof(SchemaTypes.DocumentArray);
Person.path('location.city').should.be.an.instanceof(SchemaTypes.String);
Person.path('location.state').should.be.an.instanceof(SchemaTypes.String);
should.strictEqual(Person.path('location.unexistent'), undefined);
},
'nested paths more than 2 levels deep': function () {
var Nested = new Schema({
first: {
second: {
third: String
}
}
});
Nested.path('first.second.third').should.be.an.instanceof(SchemaTypes.String);
},
'test default definition': function(){
var Test = new Schema({
simple : { type: String, default: 'a' }
, array : { type: Array, default: [1,2,3,4,5] }
, arrayX : { type: Array, default: 9 }
, arrayFn : { type: Array, default: function () { return [8] } }
, callback : { type: Number, default: function(){
this.a.should.eql('b');
return '3';
}}
});
Test.path('simple').defaultValue.should.eql('a');
Test.path('callback').defaultValue.should.be.a('function');
Test.path('simple').getDefault().should.eql('a');
(+Test.path('callback').getDefault({ a: 'b' })).should.eql(3);
Test.path('array').defaultValue.should.be.a('function');
Test.path('array').getDefault(new TestDocument)[3].should.eql(4);
Test.path('arrayX').getDefault(new TestDocument)[0].should.eql(9);
Test.path('arrayFn').defaultValue.should.be.a('function');
Test.path('arrayFn').getDefault(new TestDocument).should.be.an.instanceof(MongooseArray);
},
'test Mixed defaults can be empty arrays': function () {
var Test = new Schema({
mixed1 : { type: Mixed, default: [] }
, mixed2 : { type: Mixed, default: Array }
});
Test.path('mixed1').getDefault().should.be.an.instanceof(Array);
Test.path('mixed1').getDefault().length.should.be.eql(0);
Test.path('mixed2').getDefault().should.be.an.instanceof(Array);
Test.path('mixed2').getDefault().length.should.be.eql(0);
},
'test string required validation': function(){
var Test = new Schema({
simple: String
});
Test.path('simple').required(true);
Test.path('simple').validators.should.have.length(1);
Test.path('simple').doValidate(null, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('simple').doValidate(undefined, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('simple').doValidate('', function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('simple').doValidate('woot', function(err){
should.strictEqual(err, null);
});
},
'test string enum validation': function(){
var Test = new Schema({
complex: { type: String, enum: ['a', 'b', undefined, 'c', null] }
});
Test.path('complex').should.be.an.instanceof(SchemaTypes.String);
Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null]);
Test.path('complex').validators.should.have.length(1);
Test.path('complex').enum('d', 'e');
Test.path('complex').enumValues.should.eql(['a', 'b', 'c', null, 'd', 'e']);
Test.path('complex').doValidate('x', function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('complex').doValidate(undefined, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('complex').doValidate(null, function(err){
should.strictEqual(null, err);
});
Test.path('complex').doValidate('da', function(err){
err.should.be.an.instanceof(ValidatorError);
});
},
'test string regular expression validation': function(){
var Test = new Schema({
simple: { type: String, match: /[a-z]/ }
});
Test.path('simple').validators.should.have.length(1);
Test.path('simple').doValidate('az', function(err){
should.strictEqual(err, null);
});
Test.path('simple').match(/[0-9]/);
Test.path('simple').validators.should.have.length(2);
Test.path('simple').doValidate('12', function(err){
err.should.be.an.instanceof(ValidatorError);
});
Test.path('simple').doValidate('a12', function(err){
should.strictEqual(err, null);
});
},
'test string casting': function(){
var Tobi = new Schema({
nickname: String
});
function Test(){};
Test.prototype.toString = function(){
return 'woot';
};
// test Number -> String cast
Tobi.path('nickname').cast(0).should.be.a('string');
Tobi.path('nickname').cast(0).should.eql('0');
// test any object that implements toString
Tobi.path('nickname').cast(new Test()).should.be.a('string');
Tobi.path('nickname').cast(new Test()).should.eql('woot');
},
'test number minimums and maximums validation': function(){
var Tobi = new Schema({
friends: { type: Number, max: 15, min: 5 }
});
Tobi.path('friends').validators.should.have.length(2);
Tobi.path('friends').doValidate(10, function(err){
should.strictEqual(err, null);
});
Tobi.path('friends').doValidate(100, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Tobi.path('friends').doValidate(1, function(err){
err.should.be.an.instanceof(ValidatorError);
});
},
'test number required validation': function(){
var Edwald = new Schema({
friends: { type: Number, required: true }
});
Edwald.path('friends').doValidate(null, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Edwald.path('friends').doValidate(undefined, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Edwald.path('friends').doValidate(0, function(err){
should.strictEqual(err, null);
});
},
'test number casting': function(){
var Tobi = new Schema({
age: Number
});
// test String -> Number cast
Tobi.path('age').cast('0').should.be.an.instanceof(MongooseNumber);
(+Tobi.path('age').cast('0')).should.eql(0);
Tobi.path('age').cast(0).should.be.an.instanceof(MongooseNumber);
(+Tobi.path('age').cast(0)).should.eql(0);
},
'test date required validation': function(){
var Loki = new Schema({
birth_date: { type: Date, required: true }
});
Loki.path('birth_date').doValidate(null, function (err) {
err.should.be.an.instanceof(ValidatorError);
});
Loki.path('birth_date').doValidate(undefined, function (err) {
err.should.be.an.instanceof(ValidatorError);
});
Loki.path('birth_date').doValidate(new Date(), function (err) {
should.strictEqual(err, null);
});
},
'test date casting': function(){
var Loki = new Schema({
birth_date: { type: Date }
});
Loki.path('birth_date').cast(1294525628301).should.be.an.instanceof(Date);
Loki.path('birth_date').cast('8/24/2000').should.be.an.instanceof(Date);
Loki.path('birth_date').cast(new Date).should.be.an.instanceof(Date);
},
'test object id required validator': function(){
var Loki = new Schema({
owner: { type: ObjectId, required: true }
});
Loki.path('owner').doValidate(new DocumentObjectId(), function(err){
should.strictEqual(err, null);
});
Loki.path('owner').doValidate(null, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Loki.path('owner').doValidate(undefined, function(err){
err.should.be.an.instanceof(ValidatorError);
});
},
'test object id casting': function(){
var Loki = new Schema({
owner: { type: ObjectId }
});
var doc = new TestDocument()
, id = doc._id.toString();
Loki.path('owner').cast('4c54f3453e688c000000001a')
.should.be.an.instanceof(DocumentObjectId);
Loki.path('owner').cast(new DocumentObjectId())
.should.be.an.instanceof(DocumentObjectId);
Loki.path('owner').cast(doc)
.should.be.an.instanceof(DocumentObjectId);
Loki.path('owner').cast(doc).toString().should.eql(id);
},
'test array required validation': function(){
var Loki = new Schema({
likes: { type: Array, required: true }
});
Loki.path('likes').doValidate(null, function (err) {
err.should.be.an.instanceof(ValidatorError);
});
Loki.path('likes').doValidate(undefined, function (err) {
err.should.be.an.instanceof(ValidatorError);
});
Loki.path('likes').doValidate([], function (err) {
err.should.be.an.instanceof(ValidatorError);
});
},
'test array casting': function(){
var Loki = new Schema({
oids : [ObjectId]
, dates : [Date]
, numbers : [Number]
, strings : [String]
, buffers : [Buffer]
, nocast : []
, mixed : [Mixed]
});
var oids = Loki.path('oids').cast(['4c54f3453e688c000000001a', new DocumentObjectId]);
oids[0].should.be.an.instanceof(DocumentObjectId);
oids[1].should.be.an.instanceof(DocumentObjectId);
var dates = Loki.path('dates').cast(['8/24/2010', 1294541504958]);
dates[0].should.be.an.instanceof(Date);
dates[1].should.be.an.instanceof(Date);
var numbers = Loki.path('numbers').cast([152, '31']);
numbers[0].should.be.a('number');
numbers[1].should.be.a('number');
var strings = Loki.path('strings').cast(['test', 123]);
strings[0].should.be.a('string');
strings[0].should.eql('test');
strings[1].should.be.a('string');
strings[1].should.eql('123');
var buffers = Loki.path('buffers').cast(['\0\0\0', new Buffer("abc")]);
buffers[0].should.be.an.instanceof(Buffer);
buffers[1].should.be.an.instanceof(Buffer);
var nocasts = Loki.path('nocast').cast(['test', 123]);
nocasts[0].should.be.a('string');
nocasts[0].should.eql('test');
nocasts[1].should.be.a('number');
nocasts[1].should.eql(123);
var mixed = Loki.path('mixed').cast(['test', 123, '123', {}, new Date, new DocumentObjectId]);
mixed[0].should.be.a('string');
mixed[1].should.be.a('number');
mixed[2].should.be.a('string');
mixed[3].should.be.a('object');
mixed[4].should.be.an.instanceof(Date);
mixed[5].should.be.an.instanceof(DocumentObjectId);
},
'test boolean required validator': function(){
var Animal = new Schema({
isFerret: { type: Boolean, required: true }
});
Animal.path('isFerret').doValidate(null, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Animal.path('isFerret').doValidate(undefined, function(err){
err.should.be.an.instanceof(ValidatorError);
});
Animal.path('isFerret').doValidate(true, function(err){
should.strictEqual(err, null);
});
Animal.path('isFerret').doValidate(false, function(err){
should.strictEqual(err, null);
});
},
'test boolean casting': function(){
var Animal = new Schema({
isFerret: { type: Boolean, required: true }
});
Animal.path('isFerret').cast(null).should.be.false;
Animal.path('isFerret').cast(undefined).should.be.false;
Animal.path('isFerret').cast(false).should.be.false;
Animal.path('isFerret').cast(0).should.be.false;
Animal.path('isFerret').cast('0').should.be.false;
Animal.path('isFerret').cast({}).should.be.true;
Animal.path('isFerret').cast(true).should.be.true;
Animal.path('isFerret').cast(1).should.be.true;
Animal.path('isFerret').cast('1').should.be.true;
},
'test async validators': function(beforeExit){
var executed = 0;
function validator (value, fn) {
setTimeout(function(){
executed++;
fn(value === true);
}, 50);
};
var Animal = new Schema({
ferret: { type: Boolean, validate: validator }
});
Animal.path('ferret').doValidate(true, function(err){
should.strictEqual(err, null);
});
Animal.path('ferret').doValidate(false, function(err){
err.should.be.an.instanceof(Error);
});
beforeExit(function(){
executed.should.eql(2);
});
},
'test async validators scope': function(beforeExit){
var executed = false;
function validator (value, fn) {
this.a.should.eql('b');
setTimeout(function(){
executed = true;
fn(true);
}, 50);
};
var Animal = new Schema({
ferret: { type: Boolean, validate: validator }
});
Animal.path('ferret').doValidate(true, function(err){
should.strictEqual(err, null);
}, { a: 'b' });
beforeExit(function(){
executed.should.be.true;
});
},
'test declaring new methods': function(){
var a = new Schema();
a.method('test', function(){});
a.method({
a: function(){}
, b: function(){}
});
Object.keys(a.methods).should.have.length(3);
},
'test declaring new statics': function(){
var a = new Schema();
a.static('test', function(){});
a.static({
a: function(){}
, b: function(){}
, c: function(){}
});
Object.keys(a.statics).should.have.length(4);
},
'test setter(s)': function(){
function lowercase (v) {
return v.toLowerCase();
};
var Tobi = new Schema({
name: { type: String, set: lowercase }
});
Tobi.path('name').applySetters('WOOT').should.eql('woot');
Tobi.path('name').setters.should.have.length(1);
Tobi.path('name').set(function(v){
return v + 'WOOT';
});
Tobi.path('name').applySetters('WOOT').should.eql('wootwoot');
Tobi.path('name').setters.should.have.length(2);
},
'test setters scope': function(){
function lowercase (v) {
this.a.should.eql('b');
return v.toLowerCase();
};
var Tobi = new Schema({
name: { type: String, set: lowercase }
});
Tobi.path('name').applySetters('WHAT', { a: 'b' }).should.eql('what');
},
'test string built-in setter `lowercase`': function () {
var Tobi = new Schema({
name: { type: String, lowercase: true }
});
Tobi.path('name').applySetters('WHAT').should.eql('what');
},
'test string built-in setter `uppercase`': function () {
var Tobi = new Schema({
name: { type: String, uppercase: true }
});
Tobi.path('name').applySetters('what').should.eql('WHAT');
},
'test string built-in setter `trim`': function () {
var Tobi = new Schema({
name: { type: String, uppercase: true, trim: true }
});
Tobi.path('name').applySetters(' what ').should.eql('WHAT');
},
'test getter(s)': function(){
function woot (v) {
return v + ' woot';
};
var Tobi = new Schema({
name: { type: String, get: woot }
});
Tobi.path('name').getters.should.have.length(1);
Tobi.path('name').applyGetters('test').should.eql('test woot');
},
'test getters scope': function(){
function woot (v) {
this.a.should.eql('b');
return v.toLowerCase();
};
var Tobi = new Schema({
name: { type: String, get: woot }
});
Tobi.path('name').applyGetters('YEP', { a: 'b' }).should.eql('yep');
},
'test setters casting': function(){
function last (v) {
v.should.be.a('string');
v.should.eql('0');
return 'last';
};
function first (v) {
return 0;
};
var Tobi = new Schema({
name: { type: String, set: last }
});
Tobi.path('name').set(first);
Tobi.path('name').applySetters('woot').should.eql('last');
},
'test getters casting': function(){
function last (v) {
v.should.be.a('string');
v.should.eql('0');
return 'last';
};
function first (v) {
return 0;
};
var Tobi = new Schema({
name: { type: String, get: last }
});
Tobi.path('name').get(first);
Tobi.path('name').applyGetters('woot').should.eql('last');
},
'test hooks registration': function(){
var Tobi = new Schema();
Tobi.pre('save', function(){});
Tobi.callQueue.should.have.length(1);
Tobi.post('save', function(){});
Tobi.callQueue.should.have.length(2);
Tobi.pre('save', function(){});
Tobi.callQueue.should.have.length(3);
},
'test applying setters when none have been defined': function(){
var Tobi = new Schema({
name: String
});
Tobi.path('name').applySetters('woot').should.eql('woot');
},
'test applying getters when none have been defined': function(){
var Tobi = new Schema({
name: String
});
Tobi.path('name').applyGetters('woot').should.eql('woot');
},
'test defining an index': function(){
var Tobi = new Schema({
name: { type: String, index: true }
});
Tobi.path('name')._index.should.be.true;
Tobi.path('name').index({ unique: true });
Tobi.path('name')._index.should.eql({ unique: true });
Tobi.path('name').unique(false);
Tobi.path('name')._index.should.eql({ unique: false});
var T1 = new Schema({
name: { type: String, sparse: true }
});
T1.path('name')._index.should.eql({ sparse: true });
var T2 = new Schema({
name: { type: String, unique: true }
});
T2.path('name')._index.should.eql({ unique: true });
var T3 = new Schema({
name: { type: String, sparse: true, unique: true }
});
T3.path('name')._index.should.eql({ sparse: true, unique: true });
var T4 = new Schema({
name: { type: String, unique: true, sparse: true }
});
var i = T4.path('name')._index;
i.unique.should.be.true;
i.sparse.should.be.true;
var T5 = new Schema({
name: { type: String, index: { sparse: true, unique: true } }
});
var i = T5.path('name')._index;
i.unique.should.be.true;
i.sparse.should.be.true;
},
'test defining compound indexes': function(){
var Tobi = new Schema({
name: { type: String, index: true }
, last: { type: Number, sparse: true }
});
Tobi.index({ firstname: 1, last: 1 }, { unique: true });
Tobi.indexes.should.eql([
[{ name: 1 }, {}]
, [{ last: 1 }, { sparse: true }]
, [{ firstname: 1, last: 1}, {unique: true}]
]);
},
'test plugins': function (beforeExit) {
var Tobi = new Schema()
, called = false;
Tobi.plugin(function(schema){
schema.should.equal(Tobi);
called = true;
});
beforeExit(function () {
called.should.be.true;
});
},
'test that default options are set': function () {
var Tobi = new Schema();
Tobi.options.should.be.a('object');
Tobi.options.safe.should.be.true;
},
'test setting options': function () {
var Tobi = new Schema();
Tobi.set('a', 'b');
Tobi.set('safe', false);
Tobi.options.a.should.eql('b');
Tobi.options.safe.should.be.false;
},
'test declaring virtual attributes': function () {
var Contact = new Schema({
firstName: String
, lastName: String
});
Contact.virtual('fullName')
.get( function () {
return this.get('firstName') + ' ' + this.get('lastName');
}).set(function (fullName) {
var split = fullName.split(' ');
this.set('firstName', split[0]);
this.set('lastName', split[1]);
});
Contact.virtualpath('fullName').should.be.an.instanceof(VirtualType);
},
'test GH-298 - The default creation of a virtual `id` should be muted when someone defines their own `id` attribute': function () {
new Schema({ id: String });
},
'allow disabling the auto .id virtual': function () {
var schema = new Schema({ name: String }, { noVirtualId: true });
should.strictEqual(undefined, schema.virtuals.id);
},
'schema creation works with objects from other contexts': function () {
var str = 'code = {' +
' name: String' +
', arr1: Array ' +
', arr2: { type: [] }' +
', date: Date ' +
', num: { type: Number }' +
', bool: Boolean' +
', nest: { sub: { type: {}, required: true }}' +
'}';
var script = vm.createScript(str, 'testSchema.vm');
var sandbox = { code: null };
script.runInNewContext(sandbox);
var Ferret = new Schema(sandbox.code);
Ferret.path('nest.sub').should.be.an.instanceof(SchemaTypes.Mixed);
Ferret.path('name').should.be.an.instanceof(SchemaTypes.String);
Ferret.path('arr1').should.be.an.instanceof(SchemaTypes.Array);
Ferret.path('arr2').should.be.an.instanceof(SchemaTypes.Array);
Ferret.path('date').should.be.an.instanceof(SchemaTypes.Date);
Ferret.path('num').should.be.an.instanceof(SchemaTypes.Number);
Ferret.path('bool').should.be.an.instanceof(SchemaTypes.Boolean);
}
};