loopback-datasource-juggler/test/common_test.js

1481 lines
56 KiB
JavaScript
Raw Normal View History

2011-10-03 17:18:44 +00:00
var Schema = require('../index').Schema;
var Text = Schema.Text;
// var schemas = {
// // riak: {},
// mysql: {
// database: 'myapp_test',
// username: 'root'
// },
// postgres: {
// database: 'myapp_test',
// username: 'postgres'
// },
// sqlite3: {
// database: ':memory:'
// },
// neo4j: { url: 'http://localhost:7474/' },
// // mongoose: { url: 'mongodb://travis:test@localhost:27017/myapp' },
// mongodb: { url: 'mongodb://travis:test@localhost:27017/myapp' },
// redis2: {},
// memory: {},
// cradle: {},
// nano: { url: 'http://localhost:5984/nano-test' }
// };
2011-10-03 17:18:44 +00:00
var nbSchemaRequests = 0;
2011-10-16 17:21:08 +00:00
var batch;
2012-12-14 17:44:38 +00:00
var schemaName;
2012-08-13 06:16:01 +00:00
function it(name, cases) {
2012-12-14 17:44:38 +00:00
batch[schemaName][name] = cases;
2012-08-13 06:16:01 +00:00
}
function skip(name) {
delete batch[schemaName][name];
}
module.exports = function testSchema(exportCasesHere, schema) {
2012-03-11 04:48:38 +00:00
batch = exportCasesHere;
2012-12-14 17:44:38 +00:00
schemaName = schema.name;
if (schema.name.match(/^\/.*\/test\/\.\.$/)) {
schemaName = schemaName.split('/').slice(-3).shift();
}
var start;
2012-03-22 20:24:15 +00:00
2012-12-14 17:44:38 +00:00
batch['should connect to database'] = function (test) {
start = Date.now();
if (schema.connected) return test.done();
schema.on('connected', test.done);
2012-12-14 17:44:38 +00:00
};
schema.log = function (a) {
console.log(a);
nbSchemaRequests++;
};
2012-12-14 17:44:38 +00:00
batch[schemaName] = {};
testOrm(schema);
2012-12-14 17:44:38 +00:00
batch['all tests done'] = function (test) {
test.done();
process.nextTick(allTestsDone);
};
function allTestsDone() {
schema.disconnect();
console.log('Test done in %dms\n', Date.now() - start);
}
};
2011-10-03 17:18:44 +00:00
2012-12-14 17:44:38 +00:00
Object.defineProperty(module.exports, 'it', {
writable: true,
enumerable: false,
configurable: true,
value: it
});
Object.defineProperty(module.exports, 'skip', {
writable: true,
enumerable: false,
configurable: true,
value: skip
});
function clearAndCreate(model, data, callback) {
var createdItems = [];
model.destroyAll(function () {
nextItem(null, null);
});
var itemIndex = 0;
function nextItem(err, lastItem) {
if (lastItem !== null) {
createdItems.push(lastItem);
}
if (itemIndex >= data.length) {
callback(createdItems);
return;
}
model.create(data[itemIndex], nextItem);
itemIndex++;
}
}
2011-10-03 17:18:44 +00:00
function testOrm(schema) {
2012-11-03 13:48:57 +00:00
var requestsAreCounted = schema.name !== 'mongodb';
2011-10-03 17:18:44 +00:00
2013-02-09 20:53:37 +00:00
var Post, User, Passport, Log, Dog;
2011-10-03 17:18:44 +00:00
it('should define class', function (test) {
2011-10-05 14:47:26 +00:00
User = schema.define('User', {
2012-05-29 11:16:24 +00:00
name: { type: String, index: true },
email: { type: String, index: true },
2011-10-03 17:18:44 +00:00
bio: Text,
approved: Boolean,
joinedAt: Date,
2011-12-11 11:01:23 +00:00
age: Number,
passwd: { type: String, index: true }
});
2013-02-09 20:53:37 +00:00
Dog = schema.define('Dog', {
name : { type: String, limit: 64, allowNull: false }
});
Log = schema.define('Log', {
ownerId : { type: Number, allowNull: true },
name : { type: String, limit: 64, allowNull: false }
});
Log.belongsTo(Dog, {as: 'owner', foreignKey: 'ownerId'});
schema.extendModel('User', {
2012-09-27 07:24:20 +00:00
settings: { type: Schema.JSON },
2012-05-14 04:28:37 +00:00
extra: Object
2011-10-03 17:18:44 +00:00
});
var newuser = new User({settings: {hey: 'you'}});
test.ok(newuser.settings);
2011-10-03 17:18:44 +00:00
Post = schema.define('Post', {
2011-10-16 17:21:08 +00:00
title: { type: String, length: 255, index: true },
2012-05-14 04:28:37 +00:00
subject: { type: String },
2011-10-03 17:18:44 +00:00
content: { type: Text },
2012-09-08 13:03:49 +00:00
date: { type: Date, default: function () { return new Date }, index: true },
published: { type: Boolean, default: false, index: true },
2012-09-10 15:57:21 +00:00
likes: [],
related: [RelatedPost]
2012-03-07 07:29:08 +00:00
}, {table: 'posts'});
2011-10-03 17:18:44 +00:00
2012-09-10 15:57:21 +00:00
function RelatedPost() { }
RelatedPost.prototype.someMethod = function () {
return this.parent;
};
2011-11-19 08:58:49 +00:00
Post.validateAsync('title', function (err, done) {
process.nextTick(done);
});
2011-10-05 14:47:26 +00:00
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
// creates instance methods:
// user.posts(conds)
2011-10-15 15:57:35 +00:00
// user.posts.build(data) // like new Post({userId: user.id});
// user.posts.create(data) // build and save
// user.posts.find
2011-10-05 14:47:26 +00:00
2011-12-09 15:23:29 +00:00
// User.hasOne('latestPost', {model: Post, foreignKey: 'postId'});
// User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'});
// creates instance methods:
// user.latestPost()
// user.latestPost.build(data)
// user.latestPost.create(data)
2011-10-05 14:47:26 +00:00
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
// creates instance methods:
// post.author(callback) -- getter when called with function
// post.author() -- sync getter when called without params
// post.author(user) -- setter when called with object
2012-03-01 17:26:52 +00:00
Passport = schema.define('Passport', {
number: String
});
Passport.belongsTo(User, {as: 'owner', foreignKey: 'ownerId'});
User.hasMany(Passport, {as: 'passports', foreignKey: 'ownerId'});
2012-03-01 17:26:52 +00:00
2011-10-03 17:18:44 +00:00
var user = new User;
test.ok(User instanceof Function);
// class methods
test.ok(User.find instanceof Function);
test.ok(User.create instanceof Function);
// instance methods
test.ok(user.save instanceof Function);
schema.automigrate(function (err) {
if (err) {
console.log('Error while migrating');
console.log(err);
} else {
test.done();
}
});
});
it('should initialize object properly', function (test) {
2011-10-11 19:51:32 +00:00
var hw = 'Hello word',
now = Date.now(),
post = new Post({title: hw}),
anotherPost = Post({title: 'Resig style constructor'});
2011-10-11 19:51:32 +00:00
2011-10-03 17:18:44 +00:00
test.equal(post.title, hw);
test.ok(!post.propertyChanged('title'), 'property changed: title');
2011-10-03 17:18:44 +00:00
post.title = 'Goodbye, Lenin';
test.equal(post.title_was, hw);
test.ok(post.propertyChanged('title'));
2011-10-11 19:51:32 +00:00
test.strictEqual(post.published, false);
test.ok(post.date >= now);
test.ok(post.isNewRecord());
test.ok(anotherPost instanceof Post);
test.ok(anotherPost.title, 'Resig style constructor');
2011-10-03 17:18:44 +00:00
test.done();
});
2012-12-09 09:13:09 +00:00
it('should be exported to JSON', function (test) {
2013-03-24 21:29:07 +00:00
var outString = '{"title":"hello, json","date":1,"published":false,"likes":[],"related":[],"id":1}'
2012-12-09 09:13:09 +00:00
if (schema.name === 'nano')
outString = '{"title":"hello, json","subject":null,"content":null,"date":1,"published":false,"likes":[],"related":[],"_rev":null,"id":1,"userId":null}'
test.equal(JSON.stringify(new Post({id: 1, title: 'hello, json', date: 1})),outString);
2011-10-03 17:18:44 +00:00
test.done();
});
it('should create object', function (test) {
Post.create(function (err, post) {
if (err) throw err;
2012-01-10 15:43:32 +00:00
test.ok(post.id, 'Id present');
2011-10-03 17:18:44 +00:00
test.ok(!post.title, 'Title is blank');
Post.exists(post.id, function (err, exists) {
if (err) throw err;
test.ok(exists);
test.done();
});
});
});
2012-01-27 08:48:37 +00:00
it('should create object without callback', function (test) {
var uniqueTitle = 'Unique title ' + Date.now();
Post.create({title: uniqueTitle});
setTimeout(delayedCallback, 100);
function delayedCallback() {
Post.all({where: {title: uniqueTitle}}, function (err, posts) {
test.equal(posts.length, 1);
test.done();
});
}
});
2011-10-03 17:18:44 +00:00
it('should save object', function (test) {
var title = 'Initial title', title2 = 'Hello world',
date = new Date;
Post.create({
title: title,
date: date
}, function (err, obj) {
2011-10-23 19:43:53 +00:00
test.ok(obj.id, 'Object id should present');
2011-10-03 17:18:44 +00:00
test.equals(obj.title, title);
// test.equals(obj.date, date);
obj.title = title2;
test.ok(obj.propertyChanged('title'), 'Title changed');
obj.save(function (err, obj) {
test.equal(obj.title, title2);
test.ok(!obj.propertyChanged('title'));
var p = new Post({title: 1});
p.title = 2;
p.save(function (err, obj) {
test.ok(!p.propertyChanged('title'));
p.title = 3;
test.ok(p.propertyChanged('title'));
test.equal(p.title_was, 2);
p.save(function () {
test.equal(p.title_was, 3);
test.ok(!p.propertyChanged('title'));
test.done();
});
});
2011-10-03 17:18:44 +00:00
});
});
});
it('should create object with initial data', function (test) {
var title = 'Initial title',
date = new Date;
2011-10-03 17:18:44 +00:00
Post.create({
title: title,
date: date
}, function (err, obj) {
test.ok(obj.id);
test.equals(obj.title, title);
test.equals(obj.date, date);
Post.find(obj.id, function () {
test.equal(obj.title, title);
2011-10-23 19:43:53 +00:00
test.equal(obj.date.toString(), date.toString());
2011-10-03 17:18:44 +00:00
test.done();
});
});
});
2011-12-11 08:58:34 +00:00
it('should save only schema-defined field in database', function (test) {
Post.create({title: '1602', nonSchemaField: 'some value'}, function (err, post) {
test.ok(!post.nonSchemaField);
post.a = 1;
post.save(function () {
test.ok(post.a);
post.reload(function (err, psto) {
2012-04-11 15:36:10 +00:00
test.ok(!psto.a);
2011-12-11 08:58:34 +00:00
test.done();
});
});
});
});
2012-04-11 15:36:10 +00:00
/*
2011-10-03 17:18:44 +00:00
it('should not create new instances for the same object', function (test) {
var title = 'Initial title';
Post.create({ title: title }, function (err, post) {
test.ok(post.id, 'Object should have id');
test.equals(post.title, title);
Post.find(post.id, function (err, foundPost) {
if (err) throw err;
test.equal(post.title, title);
test.strictEqual(post, foundPost);
test.done();
});
});
});
2012-04-11 15:36:10 +00:00
*/
2011-10-03 17:18:44 +00:00
it('should not re-instantiate object on saving', function (test) {
var title = 'Initial title';
var post = new Post({title: title});
post.save(function (err, savedPost) {
test.strictEqual(post, savedPost);
test.done();
});
});
it('should destroy object', function (test) {
Post.create(function (err, post) {
Post.exists(post.id, function (err, exists) {
test.ok(exists, 'Object exists');
post.destroy(function () {
Post.exists(post.id, function (err, exists) {
2011-10-23 19:43:53 +00:00
if (err) console.log(err);
2011-10-03 17:18:44 +00:00
test.ok(!exists, 'Hey! ORM told me that object exists, but it looks like it doesn\'t. Something went wrong...');
Post.find(post.id, function (err, obj) {
test.equal(obj, null, 'Param obj should be null');
test.done();
});
});
});
});
});
});
2011-12-11 11:01:23 +00:00
it('should handle virtual attributes', function (test) {
var salt = 's0m3s3cr3t5a1t';
2011-12-11 19:25:13 +00:00
User.setter.passwd = function (password) {
this._passwd = calcHash(password, salt);
2011-12-11 11:01:23 +00:00
};
function calcHash(pass, salt) {
var crypto = require('crypto');
var hash = crypto.createHash('sha256');
hash.update(pass);
hash.update(salt);
return hash.digest('base64');
}
var u = new User;
2011-12-11 19:25:13 +00:00
u.passwd = 's3cr3t';
test.equal(u.passwd, calcHash('s3cr3t', salt));
2011-12-11 11:01:23 +00:00
test.done();
});
2012-08-14 14:47:59 +00:00
// it('should serialize JSON type', function (test) {
// User.create({settings: {hello: 'world'}}, function (err, user) {
// test.ok(user.id);
// test.equal(user.settings.hello, 'world');
// User.find(user.id, function (err, u) {
// console.log(u.settings);
// test.equal(u.settings.hello, 'world');
// test.done();
// });
// });
// });
2012-05-29 11:16:24 +00:00
2011-10-03 17:18:44 +00:00
it('should update single attribute', function (test) {
Post.create({title: 'title', content: 'content', published: true}, function (err, post) {
post.content = 'New content';
post.updateAttribute('title', 'New title', function () {
test.equal(post.title, 'New title');
test.ok(!post.propertyChanged('title'));
2011-11-27 06:08:40 +00:00
test.equal(post.content, 'New content', 'dirty state saved');
2011-10-03 17:18:44 +00:00
test.ok(post.propertyChanged('content'));
2012-04-11 15:36:10 +00:00
post.reload(function (err, post) {
2011-10-03 17:18:44 +00:00
test.equal(post.title, 'New title');
2012-04-11 19:31:10 +00:00
test.ok(!post.propertyChanged('title'), 'title not changed');
2011-11-27 06:08:40 +00:00
test.equal(post.content, 'content', 'real value turned back');
test.ok(!post.propertyChanged('content'), 'content unchanged');
2011-10-03 17:18:44 +00:00
test.done();
});
});
});
});
var countOfposts, countOfpostsFiltered;
2011-10-03 17:18:44 +00:00
it('should fetch collection', function (test) {
Post.all(function (err, posts) {
countOfposts = posts.length;
test.ok(countOfposts > 0);
test.ok(posts[0] instanceof Post);
countOfpostsFiltered = posts.filter(function (p) {
return p.title === 'title';
}).length;
2011-10-03 17:18:44 +00:00
test.done();
});
});
it('should fetch count of records in collection', function (test) {
Post.count(function (err, count) {
2012-09-08 13:03:49 +00:00
console.log(countOfposts, count);
test.equal(countOfposts, count, 'unfiltered count');
Post.count({title: 'title'}, function (err, count) {
2012-09-08 13:03:49 +00:00
console.log(countOfpostsFiltered, count, 'filtered count');
2012-03-11 04:48:38 +00:00
test.equal(countOfpostsFiltered, count, 'filtered count');
test.done();
});
2011-10-03 17:18:44 +00:00
});
});
it('should find filtered set of records', function (test) {
2012-09-08 13:03:49 +00:00
var wait = 1;
2011-10-03 17:18:44 +00:00
// exact match with string
Post.all({where: {title: 'New title'}}, function (err, res) {
2011-10-03 17:18:44 +00:00
var pass = true;
res.forEach(function (r) {
if (r.title != 'New title') pass = false;
});
2012-01-13 20:06:57 +00:00
test.ok(res.length > 0, 'Exact match with string returns dataset');
2011-10-03 17:18:44 +00:00
test.ok(pass, 'Exact match with string');
done();
});
// matching null
2012-09-08 13:03:49 +00:00
// Post.all({where: {title: null}}, function (err, res) {
2012-01-10 15:43:32 +00:00
2012-09-08 13:03:49 +00:00
// var pass = true;
// res.forEach(function (r) {
// if (r.title != null) pass = false;
// });
// test.ok(res.length > 0, 'Matching null returns dataset');
// test.ok(pass, 'Matching null');
// done();
// });
2011-10-03 17:18:44 +00:00
function done() {
if (--wait === 0) {
test.done();
}
}
});
it('should find records filtered with multiple attributes', function (test) {
var d = new Date;
Post.create({title: 'title', content: 'content', published: true, date: d}, function (err, post) {
Post.all({where: {title: 'title', date: d, published: true}}, function (err, res) {
test.equals(res.length, 1, 'Filtering Posts returns one post');
test.done();
});
});
});
2012-12-14 15:28:29 +00:00
if (
!schema.name.match(/redis/) &&
schema.name !== 'memory' &&
schema.name !== 'neo4j' &&
schema.name !== 'cradle'
)
it('relations key is working', function (test) {
test.ok(User.relations, 'Relations key should be defined');
test.ok(User.relations.posts, 'posts relation should exist on User');
test.equal(User.relations.posts.type, 'hasMany', 'Type of hasMany relation is hasMany');
test.equal(User.relations.posts.multiple, true, 'hasMany relations are multiple');
test.equal(User.relations.posts.keyFrom, 'id', 'keyFrom is primary key of model table');
test.equal(User.relations.posts.keyTo, 'userId', 'keyTo is foreign key of related model table');
test.ok(Post.relations, 'Relations key should be defined');
test.ok(Post.relations.author, 'author relation should exist on Post');
test.equal(Post.relations.author.type, 'belongsTo', 'Type of belongsTo relation is belongsTo');
test.equal(Post.relations.author.multiple, false, 'belongsTo relations are not multiple');
test.equal(Post.relations.author.keyFrom, 'userId', 'keyFrom is foreign key of model table');
test.equal(Post.relations.author.keyTo, 'id', 'keyTo is primary key of related model table');
test.done();
});
2011-10-05 14:47:26 +00:00
it('should handle hasMany relationship', function (test) {
User.create(function (err, u) {
if (err) return console.log(err);
test.ok(u.posts, 'Method defined: posts');
2011-10-15 15:57:35 +00:00
test.ok(u.posts.build, 'Method defined: posts.build');
test.ok(u.posts.create, 'Method defined: posts.create');
u.posts.create(function (err, post) {
2011-10-05 14:47:26 +00:00
if (err) return console.log(err);
u.posts(function (err, posts) {
test.equal(posts.pop().id.toString(), post.id.toString());
2011-10-05 14:47:26 +00:00
test.done();
});
});
});
});
it('should navigate variations of belongsTo regardless of column name', function(test){
2013-02-09 20:15:43 +00:00
Dog.create({name: 'theDog'}, function(err, obj){
test.ok(obj instanceof Dog);
2013-02-09 20:53:37 +00:00
Log.create({name: 'theLog', ownerId: obj.id}, function(err, obj){
2013-02-09 20:15:43 +00:00
test.ok(obj instanceof Log);
obj.owner(function(err, obj){
test.ok(!err, 'Should not have an error.'); // Before cba174b this would be 'Error: Permission denied'
if(err){
console.log('Found: ' + err);
}
test.ok(obj, 'Should not find null or undefined.'); // Before cba174b this could be null or undefined.
test.ok(obj instanceof Dog, 'Should find a Dog.');
if(obj){ // Since test won't stop on fail, have to check before accessing obj.name.
test.ok(obj.name, 'Should have a name.');
}
if(obj && obj.name){
test.equal(obj.name, 'theDog', 'The owner of theLog is theDog.');
}
test.done();
});
});
});
});
2012-12-15 12:06:17 +00:00
it('hasMany should support additional conditions', function (test) {
2012-11-03 13:48:57 +00:00
2012-12-15 12:06:17 +00:00
User.create(function (e, u) {
u.posts.create({}, function (e, p) {
u.posts({where: {id: p.id}}, function (e, posts) {
test.equal(posts.length, 1, 'There should be only 1 post.');
test.done();
});
});
});
2012-12-15 12:06:17 +00:00
});
2012-12-15 12:06:17 +00:00
it('hasMany should be cached', function (test) {
//User.create(function (e, u) {
// u.posts.create({}, function (e, p) {
// find all posts for a user.
2012-12-15 12:06:17 +00:00
// Finding one post with an existing author associated
Post.all(function (err, posts) {
// We try to get the first post with a userId != NULL
for (var i = 0; i < posts.length; i++) {
var post = posts[i];
if (post.userId) {
// We could get the user with belongs to relationship but it is better if there is no interactions.
User.find(post.userId, function(err, user) {
User.create(function(err, voidUser) {
Post.create({userId: user.id}, function() {
// There can't be any concurrency because we are counting requests
// We are first testing cases when user has posts
user.posts(function(err, data) {
var nbInitialRequests = nbSchemaRequests;
user.posts(function(err, data2) {
test.equal(data.length, 2, 'There should be 2 posts.');
test.equal(data.length, data2.length, 'Posts should be the same, since we are loading on the same object.');
requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
if (schema.name === 'mongodb') { // for the moment mongodb doesn\'t support additional conditions on hasMany relations (see above)
test.done();
} else {
user.posts({where: {id: data[0].id}}, function(err, data) {
test.equal(data.length, 1, 'There should be only one post.');
requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we added conditions.');
user.posts(function(err, data) {
test.equal(data.length, 2, 'Previous get shouldn\'t have changed cached value though, since there was additional conditions.');
requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should not be any request because value is cached.');
// We are now testing cases when user doesn't have any post
voidUser.posts(function(err, data) {
var nbInitialRequests = nbSchemaRequests;
voidUser.posts(function(err, data2) {
test.equal(data.length, 0, 'There shouldn\'t be any posts (1/2).');
test.equal(data2.length, 0, 'There shouldn\'t be any posts (2/2).');
requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
voidUser.posts(true, function(err, data3) {
test.equal(data3.length, 0, 'There shouldn\'t be any posts.');
requestsAreCounted && test.equal(nbInitialRequests + 1, nbSchemaRequests, 'There should be one additional request since we forced refresh.');
test.done();
});
});
});
});
});
}
});
});
});
});
});
break;
}
}
});
});
2011-12-09 15:23:29 +00:00
// it('should handle hasOne relationship', function (test) {
// User.create(function (err, u) {
// if (err) return console.log(err);
// });
// });
2011-10-15 15:57:35 +00:00
it('should support scopes', function (test) {
var wait = 2;
test.ok(Post.scope, 'Scope supported');
Post.scope('published', {where: {published: true}});
2011-10-15 15:57:35 +00:00
test.ok(typeof Post.published === 'function');
test.ok(Post.published._scope.where.published === true);
2011-10-15 15:57:35 +00:00
var post = Post.published.build();
test.ok(post.published, 'Can build');
test.ok(post.isNewRecord());
Post.published.create(function (err, psto) {
if (err) return console.log(err);
test.ok(psto.published);
test.ok(!psto.isNewRecord());
done();
});
User.create(function (err, u) {
if (err) return console.log(err);
test.ok(typeof u.posts.published == 'function');
test.ok(u.posts.published._scope.where.published);
console.log(u.posts.published._scope);
test.equal(u.posts.published._scope.where.userId, u.id);
2011-10-15 15:57:35 +00:00
done();
});
function done() {
if (--wait === 0) test.done();
};
});
if (
schema.name === 'mysql' ||
schema.name === 'sqlite3' ||
schema.name === 'postgres'
)
it('should handle include function', function (test) {
var createdUsers = [];
var createdPassports = [];
var createdPosts = [];
var context = null;
createUsers();
function createUsers() {
clearAndCreate(
User,
[
{name: 'User A', age: 21},
{name: 'User B', age: 22},
{name: 'User C', age: 23},
{name: 'User D', age: 24},
{name: 'User E', age: 25}
],
function(items) {
createdUsers = items;
createPassports();
}
);
}
function createPassports() {
clearAndCreate(
Passport,
[
{number: '1', ownerId: createdUsers[0].id},
{number: '2', ownerId: createdUsers[1].id},
{number: '3'}
],
function(items) {
createdPassports = items;
createPosts();
}
);
}
function createPosts() {
clearAndCreate(
Post,
[
{title: 'Post A', userId: createdUsers[0].id},
{title: 'Post B', userId: createdUsers[0].id},
{title: 'Post C', userId: createdUsers[0].id},
{title: 'Post D', userId: createdUsers[1].id},
{title: 'Post E'}
],
function(items) {
createdPosts = items;
makeTests();
}
);
}
function makeTests() {
var unitTests = [
function() {
context = ' (belongsTo simple string from passports to users)';
Passport.all({include: 'owner'}, testPassportsUser);
},
function() {
context = ' (belongsTo simple string from posts to users)';
Post.all({include: 'author'}, testPostsUser);
},
function() {
context = ' (belongsTo simple array)';
Passport.all({include: ['owner']}, testPassportsUser);
},
function() {
context = ' (hasMany simple string from users to posts)';
User.all({include: 'posts'}, testUsersPosts);
},
function() {
context = ' (hasMany simple string from users to passports)';
User.all({include: 'passports'}, testUsersPassports);
},
function() {
context = ' (hasMany simple array)';
User.all({include: ['posts']}, testUsersPosts);
},
function() {
context = ' (Passports - User - Posts in object)';
Passport.all({include: {'owner': 'posts'}}, testPassportsUserPosts);
},
function() {
context = ' (Passports - User - Posts in array)';
Passport.all({include: [{'owner': 'posts'}]}, testPassportsUserPosts);
},
function() {
context = ' (Passports - User - Posts - User)';
Passport.all({include: {'owner': {'posts': 'author'}}}, testPassportsUserPosts);
},
function() {
context = ' (User - Posts AND Passports)';
User.all({include: ['posts', 'passports']}, testUsersPostsAndPassports);
}
];
function testPassportsUser(err, passports, callback) {
testBelongsTo(passports, 'owner', callback);
}
function testPostsUser(err, posts, callback) {
testBelongsTo(posts, 'author', callback);
}
function testBelongsTo(items, relationName, callback) {
if (typeof callback === 'undefined') {
callback = nextUnitTest;
}
var nbInitialRequests = nbSchemaRequests;
var nbItemsRemaining = items.length;
for (var i = 0; i < items.length; i++) {
testItem(items[i]);
}
function testItem(item) {
var relation = item.constructor.relations[relationName];
var modelNameFrom = item.constructor.modelName;
var modelNameTo = relation.modelTo.modelName;
item[relationName](function(err, relatedItem) {
if (relatedItem !== null) {
test.equal(relatedItem[relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instance match ' + modelNameFrom + '\'s instance' + context);
} else {
test.ok(item[relation.keyFrom] == null, 'User match passport even when user is null.' + context);
}
nbItemsRemaining--;
if (nbItemsRemaining == 0) {
requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context)
callback();
}
});
}
}
function testUsersPosts(err, users, expectedUserNumber, callback) {
if (typeof expectedUserNumber === 'undefined') {
expectedUserNumber = 5;
}
test.equal(users.length, expectedUserNumber, 'Exactly ' + expectedUserNumber + ' users returned by query' + context);
testHasMany(users, 'posts', callback);
}
function testUsersPassports(err, users, callback) {
testHasMany(users, 'passports', callback);
}
function testHasMany(items, relationName, callback) {
if (typeof callback === 'undefined') {
callback = nextUnitTest;
}
var nbInitialRequests = nbSchemaRequests;
var nbItemRemaining = items.length;
for (var i = 0; i < items.length; i++) {
testItem(items[i]);
}
function testItem(item) {
var relation = item.constructor.relations[relationName];
var modelNameFrom = item.constructor.modelName;
var modelNameTo = relation.modelTo.modelName;
item[relationName](function(err, relatedItems) {
for (var j = 0; j < relatedItems.length; j++) {
test.equal(relatedItems[j][relation.keyTo], item[relation.keyFrom], modelNameTo + '\'s instances match ' + modelNameFrom + '\'s instance' + context);
}
nbItemRemaining--;
if (nbItemRemaining == 0) {
requestsAreCounted && test.equal(nbSchemaRequests, nbInitialRequests, 'No more request have been executed for loading ' + relationName + ' relation' + context)
callback();
}
});
}
}
function testPassportsUserPosts(err, passports) {
testPassportsUser(err, passports, function() {
var nbPassportsRemaining = passports.length;
for (var i = 0; i < passports.length; i++) {
if (passports[i].ownerId !== null) {
passports[i].owner(function(err, user) {
testUsersPosts(null, [user], 1, function() {
nextPassport();
});
});
} else {
nextPassport();
}
}
function nextPassport() {
nbPassportsRemaining--
if (nbPassportsRemaining == 0) {
nextUnitTest();
}
}
});
}
function testUsersPostsAndPassports(err, users) {
testUsersPosts(err, users, 5, function() {
testUsersPassports(err, users, function() {
nextUnitTest();
});
});
}
var testNum = 0;
function nextUnitTest() {
if (testNum >= unitTests.length) {
test.done();
return;
}
unitTests[testNum]();
testNum++;
}
nextUnitTest();
}
});
2011-10-03 17:18:44 +00:00
it('should destroy all records', function (test) {
Post.destroyAll(function (err) {
if (err) {
console.log('Error in destroyAll');
2012-03-11 04:48:38 +00:00
console.log(err);
throw err;
}
2011-10-03 17:18:44 +00:00
Post.all(function (err, posts) {
test.equal(posts.length, 0);
Post.count(function (err, count) {
test.equal(count, 0);
test.done();
});
});
});
});
2012-01-18 15:20:05 +00:00
it('should return type of property', function (test) {
test.equal(Post.whatTypeName('title'), 'String');
test.equal(Post.whatTypeName('content'), 'Text');
var p = new Post;
test.equal(p.whatTypeName('title'), 'String');
test.equal(p.whatTypeName('content'), 'Text');
test.done();
});
it('should handle ORDER clause', function (test) {
2012-05-14 04:28:37 +00:00
var titles = [ { title: 'Title A', subject: "B" },
{ title: 'Title Z', subject: "A" },
{ title: 'Title M', subject: "C" },
{ title: 'Title A', subject: "A" },
{ title: 'Title B', subject: "A" },
{ title: 'Title C', subject: "D" }];
2012-10-01 08:20:55 +00:00
var isRedis = Post.schema.name === 'redis';
2012-05-14 04:28:37 +00:00
var dates = isRedis ? [ 5, 9, 0, 17, 10, 9 ] : [
2012-01-19 20:16:30 +00:00
new Date(1000 * 5 ),
new Date(1000 * 9),
new Date(1000 * 0),
new Date(1000 * 17),
2012-05-14 04:28:37 +00:00
new Date(1000 * 10),
2012-01-19 20:16:30 +00:00
new Date(1000 * 9)
];
titles.forEach(function (t, i) {
2012-05-14 04:28:37 +00:00
Post.create({title: t.title, subject: t.subject, date: dates[i]}, done);
});
var i = 0, tests = 0;
function done(err, obj) {
if (++i === titles.length) {
2012-01-19 20:25:50 +00:00
doFilterAndSortTest();
doFilterAndSortReverseTest();
doStringTest();
doNumberTest();
2012-05-14 04:28:37 +00:00
if (schema.name == 'mongoose') {
doMultipleSortTest();
doMultipleReverseSortTest();
}
}
}
2012-05-14 04:28:37 +00:00
function compare(a, b) {
if (a.title < b.title) return -1;
if (a.title > b.title) return 1;
return 0;
}
// Post.schema.log = console.log;
function doStringTest() {
tests += 1;
Post.all({order: 'title'}, function (err, posts) {
2012-01-19 20:16:30 +00:00
if (err) console.log(err);
2012-05-14 04:28:37 +00:00
test.equal(posts.length, 6);
titles.sort(compare).forEach(function (t, i) {
if (posts[i]) test.equal(posts[i].title, t.title);
});
finished();
});
}
function doNumberTest() {
tests += 1;
Post.all({order: 'date'}, function (err, posts) {
2012-01-19 20:16:30 +00:00
if (err) console.log(err);
2012-05-14 04:28:37 +00:00
test.equal(posts.length, 6);
dates.sort(numerically).forEach(function (d, i) {
2012-03-11 04:48:38 +00:00
if (posts[i])
2012-09-08 13:03:49 +00:00
test.equal(posts[i].date.toString(), d.toString(), 'doNumberTest');
});
finished();
});
}
function doFilterAndSortTest() {
tests += 1;
2012-09-08 13:03:49 +00:00
Post.all({where: {date: new Date(1000 * 9)}, order: 'title', limit: 3}, function (err, posts) {
2012-01-19 20:16:30 +00:00
if (err) console.log(err);
2012-10-01 08:20:55 +00:00
console.log(posts.length);
test.equal(posts.length, 2, 'Exactly 2 posts returned by query');
[ 'Title C', 'Title Z' ].forEach(function (t, i) {
if (posts[i]) {
2012-09-08 13:03:49 +00:00
test.equal(posts[i].title, t, 'doFilterAndSortTest');
}
});
finished();
});
}
function doFilterAndSortReverseTest() {
tests += 1;
2012-09-08 13:03:49 +00:00
Post.all({where: {date: new Date(1000 * 9)}, order: 'title DESC', limit: 3}, function (err, posts) {
if (err) console.log(err);
test.equal(posts.length, 2, 'Exactly 2 posts returned by query');
[ 'Title Z', 'Title C' ].forEach(function (t, i) {
if (posts[i]) {
2012-09-08 13:03:49 +00:00
test.equal(posts[i].title, t, 'doFilterAndSortReverseTest');
}
});
finished();
});
}
2012-05-14 04:28:37 +00:00
function doMultipleSortTest() {
tests += 1;
Post.all({order: "title ASC, subject ASC"}, function(err, posts) {
if (err) console.log(err);
test.equal(posts.length, 6);
test.equal(posts[0].title, "Title A");
test.equal(posts[0].subject, "A");
test.equal(posts[1].title, "Title A");
test.equal(posts[1].subject, "B");
test.equal(posts[5].title, "Title Z");
finished();
});
}
function doMultipleReverseSortTest() {
tests += 1;
Post.all({order: "title ASC, subject DESC"}, function(err, posts) {
if (err) console.log(err);
test.equal(posts.length, 6);
test.equal(posts[0].title, "Title A");
test.equal(posts[0].subject, "B");
test.equal(posts[1].title,"Title A");
test.equal(posts[1].subject, "A");
test.equal(posts[5].title, "Title Z");
finished();
});
}
var fin = 0;
function finished() {
if (++fin === tests) {
test.done();
}
}
// TODO: do mixed test, do real dates tests, ensure that dates stored in UNIX timestamp format
function numerically(a, b) {
return a - b;
}
});
// if (
// !schema.name.match(/redis/) &&
// schema.name !== 'memory' &&
// schema.name !== 'neo4j' &&
// schema.name !== 'cradle' &&
// schema.name !== 'nano'
// )
// it('should allow advanced queying: lt, gt, lte, gte, between', function (test) {
// Post.destroyAll(function () {
// Post.create({date: new Date('Wed, 01 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Thu, 02 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Fri, 03 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Sat, 04 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Sun, 05 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Mon, 06 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Wed, 08 Feb 2012 13:56:12 GMT')}, done);
// Post.create({date: new Date('Thu, 09 Feb 2012 13:56:12 GMT')}, done);
// });
2012-02-01 17:33:08 +00:00
// var posts = 9;
// function done() {
// if (--posts === 0) makeTest();
// }
2012-02-01 17:33:08 +00:00
// function makeTest() {
// // gt
// Post.all({where: {date: {gt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
// test.equal(posts.length, 2, 'gt');
// ok();
// });
2012-02-01 17:33:08 +00:00
// // gte
// Post.all({where: {date: {gte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
// test.equal(posts.length, 3, 'gte');
// ok();
// });
2012-02-01 17:33:08 +00:00
// // lte
// Post.all({where: {date: {lte: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
// test.equal(posts.length, 7, 'lte');
// ok();
// });
2012-02-01 17:33:08 +00:00
// // lt
// Post.all({where: {date: {lt: new Date('Tue, 07 Feb 2012 13:56:12 GMT')}}}, function (err, posts) {
// test.equal(posts.length, 6, 'lt');
// ok();
// });
2012-02-01 17:33:08 +00:00
// // between
// Post.all({where: {date: {between: [new Date('Tue, 05 Feb 2012 13:56:12 GMT'), new Date('Tue, 09 Feb 2012 13:56:12 GMT')]}}}, function (err, posts) {
// test.equal(posts.length, 5, 'between');
// ok();
// });
// }
2012-02-01 17:33:08 +00:00
// var tests = 5;
// function ok() {
// if (--tests === 0) test.done();
// }
// });
2012-02-01 17:33:08 +00:00
// if (
// schema.name === 'mysql' ||
// schema.name === 'postgres'
// )
// it('should allow IN or NOT IN', function (test) {
// User.destroyAll(function () {
// User.create({name: 'User A', age: 21}, done);
// User.create({name: 'User B', age: 22}, done);
// User.create({name: 'User C', age: 23}, done);
// User.create({name: 'User D', age: 24}, done);
// User.create({name: 'User E', age: 25}, done);
// });
// var users = 5;
// function done() {
// if (--users === 0) makeTest();
// }
// function makeTest() {
// // IN with empty array should return nothing
// User.all({where: {name: {inq: []}}}, function (err, users) {
// test.equal(users.length, 0, 'IN with empty array returns nothing');
// ok();
// });
// // NOT IN with empty array should return everything
// User.all({where: {name: {nin: []}}}, function (err, users) {
// test.equal(users.length, 5, 'NOT IN with empty array returns everything');
// ok();
// });
// // IN [User A] returns user with name = User A
// User.all({where: {name: {inq: ['User A']}}}, function (err, users) {
// test.equal(users.length, 1, 'IN searching one existing value returns 1 user');
// test.equal(users[0].name, 'User A', 'IN [User A] returns user with name = User A');
// ok();
// });
// // NOT IN [User A] returns users with name != User A
// User.all({where: {name: {nin: ['User A']}}}, function (err, users) {
// test.equal(users.length, 4, 'IN [User A] returns users with name != User A');
// ok();
// });
// // IN [User A, User B] returns users with name = User A OR name = User B
// User.all({where: {name: {inq: ['User A', 'User B']}}}, function (err, users) {
// test.equal(users.length, 2, 'IN searching two existing values returns 2 users');
// ok();
// });
// // NOT IN [User A, User B] returns users with name != User A AND name != User B
// User.all({where: {name: {nin: ['User A', 'User B']}}}, function (err, users) {
// test.equal(users.length, 3, 'NOT IN searching two existing values returns users with name != User A AND name != User B');
// ok();
// });
// // IN works with numbers too
// User.all({where: {age: {inq: [21, 22]}}}, function (err, users) {
// test.equal(users.length, 2, 'IN works with numbers too');
// ok();
// });
// // NOT IN works with numbers too
// User.all({where: {age: {nin: [21, 22]}}}, function (err, users) {
// test.equal(users.length, 3, 'NOT IN works with numbers too');
// ok();
// });
// }
// var tests = 8;
// function ok() {
// if (--tests === 0) test.done();
// }
// });
it('should handle order clause with direction', function (test) {
var wait = 0;
var emails = [
'john@hcompany.com',
'tom@hcompany.com',
'admin@hcompany.com',
'tin@hcompany.com',
'mike@hcompany.com',
'susan@hcompany.com',
'test@hcompany.com'
];
User.destroyAll(function () {
emails.forEach(function (email) {
wait += 1;
User.create({email: email, name: 'Nick'}, done);
});
});
var tests = 2;
function done() {
process.nextTick(function () {
if (--wait === 0) {
doSortTest();
doReverseSortTest();
}
});
}
function doSortTest() {
User.all({order: 'email ASC', where: {name: 'Nick'}}, function (err, users) {
var _emails = emails.sort();
users.forEach(function (user, i) {
test.equal(_emails[i], user.email, 'ASC sorting');
});
testDone();
});
}
function doReverseSortTest() {
User.all({order: 'email DESC', where: {name: 'Nick'}}, function (err, users) {
var _emails = emails.sort().reverse();
users.forEach(function (user, i) {
test.equal(_emails[i], user.email, 'DESC sorting');
});
testDone();
});
}
function testDone() {
if (--tests === 0) test.done();
}
});
it('should return id in find result even after updateAttributes', function (test) {
Post.create(function (err, post) {
var id = post.id;
test.ok(post.published === false);
post.updateAttributes({title: 'hey', published: true}, function () {
Post.find(id, function (err, post) {
test.ok(!!post.published, 'Update boolean field');
test.ok(post.id);
test.done();
});
});
});
});
2012-03-01 17:26:52 +00:00
it('should handle belongsTo correctly', function (test) {
var passport = new Passport({ownerId: 16});
// sync getter
test.equal(passport.owner(), 16);
// sync setter
passport.owner(18);
test.equal(passport.owner(), 18);
test.done();
});
it('should query one record', function (test) {
2012-03-11 04:48:38 +00:00
test.expect(4);
Post.findOne(function (err, post) {
2012-03-11 04:48:38 +00:00
test.ok(post && post.id);
Post.findOne({ where: { title: 'hey' } }, function (err, post) {
2012-03-11 04:48:38 +00:00
if (err) {
console.log(err);
return test.done();
}
2012-09-08 13:03:49 +00:00
test.equal(post && post.constructor.modelName, 'Post');
test.equal(post && post.title, 'hey');
Post.findOne({ where: { title: 'not exists' } }, function (err, post) {
2013-01-22 16:57:30 +00:00
test.ok(post === null);
test.done();
});
});
});
});
// if (
// !schema.name.match(/redis/) &&
// schema.name !== 'memory' &&
// schema.name !== 'neo4j' &&
// schema.name !== 'cradle' &&
// schema.name !== 'nano'
// )
// it('belongsTo should be cached', function (test) {
// User.findOne(function(err, user) {
// var passport = new Passport({ownerId: user.id});
// var passport2 = new Passport({ownerId: null});
// // There can't be any concurrency because we are counting requests
// // We are first testing cases when passport has an owner
// passport.owner(function(err, data) {
// var nbInitialRequests = nbSchemaRequests;
// passport.owner(function(err, data2) {
// test.equal(data.id, data2.id, 'The value should remain the same');
// requestsAreCounted && test.equal(nbInitialRequests, nbSchemaRequests, 'There should not be any request because value is cached.');
// // We are now testing cases when passport has not an owner
// passport2.owner(function(err, data) {
// var nbInitialRequests2 = nbSchemaRequests;
// passport2.owner(function(err, data2) {
// test.equal(data, null, 'The value should be null since there is no owner');
// test.equal(data, data2, 'The value should remain the same (null)');
// requestsAreCounted && test.equal(nbInitialRequests2, nbSchemaRequests, 'There should not be any request because value is cached.');
// passport2.owner(user.id);
// passport2.owner(function(err, data3) {
// test.equal(data3.id, user.id, 'Owner should now be the user.');
// requestsAreCounted && test.equal(nbInitialRequests2 + 1, nbSchemaRequests, 'If we changed owner id, there should be one more request.');
// passport2.owner(true, function(err, data4) {
// test.equal(data3.id, data3.id, 'The value should remain the same');
// requestsAreCounted && test.equal(nbInitialRequests2 + 2, nbSchemaRequests, 'If we forced refreshing, there should be one more request.');
// test.done();
// });
// });
// });
// });
// });
// });
// });
// });
2012-03-22 19:46:16 +00:00
if (schema.name !== 'mongoose' && schema.name !== 'neo4j')
it('should update or create record', function (test) {
var newData = {
id: 1,
title: 'New title (really new)',
content: 'Some example content (updated)'
};
Post.updateOrCreate(newData, function (err, updatedPost) {
if (err) throw err;
test.ok(updatedPost);
if (!updatedPost) throw Error('No post!');
2012-04-19 16:06:38 +00:00
if (schema.name !== 'mongodb') {
test.equal(newData.id, updatedPost.toObject().id);
}
2012-03-22 19:46:16 +00:00
test.equal(newData.title, updatedPost.toObject().title);
test.equal(newData.content, updatedPost.toObject().content);
Post.find(updatedPost.id, function (err, post) {
if (err) throw err;
if (!post) throw Error('No post!');
2012-04-19 16:06:38 +00:00
if (schema.name !== 'mongodb') {
test.equal(newData.id, post.toObject().id);
}
2012-03-22 19:46:16 +00:00
test.equal(newData.title, post.toObject().title);
test.equal(newData.content, post.toObject().content);
Post.updateOrCreate({id: 100001, title: 'hey'}, function (err, post) {
if (schema.name !== 'mongodb') test.equal(post.id, 100001);
test.equal(post.title, 'hey');
Post.find(post.id, function (err, post) {
if (!post) throw Error('No post!');
test.done();
});
});
});
});
});
2012-04-19 15:01:40 +00:00
it('should work with custom setters and getters', function (test) {
2012-08-13 06:16:01 +00:00
User.schema.defineForeignKey('User', 'passwd');
2012-04-19 15:01:40 +00:00
User.setter.passwd = function (pass) {
this._passwd = pass + 'salt';
};
var u = new User({passwd: 'qwerty'});
test.equal(u.passwd, 'qwertysalt');
u.save(function (err, user) {
User.find(user.id, function (err, user) {
test.ok(user !== u);
test.equal(user.passwd, 'qwertysalt');
2012-04-19 15:20:10 +00:00
User.all({where: {passwd: 'qwertysalt'}}, function (err, users) {
2012-04-19 15:01:40 +00:00
test.ok(users[0] !== user);
test.equal(users[0].passwd, 'qwertysalt');
User.create({passwd: 'asalat'}, function (err, usr) {
test.equal(usr.passwd, 'asalatsalt');
2012-04-19 16:06:38 +00:00
User.upsert({passwd: 'heyman'}, function (err, us) {
test.equal(us.passwd, 'heymansalt');
User.find(us.id, function (err, user) {
test.equal(user.passwd, 'heymansalt');
test.done();
});
});
2012-04-19 15:01:40 +00:00
});
});
});
});
});
2012-09-10 15:57:21 +00:00
it('should work with typed and untyped nested collections', function (test) {
var post = new Post;
var like = post.likes.push({foo: 'bar'});
test.equal(like.constructor.name, 'ListItem');
var related = post.related.push({hello: 'world'});
test.ok(related.someMethod);
2012-09-11 16:51:31 +00:00
post.save(function (err, p) {
test.equal(p.likes.nextid, 2);
p.likes.push({second: 2});
p.likes.push({third: 3});
p.save(function (err) {
Post.find(p.id, function (err, pp) {
test.equal(pp.likes.length, 3);
test.ok(pp.likes[3].third);
test.ok(pp.likes[2].second);
test.ok(pp.likes[1].foo);
pp.likes.remove(2);
test.equal(pp.likes.length, 2);
test.ok(!pp.likes[2]);
pp.likes.remove(pp.likes[1]);
test.equal(pp.likes.length, 1);
test.ok(!pp.likes[1]);
test.ok(pp.likes[3]);
pp.save(function () {
Post.find(p.id, function (err, pp) {
test.equal(pp.likes.length, 1);
test.ok(!pp.likes[1]);
test.ok(pp.likes[3]);
test.done();
});
});
});
});
});
2012-09-10 15:57:21 +00:00
});
2013-01-21 18:21:43 +00:00
it('should find or create', function (test) {
var email = 'some email ' + Math.random();
2013-01-23 00:48:56 +00:00
User.findOrCreate({where: {email: email}}, function (err, u) {
2013-01-21 18:21:43 +00:00
test.ok(u);
2013-01-23 00:48:56 +00:00
test.ok(!u.age);
2013-01-21 18:21:43 +00:00
User.findOrCreate({where: {email: email}}, {age: 21}, function (err, u2) {
test.equals(u.id.toString(), u2.id.toString(), 'Same user ids');
2013-01-23 00:48:56 +00:00
test.ok(!u2.age);
2013-01-21 18:21:43 +00:00
test.done();
});
});
});
2011-10-03 17:18:44 +00:00
}