loopback-datasource-juggler/test/loopback-dl.test.js

797 lines
25 KiB
JavaScript
Raw Normal View History

2013-05-28 20:50:59 +00:00
// This test written in mocha+should.js
var should = require('./init.js');
2013-07-12 19:39:38 +00:00
var assert = require('assert');
2013-05-28 20:50:59 +00:00
var jdb = require('../');
var ModelBuilder = jdb.ModelBuilder;
var DataSource = jdb.DataSource;
describe('ModelBuilder define model', function () {
it('should be able to define plain models', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number
});
// define any custom method
User.prototype.getNameAndAge = function () {
return this.name + ', ' + this.age;
};
modelBuilder.models.should.be.a('object').and.have.property('User', User);
modelBuilder.definitions.should.be.a('object').and.have.property('User');
var user = new User({name: 'Joe', age: 20});
User.modelName.should.equal('User');
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
done(null, User);
});
it('should not take unknown properties in strict mode', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {name: String, bio: String}, {strict: true});
var user = new User({name: 'Joe', age: 20});
User.modelName.should.equal('User');
user.should.be.a('object');
assert(user.name === 'Joe');
assert(user.age === undefined);
assert(user.toObject().age === undefined);
assert(user.toObject(true).age === undefined);
assert(user.bio === undefined);
done(null, User);
});
it('should throw when unknown properties are used if strict=throw', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {name: String, bio: String}, {strict: 'throw'});
try {
var user = new User({name: 'Joe', age: 20});
assert(false, 'The code should have thrown an error');
} catch(e) {
assert(true, 'The code is expected to throw an error');
}
done(null, User);
});
it('should be able to define open models', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {}, {strict: false});
var user = new User({name: 'Joe', age: 20});
2013-05-28 20:50:59 +00:00
User.modelName.should.equal('User');
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
done(null, User);
});
it('should use false as the default value for strict', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {});
var user = new User({name: 'Joe', age: 20});
User.modelName.should.equal('User');
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
done(null, User);
});
2013-07-11 21:24:47 +00:00
it('should be able to define nesting models', function (done) {
var modelBuilder = new ModelBuilder();
// simplier way to describe model
var User = modelBuilder.define('User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number,
address: {
street: String,
city: String,
state: String,
zipCode: String,
country: String
2013-07-12 19:36:14 +00:00
},
emails: [{
label: String,
email: String
2013-07-12 19:39:38 +00:00
}],
friends: [String]
2013-07-11 21:24:47 +00:00
});
// define any custom method
User.prototype.getNameAndAge = function () {
return this.name + ', ' + this.age;
};
modelBuilder.models.should.be.a('object').and.have.property('User', User);
modelBuilder.definitions.should.be.a('object').and.have.property('User');
2013-07-12 19:36:14 +00:00
var user = new User({
name: 'Joe', age: 20,
address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'},
2013-07-12 19:39:38 +00:00
emails: [{label: 'work', email: 'xyz@sample.com'}],
friends: ['Mary', 'John']
2013-07-12 19:36:14 +00:00
});
2013-07-11 21:24:47 +00:00
User.modelName.should.equal('User');
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
user.should.have.property('address');
user.address.should.have.property('city', 'San Jose');
user.address.should.have.property('state', 'CA');
2013-07-12 19:36:14 +00:00
user = user.toObject();
user.emails.should.have.property('length', 1);
user.emails[0].should.have.property('label', 'work');
user.emails[0].should.have.property('email', 'xyz@sample.com');
2013-07-12 19:39:38 +00:00
user.friends.should.have.property('length', 2);
assert.equal(user.friends[0], 'Mary');
assert.equal(user.friends[1], 'John');
2013-07-11 21:24:47 +00:00
done(null, User);
});
it('should be able to reference models by name before they are defined', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {name: String, address: 'Address'});
var user;
try {
user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
assert(false, 'An exception should have been thrown');
} catch (e) {
// Ignore
}
var Address = modelBuilder.define('Address', {
street: String,
city: String,
state: String,
zipCode: String,
country: String
});
user = new User({name: 'Joe', address: {street: '123 Main St', 'city': 'San Jose', state: 'CA'}});
User.modelName.should.equal('User');
User.definition.properties.address.should.have.property('type', Address);
user.should.be.a('object');
assert(user.name === 'Joe');
user.address.should.have.property('city', 'San Jose');
user.address.should.have.property('state', 'CA');
done(null, User);
});
2013-07-11 21:24:47 +00:00
2013-05-28 20:50:59 +00:00
});
describe('DataSource define model', function () {
it('should be able to define plain models', function () {
var ds = new DataSource('memory');
// define models
var Post = ds.define('Post', {
title: { type: String, length: 255 },
content: { type: ModelBuilder.Text },
2013-05-28 20:50:59 +00:00
date: { type: Date, default: function () {
return new Date();
2013-05-28 20:50:59 +00:00
} },
timestamp: { type: Number, default: Date.now },
published: { type: Boolean, default: false, index: true }
});
// simpler way to describe model
2013-05-28 20:50:59 +00:00
var User = ds.define('User', {
name: String,
bio: ModelBuilder.Text,
2013-05-28 20:50:59 +00:00
approved: Boolean,
joinedAt: Date,
age: Number
});
var Group = ds.define('Group', {group: String});
User.mixin(Group);
2013-05-28 20:50:59 +00:00
// define any custom method
User.prototype.getNameAndAge = function () {
return this.name + ', ' + this.age;
};
var user = new User({name: 'Joe', group: 'G1'});
assert.equal(user.name, 'Joe');
assert.equal(user.group, 'G1');
2013-05-28 20:50:59 +00:00
// setup relationships
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
User.hasAndBelongsToMany('groups');
var user2 = new User({name: 'Smith'});
user2.save(function (err) {
var post = user2.posts.build({title: 'Hello world'});
post.save(function (err, data) {
// console.log(err ? err : data);
2013-05-28 20:50:59 +00:00
});
});
Post.findOne({where: {published: false}, order: 'date DESC'}, function (err, data) {
// console.log(data);
2013-05-28 20:50:59 +00:00
});
User.create({name: 'Jeff'}, function (err, data) {
if (err) {
console.log(err);
return;
}
var post = data.posts.build({title: 'My Post'});
});
User.create({name: 'Ray'}, function (err, data) {
// console.log(data);
2013-05-28 20:50:59 +00:00
});
var Article = ds.define('Article', {title: String});
var Tag = ds.define('Tag', {name: String});
Article.hasAndBelongsToMany('tags');
Article.create(function (e, article) {
article.tags.create({name: 'popular'}, function (err, data) {
Article.findOne(function (e, article) {
article.tags(function (e, tags) {
// console.log(tags);
2013-05-28 20:50:59 +00:00
});
});
});
});
// should be able to attach a data source to an existing model
var modelBuilder = new ModelBuilder();
var Color = modelBuilder.define('Color', {
name: String
});
Color.should.not.have.property('create');
// attach
ds.attach(Color);
Color.should.have.property('create');
Color.create({name: 'red'});
Color.create({name: 'green'});
Color.create({name: 'blue'});
Color.all(function (err, colors) {
colors.should.have.lengthOf(3);
});
});
it('should not take unknown properties in strict mode', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {name: String, bio: String}, {strict: true});
User.create({name: 'Joe', age: 20}, function (err, user) {
User.modelName.should.equal('User');
user.should.be.a('object');
assert(user.name === 'Joe');
assert(user.age === undefined);
assert(user.toObject().age === undefined);
assert(user.toObject(true).age === undefined);
assert(user.bio === undefined);
done(null, User);
});
});
it('should throw when unknown properties are used if strict=throw', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'});
try {
var user = new User({name: 'Joe', age: 20});
assert(false, 'The code should have thrown an error');
} catch(e) {
assert(true, 'The code is expected to throw an error');
}
done(null, User);
});
it('should be able to define open models', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {}, {strict: false});
User.modelName.should.equal('User');
User.create({name: 'Joe', age: 20}, function (err, user) {
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
User.findById(user.id, function(err, user) {
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
done(null, User);
});
});
});
it('should use false as the default value for strict', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {});
User.create({name: 'Joe', age: 20}, function (err, user) {
User.modelName.should.equal('User');
user.should.be.a('object').and.have.property('name', 'Joe');
user.should.have.property('name', 'Joe');
user.should.have.property('age', 20);
user.should.not.have.property('bio');
done(null, User);
});
});
it('should use true as the default value for strict for relational DBs', function (done) {
var ds = new DataSource('memory');
ds.connector.relational = true; // HACK
var User = ds.define('User', {name: String, bio: String}, {strict: true});
var user = new User({name: 'Joe', age: 20});
User.modelName.should.equal('User');
user.should.be.a('object');
assert(user.name === 'Joe');
assert(user.age === undefined);
assert(user.toObject().age === undefined);
assert(user.toObject(true).age === undefined);
assert(user.bio === undefined);
done(null, User);
});
it('should throw when unknown properties are used if strict=false for relational DBs', function (done) {
var ds = new DataSource('memory');
ds.connector.relational = true; // HACK
var User = ds.define('User', {name: String, bio: String}, {strict: 'throw'});
try {
var user = new User({name: 'Joe', age: 20});
assert(false, 'The code should have thrown an error');
} catch(e) {
assert(true, 'The code is expected to throw an error');
}
done(null, User);
});
it('should change the property value for save if strict=false', function (done) {
var ds = new DataSource('memory');// define models
var Post = ds.define('Post');
Post.create({price: 900}, function(err, post) {
assert.equal(post.price, 900);
post.price = 1000;
post.save(function(err, result) {
assert.equal(1000, result.price);
done(err, result);
});
});
});
2013-05-28 20:50:59 +00:00
});
2013-05-28 22:26:12 +00:00
describe('Load models with base', function () {
it('should set up base class', function (done) {
var ds = new ModelBuilder();
var User = ds.define('User', {name: String});
2013-11-08 17:02:17 +00:00
User.staticMethod = function staticMethod() {};
User.prototype.instanceMethod = function instanceMethod() {};
var Customer = ds.define('Customer', {vip: Boolean}, {base: 'User'});
assert(Customer.prototype instanceof User);
2013-11-08 17:02:17 +00:00
assert(Customer.staticMethod === User.staticMethod);
assert(Customer.prototype.instanceMethod === User.prototype.instanceMethod);
try {
var Customer1 = ds.define('Customer1', {vip: Boolean}, {base: 'User1'});
} catch(e) {
assert(e);
}
done();
});
});
2013-05-28 22:26:12 +00:00
describe('Load models with relations', function () {
it('should set up relations', function (done) {
var ds = new DataSource('memory');
var Post = ds.define('Post', {userId: Number, content: String});
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}});
assert(User.relations['posts']);
done();
});
it('should set up belongsTo relations', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {name: String});
var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
assert(Post.relations['user']);
done();
});
it('should set up hasMany and belongsTo relations', function (done) {
var ds = new DataSource('memory');
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}, accounts: {type: 'hasMany', model: 'Account'}}});
assert(!User.relations['posts']);
assert(!User.relations['accounts']);
var Post = ds.define('Post', {userId: Number, content: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
var Account = ds.define('Account', {userId: Number, type: String}, {relations: {user: {type: 'belongsTo', model: 'User'}}});
assert(Post.relations['user']);
assert.deepEqual(Post.relations['user'], {
type: 'belongsTo',
keyFrom: 'userId',
keyTo: 'id',
modelTo: User,
multiple: false
});
assert(User.relations['posts']);
assert.deepEqual(User.relations['posts'], {
type: 'hasMany',
keyFrom: 'id',
keyTo: 'userId',
modelTo: Post,
multiple: true
});
assert(User.relations['accounts']);
assert.deepEqual(User.relations['accounts'], {
type: 'hasMany',
keyFrom: 'id',
keyTo: 'userId',
modelTo: Account,
multiple: true
});
done();
});
it('should throw if a relation is missing type', function (done) {
var ds = new DataSource('memory');
var Post = ds.define('Post', {userId: Number, content: String});
try {
var User = ds.define('User', {name: String}, {relations: {posts: {model: 'Post'}}});
} catch (e) {
done();
}
});
it('should throw if the relation type is invalid', function (done) {
var ds = new DataSource('memory');
var Post = ds.define('Post', {userId: Number, content: String});
try {
var User = ds.define('User', {name: String}, {relations: {posts: {type: 'hasXYZ', model: 'Post'}}});
} catch (e) {
done();
}
});
it('should handle hasMany through', function (done) {
var ds = new DataSource('memory');
var Physician = ds.createModel('Physician', {
name: String
}, {relations: {patients: {model: 'Patient', type: 'hasMany', through: 'Appointment'}}});
var Patient = ds.createModel('Patient', {
name: String
}, {relations: {physicians: {model: 'Physician', type: 'hasMany', through: 'Appointment'}}});
assert(!Physician.relations['patients']); // Appointment hasn't been resolved yet
assert(!Patient.relations['physicians']); // Appointment hasn't been resolved yet
var Appointment = ds.createModel('Appointment', {
physicianId: Number,
patientId: Number,
appointmentDate: Date
}, {relations: {patient: {type: 'belongsTo', model: 'Patient'}, physician: {type: 'belongsTo', model: 'Physician'}}});
assert(Physician.relations['patients']);
assert(Patient.relations['physicians']);
done();
});
it('should set up relations after attach', function (done) {
var ds = new DataSource('memory');
var modelBuilder = new ModelBuilder();
var Post = modelBuilder.define('Post', {userId: Number, content: String});
var User = modelBuilder.define('User', {name: String}, {relations: {posts: {type: 'hasMany', model: 'Post'}}});
assert(!User.relations['posts']);
Post.attachTo(ds);
User.attachTo(ds);
assert(User.relations['posts']);
done();
});
});
2013-05-28 22:26:12 +00:00
describe('Load models from json', function () {
it('should be able to define models from json', function () {
var path = require('path'),
fs = require('fs');
/**
2013-07-22 20:48:28 +00:00
* Load LDL schemas from a json doc
* @param schemaFile The dataSource json file
2013-05-28 22:26:12 +00:00
* @returns A map of schemas keyed by name
*/
function loadSchemasSync(schemaFile, dataSource) {
// Set up the data source
if (!dataSource) {
dataSource = new DataSource('memory');
}
// Read the dataSource JSON file
2013-05-28 22:26:12 +00:00
var schemas = JSON.parse(fs.readFileSync(schemaFile));
return dataSource.modelBuilder.buildModels(schemas);
2013-05-28 22:26:12 +00:00
}
var models = loadSchemasSync(path.join(__dirname, 'test1-schemas.json'));
models.should.have.property('AnonymousModel_0');
models.AnonymousModel_0.should.have.property('modelName', 'AnonymousModel_0');
2013-05-28 22:26:12 +00:00
var m1 = new models.AnonymousModel_0({title: 'Test'});
2013-05-28 22:26:12 +00:00
m1.should.have.property('title', 'Test');
2013-05-28 22:40:16 +00:00
m1.should.have.property('author', 'Raymond');
2013-05-28 22:26:12 +00:00
models = loadSchemasSync(path.join(__dirname, 'test2-schemas.json'));
models.should.have.property('Address');
models.should.have.property('Account');
models.should.have.property('Customer');
2013-05-28 22:26:12 +00:00
for (var s in models) {
var m = models[s];
assert(new m());
2013-05-28 22:26:12 +00:00
}
});
it('should be able to extend models', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {
name: String,
bio: ModelBuilder.Text,
approved: Boolean,
joinedAt: Date,
age: Number
});
var Customer = User.extend('Customer', {customerId: {type: String, id: true}});
var customer = new Customer({name: 'Joe', age: 20, customerId: 'c01'});
customer.should.be.a('object').and.have.property('name', 'Joe');
customer.should.have.property('name', 'Joe');
customer.should.have.property('age', 20);
customer.should.have.property('customerId', 'c01');
customer.should.not.have.property('bio');
// The properties are defined at prototype level
assert.equal(Object.keys(customer).length, 0);
var count = 0;
for(var p in customer) {
if(typeof customer[p] !== 'function') {
count++;
}
}
assert.equal(count, 7); // Please note there is an injected id from User prototype
assert.equal(Object.keys(customer.toObject()).length, 6);
done(null, customer);
});
2013-12-06 23:52:39 +00:00
it('should be able to extend models with merged settings', function (done) {
var modelBuilder = new ModelBuilder();
var User = modelBuilder.define('User', {
name: String
}, {
defaultPermission: 'ALLOW',
acls: [
{
principalType: 'ROLE',
principalId: '$everyone',
permission: 'ALLOW'
}
],
relations: {
posts: {
type: 'hasMany',
model:'Post'
}
}
});
var Customer = User.extend('Customer',
{customerId: {type: String, id: true}},
{
defaultPermission: 'DENY',
acls: [
{
principalType: 'ROLE',
principalId: '$unauthenticated',
permission: 'DENY'
}
],
relations: {
orders: {
type: 'hasMany',
model:'Order'
}
}
}
);
assert.deepEqual(User.settings, {
defaultPermission: 'ALLOW',
acls: [
{
principalType: 'ROLE',
principalId: '$everyone',
permission: 'ALLOW'
}
],
relations: {
posts: {
type: 'hasMany',
model:'Post'
}
},
strict: false
});
assert.deepEqual(Customer.settings, {
defaultPermission: 'DENY',
acls: [
{
principalType: 'ROLE',
principalId: '$everyone',
permission: 'ALLOW'
},
{
principalType: 'ROLE',
principalId: '$unauthenticated',
permission: 'DENY'
}
],
relations: {
posts: {
type: 'hasMany',
model:'Post'
},
orders: {
type: 'hasMany',
model:'Order'
}
},
strict: false
});
done();
});
2013-05-28 22:26:12 +00:00
});
describe('DataSource constructor', function(){
it('Takes url as the settings', function() {
var ds = new DataSource('memory://localhost/mydb?x=1');
assert.equal(ds.connector.name, 'memory');
});
it('Takes connector name', function() {
var ds = new DataSource('memory');
assert.equal(ds.connector.name, 'memory');
});
it('Takes settings object', function() {
var ds = new DataSource({connector: 'memory'});
assert.equal(ds.connector.name, 'memory');
});
it('Takes settings object and name', function() {
var ds = new DataSource('x', {connector: 'memory'});
assert.equal(ds.connector.name, 'memory');
});
});
describe('Injected methods from connectors', function(){
it('are not shared across models for remote methods', function() {
var ds = new DataSource('memory');
var M1 = ds.createModel('M1');
var M2 = ds.createModel('M2');
// Remotable methods are not shared across models
assert.notEqual(M1.create, M2.create, 'Remotable methods are not shared');
assert.equal(M1.create.shared, true, 'M1.create is remotable');
assert.equal(M2.create.shared, true, 'M2.create is remotable');
M1.create.shared = false;
assert.equal(M1.create.shared, false, 'M1.create should be local now');
assert.equal(M2.create.shared, true, 'M2.create should stay remotable');
});
it('are not shared across models for non-remote methods', function() {
var ds = new DataSource('memory');
var M1 = ds.createModel('M1');
var M2 = ds.createModel('M2');
var m1 = M1.prototype.save;
var m2 = M2.prototype.save;
assert.notEqual(m1, m2, 'non-remote methods are not shared');
assert.equal(!!m1.shared, false, 'M1.save is not remotable');
assert.equal(!!m2.shared, false, 'M2.save is not remotable');
m1.shared = true;
assert.equal(m1.shared, true, 'M1.save is now remotable');
assert.equal(!!m2.shared, false, 'M2.save is not remotable');
});
});