Use eslint with loopback config

Drop jshint and jscs in favour of eslint.

Fix style violations.

While we are at this, reduce the max line length from 150 to 100.
This commit is contained in:
Miroslav Bajtoš 2016-04-01 11:14:26 +02:00 committed by Miroslav Bajtoš
parent 2a86e9535b
commit f9702b0ace
74 changed files with 1397 additions and 1438 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist
coverage

10
.eslintrc Normal file
View File

@ -0,0 +1,10 @@
{
"extends": "loopback",
"rules": {
"max-len": ["error", 100, 4, {
"ignoreComments": true,
"ignoreUrls": true,
"ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)"
}]
}
}

View File

@ -1 +0,0 @@
node_modules

View File

@ -1,34 +0,0 @@
{
"node": true,
"camelcase": true,
"eqnull": true,
"indent": 2,
"undef": true,
"quotmark": "single",
"newcap": true,
"nonew": true,
"sub": true,
"laxcomma": true,
"laxbreak": true,
"globals": {
/* mocha */
"after": true,
"afterEach": true,
"assert": true,
"before": true,
"beforeEach": true,
"context": true,
"describe": true,
"expect": true,
"it": true,
/* loopback */
"app": true,
"assertValidDataSource": true,
"GeoPoint": true,
"loopback": true,
"memoryConnector": true,
"request": true,
"TaskEmitter": true
}
}

View File

@ -1,6 +1,5 @@
/*global module:false*/ /*global module:false*/
module.exports = function(grunt) { module.exports = function(grunt) {
// Do not report warnings from unit-tests exercising deprecated paths // Do not report warnings from unit-tests exercising deprecated paths
process.env.NO_DEPRECATION = 'loopback'; process.env.NO_DEPRECATION = 'loopback';
@ -18,58 +17,59 @@ module.exports = function(grunt) {
// Task configuration. // Task configuration.
uglify: { uglify: {
options: { options: {
banner: '<%= banner %>' banner: '<%= banner %>',
}, },
dist: { dist: {
files: { files: {
'dist/loopback.min.js': ['dist/loopback.js'] 'dist/loopback.min.js': ['dist/loopback.js'],
}
}
}, },
jshint: {
options: {
jshintrc: true
}, },
},
eslint: {
gruntfile: { gruntfile: {
src: 'Gruntfile.js' src: 'Gruntfile.js',
}, },
lib: { lib: {
src: ['lib/**/*.js'] src: ['lib/**/*.js'],
}, },
common: { common: {
src: ['common/**/*.js'] src: ['common/**/*.js'],
}, },
browser: { browser: {
src: ['browser/**/*.js'] src: ['browser/**/*.js'],
}, },
server: { server: {
src: ['server/**/*.js'] src: ['server/**/*.js'],
}, },
test: { test: {
src: ['test/**/*.js'] src: ['test/**/*.js'],
}
}, },
jscs: {
gruntfile: 'Gruntfile.js',
lib: ['lib/**/*.js'],
common: ['common/**/*.js'],
server: ['server/**/*.js'],
browser: ['browser/**/*.js'],
test: ['test/**/*.js']
}, },
watch: { watch: {
gruntfile: { gruntfile: {
files: '<%= jshint.gruntfile.src %>', files: '<%= eslint.gruntfile.src %>',
tasks: ['jshint:gruntfile'] tasks: ['eslint:gruntfile'],
},
browser: {
files: ['<%= eslint.browser.src %>'],
tasks: ['eslint:browser'],
},
common: {
files: ['<%= eslint.common.src %>'],
tasks: ['eslint:common'],
}, },
lib: { lib: {
files: ['<%= jshint.lib.src %>'], files: ['<%= eslint.lib.src %>'],
tasks: ['jshint:lib'] tasks: ['eslint:lib'],
},
server: {
files: ['<%= eslint.server.src %>'],
tasks: ['eslint:server'],
}, },
test: { test: {
files: ['<%= jshint.test.src %>'], files: ['<%= eslint.test.src %>'],
tasks: ['jshint:test'] tasks: ['eslint:test'],
} },
}, },
browserify: { browserify: {
dist: { dist: {
@ -78,24 +78,24 @@ module.exports = function(grunt) {
}, },
options: { options: {
ignore: ['nodemailer', 'passport', 'bcrypt'], ignore: ['nodemailer', 'passport', 'bcrypt'],
standalone: 'loopback' standalone: 'loopback',
} },
} },
}, },
mochaTest: { mochaTest: {
'unit': { 'unit': {
src: 'test/*.js', src: 'test/*.js',
options: { options: {
reporter: 'dot', reporter: 'dot',
} },
}, },
'unit-xml': { 'unit-xml': {
src: 'test/*.js', src: 'test/*.js',
options: { options: {
reporter: 'xunit', reporter: 'xunit',
captureFile: 'xunit.xml' captureFile: 'xunit.xml',
} },
} },
}, },
karma: { karma: {
'unit-once': { 'unit-once': {
@ -109,7 +109,7 @@ module.exports = function(grunt) {
// CI friendly test output // CI friendly test output
junitReporter: { junitReporter: {
outputFile: 'karma-xunit.xml' outputFile: 'karma-xunit.xml',
}, },
browserify: { browserify: {
@ -117,8 +117,8 @@ module.exports = function(grunt) {
// Fatal error: Maximum call stack size exceeded // Fatal error: Maximum call stack size exceeded
debug: false, debug: false,
// Disable watcher, grunt will exit after the first run // Disable watcher, grunt will exit after the first run
watch: false watch: false,
} },
}, },
unit: { unit: {
configFile: 'test/karma.conf.js', configFile: 'test/karma.conf.js',
@ -134,7 +134,7 @@ module.exports = function(grunt) {
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
files: [ files: [
'test/e2e/remote-connector.e2e.js', 'test/e2e/remote-connector.e2e.js',
'test/e2e/replication.e2e.js' 'test/e2e/replication.e2e.js',
], ],
// list of files to exclude // list of files to exclude
@ -171,7 +171,7 @@ module.exports = function(grunt) {
// - PhantomJS // - PhantomJS
// - IE (only Windows) // - IE (only Windows)
browsers: [ browsers: [
'Chrome' 'Chrome',
], ],
// If browser does not capture in given timeout [ms], kill it // If browser does not capture in given timeout [ms], kill it
@ -190,7 +190,7 @@ module.exports = function(grunt) {
'passport-local', 'passport-local',
'superagent', 'superagent',
'supertest', 'supertest',
'bcrypt' 'bcrypt',
], ],
// transform: ['coffeeify'], // transform: ['coffeeify'],
// debug: true, // debug: true,
@ -199,19 +199,18 @@ module.exports = function(grunt) {
}, },
// Add browserify to preprocessors // Add browserify to preprocessors
preprocessors: {'test/e2e/*': ['browserify']} preprocessors: { 'test/e2e/*': ['browserify'] },
} },
} },
} },
}); });
// These plugins provide necessary tasks. // These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-browserify'); grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-jscs');
grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('e2e-server', function() { grunt.registerTask('e2e-server', function() {
@ -229,8 +228,7 @@ module.exports = function(grunt) {
grunt.registerTask('default', ['browserify']); grunt.registerTask('default', ['browserify']);
grunt.registerTask('test', [ grunt.registerTask('test', [
'jscs', 'eslint',
'jshint',
process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit', process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',
'karma:unit-once']); 'karma:unit-once']);

View File

@ -27,7 +27,6 @@ var DEFAULT_TOKEN_LEN = 64;
*/ */
module.exports = function(AccessToken) { module.exports = function(AccessToken) {
// Workaround for https://github.com/strongloop/loopback/issues/292 // Workaround for https://github.com/strongloop/loopback/issues/292
AccessToken.definition.rawProperties.created.default = AccessToken.definition.rawProperties.created.default =
AccessToken.definition.properties.created.default = function() { AccessToken.definition.properties.created.default = function() {
@ -165,8 +164,7 @@ module.exports = function(AccessToken) {
var headers = options.headers || []; var headers = options.headers || [];
var cookies = options.cookies || []; var cookies = options.cookies || [];
var i = 0; var i = 0;
var length; var length, id;
var id;
// https://github.com/strongloop/loopback/issues/1326 // https://github.com/strongloop/loopback/issues/1326
if (options.searchDefaultTokenKeys !== false) { if (options.searchDefaultTokenKeys !== false) {

View File

@ -78,7 +78,6 @@ assert(Role, 'Role model must be defined before ACL model');
*/ */
module.exports = function(ACL) { module.exports = function(ACL) {
ACL.ALL = AccessContext.ALL; ACL.ALL = AccessContext.ALL;
ACL.DEFAULT = AccessContext.DEFAULT; // Not specified ACL.DEFAULT = AccessContext.DEFAULT; // Not specified
@ -112,7 +111,8 @@ module.exports = function(ACL) {
score = score * 4; score = score * 4;
var ruleValue = rule[props[i]] || ACL.ALL; var ruleValue = rule[props[i]] || ACL.ALL;
var requestedValue = req[props[i]] || ACL.ALL; var requestedValue = req[props[i]] || ACL.ALL;
var isMatchingMethodName = props[i] === 'property' && req.methodNames.indexOf(ruleValue) !== -1; var isMatchingMethodName = props[i] === 'property' &&
req.methodNames.indexOf(ruleValue) !== -1;
var isMatchingAccessType = ruleValue === requestedValue; var isMatchingAccessType = ruleValue === requestedValue;
if (props[i] === 'accessType' && !isMatchingAccessType) { if (props[i] === 'accessType' && !isMatchingAccessType) {
@ -278,7 +278,7 @@ module.exports = function(ACL) {
principalType: acl.principalType, principalType: acl.principalType,
principalId: acl.principalId, // TODO: Should it be a name? principalId: acl.principalId, // TODO: Should it be a name?
accessType: acl.accessType || ACL.ALL, accessType: acl.accessType || ACL.ALL,
permission: acl.permission permission: acl.permission,
})); }));
} }
}); });
@ -300,7 +300,7 @@ module.exports = function(ACL) {
principalType: acl.principalType, principalType: acl.principalType,
principalId: acl.principalId, principalId: acl.principalId,
accessType: acl.accessType, accessType: acl.accessType,
permission: acl.permission permission: acl.permission,
})); }));
}); });
} }
@ -327,7 +327,8 @@ module.exports = function(ACL) {
property = property || ACL.ALL; property = property || ACL.ALL;
var propertyQuery = (property === ACL.ALL) ? undefined : { inq: [property, ACL.ALL] }; var propertyQuery = (property === ACL.ALL) ? undefined : { inq: [property, ACL.ALL] };
accessType = accessType || ACL.ALL; accessType = accessType || ACL.ALL;
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL, ACL.EXECUTE]}; var accessTypeQuery = (accessType === ACL.ALL) ? undefined :
{ inq: [accessType, ACL.ALL, ACL.EXECUTE] };
var req = new AccessRequest(model, property, accessType); var req = new AccessRequest(model, property, accessType);
@ -485,7 +486,7 @@ module.exports = function(ACL) {
model: model, model: model,
property: method, property: method,
method: method, method: method,
modelId: modelId modelId: modelId,
}); });
this.checkAccessForContext(context, function(err, access) { this.checkAccessForContext(context, function(err, access) {
@ -559,8 +560,8 @@ module.exports = function(ACL) {
where: { where: {
roleId: role.id, roleId: role.id,
principalType: principalType, principalType: principalType,
principalId: String(principalId) principalId: String(principalId),
} },
}, function(err, result) { }, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
return cb(null, !!result); return cb(null, !!result);

View File

@ -65,7 +65,6 @@ function generateKey(hmacKey, algorithm, encoding) {
*/ */
module.exports = function(Application) { module.exports = function(Application) {
// Workaround for https://github.com/strongloop/loopback/issues/292 // Workaround for https://github.com/strongloop/loopback/issues/292
Application.definition.rawProperties.created.default = Application.definition.rawProperties.created.default =
Application.definition.properties.created.default = function() { Application.definition.properties.created.default = function() {
@ -195,7 +194,7 @@ module.exports = function(Application) {
if (app[keyNames[i]] === key) { if (app[keyNames[i]] === key) {
result = { result = {
application: app, application: app,
keyType: keyNames[i] keyType: keyNames[i],
}; };
break; break;
} }

View File

@ -31,7 +31,6 @@ var deprecate = require('depd')('loopback');
*/ */
module.exports = function(Change) { module.exports = function(Change) {
/*! /*!
* Constants * Constants
*/ */
@ -154,7 +153,7 @@ module.exports = function(Change) {
var ch = new Change({ var ch = new Change({
id: id, id: id,
modelName: modelName, modelName: modelName,
modelId: modelId modelId: modelId,
}); });
ch.debug('creating change'); ch.debug('creating change');
Change.updateOrCreate(ch, callback); Change.updateOrCreate(ch, callback);
@ -415,8 +414,8 @@ module.exports = function(Change) {
this.find({ this.find({
where: { where: {
modelName: modelName, modelName: modelName,
modelId: {inq: modelIds} modelId: { inq: modelIds },
} },
}, function(err, allLocalChanges) { }, function(err, allLocalChanges) {
if (err) return callback(err); if (err) return callback(err);
var deltas = []; var deltas = [];
@ -462,7 +461,7 @@ module.exports = function(Change) {
callback(null, { callback(null, {
deltas: deltas, deltas: deltas,
conflicts: conflicts conflicts: conflicts,
}); });
}); });
return callback.promise; return callback.promise;
@ -586,12 +585,11 @@ module.exports = function(Change) {
var conflict = this; var conflict = this;
var SourceModel = this.SourceModel; var SourceModel = this.SourceModel;
var TargetModel = this.TargetModel; var TargetModel = this.TargetModel;
var source; var source, target;
var target;
async.parallel([ async.parallel([
getSourceModel, getSourceModel,
getTargetModel getTargetModel,
], done); ], done);
function getSourceModel(cb) { function getSourceModel(cb) {
@ -627,12 +625,11 @@ module.exports = function(Change) {
Conflict.prototype.changes = function(cb) { Conflict.prototype.changes = function(cb) {
var conflict = this; var conflict = this;
var sourceChange; var sourceChange, targetChange;
var targetChange;
async.parallel([ async.parallel([
getSourceChange, getSourceChange,
getTargetChange getTargetChange,
], done); ], done);
function getSourceChange(cb) { function getSourceChange(cb) {

View File

@ -16,7 +16,6 @@ var assert = require('assert');
*/ */
module.exports = function(Checkpoint) { module.exports = function(Checkpoint) {
// Workaround for https://github.com/strongloop/loopback/issues/292 // Workaround for https://github.com/strongloop/loopback/issues/292
Checkpoint.definition.rawProperties.time.default = Checkpoint.definition.rawProperties.time.default =
Checkpoint.definition.properties.time.default = function() { Checkpoint.definition.properties.time.default = function() {

View File

@ -11,7 +11,6 @@
*/ */
module.exports = function(Email) { module.exports = function(Email) {
/** /**
* Send an email with the given `options`. * Send an email with the given `options`.
* *

View File

@ -15,7 +15,6 @@ assert(RoleMapping, 'RoleMapping model must be defined before Role model');
* @header Role object * @header Role object
*/ */
module.exports = function(Role) { module.exports = function(Role) {
// Workaround for https://github.com/strongloop/loopback/issues/292 // Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.created.default = Role.definition.rawProperties.created.default =
Role.definition.properties.created.default = function() { Role.definition.properties.created.default = function() {
@ -39,7 +38,6 @@ module.exports = function(Role) {
// Set up the connection to users/applications/roles once the model // Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function(roleModel) { Role.once('dataSourceAttached', function(roleModel) {
['users', 'applications', 'roles'].forEach(function(rel) { ['users', 'applications', 'roles'].forEach(function(rel) {
/** /**
* Fetch all users assigned to this role * Fetch all users assigned to this role
@ -64,14 +62,14 @@ module.exports = function(Role) {
var relsToModels = { var relsToModels = {
users: roleModel.userModel, users: roleModel.userModel,
applications: roleModel.applicationModel, applications: roleModel.applicationModel,
roles: roleModel roles: roleModel,
}; };
var ACL = loopback.ACL; var ACL = loopback.ACL;
var relsToTypes = { var relsToTypes = {
users: ACL.USER, users: ACL.USER,
applications: ACL.APP, applications: ACL.APP,
roles: ACL.ROLE roles: ACL.ROLE,
}; };
var model = relsToModels[rel]; var model = relsToModels[rel];
@ -94,7 +92,7 @@ module.exports = function(Role) {
} }
roleModel.roleMappingModel.find({ roleModel.roleMappingModel.find({
where: {roleId: this.id, principalType: principalType} where: { roleId: this.id, principalType: principalType },
}, function(err, mappings) { }, function(err, mappings) {
var ids; var ids;
if (err) { if (err) {
@ -110,7 +108,6 @@ module.exports = function(Role) {
}); });
}); });
} }
}); });
// Special roles // Special roles
@ -305,7 +302,6 @@ module.exports = function(Role) {
} }
var inRole = context.principals.some(function(p) { var inRole = context.principals.some(function(p) {
var principalType = p.type || undefined; var principalType = p.type || undefined;
var principalId = p.id || undefined; var principalId = p.id || undefined;
@ -338,8 +334,9 @@ module.exports = function(Role) {
var principalType = p.type || undefined; var principalType = p.type || undefined;
var principalId = p.id || undefined; var principalId = p.id || undefined;
var roleId = result.id.toString(); var roleId = result.id.toString();
var principalIdIsString = typeof principalId === 'string';
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) { if (principalId !== null && principalId !== undefined && !principalIdIsString) {
principalId = principalId.toString(); principalId = principalId.toString();
} }
@ -360,7 +357,6 @@ module.exports = function(Role) {
if (callback) callback(null, inRole); if (callback) callback(null, inRole);
}); });
}); });
}; };
/** /**

View File

@ -69,7 +69,6 @@ var debug = require('debug')('loopback:user');
*/ */
module.exports = function(User) { module.exports = function(User) {
/** /**
* Create access token for the logged in user. This method can be overridden to * Create access token for the logged in user. This method can be overridden to
* customize how access tokens are generated * customize how access tokens are generated
@ -99,7 +98,7 @@ module.exports = function(User) {
var userModel = this.constructor; var userModel = this.constructor;
ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL); ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL);
this.accessTokens.create({ this.accessTokens.create({
ttl: ttl ttl: ttl,
}, cb); }, cb);
return cb.promise; return cb.promise;
}; };
@ -369,11 +368,14 @@ module.exports = function(User) {
assert(typeof options === 'object', 'options required when calling user.verify()'); assert(typeof options === 'object', 'options required when calling user.verify()');
assert(options.type, 'You must supply a verification type (options.type)'); assert(options.type, 'You must supply a verification type (options.type)');
assert(options.type === 'email', 'Unsupported verification type'); assert(options.type === 'email', 'Unsupported verification type');
assert(options.to || this.email, 'Must include options.to when calling user.verify() or the user must have an email property'); assert(options.to || this.email,
'Must include options.to when calling user.verify() ' +
'or the user must have an email property');
assert(options.from, 'Must include options.from when calling user.verify()'); assert(options.from, 'Must include options.from when calling user.verify()');
options.redirect = options.redirect || '/'; options.redirect = options.redirect || '/';
options.template = path.resolve(options.template || path.join(__dirname, '..', '..', 'templates', 'verify.ejs')); var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');
options.template = path.resolve(options.template || defaultTemplate);
options.user = this; options.user = this;
options.protocol = options.protocol || 'http'; options.protocol = options.protocol || 'http';
@ -423,7 +425,8 @@ module.exports = function(User) {
function sendEmail(user) { function sendEmail(user) {
options.verifyHref += '&token=' + user.verificationToken; options.verifyHref += '&token=' + user.verificationToken;
options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}'; options.text = options.text || 'Please verify your email by opening ' +
'this link in a web browser:\n\t{href}';
options.text = options.text.replace('{href}', options.verifyHref); options.text = options.text.replace('{href}', options.verifyHref);
@ -551,7 +554,7 @@ module.exports = function(User) {
UserModel.emit('resetPasswordRequest', { UserModel.emit('resetPasswordRequest', {
email: options.email, email: options.email,
accessToken: accessToken, accessToken: accessToken,
user: user user: user,
}); });
}); });
}); });
@ -636,7 +639,7 @@ module.exports = function(User) {
{ arg: 'credentials', type: 'object', required: true, http: { source: 'body' }}, { arg: 'credentials', type: 'object', required: true, http: { source: 'body' }},
{ arg: 'include', type: ['string'], http: { source: 'query' }, { arg: 'include', type: ['string'], http: { source: 'query' },
description: 'Related objects to include in the response. ' + description: 'Related objects to include in the response. ' +
'See the description of return value for more details.'} 'See the description of return value for more details.' },
], ],
returns: { returns: {
arg: 'accessToken', type: 'object', root: true, arg: 'accessToken', type: 'object', root: true,
@ -644,9 +647,9 @@ module.exports = function(User) {
'The response body contains properties of the AccessToken created on login.\n' + 'The response body contains properties of the AccessToken created on login.\n' +
'Depending on the value of `include` parameter, the body may contain ' + 'Depending on the value of `include` parameter, the body may contain ' +
'additional properties:\n\n' + 'additional properties:\n\n' +
' - `user` - `{User}` - Data of the currently logged in user. (`include=user`)\n\n' ' - `user` - `{User}` - Data of the currently logged in user. (`include=user`)\n\n',
}, },
http: {verb: 'post'} http: { verb: 'post' },
} }
); );
@ -662,10 +665,10 @@ module.exports = function(User) {
return tokenID; return tokenID;
}, description: 'Do not supply this argument, it is automatically extracted ' + }, description: 'Do not supply this argument, it is automatically extracted ' +
'from request headers.' 'from request headers.',
} },
], ],
http: {verb: 'all'} http: { verb: 'all' },
} }
); );
@ -676,9 +679,9 @@ module.exports = function(User) {
accepts: [ accepts: [
{ arg: 'uid', type: 'string', required: true }, { arg: 'uid', type: 'string', required: true },
{ arg: 'token', type: 'string', required: true }, { arg: 'token', type: 'string', required: true },
{arg: 'redirect', type: 'string'} { arg: 'redirect', type: 'string' },
], ],
http: {verb: 'get', path: '/confirm'} http: { verb: 'get', path: '/confirm' },
} }
); );
@ -687,9 +690,9 @@ module.exports = function(User) {
{ {
description: 'Reset password for a user with email.', description: 'Reset password for a user with email.',
accepts: [ accepts: [
{arg: 'options', type: 'object', required: true, http: {source: 'body'}} { arg: 'options', type: 'object', required: true, http: { source: 'body' }},
], ],
http: {verb: 'post', path: '/reset'} http: { verb: 'post', path: '/reset' },
} }
); );
@ -730,5 +733,4 @@ module.exports = function(User) {
*/ */
User.setup(); User.setup();
}; };

View File

@ -3,7 +3,7 @@ var client = loopback();
var CartItem = require('./models').CartItem; var CartItem = require('./models').CartItem;
var remote = loopback.createDataSource({ var remote = loopback.createDataSource({
connector: loopback.Remote, connector: loopback.Remote,
url: 'http://localhost:3000' url: 'http://localhost:3000',
}); });
client.model(CartItem); client.model(CartItem);

View File

@ -5,7 +5,7 @@ var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
price: Number, price: Number,
item: String, item: String,
qty: { type: Number, default: 0 }, qty: { type: Number, default: 0 },
cartId: Number cartId: Number,
}); });
CartItem.sum = function(cartId, callback) { CartItem.sum = function(cartId, callback) {
@ -20,15 +20,15 @@ CartItem.sum = function(cartId, callback) {
callback(null, total); callback(null, total);
}); });
} };
CartItem.remoteMethod('sum', CartItem.remoteMethod('sum',
{ {
accepts: { arg: 'cartId', type: 'number' }, accepts: { arg: 'cartId', type: 'number' },
returns: {arg: 'total', type: 'number'} returns: { arg: 'total', type: 'number' },
} }
); );
CartItem.prototype.total = function() { CartItem.prototype.total = function() {
return this.price * this.qty * 1 + this.tax; return this.price * this.qty * 1 + this.tax;
} };

View File

@ -2,7 +2,7 @@ var loopback = require('../../');
var server = module.exports = loopback(); var server = module.exports = loopback();
var CartItem = require('./models').CartItem; var CartItem = require('./models').CartItem;
var memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
server.use(loopback.rest()); server.use(loopback.rest());
@ -14,7 +14,7 @@ CartItem.attachTo(memory);
CartItem.create([ CartItem.create([
{ item: 'red hat', qty: 6, price: 19.99, cartId: 1 }, { item: 'red hat', qty: 6, price: 19.99, cartId: 1 },
{ item: 'green shirt', qty: 1, price: 14.99, cartId: 1 }, { item: 'green shirt', qty: 1, price: 14.99, cartId: 1 },
{item: 'orange pants', qty: 58, price: 9.99, cartId: 1} { item: 'orange pants', qty: 58, price: 9.99, cartId: 1 },
]); ]);
CartItem.sum(1, function(err, total) { CartItem.sum(1, function(err, total) {

View File

@ -4,7 +4,7 @@ var app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
var schema = { var schema = {
name: String name: String,
}; };
var Color = app.model('color', schema); var Color = app.model('color', schema);

View File

@ -13,30 +13,31 @@ app.model(Application);
var data = { pushSettings: [ var data = { pushSettings: [
{ "platform": "apns", { 'platform': 'apns',
"apns": { 'apns': {
"pushOptions": { 'pushOptions': {
"gateway": "gateway.sandbox.push.apple.com", 'gateway': 'gateway.sandbox.push.apple.com',
"cert": "credentials/apns_cert_dev.pem", 'cert': 'credentials/apns_cert_dev.pem',
"key": "credentials/apns_key_dev.pem" 'key': 'credentials/apns_key_dev.pem',
}, },
"feedbackOptions": { 'feedbackOptions': {
"gateway": "feedback.sandbox.push.apple.com", 'gateway': 'feedback.sandbox.push.apple.com',
"cert": "credentials/apns_cert_dev.pem", 'cert': 'credentials/apns_cert_dev.pem',
"key": "credentials/apns_key_dev.pem", 'key': 'credentials/apns_key_dev.pem',
"batchFeedback": true, 'batchFeedback': true,
"interval": 300 'interval': 300,
} },
}} }},
]} ] };
Application.create(data, function(err, data) { Application.create(data, function(err, data) {
console.log('Created: ', data.toObject()); console.log('Created: ', data.toObject());
}); });
Application.register('rfeng', 'MyApp', {description: 'My first mobile application'}, function (err, result) { Application.register('rfeng', 'MyApp', { description: 'My first mobile application' },
function(err, result) {
console.log(result.toObject()); console.log(result.toObject());
result.resetKeys(function(err, result) { result.resetKeys(function(err, result) {

View File

@ -56,7 +56,7 @@ var steps = [
replicateSourceToTarget, replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'), list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data') list.bind(this, target, 'current TARGET data'),
]; ];
run(steps); run(steps);
@ -65,7 +65,7 @@ function createSomeInitialSourceData() {
Color.create([ Color.create([
{ name: 'red' }, { name: 'red' },
{ name: 'blue' }, { name: 'blue' },
{name: 'green'} { name: 'green' },
]); ]);
} }
@ -113,7 +113,7 @@ function createSomeNewSourceData() {
Color.create([ Color.create([
{ name: 'violet' }, { name: 'violet' },
{ name: 'amber' }, { name: 'amber' },
{name: 'olive'} { name: 'olive' },
]); ]);
} }

View File

@ -7,7 +7,7 @@ app.use(loopback.rest());
var dataSource = app.dataSource('db', { adapter: 'memory' }); var dataSource = app.dataSource('db', { adapter: 'memory' });
var Color = dataSource.define('color', { var Color = dataSource.define('color', {
'name': String 'name': String,
}); });
Color.create({ name: 'red' }); Color.create({ name: 'red' });

View File

@ -91,7 +91,7 @@ AccessContext.permissionOrder = {
ALLOW: 1, ALLOW: 1,
ALARM: 2, ALARM: 2,
AUDIT: 3, AUDIT: 3,
DENY: 4 DENY: 4,
}; };
/** /**

View File

@ -326,7 +326,7 @@ app.enableAuth = function(options) {
app.model(Model, { app.model(Model, {
dataSource: options.dataSource, dataSource: options.dataSource,
public: m === 'User' public: m === 'User',
}); });
}); });
} }
@ -365,20 +365,19 @@ app.enableAuth = function(options) {
} else if (allowed) { } else if (allowed) {
next(); next();
} else { } else {
var messages = { var messages = {
403: { 403: {
message: 'Access Denied', message: 'Access Denied',
code: 'ACCESS_DENIED' code: 'ACCESS_DENIED',
}, },
404: { 404: {
message: ('could not find ' + modelName + ' with id ' + modelId), message: ('could not find ' + modelName + ' with id ' + modelId),
code: 'MODEL_NOT_FOUND' code: 'MODEL_NOT_FOUND',
}, },
401: { 401: {
message: 'Authorization Required', message: 'Authorization Required',
code: 'AUTHORIZATION_REQUIRED' code: 'AUTHORIZATION_REQUIRED',
} },
}; };
var e = new Error(messages[errStatusCode].message || messages[403].message); var e = new Error(messages[errStatusCode].message || messages[403].message);

View File

@ -47,7 +47,7 @@ module.exports = function(registry) {
var dataSourceTypes = { var dataSourceTypes = {
DB: 'db', DB: 'db',
MAIL: 'mail' MAIL: 'mail',
}; };
registry.Email.autoAttach = dataSourceTypes.MAIL; registry.Email.autoAttach = dataSourceTypes.MAIL;

View File

@ -18,7 +18,6 @@ module.exports = MailConnector;
*/ */
function MailConnector(settings) { function MailConnector(settings) {
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
var transports = settings.transports; var transports = settings.transports;
@ -156,7 +155,8 @@ Mailer.send = function(options, fn) {
} }
if (transport) { if (transport) {
assert(transport.sendMail, 'You must supply an Email.settings.transports containing a valid transport'); assert(transport.sendMail,
'You must supply an Email.settings.transports containing a valid transport');
transport.sendMail(options, fn); transport.sendMail(options, fn);
} else { } else {
console.warn('Warning: No email transport specified for sending email.' + console.warn('Warning: No email transport specified for sending email.' +

View File

@ -32,7 +32,7 @@ var middlewareModules = {
'favicon': 'serve-favicon', 'favicon': 'serve-favicon',
'directory': 'serve-index', 'directory': 'serve-index',
// 'static': 'serve-static', // 'static': 'serve-static',
'vhost': 'vhost' 'vhost': 'vhost',
}; };
middlewares.bodyParser = safeRequire('body-parser'); middlewares.bodyParser = safeRequire('body-parser');

View File

@ -52,17 +52,17 @@ loopback.registry = new Registry();
Object.defineProperties(loopback, { Object.defineProperties(loopback, {
Model: { Model: {
get: function() { return this.registry.getModel('Model'); } get: function() { return this.registry.getModel('Model'); },
}, },
PersistedModel: { PersistedModel: {
get: function() { return this.registry.getModel('PersistedModel'); } get: function() { return this.registry.getModel('PersistedModel'); },
}, },
defaultDataSources: { defaultDataSources: {
get: function() { return this.registry.defaultDataSources; } get: function() { return this.registry.defaultDataSources; },
}, },
modelBuilder: { modelBuilder: {
get: function() { return this.registry.modelBuilder; } get: function() { return this.registry.modelBuilder; },
} },
}); });
/*! /*!
@ -213,7 +213,7 @@ loopback.template = function(file) {
var templates = this._templates || (this._templates = {}); var templates = this._templates || (this._templates = {});
var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
return ejs.compile(str, { return ejs.compile(str, {
filename: file filename: file,
}); });
}; };

View File

@ -7,7 +7,6 @@ var SharedClass = require('strong-remoting').SharedClass;
var extend = require('util')._extend; var extend = require('util')._extend;
module.exports = function(registry) { module.exports = function(registry) {
/** /**
* The base class for **all models**. * The base class for **all models**.
* *
@ -177,12 +176,12 @@ module.exports = function(registry) {
var idDesc = ModelCtor.modelName + ' id'; var idDesc = ModelCtor.modelName + ' id';
ModelCtor.sharedCtor.accepts = [ ModelCtor.sharedCtor.accepts = [
{ arg: 'id', type: 'any', required: true, http: { source: 'path' }, { arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: idDesc} description: idDesc },
// {arg: 'instance', type: 'object', http: {source: 'body'}} // {arg: 'instance', type: 'object', http: {source: 'body'}}
]; ];
ModelCtor.sharedCtor.http = [ ModelCtor.sharedCtor.http = [
{path: '/:id'} { path: '/:id' },
]; ];
ModelCtor.sharedCtor.returns = { root: true }; ModelCtor.sharedCtor.returns = { root: true };
@ -227,7 +226,6 @@ module.exports = function(registry) {
// resolve relation functions // resolve relation functions
sharedClass.resolve(function resolver(define) { sharedClass.resolve(function resolver(define) {
var relations = ModelCtor.relations || {}; var relations = ModelCtor.relations || {};
// get the relations // get the relations
@ -250,9 +248,11 @@ module.exports = function(registry) {
// handle scopes // handle scopes
var scopes = ModelCtor.scopes || {}; var scopes = ModelCtor.scopes || {};
/* eslint-disable one-var */
for (var scopeName in scopes) { for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define); ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
} }
/* eslint-enable one-var */
}); });
return ModelCtor; return ModelCtor;
@ -307,7 +307,7 @@ module.exports = function(registry) {
sharedMethod: sharedMethod, sharedMethod: sharedMethod,
modelId: modelId, modelId: modelId,
accessType: this._getAccessTypeForMethod(sharedMethod), accessType: this._getAccessTypeForMethod(sharedMethod),
remotingContext: ctx remotingContext: ctx,
}, function(err, accessRequest) { }, function(err, accessRequest) {
if (err) return callback(err); if (err) return callback(err);
callback(null, accessRequest.isAllowed()); callback(null, accessRequest.isAllowed());
@ -446,7 +446,7 @@ module.exports = function(registry) {
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }}, accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
accessType: 'READ', accessType: 'READ',
description: 'Fetches belongsTo relation ' + relationName + '.', description: 'Fetches belongsTo relation ' + relationName + '.',
returns: {arg: relationName, type: modelName, root: true} returns: { arg: relationName, type: modelName, root: true },
}, fn); }, fn);
}; };
@ -472,7 +472,7 @@ module.exports = function(registry) {
description: 'Fetches hasOne relation ' + relationName + '.', description: 'Fetches hasOne relation ' + relationName + '.',
accessType: 'READ', accessType: 'READ',
returns: { arg: relationName, type: relation.modelTo.modelName, root: true }, returns: { arg: relationName, type: relation.modelTo.modelName, root: true },
rest: {after: convertNullToNotFoundError.bind(null, toModelName)} rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
}); });
define('__create__' + relationName, { define('__create__' + relationName, {
@ -481,7 +481,7 @@ module.exports = function(registry) {
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Creates a new instance in ' + relationName + ' of this model.', description: 'Creates a new instance in ' + relationName + ' of this model.',
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true} returns: { arg: 'data', type: toModelName, root: true },
}); });
define('__update__' + relationName, { define('__update__' + relationName, {
@ -490,14 +490,14 @@ module.exports = function(registry) {
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Update ' + relationName + ' of this model.', description: 'Update ' + relationName + ' of this model.',
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true} returns: { arg: 'data', type: toModelName, root: true },
}); });
define('__destroy__' + relationName, { define('__destroy__' + relationName, {
isStatic: false, isStatic: false,
http: { verb: 'delete', path: '/' + pathName }, http: { verb: 'delete', path: '/' + pathName },
description: 'Deletes ' + relationName + ' of this model.', description: 'Deletes ' + relationName + ' of this model.',
accessType: 'WRITE' accessType: 'WRITE',
}); });
}; };
@ -515,7 +515,7 @@ module.exports = function(registry) {
description: 'Find a related item by id for ' + relationName + '.', description: 'Find a related item by id for ' + relationName + '.',
accessType: 'READ', accessType: 'READ',
returns: { arg: 'result', type: toModelName, root: true }, returns: { arg: 'result', type: toModelName, root: true },
rest: {after: convertNullToNotFoundError.bind(null, toModelName)} rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
}, findByIdFunc); }, findByIdFunc);
var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
@ -527,7 +527,7 @@ module.exports = function(registry) {
http: { source: 'path' }}, http: { source: 'path' }},
description: 'Delete a related item by id for ' + relationName + '.', description: 'Delete a related item by id for ' + relationName + '.',
accessType: 'WRITE', accessType: 'WRITE',
returns: [] returns: [],
}, destroyByIdFunc); }, destroyByIdFunc);
var updateByIdFunc = this.prototype['__updateById__' + relationName]; var updateByIdFunc = this.prototype['__updateById__' + relationName];
@ -538,11 +538,11 @@ module.exports = function(registry) {
{ arg: 'fk', type: 'any', { arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true, description: 'Foreign key for ' + relationName, required: true,
http: { source: 'path' }}, http: { source: 'path' }},
{arg: 'data', type: toModelName, http: {source: 'body'}} { arg: 'data', type: toModelName, http: { source: 'body' }},
], ],
description: 'Update a related item by id for ' + relationName + '.', description: 'Update a related item by id for ' + relationName + '.',
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'result', type: toModelName, root: true} returns: { arg: 'result', type: toModelName, root: true },
}, updateByIdFunc); }, updateByIdFunc);
if (relation.modelThrough || relation.type === 'referencesMany') { if (relation.modelThrough || relation.type === 'referencesMany') {
@ -563,7 +563,7 @@ module.exports = function(registry) {
http: { source: 'path' }}].concat(accepts), http: { source: 'path' }}].concat(accepts),
description: 'Add a related item by id for ' + relationName + '.', description: 'Add a related item by id for ' + relationName + '.',
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: relationName, type: modelThrough.modelName, root: true} returns: { arg: relationName, type: modelThrough.modelName, root: true },
}, addFunc); }, addFunc);
var removeFunc = this.prototype['__unlink__' + relationName]; var removeFunc = this.prototype['__unlink__' + relationName];
@ -575,7 +575,7 @@ module.exports = function(registry) {
http: { source: 'path' }}, http: { source: 'path' }},
description: 'Remove the ' + relationName + ' relation to an item by id.', description: 'Remove the ' + relationName + ' relation to an item by id.',
accessType: 'WRITE', accessType: 'WRITE',
returns: [] returns: [],
}, removeFunc); }, removeFunc);
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
@ -604,8 +604,8 @@ module.exports = function(registry) {
} else { } else {
cb(); cb();
} }
} },
} },
}, existsFunc); }, existsFunc);
} }
}; };
@ -632,7 +632,7 @@ module.exports = function(registry) {
accepts: { arg: 'filter', type: 'object' }, accepts: { arg: 'filter', type: 'object' },
description: 'Queries ' + scopeName + ' of ' + this.modelName + '.', description: 'Queries ' + scopeName + ' of ' + this.modelName + '.',
accessType: 'READ', accessType: 'READ',
returns: {arg: scopeName, type: [toModelName], root: true} returns: { arg: scopeName, type: [toModelName], root: true },
}); });
define('__create__' + scopeName, { define('__create__' + scopeName, {
@ -641,14 +641,14 @@ module.exports = function(registry) {
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Creates a new instance in ' + scopeName + ' of this model.', description: 'Creates a new instance in ' + scopeName + ' of this model.',
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true} returns: { arg: 'data', type: toModelName, root: true },
}); });
define('__delete__' + scopeName, { define('__delete__' + scopeName, {
isStatic: isStatic, isStatic: isStatic,
http: { verb: 'delete', path: '/' + pathName }, http: { verb: 'delete', path: '/' + pathName },
description: 'Deletes all ' + scopeName + ' of this model.', description: 'Deletes all ' + scopeName + ' of this model.',
accessType: 'WRITE' accessType: 'WRITE',
}); });
define('__count__' + scopeName, { define('__count__' + scopeName, {
@ -657,9 +657,8 @@ module.exports = function(registry) {
accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' }, accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' },
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.', description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
accessType: 'READ', accessType: 'READ',
returns: {arg: 'count', type: 'number'} returns: { arg: 'count', type: 'number' },
}); });
}; };
/** /**
@ -696,8 +695,7 @@ module.exports = function(registry) {
var paramName = options.paramName || 'nk'; var paramName = options.paramName || 'nk';
var http = [].concat(sharedToClass.http || [])[0]; var http = [].concat(sharedToClass.http || [])[0];
var httpPath; var httpPath, acceptArgs;
var acceptArgs;
if (relation.multiple) { if (relation.multiple) {
httpPath = pathName + '/:' + paramName; httpPath = pathName + '/:' + paramName;
@ -705,8 +703,8 @@ module.exports = function(registry) {
{ {
arg: paramName, type: 'any', http: { source: 'path' }, arg: paramName, type: 'any', http: { source: 'path' },
description: 'Foreign key for ' + relation.name + '.', description: 'Foreign key for ' + relation.name + '.',
required: true required: true,
} },
]; ];
} else { } else {
httpPath = pathName; httpPath = pathName;
@ -830,9 +828,10 @@ module.exports = function(registry) {
} }
}); });
}); });
} else { } else {
throw new Error('Relation `' + relationName + '` does not exist for model `' + this.modelName + '`'); var msg = 'Relation `' + relationName +
'` does not exist for model `' + this.modelName + '`';
throw new Error(msg);
} }
}; };

View File

@ -555,18 +555,20 @@ module.exports = function(registry) {
setRemoting(PersistedModel, 'create', { setRemoting(PersistedModel, 'create', {
description: 'Create a new instance of the model and persist it into the data source.', description: 'Create a new instance of the model and persist it into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
'Model instance data' },
returns: { arg: 'data', type: typeName, root: true }, returns: { arg: 'data', type: typeName, root: true },
http: {verb: 'post', path: '/'} http: { verb: 'post', path: '/' },
}); });
setRemoting(PersistedModel, 'upsert', { setRemoting(PersistedModel, 'upsert', {
aliases: ['updateOrCreate'], aliases: ['updateOrCreate'],
description: 'Update an existing model instance or insert a new one into the data source.', description: 'Update an existing model instance or insert a new one into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
'Model instance data' },
returns: { arg: 'data', type: typeName, root: true }, returns: { arg: 'data', type: typeName, root: true },
http: {verb: 'put', path: '/'} http: { verb: 'put', path: '/' },
}); });
setRemoting(PersistedModel, 'exists', { setRemoting(PersistedModel, 'exists', {
@ -576,7 +578,7 @@ module.exports = function(registry) {
returns: { arg: 'exists', type: 'boolean' }, returns: { arg: 'exists', type: 'boolean' },
http: [ http: [
{ verb: 'get', path: '/:id/exists' }, { verb: 'get', path: '/:id/exists' },
{verb: 'head', path: '/:id'} { verb: 'head', path: '/:id' },
], ],
rest: { rest: {
// After hook to map exists to 200/404 for HEAD // After hook to map exists to 200/404 for HEAD
@ -596,8 +598,8 @@ module.exports = function(registry) {
} else { } else {
cb(); cb();
} }
} },
} },
}); });
setRemoting(PersistedModel, 'findById', { setRemoting(PersistedModel, 'findById', {
@ -607,28 +609,30 @@ module.exports = function(registry) {
{ arg: 'id', type: 'any', description: 'Model id', required: true, { arg: 'id', type: 'any', description: 'Model id', required: true,
http: { source: 'path' }}, http: { source: 'path' }},
{ arg: 'filter', type: 'object', { arg: 'filter', type: 'object',
description: 'Filter defining fields and include'} description: 'Filter defining fields and include' },
], ],
returns: { arg: 'data', type: typeName, root: true }, returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'get', path: '/:id' }, http: { verb: 'get', path: '/:id' },
rest: {after: convertNullToNotFoundError} rest: { after: convertNullToNotFoundError },
}); });
setRemoting(PersistedModel, 'find', { setRemoting(PersistedModel, 'find', {
description: 'Find all instances of the model matched by filter from the data source.', description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ', accessType: 'READ',
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'}, accepts: { arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit' },
returns: { arg: 'data', type: [typeName], root: true }, returns: { arg: 'data', type: [typeName], root: true },
http: {verb: 'get', path: '/'} http: { verb: 'get', path: '/' },
}); });
setRemoting(PersistedModel, 'findOne', { setRemoting(PersistedModel, 'findOne', {
description: 'Find first instance of the model matched by filter from the data source.', description: 'Find first instance of the model matched by filter from the data source.',
accessType: 'READ', accessType: 'READ',
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'}, accepts: { arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit' },
returns: { arg: 'data', type: typeName, root: true }, returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'get', path: '/findOne' }, http: { verb: 'get', path: '/findOne' },
rest: {after: convertNullToNotFoundError} rest: { after: convertNullToNotFoundError },
}); });
setRemoting(PersistedModel, 'destroyAll', { setRemoting(PersistedModel, 'destroyAll', {
@ -639,10 +643,10 @@ module.exports = function(registry) {
arg: 'count', arg: 'count',
type: 'object', type: 'object',
description: 'The number of instances deleted', description: 'The number of instances deleted',
root: true root: true,
}, },
http: { verb: 'del', path: '/' }, http: { verb: 'del', path: '/' },
shared: false shared: false,
}); });
setRemoting(PersistedModel, 'updateAll', { setRemoting(PersistedModel, 'updateAll', {
@ -659,9 +663,9 @@ module.exports = function(registry) {
arg: 'count', arg: 'count',
description: 'The number of instances updated', description: 'The number of instances updated',
type: 'object', type: 'object',
root: true root: true,
}, },
http: {verb: 'post', path: '/update'} http: { verb: 'post', path: '/update' },
}); });
setRemoting(PersistedModel, 'deleteById', { setRemoting(PersistedModel, 'deleteById', {
@ -671,7 +675,7 @@ module.exports = function(registry) {
accepts: { arg: 'id', type: 'any', description: 'Model id', required: true, accepts: { arg: 'id', type: 'any', description: 'Model id', required: true,
http: { source: 'path' }}, http: { source: 'path' }},
http: { verb: 'del', path: '/:id' }, http: { verb: 'del', path: '/:id' },
returns: {arg: 'count', type: 'object', root: true} returns: { arg: 'count', type: 'object', root: true },
}); });
setRemoting(PersistedModel, 'count', { setRemoting(PersistedModel, 'count', {
@ -679,7 +683,7 @@ module.exports = function(registry) {
accessType: 'READ', accessType: 'READ',
accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' }, accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' },
returns: { arg: 'count', type: 'number' }, returns: { arg: 'count', type: 'number' },
http: {verb: 'get', path: '/count'} http: { verb: 'get', path: '/count' },
}); });
setRemoting(PersistedModel.prototype, 'updateAttributes', { setRemoting(PersistedModel.prototype, 'updateAttributes', {
@ -687,7 +691,7 @@ module.exports = function(registry) {
accessType: 'WRITE', accessType: 'WRITE',
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'An object of model property name/value pairs' }, accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'An object of model property name/value pairs' },
returns: { arg: 'data', type: typeName, root: true }, returns: { arg: 'data', type: typeName, root: true },
http: {verb: 'put', path: '/'} http: { verb: 'put', path: '/' },
}); });
if (options.trackChanges || options.enableRemoteReplication) { if (options.trackChanges || options.enableRemoteReplication) {
@ -697,10 +701,10 @@ module.exports = function(registry) {
accepts: [ accepts: [
{ arg: 'since', type: 'number', description: 'Find deltas since this checkpoint' }, { arg: 'since', type: 'number', description: 'Find deltas since this checkpoint' },
{ arg: 'remoteChanges', type: 'array', description: 'an array of change objects', { arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
http: {source: 'body'}} http: { source: 'body' }},
], ],
returns: { arg: 'result', type: 'object', root: true }, returns: { arg: 'result', type: 'object', root: true },
http: {verb: 'post', path: '/diff'} http: { verb: 'post', path: '/diff' },
}); });
setRemoting(PersistedModel, 'changes', { setRemoting(PersistedModel, 'changes', {
@ -708,11 +712,13 @@ module.exports = function(registry) {
'Provide a filter object to reduce the number of results returned.', 'Provide a filter object to reduce the number of results returned.',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
{arg: 'since', type: 'number', description: 'Only return changes since this checkpoint'}, { arg: 'since', type: 'number', description:
{arg: 'filter', type: 'object', description: 'Only include changes that match this filter'} 'Only return changes since this checkpoint' },
{ arg: 'filter', type: 'object', description:
'Only include changes that match this filter' },
], ],
returns: { arg: 'changes', type: 'array', root: true }, returns: { arg: 'changes', type: 'array', root: true },
http: {verb: 'get', path: '/changes'} http: { verb: 'get', path: '/changes' },
}); });
setRemoting(PersistedModel, 'checkpoint', { setRemoting(PersistedModel, 'checkpoint', {
@ -723,14 +729,14 @@ module.exports = function(registry) {
// WRITE permissions. // WRITE permissions.
accessType: 'REPLICATE', accessType: 'REPLICATE',
returns: { arg: 'checkpoint', type: 'object', root: true }, returns: { arg: 'checkpoint', type: 'object', root: true },
http: {verb: 'post', path: '/checkpoint'} http: { verb: 'post', path: '/checkpoint' },
}); });
setRemoting(PersistedModel, 'currentCheckpoint', { setRemoting(PersistedModel, 'currentCheckpoint', {
description: 'Get the current checkpoint.', description: 'Get the current checkpoint.',
accessType: 'READ', accessType: 'READ',
returns: { arg: 'checkpoint', type: 'object', root: true }, returns: { arg: 'checkpoint', type: 'object', root: true },
http: {verb: 'get', path: '/checkpoint'} http: { verb: 'get', path: '/checkpoint' },
}); });
setRemoting(PersistedModel, 'createUpdates', { setRemoting(PersistedModel, 'createUpdates', {
@ -741,14 +747,14 @@ module.exports = function(registry) {
accessType: 'READ', accessType: 'READ',
accepts: { arg: 'deltas', type: 'array', http: { source: 'body' }}, accepts: { arg: 'deltas', type: 'array', http: { source: 'body' }},
returns: { arg: 'updates', type: 'array', root: true }, returns: { arg: 'updates', type: 'array', root: true },
http: {verb: 'post', path: '/create-updates'} http: { verb: 'post', path: '/create-updates' },
}); });
setRemoting(PersistedModel, 'bulkUpdate', { setRemoting(PersistedModel, 'bulkUpdate', {
description: 'Run multiple updates at once. Note: this is not atomic.', description: 'Run multiple updates at once. Note: this is not atomic.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: { arg: 'updates', type: 'array' }, accepts: { arg: 'updates', type: 'array' },
http: {verb: 'post', path: '/bulk-update'} http: { verb: 'post', path: '/bulk-update' },
}); });
setRemoting(PersistedModel, 'findLastChange', { setRemoting(PersistedModel, 'findLastChange', {
@ -756,30 +762,30 @@ module.exports = function(registry) {
accessType: 'READ', accessType: 'READ',
accepts: { accepts: {
arg: 'id', type: 'any', required: true, http: { source: 'path' }, arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: 'Model id' description: 'Model id',
}, },
returns: { arg: 'result', type: this.Change.modelName, root: true }, returns: { arg: 'result', type: this.Change.modelName, root: true },
http: { verb: 'get', path: '/:id/changes/last' } http: { verb: 'get', path: '/:id/changes/last' },
}); });
setRemoting(PersistedModel, 'updateLastChange', { setRemoting(PersistedModel, 'updateLastChange', {
description: [ description: [
'Update the properties of the most recent change record', 'Update the properties of the most recent change record',
'kept for this instance.' 'kept for this instance.',
], ],
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{ {
arg: 'id', type: 'any', required: true, http: { source: 'path' }, arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: 'Model id' description: 'Model id',
}, },
{ {
arg: 'data', type: 'object', http: { source: 'body' }, arg: 'data', type: 'object', http: { source: 'body' },
description: 'An object of Change property name/value pairs' description: 'An object of Change property name/value pairs',
}, },
], ],
returns: { arg: 'result', type: this.Change.modelName, root: true }, returns: { arg: 'result', type: this.Change.modelName, root: true },
http: { verb: 'put', path: '/:id/changes/last' } http: { verb: 'put', path: '/:id/changes/last' },
}); });
} }
@ -789,14 +795,14 @@ module.exports = function(registry) {
setRemoting(PersistedModel, 'rectifyAllChanges', { setRemoting(PersistedModel, 'rectifyAllChanges', {
description: 'Rectify all Model changes.', description: 'Rectify all Model changes.',
accessType: 'WRITE', accessType: 'WRITE',
http: {verb: 'post', path: '/rectify-all'} http: { verb: 'post', path: '/rectify-all' },
}); });
setRemoting(PersistedModel, 'rectifyChange', { setRemoting(PersistedModel, 'rectifyChange', {
description: 'Tell loopback that a change to the model with the given id has occurred.', description: 'Tell loopback that a change to the model with the given id has occurred.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: { arg: 'id', type: 'any', http: { source: 'path' }}, accepts: { arg: 'id', type: 'any', http: { source: 'path' }},
http: {verb: 'post', path: '/:id/rectify-change'} http: { verb: 'post', path: '/:id/rectify-change' },
}); });
} }
@ -805,17 +811,17 @@ module.exports = function(registry) {
accessType: 'READ', accessType: 'READ',
http: [ http: [
{ verb: 'post', path: '/change-stream' }, { verb: 'post', path: '/change-stream' },
{verb: 'get', path: '/change-stream'} { verb: 'get', path: '/change-stream' },
], ],
accepts: { accepts: {
arg: 'options', arg: 'options',
type: 'object' type: 'object',
}, },
returns: { returns: {
arg: 'changes', arg: 'changes',
type: 'ReadableStream', type: 'ReadableStream',
json: true json: true,
} },
}); });
}; };
@ -870,7 +876,7 @@ module.exports = function(registry) {
// TODO(ritch) this whole thing could be optimized a bit more // TODO(ritch) this whole thing could be optimized a bit more
Change.find({ where: { Change.find({ where: {
checkpoint: { gte: since }, checkpoint: { gte: since },
modelName: this.modelName modelName: this.modelName,
}}, function(err, changes) { }}, function(err, changes) {
if (err) return callback(err); if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
@ -1004,16 +1010,14 @@ module.exports = function(registry) {
'You must enable change tracking before replicating' 'You must enable change tracking before replicating'
); );
var diff; var diff, updates, newSourceCp, newTargetCp;
var updates;
var newSourceCp, newTargetCp;
var tasks = [ var tasks = [
checkpoints, checkpoints,
getSourceChanges, getSourceChanges,
getDiffFromTarget, getDiffFromTarget,
createSourceUpdates, createSourceUpdates,
bulkUpdate bulkUpdate,
]; ];
async.waterfall(tasks, done); async.waterfall(tasks, done);
@ -1521,7 +1525,7 @@ module.exports = function(registry) {
this.Change = BaseChangeModel.extend(this.modelName + '-change', this.Change = BaseChangeModel.extend(this.modelName + '-change',
{}, {},
{ {
trackModel: this trackModel: this,
} }
); );
@ -1661,7 +1665,7 @@ module.exports = function(registry) {
var change = { var change = {
target: target, target: target,
where: where, where: where,
data: data data: data,
}; };
switch (type) { switch (type) {

View File

@ -89,7 +89,6 @@ function Registry() {
*/ */
Registry.prototype.createModel = function(name, properties, options) { Registry.prototype.createModel = function(name, properties, options) {
if (arguments.length === 1 && typeof name === 'object') { if (arguments.length === 1 && typeof name === 'object') {
var config = name; var config = name;
name = config.name; name = config.name;
@ -208,7 +207,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
'super': true, 'super': true,
relations: true, relations: true,
acls: true, acls: true,
dataSource: true dataSource: true,
}; };
if (typeof config.options === 'object' && config.options !== null) { if (typeof config.options === 'object' && config.options !== null) {
for (var p in config.options) { for (var p in config.options) {
@ -390,7 +389,7 @@ Registry.prototype.memory = function(name) {
if (!memory) { if (!memory) {
memory = this._memoryDataSources[name] = this.createDataSource({ memory = this._memoryDataSources[name] = this.createDataSource({
connector: 'memory' connector: 'memory',
}); });
} }

View File

@ -265,7 +265,7 @@ proto.lazyrouter = function() {
self._requestHandlingPhases = [ self._requestHandlingPhases = [
'initial', 'session', 'auth', 'parse', 'initial', 'session', 'auth', 'parse',
'routes', 'files', 'final' 'routes', 'files', 'final',
]; ];
}; };

View File

@ -63,13 +63,13 @@
"browserify": "^10.0.0", "browserify": "^10.0.0",
"chai": "^2.1.1", "chai": "^2.1.1",
"es5-shim": "^4.1.0", "es5-shim": "^4.1.0",
"eslint-config-loopback": "^1.0.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-browserify": "^3.5.0", "grunt-browserify": "^3.5.0",
"grunt-cli": "^0.1.13", "grunt-cli": "^0.1.13",
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.9.1", "grunt-contrib-uglify": "^0.9.1",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"grunt-jscs": "^1.5.0", "grunt-eslint": "^18.0.0",
"grunt-karma": "^0.10.1", "grunt-karma": "^0.10.1",
"grunt-mocha-test": "^0.12.7", "grunt-mocha-test": "^0.12.7",
"karma": "^0.12.31", "karma": "^0.12.31",

View File

@ -4,7 +4,6 @@ var cls = require('continuation-local-storage');
var domain = require('domain'); var domain = require('domain');
module.exports = function(loopback) { module.exports = function(loopback) {
/** /**
* Get the current context object. The context is preserved * Get the current context object. The context is preserved
* across async calls, it behaves like a thread-local storage. * across async calls, it behaves like a thread-local storage.

View File

@ -23,7 +23,7 @@ function status() {
return function(req, res) { return function(req, res) {
res.send({ res.send({
started: started, started: started,
uptime: (Date.now() - Number(started)) / 1000 uptime: (Date.now() - Number(started)) / 1000,
}); });
}; };
} }

View File

@ -1,5 +1,3 @@
/*jshint -W030 */
var loopback = require('../'); var loopback = require('../');
var lt = require('./helpers/loopback-testing-helper'); var lt = require('./helpers/loopback-testing-helper');
var path = require('path'); var path = require('path');
@ -11,7 +9,6 @@ var CURRENT_USER = {email: 'current@test.test', password: 'test'};
var debug = require('debug')('loopback:test:access-control.integration'); var debug = require('debug')('loopback:test:access-control.integration');
describe('access control - integration', function() { describe('access control - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
/* /*
@ -61,7 +58,6 @@ describe('access control - integration', function() {
*/ */
describe('/users', function() { describe('/users', function() {
lt.beforeEach.givenModel('user', USER, 'randomUser'); lt.beforeEach.givenModel('user', USER, 'randomUser');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users'); lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');
@ -128,7 +124,7 @@ describe('access control - integration', function() {
userCounter = userCounter ? ++userCounter : 1; userCounter = userCounter ? ++userCounter : 1;
return { return {
email: 'new-' + userCounter + '@test.test', email: 'new-' + userCounter + '@test.test',
password: 'test' password: 'test',
}; };
} }
}); });
@ -200,13 +196,13 @@ describe('access control - integration', function() {
// Create an account under the given user // Create an account under the given user
app.models.account.create({ app.models.account.create({
userId: self.user.id, userId: self.user.id,
balance: 100 balance: 100,
}, function(err, act) { }, function(err, act) {
self.url = '/api/accounts/' + act.id; self.url = '/api/accounts/' + act.id;
done(); done();
}); });
}); });
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() { lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
lt.it.shouldBeAllowed(); lt.it.shouldBeAllowed();
}); });
@ -226,5 +222,4 @@ describe('access control - integration', function() {
return '/api/accounts/' + this.account.id; return '/api/accounts/' + this.account.id;
} }
}); });
}); });

View File

@ -227,7 +227,7 @@ describe('AccessToken', function() {
it('supports two-arg variant with no options', function(done) { it('supports two-arg variant with no options', function(done) {
var expectedTokenId = this.token.id; var expectedTokenId = this.token.id;
var req = mockRequest({ var req = mockRequest({
headers: { 'authorization': expectedTokenId } headers: { 'authorization': expectedTokenId },
}); });
Token.findForRequest(req, function(err, token) { Token.findForRequest(req, function(err, token) {
@ -247,7 +247,7 @@ describe('AccessToken', function() {
// express helpers // express helpers
param: function(name) { return this._params[name]; }, param: function(name) { return this._params[name]; },
header: function(name) { return this.headers[name]; } header: function(name) { return this.headers[name]; },
}, },
opts); opts);
} }
@ -328,7 +328,7 @@ describe('app.enableAuth()', function() {
}; };
TestModel.remoteMethod('getToken', { TestModel.remoteMethod('getToken', {
returns: { arg: 'token', type: 'object' }, returns: { arg: 'token', type: 'object' },
http: { verb: 'GET', path: '/token' } http: { verb: 'GET', path: '/token' },
}); });
var app = loopback(); var app = loopback();
@ -376,7 +376,7 @@ function createTestApp(testToken, settings, done) {
var modelSettings = settings.model || {}; var modelSettings = settings.model || {};
var tokenSettings = extend({ var tokenSettings = extend({
model: Token, model: Token,
currentUserLiteral: 'me' currentUserLiteral: 'me',
}, settings.token); }, settings.token);
var app = loopback(); var app = loopback();
@ -423,9 +423,9 @@ function createTestApp(testToken, settings, done) {
principalId: '$everyone', principalId: '$everyone',
accessType: ACL.ALL, accessType: ACL.ALL,
permission: ACL.DENY, permission: ACL.DENY,
property: 'deleteById' property: 'deleteById',
} },
] ],
}; };
Object.keys(modelSettings).forEach(function(key) { Object.keys(modelSettings).forEach(function(key) {

View File

@ -30,45 +30,53 @@ describe('security scopes', function() {
}); });
it('should allow access to models for the given scope by wildcard', function() { it('should allow access to models for the given scope by wildcard', function() {
Scope.create({name: 'userScope', description: 'access user information'}, function(err, scope) { Scope.create({ name: 'userScope', description: 'access user information' },
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL, function(err, scope) {
accessType: ACL.ALL, permission: ACL.ALLOW}, ACL.create({
function(err, resource) { principalType: ACL.SCOPE, principalId: scope.id,
model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, resource) {
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult); Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult); Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult); Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
}); });
}); });
}); });
it('should allow access to models for the given scope', function() { it('should allow access to models for the given scope', function() {
Scope.create({name: 'testModelScope', description: 'access testModel information'}, function(err, scope) { Scope.create({ name: 'testModelScope', description: 'access testModel information' },
function(err, scope) {
ACL.create({
principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name',
accessType: ACL.READ, permission: ACL.ALLOW,
}, function(err, resource) {
ACL.create({ principalType: ACL.SCOPE, principalId: scope.id, ACL.create({ principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW}, model: 'testModel', property: 'name',
function(err, resource) { accessType: ACL.WRITE, permission: ACL.DENY,
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, }, function(err, resource) {
model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
function(err, resource) {
// console.log(resource); // console.log(resource);
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function(err, perm) { Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL,
function(err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
}); });
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function(err, perm) { Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL,
function(err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
}); });
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function(err, perm) { Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function(err, perm) { Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
}); });
}); });
}); });
}); });
}); });
describe('security ACLs', function() { describe('security ACLs', function() {
@ -79,26 +87,26 @@ describe('security ACLs', function() {
'accessType': '*', 'accessType': '*',
'permission': 'DENY', 'permission': 'DENY',
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$everyone' 'principalId': '$everyone',
}, },
{ {
'model': 'account', 'model': 'account',
'accessType': '*', 'accessType': '*',
'permission': 'ALLOW', 'permission': 'ALLOW',
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$owner' 'principalId': '$owner',
}, },
{ {
'model': 'account', 'model': 'account',
'accessType': 'READ', 'accessType': 'READ',
'permission': 'ALLOW', 'permission': 'ALLOW',
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$everyone' 'principalId': '$everyone',
}]; }];
var req = { var req = {
model: 'account', model: 'account',
property: 'find', property: 'find',
accessType: 'WRITE' accessType: 'WRITE',
}; };
acls = acls.map(function(a) { return new ACL(a); }); acls = acls.map(function(a) { return new ACL(a); });
@ -113,12 +121,14 @@ describe('security ACLs', function() {
it('should allow access to models for the given principal by wildcard', function() { it('should allow access to models for the given principal by wildcard', function() {
// jscs:disable validateIndentation // jscs:disable validateIndentation
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, ACL.create({
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) { principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, }, function(err, acl) {
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) { ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
@ -126,52 +136,55 @@ describe('security ACLs', function() {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
}); });
}); });
}); });
it('should allow access to models by exception', function() { it('should allow access to models by exception', function() {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL, ACL.create({
accessType: ACL.ALL, permission: ACL.DENY}, function(err, acl) { principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY,
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL, }, function(err, acl) {
accessType: ACL.READ, permission: ACL.ALLOW}, function(err, acl) { ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
ACL.create({principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL, accessType: ACL.READ, permission: ACL.ALLOW,
accessType: ACL.EXECUTE, permission: ACL.ALLOW}, function(err, acl) { }, function(err, acl) {
ACL.create({
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function(err, perm) { principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
accessType: ACL.EXECUTE, permission: ACL.ALLOW,
}, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, function(err, perm) { ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
}); });
}); });
}); });
}); });
it('should honor defaultPermission from the model', function() { it('should honor defaultPermission from the model', function() {
@ -179,19 +192,23 @@ describe('security ACLs', function() {
name: { name: {
type: String, type: String,
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY}, { principalType: ACL.USER, principalId: 'u001',
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW} accessType: ACL.WRITE, permission: ACL.DENY },
] { principalType: ACL.USER, principalId: 'u001',
} accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, { }, {
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW} { principalType: ACL.USER, principalId: 'u001',
] accessType: ACL.ALL, permission: ACL.ALLOW },
],
}); });
Customer.settings.defaultPermission = ACL.DENY; Customer.settings.defaultPermission = ACL.DENY;
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
@ -202,7 +219,6 @@ describe('security ACLs', function() {
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
}); });
it('should honor static ACLs from the model', function() { it('should honor static ACLs from the model', function() {
@ -210,16 +226,21 @@ describe('security ACLs', function() {
name: { name: {
type: String, type: String,
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY}, { principalType: ACL.USER, principalId: 'u001',
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW} accessType: ACL.WRITE, permission: ACL.DENY },
] { principalType: ACL.USER, principalId: 'u001',
} accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, { }, {
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}, { principalType: ACL.USER, principalId: 'u001',
{principalType: ACL.USER, principalId: 'u002', accessType: ACL.EXECUTE, permission: ACL.ALLOW}, accessType: ACL.ALL, permission: ACL.ALLOW },
{principalType: ACL.USER, principalId: 'u003', accessType: ACL.EXECUTE, permission: ACL.DENY} { principalType: ACL.USER, principalId: 'u002',
] accessType: ACL.EXECUTE, permission: ACL.ALLOW },
{ principalType: ACL.USER, principalId: 'u003',
accessType: ACL.EXECUTE, permission: ACL.DENY },
],
}); });
/* /*
@ -228,26 +249,30 @@ describe('security ACLs', function() {
]; ];
*/ */
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, function(err, perm) { ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, function(err, perm) { ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW); assert(perm.permission === ACL.ALLOW);
}); });
ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, function(err, perm) { ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY); assert(perm.permission === ACL.DENY);
}); });
}); });
it('should filter static ACLs by model/property', function() { it('should filter static ACLs by model/property', function() {
@ -258,9 +283,9 @@ describe('security ACLs', function() {
{ principalType: ACL.USER, principalId: 'u001', { principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY }, accessType: ACL.WRITE, permission: ACL.DENY },
{ principalType: ACL.USER, principalId: 'u001', { principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW} accessType: ACL.ALL, permission: ACL.ALLOW },
] ],
} },
}, { }, {
acls: [ acls: [
{ principalType: ACL.USER, principalId: 'u001', property: 'name', { principalType: ACL.USER, principalId: 'u001', property: 'name',
@ -268,8 +293,8 @@ describe('security ACLs', function() {
{ principalType: ACL.USER, principalId: 'u002', property: 'findOne', { principalType: ACL.USER, principalId: 'u002', property: 'findOne',
accessType: ACL.ALL, permission: ACL.ALLOW }, accessType: ACL.ALL, permission: ACL.ALLOW },
{ principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'], { principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],
accessType: ACL.ALL, permission: ACL.ALLOW} accessType: ACL.ALL, permission: ACL.ALLOW },
] ],
}); });
var staticACLs = ACL.getStaticACLs('Model1', 'name'); var staticACLs = ACL.getStaticACLs('Model1', 'name');
@ -289,7 +314,6 @@ describe('security ACLs', function() {
// Create // Create
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
log('User: ', user.toObject()); log('User: ', user.toObject());
var userId = user.id; var userId = user.id;
@ -299,56 +323,62 @@ describe('security ACLs', function() {
name: { name: {
type: String, type: String,
acls: [ acls: [
{principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY}, { principalType: ACL.USER, principalId: userId,
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW} accessType: ACL.WRITE, permission: ACL.DENY },
] { principalType: ACL.USER, principalId: userId,
} accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, { }, {
acls: [ acls: [
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW} { principalType: ACL.USER, principalId: userId,
accessType: ACL.ALL, permission: ACL.ALLOW },
], ],
defaultPermission: 'DENY' defaultPermission: 'DENY',
}); });
ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL, ACL.create({
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) { principalType: ACL.USER, principalId: userId,
model: 'Customer', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, acl) {
log('ACL 1: ', acl.toObject()); log('ACL 1: ', acl.toObject());
Role.create({ name: 'MyRole' }, function(err, myRole) { Role.create({ name: 'MyRole' }, function(err, myRole) {
log('Role: ', myRole.toObject()); log('Role: ', myRole.toObject());
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, function(err, p) { myRole.principals.create({ principalType: RoleMapping.USER, principalId: userId },
function(err, p) {
log('Principal added to role: ', p.toObject()); log('Principal added to role: ', p.toObject());
ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL, ACL.create({
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) { principalType: ACL.ROLE, principalId: 'MyRole',
model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) {
log('ACL 2: ', acl.toObject()); log('ACL 2: ', acl.toObject());
ACL.checkAccessForContext({ ACL.checkAccessForContext({
principals: [ principals: [
{type: ACL.USER, id: userId} { type: ACL.USER, id: userId },
], ],
model: 'Customer', model: 'Customer',
property: 'name', property: 'name',
accessType: ACL.READ accessType: ACL.READ,
}, function(err, access) { }, function(err, access) {
assert(!err && access.permission === ACL.ALLOW); assert(!err && access.permission === ACL.ALLOW);
}); });
ACL.checkAccessForContext({ ACL.checkAccessForContext({
principals: [ principals: [
{type: ACL.ROLE, id: Role.EVERYONE} { type: ACL.ROLE, id: Role.EVERYONE },
], ],
model: 'Customer', model: 'Customer',
property: 'name', property: 'name',
accessType: ACL.READ accessType: ACL.READ,
}, function(err, access) { }, function(err, access) {
assert(!err && access.permission === ACL.DENY); assert(!err && access.permission === ACL.DENY);
}); });
}); });
}); });
}); });

View File

@ -1,5 +1,3 @@
/*jshint -W030 */
var async = require('async'); var async = require('async');
var path = require('path'); var path = require('path');
@ -13,8 +11,7 @@ var it = require('./util/it');
describe('app', function() { describe('app', function() {
describe.onServer('.middleware(phase, handler)', function() { describe.onServer('.middleware(phase, handler)', function() {
var app; var app, steps;
var steps;
beforeEach(function setup() { beforeEach(function setup() {
app = loopback(); app = loopback();
@ -24,7 +21,7 @@ describe('app', function() {
it('runs middleware in phases', function(done) { it('runs middleware in phases', function(done) {
var PHASES = [ var PHASES = [
'initial', 'session', 'auth', 'parse', 'initial', 'session', 'auth', 'parse',
'routes', 'files', 'final' 'routes', 'files', 'final',
]; ];
PHASES.forEach(function(name) { PHASES.forEach(function(name) {
@ -36,7 +33,7 @@ describe('app', function() {
if (err) return done(err); if (err) return done(err);
expect(steps).to.eql([ expect(steps).to.eql([
'initial', 'session', 'auth', 'parse', 'initial', 'session', 'auth', 'parse',
'main', 'routes', 'files', 'final' 'main', 'routes', 'files', 'final',
]); ]);
done(); done();
}); });
@ -233,8 +230,7 @@ describe('app', function() {
}); });
it('exposes express helpers on req and res objects', function(done) { it('exposes express helpers on req and res objects', function(done) {
var req; var req, res;
var res;
app.middleware('initial', function(rq, rs, next) { app.middleware('initial', function(rq, rs, next) {
req = rq; req = rq;
@ -250,7 +246,7 @@ describe('app', function() {
'param', 'param',
'params', 'params',
'query', 'query',
'res' 'res',
]); ]);
expect(getObjectAndPrototypeKeys(res), 'response').to.include.members([ expect(getObjectAndPrototypeKeys(res), 'response').to.include.members([
@ -262,7 +258,7 @@ describe('app', function() {
'req', 'req',
'send', 'send',
'sendFile', 'sendFile',
'set' 'set',
]); ]);
done(); done();
@ -316,13 +312,12 @@ describe('app', function() {
}); });
it('correctly mounts express apps', function(done) { it('correctly mounts express apps', function(done) {
var data; var data, mountWasEmitted;
var mountWasEmitted;
var subapp = express(); var subapp = express();
subapp.use(function(req, res, next) { subapp.use(function(req, res, next) {
data = { data = {
mountpath: req.app.mountpath, mountpath: req.app.mountpath,
parent: req.app.parent parent: req.app.parent,
}; };
next(); next();
}); });
@ -335,14 +330,13 @@ describe('app', function() {
expect(mountWasEmitted, 'mountWasEmitted').to.be.true; expect(mountWasEmitted, 'mountWasEmitted').to.be.true;
expect(data).to.eql({ expect(data).to.eql({
mountpath: '/mountpath', mountpath: '/mountpath',
parent: app parent: app,
}); });
done(); done();
}); });
}); });
it('restores req & res on return from mounted express app', function(done) { it('restores req & res on return from mounted express app', function(done) {
// jshint proto:true
var expected = {}; var expected = {};
var actual = {}; var actual = {};
@ -414,28 +408,28 @@ describe('app', function() {
app.middlewareFromConfig(handlerFactory, { app.middlewareFromConfig(handlerFactory, {
enabled: true, enabled: true,
phase: 'session', phase: 'session',
params: expectedConfig params: expectedConfig,
}); });
// Config as a value (single arg) // Config as a value (single arg)
app.middlewareFromConfig(handlerFactory, { app.middlewareFromConfig(handlerFactory, {
enabled: true, enabled: true,
phase: 'session:before', phase: 'session:before',
params: 'before' params: 'before',
}); });
// Config as a list of args // Config as a list of args
app.middlewareFromConfig(handlerFactory, { app.middlewareFromConfig(handlerFactory, {
enabled: true, enabled: true,
phase: 'session:after', phase: 'session:after',
params: ['after', 2] params: ['after', 2],
}); });
// Disabled by configuration // Disabled by configuration
app.middlewareFromConfig(handlerFactory, { app.middlewareFromConfig(handlerFactory, {
enabled: false, enabled: false,
phase: 'initial', phase: 'initial',
params: null params: null,
}); });
// This should be triggered with matching verbs // This should be triggered with matching verbs
@ -443,7 +437,7 @@ describe('app', function() {
enabled: true, enabled: true,
phase: 'routes:before', phase: 'routes:before',
methods: ['get', 'head'], methods: ['get', 'head'],
params: {x: 1} params: { x: 1 },
}); });
// This should be skipped as the verb doesn't match // This should be skipped as the verb doesn't match
@ -451,7 +445,7 @@ describe('app', function() {
enabled: true, enabled: true,
phase: 'routes:before', phase: 'routes:before',
methods: ['post'], methods: ['post'],
params: {x: 2} params: { x: 2 },
}); });
executeMiddlewareHandlers(app, function(err) { executeMiddlewareHandlers(app, function(err) {
@ -460,7 +454,7 @@ describe('app', function() {
['before'], ['before'],
[expectedConfig], [expectedConfig],
['after', 2], ['after', 2],
[{x: 1}] [{ x: 1 }],
]); ]);
done(); done();
}); });
@ -477,7 +471,7 @@ describe('app', function() {
}, },
{ {
phase: 'initial', phase: 'initial',
paths: ['/scope', /^\/(a|b)/] paths: ['/scope', /^\/(a|b)/],
}); });
async.eachSeries( async.eachSeries(
@ -508,7 +502,7 @@ describe('app', function() {
'first', 'first',
'initial', // this was the original first phase 'initial', // this was the original first phase
'routes', 'routes',
'subapps' 'subapps',
], done); ], done);
}); });
@ -519,7 +513,7 @@ describe('app', function() {
'auth', 'routes', 'auth', 'routes',
'subapps', // add 'subapps', // add
'final', 'final',
'last' // add 'last', // add
]); ]);
verifyMiddlewarePhases([ verifyMiddlewarePhases([
'initial', 'initial',
@ -527,7 +521,7 @@ describe('app', function() {
'auth', 'routes', 'auth', 'routes',
'subapps', // new 'subapps', // new
'files', 'final', 'files', 'final',
'last' // new 'last', // new
], done); ], done);
}); });
@ -555,8 +549,7 @@ describe('app', function() {
}); });
describe('app.model(Model)', function() { describe('app.model(Model)', function() {
var app; var app, db;
var db;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
db = loopback.createDataSource({ connector: loopback.Memory }); db = loopback.createDataSource({ connector: loopback.Memory });
@ -628,7 +621,6 @@ describe('app', function() {
it('should not require dataSource', function() { it('should not require dataSource', function() {
app.model('MyTestModel', {}); app.model('MyTestModel', {});
}); });
}); });
describe('app.model(name, config)', function() { describe('app.model(name, config)', function() {
@ -637,13 +629,13 @@ describe('app', function() {
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
app.dataSource('db', { app.dataSource('db', {
connector: 'memory' connector: 'memory',
}); });
}); });
it('Sugar for defining a fully built model', function() { it('Sugar for defining a fully built model', function() {
app.model('foo', { app.model('foo', {
dataSource: 'db' dataSource: 'db',
}); });
var Foo = app.models.foo; var Foo = app.models.foo;
@ -655,7 +647,7 @@ describe('app', function() {
it('interprets extra first-level keys as options', function() { it('interprets extra first-level keys as options', function() {
app.model('foo', { app.model('foo', {
dataSource: 'db', dataSource: 'db',
base: 'User' base: 'User',
}); });
expect(app.models.foo.definition.settings.base).to.equal('User'); expect(app.models.foo.definition.settings.base).to.equal('User');
@ -666,8 +658,8 @@ describe('app', function() {
dataSource: 'db', dataSource: 'db',
base: 'User', base: 'User',
options: { options: {
base: 'Application' base: 'Application',
} },
}); });
expect(app.models.foo.definition.settings.base).to.equal('Application'); expect(app.models.foo.definition.settings.base).to.equal('Application');
@ -676,7 +668,7 @@ describe('app', function() {
it('honors config.public options', function() { it('honors config.public options', function() {
app.model('foo', { app.model('foo', {
dataSource: 'db', dataSource: 'db',
public: false public: false,
}); });
expect(app.models.foo.app).to.equal(app); expect(app.models.foo.app).to.equal(app);
expect(app.models.foo.shared).to.equal(false); expect(app.models.foo.shared).to.equal(false);
@ -684,12 +676,11 @@ describe('app', function() {
it('defaults config.public to be true', function() { it('defaults config.public to be true', function() {
app.model('foo', { app.model('foo', {
dataSource: 'db' dataSource: 'db',
}); });
expect(app.models.foo.app).to.equal(app); expect(app.models.foo.app).to.equal(app);
expect(app.models.foo.shared).to.equal(true); expect(app.models.foo.shared).to.equal(true);
}); });
}); });
describe('app.model(ModelCtor, config)', function() { describe('app.model(ModelCtor, config)', function() {
@ -943,8 +934,7 @@ describe('app', function() {
}); });
describe('normalizeHttpPath option', function() { describe('normalizeHttpPath option', function() {
var app; var app, db;
var db;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
db = loopback.createDataSource({ connector: loopback.Memory }); db = loopback.createDataSource({ connector: loopback.Memory });
@ -955,7 +945,7 @@ describe('app', function() {
'UserAccount', 'UserAccount',
{ name: String }, { name: String },
{ {
remoting: { normalizeHttpPath: true } remoting: { normalizeHttpPath: true },
}); });
app.model(UserAccount); app.model(UserAccount);
UserAccount.attachTo(db); UserAccount.attachTo(db);

View File

@ -6,7 +6,7 @@ describe('PersistedModel.createChangeStream()', function() {
var ds = app.dataSource('ds', { connector: 'memory' }); var ds = app.dataSource('ds', { connector: 'memory' });
this.Score = app.model('Score', { this.Score = app.model('Score', {
dataSource: 'ds', dataSource: 'ds',
changeDataSource: false // use only local observers changeDataSource: false, // use only local observers
}); });
}); });
@ -34,7 +34,7 @@ describe('PersistedModel.createChangeStream()', function() {
done(); done();
}); });
newScore.updateAttributes({ newScore.updateAttributes({
bat: 'baz' bat: 'baz',
}); });
}); });
}); });
@ -66,11 +66,11 @@ describe('PersistedModel.createChangeStream()', function() {
host: 'localhost', host: 'localhost',
port: '12345', port: '12345',
connector: 'pubsub', connector: 'pubsub',
pubsubAdapter: 'mqtt' pubsubAdapter: 'mqtt',
}); });
this.Score = app.model('Score', { this.Score = app.model('Score', {
dataSource: 'db', dataSource: 'db',
changeDataSource: 'ps' changeDataSource: 'ps',
}); });
}); });

View File

@ -1,20 +1,19 @@
var async = require('async'); var async = require('async');
var expect = require('chai').expect; var expect = require('chai').expect;
var Change; var Change, TestModel;
var TestModel;
describe('Change', function() { describe('Change', function() {
beforeEach(function() { beforeEach(function() {
var memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
TestModel = loopback.PersistedModel.extend('ChangeTestModel', TestModel = loopback.PersistedModel.extend('ChangeTestModel',
{ {
id: { id: true, type: 'string', defaultFn: 'guid' } id: { id: true, type: 'string', defaultFn: 'guid' },
}, },
{ {
trackChanges: true trackChanges: true,
}); });
this.modelName = TestModel.modelName; this.modelName = TestModel.modelName;
TestModel.attachTo(memory); TestModel.attachTo(memory);
@ -24,7 +23,7 @@ describe('Change', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; var test = this;
test.data = { test.data = {
foo: 'bar' foo: 'bar',
}; };
TestModel.create(test.data, function(err, model) { TestModel.create(test.data, function(err, model) {
if (err) return done(err); if (err) return done(err);
@ -46,7 +45,7 @@ describe('Change', function() {
var change = new Change({ var change = new Change({
rev: 'abc', rev: 'abc',
modelName: 'foo', modelName: 'foo',
modelId: 'bar' modelId: 'bar',
}); });
var hash = Change.hash([change.modelName, change.modelId].join('-')); var hash = Change.hash([change.modelName, change.modelId].join('-'));
@ -115,7 +114,6 @@ describe('Change', function() {
}); });
describe('Change.findOrCreateChange(modelName, modelId, callback)', function() { describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {
describe('when a change doesnt exist', function() { describe('when a change doesnt exist', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; var test = this;
@ -162,7 +160,7 @@ describe('Change', function() {
var test = this; var test = this;
Change.create({ Change.create({
modelName: test.modelName, modelName: test.modelName,
modelId: test.modelId modelId: test.modelId,
}, function(err, change) { }, function(err, change) {
test.existingChange = change; test.existingChange = change;
done(); done();
@ -192,7 +190,7 @@ describe('Change', function() {
Change.findOrCreate( Change.findOrCreate(
{ {
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId modelId: this.modelId,
}, },
function(err, ch) { function(err, ch) {
change = ch; change = ch;
@ -228,7 +226,7 @@ describe('Change', function() {
expect(change.prev, 'prev').to.equal(originalRev); expect(change.prev, 'prev').to.equal(originalRev);
expect(change.rev, 'rev').to.equal(test.revisionForModel); expect(change.rev, 'rev').to.equal(test.revisionForModel);
next(); next();
} },
], done); ], done);
function rectify(next) { function rectify(next) {
@ -299,7 +297,7 @@ describe('Change', function() {
var test = this; var test = this;
var change = new Change({ var change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId modelId: this.modelId,
}); });
change.currentRevision(function(err, rev) { change.currentRevision(function(err, rev) {
@ -314,7 +312,7 @@ describe('Change', function() {
var test = this; var test = this;
var change = new Change({ var change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId modelId: this.modelId,
}); });
change.currentRevision() change.currentRevision()
@ -341,14 +339,14 @@ describe('Change', function() {
var a = { var a = {
b: { b: {
b: ['c', 'd'], b: ['c', 'd'],
c: ['d', 'e'] c: ['d', 'e'],
} },
}; };
var b = { var b = {
b: { b: {
c: ['d', 'e'], c: ['d', 'e'],
b: ['c', 'd'] b: ['c', 'd'],
} },
}; };
var aRev = Change.revisionForInst(a); var aRev = Change.revisionForInst(a);
@ -360,20 +358,20 @@ describe('Change', function() {
describe('change.type()', function() { describe('change.type()', function() {
it('CREATE', function() { it('CREATE', function() {
var change = new Change({ var change = new Change({
rev: this.revisionForModel rev: this.revisionForModel,
}); });
assert.equal(Change.CREATE, change.type()); assert.equal(Change.CREATE, change.type());
}); });
it('UPDATE', function() { it('UPDATE', function() {
var change = new Change({ var change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
prev: this.revisionForModel prev: this.revisionForModel,
}); });
assert.equal(Change.UPDATE, change.type()); assert.equal(Change.UPDATE, change.type());
}); });
it('DELETE', function() { it('DELETE', function() {
var change = new Change({ var change = new Change({
prev: this.revisionForModel prev: this.revisionForModel,
}); });
assert.equal(Change.DELETE, change.type()); assert.equal(Change.DELETE, change.type());
}); });
@ -386,7 +384,7 @@ describe('Change', function() {
describe('change.getModelCtor()', function() { describe('change.getModelCtor()', function() {
it('should get the correct model class', function() { it('should get the correct model class', function() {
var change = new Change({ var change = new Change({
modelName: this.modelName modelName: this.modelName,
}); });
assert.equal(change.getModelCtor(), TestModel); assert.equal(change.getModelCtor(), TestModel);
@ -396,11 +394,11 @@ describe('Change', function() {
describe('change.equals(otherChange)', function() { describe('change.equals(otherChange)', function() {
it('should return true when the change is equal', function() { it('should return true when the change is equal', function() {
var change = new Change({ var change = new Change({
rev: this.revisionForModel rev: this.revisionForModel,
}); });
var otherChange = new Change({ var otherChange = new Change({
rev: this.revisionForModel rev: this.revisionForModel,
}); });
assert.equal(change.equals(otherChange), true); assert.equal(change.equals(otherChange), true);
@ -415,7 +413,7 @@ describe('Change', function() {
var otherChange = new Change({ var otherChange = new Change({
rev: undefined, rev: undefined,
prev: REV prev: REV,
}); });
assert.equal(change.type(), Change.DELETE); assert.equal(change.type(), Change.DELETE);
@ -428,11 +426,11 @@ describe('Change', function() {
describe('change.isBasedOn(otherChange)', function() { describe('change.isBasedOn(otherChange)', function() {
it('should return true when the change is based on the other', function() { it('should return true when the change is based on the other', function() {
var change = new Change({ var change = new Change({
prev: this.revisionForModel prev: this.revisionForModel,
}); });
var otherChange = new Change({ var otherChange = new Change({
rev: this.revisionForModel rev: this.revisionForModel,
}); });
assert.equal(change.isBasedOn(otherChange), true); assert.equal(change.isBasedOn(otherChange), true);
@ -491,7 +489,7 @@ describe('Change', function() {
prev: 'foo', prev: 'foo',
modelName: this.modelName, modelName: this.modelName,
modelId: '9', modelId: '9',
checkpoint: 2 checkpoint: 2,
}; };
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) { Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {
if (err) return done(err); if (err) return done(err);
@ -516,7 +514,7 @@ describe('Change', function() {
prev: 'foo-prev', prev: 'foo-prev',
modelName: this.modelName, modelName: this.modelName,
modelId: '9', modelId: '9',
checkpoint: 2 checkpoint: 2,
}; };
// IMPORTANT: the diff call excludes the local change // IMPORTANT: the diff call excludes the local change
// with rev=foo CP=1 // with rev=foo CP=1
@ -543,7 +541,7 @@ describe('Change', function() {
prev: 'new-prev', prev: 'new-prev',
modelName: this.modelName, modelName: this.modelName,
modelId: 'new-id', modelId: 'new-id',
checkpoint: 2 checkpoint: 2,
}; };
Change.diff(this.modelName, 0, [updateRecord], function(err, diff) { Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {

View File

@ -8,7 +8,7 @@ describe('Checkpoint', function() {
describe('bumpLastSeq() and current()', function() { describe('bumpLastSeq() and current()', function() {
beforeEach(function() { beforeEach(function() {
var memory = loopback.createDataSource({ var memory = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
Checkpoint.attachTo(memory); Checkpoint.attachTo(memory);
}); });
@ -23,14 +23,14 @@ describe('Checkpoint', function() {
expect(seq).to.equal(3); expect(seq).to.equal(3);
next(); next();
}); });
} },
], done); ], done);
}); });
it('Should be no race condition for current() when calling in parallel', function(done) { it('Should be no race condition for current() when calling in parallel', function(done) {
async.parallel([ async.parallel([
function(next) { Checkpoint.current(next); }, function(next) { Checkpoint.current(next); },
function(next) { Checkpoint.current(next); } function(next) { Checkpoint.current(next); },
], function(err, list) { ], function(err, list) {
if (err) return done(err); if (err) return done(err);
Checkpoint.find(function(err, data) { Checkpoint.find(function(err, data) {
@ -44,7 +44,7 @@ describe('Checkpoint', function() {
it('Should be no race condition for bumpLastSeq() when calling in parallel', function(done) { it('Should be no race condition for bumpLastSeq() when calling in parallel', function(done) {
async.parallel([ async.parallel([
function(next) { Checkpoint.bumpLastSeq(next); }, function(next) { Checkpoint.bumpLastSeq(next); },
function(next) { Checkpoint.bumpLastSeq(next); } function(next) { Checkpoint.bumpLastSeq(next); },
], function(err, list) { ], function(err, list) {
if (err) return done(err); if (err) return done(err);
Checkpoint.find(function(err, data) { Checkpoint.find(function(err, data) {
@ -64,7 +64,8 @@ describe('Checkpoint', function() {
}); });
}); });
it('Checkpoint.current() for non existing checkpoint should initialize checkpoint', function(done) { it('Checkpoint.current() for non existing checkpoint should initialize checkpoint',
function(done) {
Checkpoint.current(function(err, seq) { Checkpoint.current(function(err, seq) {
expect(seq).to.equal(1); expect(seq).to.equal(1);
done(err); done(err);

View File

@ -3,7 +3,7 @@ describe('DataSource', function() {
beforeEach(function() { beforeEach(function() {
memory = loopback.createDataSource({ memory = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
assertValidDataSource(memory); assertValidDataSource(memory);
@ -56,14 +56,13 @@ describe('DataSource', function() {
}; };
var ds = loopback.createDataSource({ var ds = loopback.createDataSource({
connector: new Connector() connector: new Connector(),
}); });
var Color = ds.createModel('color', { name: String }); var Color = ds.createModel('color', { name: String });
assert(Color.prototype instanceof Color.registry.getModel('Model')); assert(Color.prototype instanceof Color.registry.getModel('Model'));
assert.equal(Color.base.modelName, 'PersistedModel'); assert.equal(Color.base.modelName, 'PersistedModel');
}); });
}); });
describe.skip('PersistedModel Methods', function() { describe.skip('PersistedModel Methods', function() {
@ -103,7 +102,9 @@ describe('DataSource', function() {
var fn = scope[name]; var fn = scope[name];
var actuallyEnabled = Model.getRemoteMethod(name); var actuallyEnabled = Model.getRemoteMethod(name);
assert(fn, name + ' should be defined!'); assert(fn, name + ' should be defined!');
assert(actuallyEnabled === isRemoteEnabled, name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + ' be remote enabled'); assert(actuallyEnabled === isRemoteEnabled,
name + ' ' + (isRemoteEnabled ? 'should' : 'should not') +
' be remote enabled');
} }
}); });
}); });

View File

@ -9,14 +9,14 @@ describe('RemoteConnector', function() {
// setup the remote connector // setup the remote connector
var ds = loopback.createDataSource({ var ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote connector: loopback.Remote,
}); });
TestModel.attachTo(ds); TestModel.attachTo(ds);
}); });
it('should be able to call create', function(done) { it('should be able to call create', function(done) {
TestModel.create({ TestModel.create({
foo: 'bar' foo: 'bar',
}, function(err, inst) { }, function(err, inst) {
if (err) return done(err); if (err) return done(err);
assert(inst.id); assert(inst.id);
@ -26,7 +26,7 @@ describe('RemoteConnector', function() {
it('should be able to call save', function(done) { it('should be able to call save', function(done) {
var m = new TestModel({ var m = new TestModel({
foo: 'bar' foo: 'bar',
}); });
m.save(function(err, data) { m.save(function(err, data) {
if (err) return done(err); if (err) return done(err);

View File

@ -3,7 +3,7 @@ var loopback = require('../../');
var models = require('../fixtures/e2e/models'); var models = require('../fixtures/e2e/models');
var TestModel = models.TestModel; var TestModel = models.TestModel;
var LocalTestModel = TestModel.extend('LocalTestModel', {}, { var LocalTestModel = TestModel.extend('LocalTestModel', {}, {
trackChanges: true trackChanges: true,
}); });
var assert = require('assert'); var assert = require('assert');
@ -12,7 +12,7 @@ describe('Replication', function() {
// setup the remote connector // setup the remote connector
var ds = loopback.createDataSource({ var ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote connector: loopback.Remote,
}); });
TestModel.attachTo(ds); TestModel.attachTo(ds);
var memory = loopback.memory(); var memory = loopback.memory();
@ -23,7 +23,7 @@ describe('Replication', function() {
var RANDOM = Math.random(); var RANDOM = Math.random();
LocalTestModel.create({ LocalTestModel.create({
n: RANDOM n: RANDOM,
}, function(err, created) { }, function(err, created) {
LocalTestModel.replicate(0, TestModel, function() { LocalTestModel.replicate(0, TestModel, function() {
if (err) return done(err); if (err) return done(err);

View File

@ -6,33 +6,32 @@ var MailConnector = require('../lib/connectors/mail');
describe('Email connector', function() { describe('Email connector', function() {
it('should set up SMTP', function() { it('should set up SMTP', function() {
var connector = new MailConnector({ transports: [ var connector = new MailConnector({ transports: [
{type: 'smtp', service: 'gmail'} { type: 'smtp', service: 'gmail' },
] }); ] });
assert(connector.transportForName('smtp')); assert(connector.transportForName('smtp'));
}); });
it('should set up DIRECT', function() { it('should set up DIRECT', function() {
var connector = new MailConnector({ transports: [ var connector = new MailConnector({ transports: [
{type: 'direct', name: 'localhost'} { type: 'direct', name: 'localhost' },
] }); ] });
assert(connector.transportForName('direct')); assert(connector.transportForName('direct'));
}); });
it('should set up STUB', function() { it('should set up STUB', function() {
var connector = new MailConnector({ transports: [ var connector = new MailConnector({ transports: [
{type: 'stub', service: 'gmail'} { type: 'stub', service: 'gmail' },
] }); ] });
assert(connector.transportForName('stub')); assert(connector.transportForName('stub'));
}); });
it('should set up a single transport for SMTP', function() { it('should set up a single transport for SMTP', function() {
var connector = new MailConnector({ transport: var connector = new MailConnector({ transport:
{type: 'smtp', service: 'gmail'} { type: 'smtp', service: 'gmail' },
}); });
assert(connector.transportForName('smtp')); assert(connector.transportForName('smtp'));
}); });
}); });
describe('Email and SMTP', function() { describe('Email and SMTP', function() {
@ -53,7 +52,7 @@ describe('Email and SMTP', function() {
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',
text: 'text', text: 'text',
html: '<h1>html</h1>' html: '<h1>html</h1>',
}; };
MyEmail.send(options, function(err, mail) { MyEmail.send(options, function(err, mail) {
@ -71,7 +70,7 @@ describe('Email and SMTP', function() {
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',
text: 'text', text: 'text',
html: '<h1>html</h1>' html: '<h1>html</h1>',
}); });
message.send(function(err, mail) { message.send(function(err, mail) {

View File

@ -4,9 +4,7 @@ var assert = require('assert');
var request = require('supertest'); var request = require('supertest');
describe('loopback.errorHandler(options)', function() { describe('loopback.errorHandler(options)', function() {
it('should return default middleware when options object is not present', function(done) { it('should return default middleware when options object is not present', function(done) {
//arrange //arrange
var app = loopback(); var app = loopback();
app.use(loopback.urlNotFound()); app.use(loopback.urlNotFound());
@ -22,7 +20,6 @@ describe('loopback.errorHandler(options)', function() {
}); });
it('should delete stack when options.includeStack is false', function(done) { it('should delete stack when options.includeStack is false', function(done) {
//arrange //arrange
var app = loopback(); var app = loopback();
app.use(loopback.urlNotFound()); app.use(loopback.urlNotFound());
@ -47,7 +44,7 @@ describe('loopback.errorHandler(options)', function() {
includeStack: false, includeStack: false,
log: function customLogger(err, str, req) { log: function customLogger(err, str, req) {
errorLogged = err; errorLogged = err;
} },
})); }));
//act //act
@ -58,6 +55,5 @@ describe('loopback.errorHandler(options)', function() {
.to.have.property('message', 'Cannot GET /url-does-not-exist'); .to.have.property('message', 'Cannot GET /url-does-not-exist');
done(); done();
}); });
}); });
}); });

View File

@ -2,5 +2,5 @@ var loopback = require('../../../../index');
var PersistedModel = loopback.PersistedModel; var PersistedModel = loopback.PersistedModel;
exports.TestModel = PersistedModel.extend('TestModel', {}, { exports.TestModel = PersistedModel.extend('TestModel', {}, {
trackChanges: true trackChanges: true,
}); });

View File

@ -43,11 +43,11 @@ describe('GeoPoint', function() {
}); });
it('Create as Model property', function() { it('Create as Model property', function() {
var Model = loopback.createModel('geo-model', { var Model = loopback.createModel('geo-model', {
geo: {type: 'GeoPoint'} geo: { type: 'GeoPoint' },
}); });
var m = new Model({ var m = new Model({
geo: '1.222,3.444' geo: '1.222,3.444',
}); });
assert(m.geo instanceof GeoPoint); assert(m.geo instanceof GeoPoint);

View File

@ -4,7 +4,7 @@ var _beforeEach = {};
var helpers = { var helpers = {
describe: _describe, describe: _describe,
it: _it, it: _it,
beforeEach: _beforeEach beforeEach: _beforeEach,
}; };
module.exports = helpers; module.exports = helpers;
@ -145,7 +145,9 @@ _describe.whenCalledRemotely = function(verb, url, data, cb) {
if (methodForVerb === 'delete') methodForVerb = 'del'; if (methodForVerb === 'delete') methodForVerb = 'del';
if (this.request === undefined) { if (this.request === undefined) {
throw new Error('App is not specified. Please use lt.beforeEach.withApp to specify the app.'); var msg = 'App is not specified. ' +
'Please use lt.beforeEach.withApp to specify the app.';
throw new Error(msg);
} }
this.http = this.request[methodForVerb](this.url); this.http = this.request[methodForVerb](this.url);

View File

@ -18,11 +18,11 @@ describe('hidden properties', function() {
app.use(loopback.rest()); app.use(loopback.rest());
Category.create({ Category.create({
name: 'my category' name: 'my category',
}, function(err, category) { }, function(err, category) {
category.products.create({ category.products.create({
name: 'pencil', name: 'pencil',
secret: 'a secret' secret: 'a secret',
}, done); }, done);
}); });
}); });

View File

@ -24,7 +24,7 @@ describe('loopback application', function() {
function setupAppWithStreamingMethod() { function setupAppWithStreamingMethod() {
app.dataSource('db', { app.dataSource('db', {
connector: loopback.Memory, connector: loopback.Memory,
defaultForType: 'db' defaultForType: 'db',
}); });
var db = app.datasources.db; var db = app.datasources.db;
@ -53,8 +53,8 @@ describe('loopback application', function() {
http: { method: 'post' }, http: { method: 'post' },
accepts: [ accepts: [
{ arg: 'req', type: 'Object', http: { source: 'req' }}, { arg: 'req', type: 'Object', http: { source: 'req' }},
{ arg: 'res', type: 'Object', http: { source: 'res' } } { arg: 'res', type: 'Object', http: { source: 'res' }},
] ],
}); });
app.enableAuth(); app.enableAuth();

View File

@ -26,7 +26,7 @@ module.exports = function(config) {
'test/replication.test.js', 'test/replication.test.js',
'test/change.test.js', 'test/change.test.js',
'test/checkpoint.test.js', 'test/checkpoint.test.js',
'test/app.test.js' 'test/app.test.js',
], ],
// list of files / patterns to exclude // list of files / patterns to exclude
@ -52,7 +52,7 @@ module.exports = function(config) {
// - PhantomJS // - PhantomJS
// - IE (only Windows) // - IE (only Windows)
browsers: [ browsers: [
'Chrome' 'Chrome',
], ],
// Which plugins to enable // Which plugins to enable
@ -61,7 +61,7 @@ module.exports = function(config) {
'karma-mocha', 'karma-mocha',
'karma-phantomjs-launcher', 'karma-phantomjs-launcher',
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-junit-reporter' 'karma-junit-reporter',
], ],
// If browser does not capture in given timeout [ms], kill it // If browser does not capture in given timeout [ms], kill it
@ -97,7 +97,7 @@ module.exports = function(config) {
'passport', 'passport',
'passport-local', 'passport-local',
'superagent', 'superagent',
'supertest' 'supertest',
], ],
// transform: ['coffeeify'], // transform: ['coffeeify'],
debug: true, debug: true,
@ -106,6 +106,6 @@ module.exports = function(config) {
}, },
// Add browserify to preprocessors // Add browserify to preprocessors
preprocessors: {'test/*': ['browserify']} preprocessors: { 'test/*': ['browserify'] },
}); });
}; };

View File

@ -105,7 +105,7 @@ describe('loopback', function() {
'urlNotFound', 'urlNotFound',
'urlencoded', 'urlencoded',
'version', 'version',
'vhost' 'vhost',
]; ];
var actual = Object.getOwnPropertyNames(loopback); var actual = Object.getOwnPropertyNames(loopback);
@ -135,7 +135,7 @@ describe('loopback', function() {
describe('loopback.createDataSource(options)', function() { describe('loopback.createDataSource(options)', function() {
it('Create a data source with a connector.', function() { it('Create a data source with a connector.', function() {
var dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
assert(dataSource.connector); assert(dataSource.connector);
}); });
@ -144,7 +144,7 @@ describe('loopback', function() {
describe('data source created by loopback', function() { describe('data source created by loopback', function() {
it('should create model extending Model by default', function() { it('should create model extending Model by default', function() {
var dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
var m1 = dataSource.createModel('m1', {}); var m1 = dataSource.createModel('m1', {});
assert(m1.prototype instanceof loopback.Model); assert(m1.prototype instanceof loopback.Model);
@ -161,14 +161,14 @@ describe('loopback', function() {
describe('loopback.autoAttach', function() { describe('loopback.autoAttach', function() {
it('doesn\'t overwrite model with datasource configured', function() { it('doesn\'t overwrite model with datasource configured', function() {
var ds1 = loopback.createDataSource('db1', { var ds1 = loopback.createDataSource('db1', {
connector: loopback.Memory connector: loopback.Memory,
}); });
// setup default data sources // setup default data sources
loopback.setDefaultDataSourceForType('db', ds1); loopback.setDefaultDataSourceForType('db', ds1);
var ds2 = loopback.createDataSource('db2', { var ds2 = loopback.createDataSource('db2', {
connector: loopback.Memory connector: loopback.Memory,
}); });
var model1 = ds2.createModel('m1', {}); var model1 = ds2.createModel('m1', {});
@ -196,7 +196,7 @@ describe('loopback', function() {
Product.stats, Product.stats,
{ {
returns: { arg: 'stats', type: 'array' }, returns: { arg: 'stats', type: 'array' },
http: {path: '/info', verb: 'get'} http: { path: '/info', verb: 'get' },
} }
); );
@ -213,14 +213,14 @@ describe('loopback', function() {
it('should extend from options.base', function() { it('should extend from options.base', function() {
var MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat' bar: 'bat',
} },
}); });
var MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz' bat: 'baz',
} },
}); });
assert(MyCustomModel.super_ === MyModel); assert(MyCustomModel.super_ === MyModel);
assert.deepEqual(MyCustomModel.settings.foo, { bar: 'bat', bat: 'baz' }); assert.deepEqual(MyCustomModel.settings.foo, { bar: 'bat', bat: 'baz' });
@ -232,14 +232,14 @@ describe('loopback', function() {
it('should be able to get model by name', function() { it('should be able to get model by name', function() {
var MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat' bar: 'bat',
} },
}); });
var MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz' bat: 'baz',
} },
}); });
assert(loopback.getModel('MyModel') === MyModel); assert(loopback.getModel('MyModel') === MyModel);
assert(loopback.getModel('MyCustomModel') === MyCustomModel); assert(loopback.getModel('MyCustomModel') === MyCustomModel);
@ -249,14 +249,14 @@ describe('loopback', function() {
it('should be able to get model by type', function() { it('should be able to get model by type', function() {
var MyModel = loopback.createModel('MyModel', {}, { var MyModel = loopback.createModel('MyModel', {}, {
foo: { foo: {
bar: 'bat' bar: 'bat',
} },
}); });
var MyCustomModel = loopback.createModel('MyCustomModel', {}, { var MyCustomModel = loopback.createModel('MyCustomModel', {}, {
base: 'MyModel', base: 'MyModel',
foo: { foo: {
bat: 'baz' bat: 'baz',
} },
}); });
assert(loopback.getModelByType(MyModel) === MyCustomModel); assert(loopback.getModelByType(MyModel) === MyCustomModel);
assert(loopback.getModelByType(MyCustomModel) === MyCustomModel); assert(loopback.getModelByType(MyCustomModel) === MyCustomModel);
@ -273,13 +273,13 @@ describe('loopback', function() {
methods: { methods: {
staticMethod: { staticMethod: {
isStatic: true, isStatic: true,
http: { path: '/static' } http: { path: '/static' },
}, },
instanceMethod: { instanceMethod: {
isStatic: false, isStatic: false,
http: { path: '/instance' } http: { path: '/instance' },
} },
} },
}); });
var methodNames = TestModel.sharedClass.methods().map(function(m) { var methodNames = TestModel.sharedClass.methods().map(function(m) {
@ -288,7 +288,7 @@ describe('loopback', function() {
expect(methodNames).to.include.members([ expect(methodNames).to.include.members([
'staticMethod', 'staticMethod',
'prototype.instanceMethod' 'prototype.instanceMethod',
]); ]);
}); });
}); });
@ -296,7 +296,7 @@ describe('loopback', function() {
describe('loopback.createModel(config)', function() { describe('loopback.createModel(config)', function() {
it('creates the model', function() { it('creates the model', function() {
var model = loopback.createModel({ var model = loopback.createModel({
name: uniqueModelName name: uniqueModelName,
}); });
expect(model.prototype).to.be.instanceof(loopback.Model); expect(model.prototype).to.be.instanceof(loopback.Model);
@ -305,7 +305,7 @@ describe('loopback', function() {
it('interprets extra first-level keys as options', function() { it('interprets extra first-level keys as options', function() {
var model = loopback.createModel({ var model = loopback.createModel({
name: uniqueModelName, name: uniqueModelName,
base: 'User' base: 'User',
}); });
expect(model.prototype).to.be.instanceof(loopback.User); expect(model.prototype).to.be.instanceof(loopback.User);
@ -316,8 +316,8 @@ describe('loopback', function() {
name: uniqueModelName, name: uniqueModelName,
base: 'User', base: 'User',
options: { options: {
base: 'Application' base: 'Application',
} },
}); });
expect(model.prototype).to.be.instanceof(loopback.Application); expect(model.prototype).to.be.instanceof(loopback.Application);
@ -333,9 +333,9 @@ describe('loopback', function() {
relations: { relations: {
owner: { owner: {
type: 'belongsTo', type: 'belongsTo',
model: 'User' model: 'User',
} },
} },
}); });
expect(model.settings.relations).to.have.property('owner'); expect(model.settings.relations).to.have.property('owner');
@ -346,23 +346,23 @@ describe('loopback', function() {
relations: { relations: {
owner: { owner: {
type: 'belongsTo', type: 'belongsTo',
model: 'User' model: 'User',
} },
} },
}); });
loopback.configureModel(model, { loopback.configureModel(model, {
dataSource: false, dataSource: false,
relations: { relations: {
owner: { owner: {
model: 'Application' model: 'Application',
} },
} },
}); });
expect(model.settings.relations.owner).to.eql({ expect(model.settings.relations.owner).to.eql({
type: 'belongsTo', type: 'belongsTo',
model: 'Application' model: 'Application',
}); });
}); });
@ -375,9 +375,9 @@ describe('loopback', function() {
relations: { relations: {
owner: { owner: {
type: 'belongsTo', type: 'belongsTo',
model: 'User' model: 'User',
} },
} },
}); });
var owner = model.prototype.owner; var owner = model.prototype.owner;
@ -393,9 +393,9 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'DENY' permission: 'DENY',
} },
] ],
}); });
loopback.configureModel(model, { loopback.configureModel(model, {
@ -406,9 +406,9 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: 'admin', principalId: 'admin',
permission: 'ALLOW' permission: 'ALLOW',
} },
] ],
}); });
expect(model.settings.acls).eql([ expect(model.settings.acls).eql([
@ -417,15 +417,15 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'DENY' permission: 'DENY',
}, },
{ {
property: 'find', property: 'find',
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: 'admin', principalId: 'admin',
permission: 'ALLOW' permission: 'ALLOW',
} },
]); ]);
}); });
@ -437,9 +437,9 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'DENY' permission: 'DENY',
} },
] ],
}); });
loopback.configureModel(model, { loopback.configureModel(model, {
@ -450,9 +450,9 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'ALLOW' permission: 'ALLOW',
} },
] ],
}); });
expect(model.settings.acls).eql([ expect(model.settings.acls).eql([
@ -461,15 +461,15 @@ describe('loopback', function() {
accessType: 'EXECUTE', accessType: 'EXECUTE',
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'ALLOW' permission: 'ALLOW',
} },
]); ]);
}); });
it('updates existing settings', function() { it('updates existing settings', function() {
var model = loopback.Model.extend(uniqueModelName, {}, { var model = loopback.Model.extend(uniqueModelName, {}, {
ttl: 10, ttl: 10,
emailVerificationRequired: false emailVerificationRequired: false,
}); });
var baseName = model.settings.base.name; var baseName = model.settings.base.name;
@ -479,8 +479,8 @@ describe('loopback', function() {
options: { options: {
ttl: 20, ttl: 20,
realmRequired: true, realmRequired: true,
base: 'X' base: 'X',
} },
}); });
expect(model.settings).to.have.property('ttl', 20); expect(model.settings).to.have.property('ttl', 20);
@ -499,13 +499,13 @@ describe('loopback', function() {
methods: { methods: {
staticMethod: { staticMethod: {
isStatic: true, isStatic: true,
http: { path: '/static' } http: { path: '/static' },
}, },
instanceMethod: { instanceMethod: {
isStatic: false, isStatic: false,
http: { path: '/instance' } http: { path: '/instance' },
} },
} },
}); });
var methodNames = TestModel.sharedClass.methods().map(function(m) { var methodNames = TestModel.sharedClass.methods().map(function(m) {
@ -514,7 +514,7 @@ describe('loopback', function() {
expect(methodNames).to.include.members([ expect(methodNames).to.include.members([
'staticMethod', 'staticMethod',
'prototype.instanceMethod' 'prototype.instanceMethod',
]); ]);
}); });
}); });
@ -538,7 +538,7 @@ describe('loopback', function() {
'ACL', 'ACL',
'Scope', 'Scope',
'Change', 'Change',
'Checkpoint' 'Checkpoint',
]; ];
expect(Object.keys(loopback)).to.include.members(expectedModelNames); expect(Object.keys(loopback)).to.include.members(expectedModelNames);
@ -551,8 +551,7 @@ describe('loopback', function() {
}); });
describe.onServer('loopback.getCurrentContext', function() { describe.onServer('loopback.getCurrentContext', function() {
var runInOtherDomain; var runInOtherDomain, runnerInterval;
var runnerInterval;
before(function setupRunInOtherDomain() { before(function setupRunInOtherDomain() {
var emitterInOtherDomain = new EventEmitter(); var emitterInOtherDomain = new EventEmitter();
@ -594,7 +593,7 @@ describe('loopback', function() {
TestModel.remoteMethod('test', { TestModel.remoteMethod('test', {
accepts: { arg: 'inst', type: uniqueModelName }, accepts: { arg: 'inst', type: uniqueModelName },
returns: { root: true }, returns: { root: true },
http: { path: '/test', verb: 'get' } http: { path: '/test', verb: 'get' },
}); });
// after remote hook // after remote hook
@ -645,9 +644,9 @@ describe('loopback', function() {
dataSource: null, dataSource: null,
methods: { methods: {
staticMethod: { staticMethod: {
http: { path: '/static' } http: { path: '/static' },
} },
} },
}); });
var methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
@ -661,9 +660,9 @@ describe('loopback', function() {
dataSource: null, dataSource: null,
methods: { methods: {
'prototype.instanceMethod': { 'prototype.instanceMethod': {
http: { path: '/instance' } http: { path: '/instance' },
} },
} },
}); });
var methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
@ -673,15 +672,17 @@ describe('loopback', function() {
it('throws an error when "isStatic:true" and method name starts with "prototype."', function() { it('throws an error when "isStatic:true" and method name starts with "prototype."', function() {
var TestModel = loopback.createModel(uniqueModelName); var TestModel = loopback.createModel(uniqueModelName);
expect(function() { loopback.configureModel(TestModel, { expect(function() {
loopback.configureModel(TestModel, {
dataSource: null, dataSource: null,
methods: { methods: {
'prototype.instanceMethod': { 'prototype.instanceMethod': {
isStatic: true, isStatic: true,
http: { path: '/instance' } http: { path: '/instance' },
} },
} },
});}).to.throw(Error, new Error('Remoting metadata for' + TestModel.modelName + });
}).to.throw(Error, new Error('Remoting metadata for' + TestModel.modelName +
' "isStatic" does not match new method name-based style.')); ' "isStatic" does not match new method name-based style.'));
}); });
@ -692,9 +693,9 @@ describe('loopback', function() {
methods: { methods: {
staticMethod: { staticMethod: {
isStatic: true, isStatic: true,
http: { path: '/static' } http: { path: '/static' },
} },
} },
}); });
var methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);
@ -709,9 +710,9 @@ describe('loopback', function() {
methods: { methods: {
'prototype.instanceMethod': { 'prototype.instanceMethod': {
isStatic: false, isStatic: false,
http: { path: '/instance' } http: { path: '/instance' },
} },
} },
}); });
var methodNames = getAllMethodNamesWithoutClassName(TestModel); var methodNames = getAllMethodNamesWithoutClassName(TestModel);

View File

@ -7,14 +7,14 @@ describe('Memory Connector', function() {
// or create it using the standard // or create it using the standard
// data source creation api // data source creation api
memory = loopback.createDataSource({ memory = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
// create a model using the // create a model using the
// memory data source // memory data source
var properties = { var properties = {
name: String, name: String,
price: Number price: Number,
}; };
var Product = memory.createModel('product', properties); var Product = memory.createModel('product', properties);

View File

@ -62,18 +62,18 @@ describe('Application', function() {
keyData: 'key', keyData: 'key',
pushOptions: { pushOptions: {
gateway: 'gateway.sandbox.push.apple.com', gateway: 'gateway.sandbox.push.apple.com',
port: 2195 port: 2195,
}, },
feedbackOptions: { feedbackOptions: {
gateway: 'feedback.sandbox.push.apple.com', gateway: 'feedback.sandbox.push.apple.com',
port: 2196, port: 2196,
interval: 300, interval: 300,
batchFeedback: true batchFeedback: true,
} },
}, },
gcm: { gcm: {
serverApiKey: 'serverKey' serverApiKey: 'serverKey',
} },
}}, }},
function(err, result) { function(err, result) {
var app = result; var app = result;
@ -84,18 +84,18 @@ describe('Application', function() {
keyData: 'key', keyData: 'key',
pushOptions: { pushOptions: {
gateway: 'gateway.sandbox.push.apple.com', gateway: 'gateway.sandbox.push.apple.com',
port: 2195 port: 2195,
}, },
feedbackOptions: { feedbackOptions: {
gateway: 'feedback.sandbox.push.apple.com', gateway: 'feedback.sandbox.push.apple.com',
port: 2196, port: 2196,
interval: 300, interval: 300,
batchFeedback: true batchFeedback: true,
} },
}, },
gcm: { gcm: {
serverApiKey: 'serverKey' serverApiKey: 'serverKey',
} },
}); });
done(err, result); done(err, result);
}); });

View File

@ -10,8 +10,8 @@ var describe = require('./util/describe');
describe('Model / PersistedModel', function() { describe('Model / PersistedModel', function() {
defineModelTestsWithDataSource({ defineModelTestsWithDataSource({
dataSource: { dataSource: {
connector: loopback.Memory connector: loopback.Memory,
} },
}); });
describe('Model.validatesUniquenessOf(property, options)', function() { describe('Model.validatesUniquenessOf(property, options)', function() {
@ -23,11 +23,11 @@ describe('Model / PersistedModel', function() {
'password': String, 'password': String,
'gender': String, 'gender': String,
'domain': String, 'domain': String,
'email': String 'email': String,
}); });
var dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
User.attachTo(dataSource); User.attachTo(dataSource);
@ -52,23 +52,21 @@ describe('Model / PersistedModel', function() {
it('Attach a model to a [DataSource](#data-source)', function() { it('Attach a model to a [DataSource](#data-source)', function() {
var MyModel = loopback.createModel('my-model', { name: String }); var MyModel = loopback.createModel('my-model', { name: String });
var dataSource = loopback.createDataSource({ var dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
MyModel.attachTo(dataSource); MyModel.attachTo(dataSource);
MyModel.find(function(err, results) { MyModel.find(function(err, results) {
assert(results.length === 0, 'should have data access methods after attaching to a data source'); assert(results.length === 0,
'should have data access methods after attaching to a data source');
}); });
}); });
}); });
}); });
describe.onServer('Remote Methods', function() { describe.onServer('Remote Methods', function() {
var User, Post, dataSource, app;
var User, Post;
var dataSource;
var app;
beforeEach(function() { beforeEach(function() {
User = PersistedModel.extend('user', { User = PersistedModel.extend('user', {
@ -79,21 +77,21 @@ describe.onServer('Remote Methods', function() {
'password': String, 'password': String,
'gender': String, 'gender': String,
'domain': String, 'domain': String,
'email': String 'email': String,
}, { }, {
trackChanges: true trackChanges: true,
}); });
Post = PersistedModel.extend('post', { Post = PersistedModel.extend('post', {
id: { id: true, type: String, defaultFn: 'guid' }, id: { id: true, type: String, defaultFn: 'guid' },
title: String, title: String,
content: String content: String,
}, { }, {
trackChanges: true trackChanges: true,
}); });
dataSource = loopback.createDataSource({ dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
User.attachTo(dataSource); User.attachTo(dataSource);
@ -114,10 +112,10 @@ describe.onServer('Remote Methods', function() {
{ {
accepts: [ accepts: [
{ arg: 'username', type: 'string', required: true }, { arg: 'username', type: 'string', required: true },
{arg: 'password', type: 'string', required: true} { arg: 'password', type: 'string', required: true },
], ],
returns: { arg: 'sessionId', type: 'any', root: true }, returns: { arg: 'sessionId', type: 'any', root: true },
http: {path: '/sign-in', verb: 'get'} http: { path: '/sign-in', verb: 'get' },
} }
); );
@ -230,7 +228,6 @@ describe.onServer('Remote Methods', function() {
}); });
}); });
}); });
}); });
describe('Model.beforeRemote(name, fn)', function() { describe('Model.beforeRemote(name, fn)', function() {
@ -399,7 +396,7 @@ describe.onServer('Remote Methods', function() {
n: { type: 'Number' }, n: { type: 'Number' },
o: { type: 'String', min: 10, max: 100 }, o: { type: 'String', min: 10, max: 100 },
d: Date, d: Date,
g: loopback.GeoPoint g: loopback.GeoPoint,
}; };
var MyModel = loopback.createModel('foo', props); var MyModel = loopback.createModel('foo', props);
@ -426,7 +423,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.extend()', function() { describe('Model.extend()', function() {
it('Create a new model by extending an existing model', function() { it('Create a new model by extending an existing model', function() {
var User = loopback.PersistedModel.extend('test-user', { var User = loopback.PersistedModel.extend('test-user', {
email: String email: String,
}); });
User.foo = function() { User.foo = function() {
@ -439,7 +436,7 @@ describe.onServer('Remote Methods', function() {
var MyUser = User.extend('my-user', { var MyUser = User.extend('my-user', {
a: String, a: String,
b: String b: String,
}); });
assert.equal(MyUser.prototype.bar, User.prototype.bar); assert.equal(MyUser.prototype.bar, User.prototype.bar);
@ -448,7 +445,7 @@ describe.onServer('Remote Methods', function() {
var user = new MyUser({ var user = new MyUser({
email: 'foo@bar.com', email: 'foo@bar.com',
a: 'foo', a: 'foo',
b: 'bar' b: 'bar',
}); });
assert.equal(user.email, 'foo@bar.com'); assert.equal(user.email, 'foo@bar.com');
@ -461,11 +458,11 @@ describe.onServer('Remote Methods', function() {
it('create isolated emitters for subclasses', function() { it('create isolated emitters for subclasses', function() {
var User1 = loopback.createModel('User1', { var User1 = loopback.createModel('User1', {
'first': String, 'first': String,
'last': String 'last': String,
}); });
var User2 = loopback.createModel('User2', { var User2 = loopback.createModel('User2', {
'name': String 'name': String,
}); });
var user1Triggered = false; var user1Triggered = false;
@ -486,7 +483,6 @@ describe.onServer('Remote Methods', function() {
assert(user1Triggered); assert(user1Triggered);
assert(!user2Triggered); assert(!user2Triggered);
}); });
}); });
describe('Model.checkAccessTypeForMethod(remoteMethod)', function() { describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {
@ -538,10 +534,9 @@ describe.onServer('Remote Methods', function() {
var Checkpoint = User.getChangeModel().getCheckpointModel(); var Checkpoint = User.getChangeModel().getCheckpointModel();
var tasks = [ var tasks = [
getCurrentCheckpoint, getCurrentCheckpoint,
checkpoint checkpoint,
]; ];
var result; var result, current;
var current;
async.series(tasks, function(err) { async.series(tasks, function(err) {
if (err) return done(err); if (err) return done(err);
@ -615,7 +610,7 @@ describe.onServer('Remote Methods', function() {
'removeById', 'removeById',
'count', 'count',
'prototype.updateAttributes', 'prototype.updateAttributes',
'createChangeStream' 'createChangeStream',
]); ]);
}); });
}); });

View File

@ -1,5 +1,3 @@
/*jshint -W030 */
var loopback = require('../'); var loopback = require('../');
var lt = require('./helpers/loopback-testing-helper'); var lt = require('./helpers/loopback-testing-helper');
var path = require('path'); var path = require('path');
@ -11,14 +9,13 @@ var debug = require('debug')('loopback:test:relations.integration');
var async = require('async'); var async = require('async');
describe('relations - integration', function() { describe('relations - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
lt.beforeEach.givenModel('store'); lt.beforeEach.givenModel('store');
beforeEach(function(done) { beforeEach(function(done) {
this.widgetName = 'foo'; this.widgetName = 'foo';
this.store.widgets.create({ this.store.widgets.create({
name: this.widgetName name: this.widgetName,
}, function() { }, function() {
done(); done();
}); });
@ -28,36 +25,35 @@ describe('relations - integration', function() {
}); });
describe('polymorphicHasMany', function() { describe('polymorphicHasMany', function() {
before(function defineProductAndCategoryModels() { before(function defineProductAndCategoryModels() {
var Team = app.model( var Team = app.model(
'Team', 'Team',
{ properties: { name: 'string' }, { properties: { name: 'string' },
dataSource: 'db' dataSource: 'db',
} }
); );
var Reader = app.model( var Reader = app.model(
'Reader', 'Reader',
{ properties: { name: 'string' }, { properties: { name: 'string' },
dataSource: 'db' dataSource: 'db',
} }
); );
var Picture = app.model( var Picture = app.model(
'Picture', 'Picture',
{ properties: { name: 'string', imageableId: 'number', imageableType: 'string' }, { properties: { name: 'string', imageableId: 'number', imageableType: 'string' },
dataSource: 'db' dataSource: 'db',
} }
); );
Reader.hasMany(Picture, { polymorphic: { // alternative syntax Reader.hasMany(Picture, { polymorphic: { // alternative syntax
as: 'imageable', // if not set, default to: reference as: 'imageable', // if not set, default to: reference
foreignKey: 'imageableId', // defaults to 'as + Id' foreignKey: 'imageableId', // defaults to 'as + Id'
discriminator: 'imageableType' // defaults to 'as + Type' discriminator: 'imageableType', // defaults to 'as + Type'
}}); }});
Picture.belongsTo('imageable', { polymorphic: { Picture.belongsTo('imageable', { polymorphic: {
foreignKey: 'imageableId', foreignKey: 'imageableId',
discriminator: 'imageableType' discriminator: 'imageableType',
}}); }});
Reader.belongsTo(Team); Reader.belongsTo(Team);
@ -119,7 +115,10 @@ describe('relations - integration', function() {
it('includes related models scoped to the related parent model', function(done) { it('includes related models scoped to the related parent model', function(done) {
var url = '/api/pictures'; var url = '/api/pictures';
this.get(url) this.get(url)
.query({'filter': {'include' : {'relation': 'imageable', 'scope': { 'include' : 'team'}}}}) .query({ 'filter': { 'include': {
'relation': 'imageable',
'scope': { 'include': 'team' },
}}})
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[0].name).to.be.equal('Picture 1');
@ -129,7 +128,6 @@ describe('relations - integration', function() {
done(); done();
}); });
}); });
}); });
describe('/store/superStores', function() { describe('/store/superStores', function() {
@ -148,7 +146,6 @@ describe('relations - integration', function() {
this.url = '/api/stores/' + this.store.id + '/widgets'; this.url = '/api/stores/' + this.store.id + '/widgets';
}); });
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() { lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
it('should succeed with statusCode 200', function() { it('should succeed with statusCode 200', function() {
assert.equal(this.res.statusCode, 200); assert.equal(this.res.statusCode, 200);
}); });
@ -180,7 +177,7 @@ describe('relations - integration', function() {
beforeEach(function() { beforeEach(function() {
this.newWidgetName = 'baz'; this.newWidgetName = 'baz';
this.newWidget = { this.newWidget = {
name: this.newWidgetName name: this.newWidgetName,
}; };
}); });
beforeEach(function(done) { beforeEach(function(done) {
@ -212,7 +209,7 @@ describe('relations - integration', function() {
}); });
it('should have a single widget with storeId', function(done) { it('should have a single widget with storeId', function(done) {
this.app.models.widget.count({ this.app.models.widget.count({
storeId: this.store.id storeId: this.store.id,
}, function(err, count) { }, function(err, count) {
if (err) return done(err); if (err) return done(err);
assert.equal(count, 2); assert.equal(count, 2);
@ -226,7 +223,7 @@ describe('relations - integration', function() {
beforeEach(function(done) { beforeEach(function(done) {
var self = this; var self = this;
this.store.widgets.create({ this.store.widgets.create({
name: this.widgetName name: this.widgetName,
}, function(err, widget) { }, function(err, widget) {
self.widget = widget; self.widget = widget;
self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id;
@ -299,7 +296,7 @@ describe('relations - integration', function() {
beforeEach(function(done) { beforeEach(function(done) {
var self = this; var self = this;
this.store.widgets.create({ this.store.widgets.create({
name: this.widgetName name: this.widgetName,
}, function(err, widget) { }, function(err, widget) {
self.widget = widget; self.widget = widget;
self.url = '/api/widgets/' + self.widget.id + '/store'; self.url = '/api/widgets/' + self.widget.id + '/store';
@ -315,7 +312,6 @@ describe('relations - integration', function() {
}); });
describe('hasMany through', function() { describe('hasMany through', function() {
function setup(connecting, cb) { function setup(connecting, cb) {
var root = {}; var root = {};
@ -334,7 +330,7 @@ describe('relations - integration', function() {
// Create a physician // Create a physician
function(done) { function(done) {
app.models.physician.create({ app.models.physician.create({
name: 'ph1' name: 'ph1',
}, function(err, physician) { }, function(err, physician) {
root.physician = physician; root.physician = physician;
done(); done();
@ -344,7 +340,7 @@ describe('relations - integration', function() {
// Create a patient // Create a patient
connecting ? function(done) { connecting ? function(done) {
root.physician.patients.create({ root.physician.patients.create({
name: 'pa1' name: 'pa1',
}, function(err, patient) { }, function(err, patient) {
root.patient = patient; root.patient = patient;
root.relUrl = '/api/physicians/' + root.physician.id + root.relUrl = '/api/physicians/' + root.physician.id +
@ -353,7 +349,7 @@ describe('relations - integration', function() {
}); });
} : function(done) { } : function(done) {
app.models.patient.create({ app.models.patient.create({
name: 'pa1' name: 'pa1',
}, function(err, patient) { }, function(err, patient) {
root.patient = patient; root.patient = patient;
root.relUrl = '/api/physicians/' + root.physician.id + root.relUrl = '/api/physicians/' + root.physician.id +
@ -366,7 +362,6 @@ describe('relations - integration', function() {
} }
describe('PUT /physicians/:id/patients/rel/:fk', function() { describe('PUT /physicians/:id/patients/rel/:fk', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(false, function(err, root) { setup(false, function(err, root) {
@ -405,7 +400,6 @@ describe('relations - integration', function() {
}); });
describe('PUT /physicians/:id/patients/rel/:fk with data', function() { describe('PUT /physicians/:id/patients/rel/:fk with data', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(false, function(err, root) { setup(false, function(err, root) {
@ -450,7 +444,6 @@ describe('relations - integration', function() {
}); });
describe('HEAD /physicians/:id/patients/rel/:fk', function() { describe('HEAD /physicians/:id/patients/rel/:fk', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(true, function(err, root) { setup(true, function(err, root) {
@ -469,7 +462,6 @@ describe('relations - integration', function() {
}); });
describe('HEAD /physicians/:id/patients/rel/:fk that does not exist', function() { describe('HEAD /physicians/:id/patients/rel/:fk that does not exist', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(true, function(err, root) { setup(true, function(err, root) {
@ -489,7 +481,6 @@ describe('relations - integration', function() {
}); });
describe('DELETE /physicians/:id/patients/rel/:fk', function() { describe('DELETE /physicians/:id/patients/rel/:fk', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(true, function(err, root) { setup(true, function(err, root) {
@ -543,7 +534,6 @@ describe('relations - integration', function() {
}); });
describe('GET /physicians/:id/patients/:fk', function() { describe('GET /physicians/:id/patients/:fk', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(true, function(err, root) { setup(true, function(err, root) {
@ -564,7 +554,6 @@ describe('relations - integration', function() {
}); });
describe('DELETE /physicians/:id/patients/:fk', function() { describe('DELETE /physicians/:id/patients/:fk', function() {
before(function(done) { before(function(done) {
var self = this; var self = this;
setup(true, function(err, root) { setup(true, function(err, root) {
@ -605,7 +594,6 @@ describe('relations - integration', function() {
done(); done();
}); });
}); });
}); });
}); });
}); });
@ -630,7 +618,7 @@ describe('relations - integration', function() {
beforeEach(function createProductsInCategory(done) { beforeEach(function createProductsInCategory(done) {
var test = this; var test = this;
this.category.products.create({ this.category.products.create({
name: 'a-product' name: 'a-product',
}, function(err, product) { }, function(err, product) {
if (err) return done(err); if (err) return done(err);
test.product = product; test.product = product;
@ -659,8 +647,8 @@ describe('relations - integration', function() {
expect(res.body).to.eql([ expect(res.body).to.eql([
{ {
id: expectedProduct.id, id: expectedProduct.id,
name: expectedProduct.name name: expectedProduct.name,
} },
]); ]);
done(); done();
}); });
@ -674,8 +662,8 @@ describe('relations - integration', function() {
expect(res.body).to.eql([ expect(res.body).to.eql([
{ {
id: expectedProduct.id, id: expectedProduct.id,
name: expectedProduct.name name: expectedProduct.name,
} },
]); ]);
done(); done();
}); });
@ -693,8 +681,8 @@ describe('relations - integration', function() {
expect(res.body.products).to.eql([ expect(res.body.products).to.eql([
{ {
id: expectedProduct.id, id: expectedProduct.id,
name: expectedProduct.name name: expectedProduct.name,
} },
]); ]);
done(); done();
}); });
@ -713,8 +701,8 @@ describe('relations - integration', function() {
expect(res.body.products).to.eql([ expect(res.body.products).to.eql([
{ {
id: expectedProduct.id, id: expectedProduct.id,
name: expectedProduct.name name: expectedProduct.name,
} },
]); ]);
done(); done();
}); });
@ -722,13 +710,12 @@ describe('relations - integration', function() {
}); });
describe('embedsOne', function() { describe('embedsOne', function() {
before(function defineGroupAndPosterModels() { before(function defineGroupAndPosterModels() {
var group = app.model( var group = app.model(
'group', 'group',
{ properties: { name: 'string' }, { properties: { name: 'string' },
dataSource: 'db', dataSource: 'db',
plural: 'groups' plural: 'groups',
} }
); );
var poster = app.model( var poster = app.model(
@ -825,17 +812,15 @@ describe('relations - integration', function() {
var url = '/api/groups/' + this.group.id + '/cover'; var url = '/api/groups/' + this.group.id + '/cover';
this.get(url).expect(404, done); this.get(url).expect(404, done);
}); });
}); });
describe('embedsMany', function() { describe('embedsMany', function() {
before(function defineProductAndCategoryModels() { before(function defineProductAndCategoryModels() {
var todoList = app.model( var todoList = app.model(
'todoList', 'todoList',
{ properties: { name: 'string' }, { properties: { name: 'string' },
dataSource: 'db', dataSource: 'db',
plural: 'todo-lists' plural: 'todo-lists',
} }
); );
var todoItem = app.model( var todoItem = app.model(
@ -870,7 +855,7 @@ describe('relations - integration', function() {
expect(res.body.name).to.be.equal('List A'); expect(res.body.name).to.be.equal('List A');
expect(res.body.todoItems).to.be.eql([ expect(res.body.todoItems).to.be.eql([
{ content: 'Todo 1', id: 1 }, { content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 } { content: 'Todo 2', id: 2 },
]); ]);
done(); done();
}); });
@ -884,7 +869,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 }, { content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 } { content: 'Todo 2', id: 2 },
]); ]);
done(); done();
}); });
@ -898,7 +883,7 @@ describe('relations - integration', function() {
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ content: 'Todo 2', id: 2 } { content: 'Todo 2', id: 2 },
]); ]);
done(); done();
}); });
@ -926,7 +911,7 @@ describe('relations - integration', function() {
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 }, { content: 'Todo 1', id: 1 },
{ content: 'Todo 2', id: 2 }, { content: 'Todo 2', id: 2 },
{ content: 'Todo 3', id: 3 } { content: 'Todo 3', id: 3 },
]); ]);
done(); done();
}); });
@ -963,7 +948,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ content: 'Todo 1', id: 1 }, { content: 'Todo 1', id: 1 },
{ content: 'Todo 3', id: 3 } { content: 'Todo 3', id: 3 },
]); ]);
done(); done();
}); });
@ -997,11 +982,9 @@ describe('relations - integration', function() {
done(); done();
}); });
}); });
}); });
describe('referencesMany', function() { describe('referencesMany', function() {
before(function defineProductAndCategoryModels() { before(function defineProductAndCategoryModels() {
var recipe = app.model( var recipe = app.model(
'recipe', 'recipe',
@ -1018,7 +1001,7 @@ describe('relations - integration', function() {
recipe.referencesMany(ingredient); recipe.referencesMany(ingredient);
// contrived example for test: // contrived example for test:
recipe.hasOne(photo, { as: 'picture', options: { recipe.hasOne(photo, { as: 'picture', options: {
http: { path: 'image' } http: { path: 'image' },
}}); }});
}); });
@ -1090,7 +1073,7 @@ describe('relations - integration', function() {
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 }, { name: 'Sugar', id: test.ingredient2 },
{ name: 'Butter', id: test.ingredient3 } { name: 'Butter', id: test.ingredient3 },
]); ]);
done(); done();
}); });
@ -1105,7 +1088,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Butter', id: test.ingredient3 } { name: 'Butter', id: test.ingredient3 },
]); ]);
done(); done();
}); });
@ -1120,7 +1103,7 @@ describe('relations - integration', function() {
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Butter', id: test.ingredient3 } { name: 'Butter', id: test.ingredient3 },
]); ]);
done(); done();
}); });
@ -1135,11 +1118,11 @@ describe('relations - integration', function() {
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body.ingredientIds).to.eql([ expect(res.body.ingredientIds).to.eql([
test.ingredient1, test.ingredient3 test.ingredient1, test.ingredient3,
]); ]);
expect(res.body.ingredients).to.eql([ expect(res.body.ingredients).to.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Butter', id: test.ingredient3 } { name: 'Butter', id: test.ingredient3 },
]); ]);
done(); done();
}); });
@ -1195,7 +1178,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 } { name: 'Sugar', id: test.ingredient2 },
]); ]);
done(); done();
}); });
@ -1209,7 +1192,7 @@ describe('relations - integration', function() {
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 } { name: 'Chocolate', id: test.ingredient1 },
]); ]);
done(); done();
}); });
@ -1238,7 +1221,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 } { name: 'Sugar', id: test.ingredient2 },
]); ]);
done(); done();
}); });
@ -1263,7 +1246,7 @@ describe('relations - integration', function() {
.expect(200, function(err, res) { .expect(200, function(err, res) {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Sugar', id: test.ingredient2 } { name: 'Sugar', id: test.ingredient2 },
]); ]);
done(); done();
}); });
@ -1278,7 +1261,7 @@ describe('relations - integration', function() {
if (err) return done(err); if (err) return done(err);
expect(res.body).to.be.eql([ expect(res.body).to.be.eql([
{ name: 'Chocolate', id: test.ingredient1 }, { name: 'Chocolate', id: test.ingredient1 },
{ name: 'Sugar', id: test.ingredient2 } { name: 'Sugar', id: test.ingredient2 },
]); ]);
done(); done();
}); });
@ -1315,11 +1298,9 @@ describe('relations - integration', function() {
done(); done();
}); });
}); });
}); });
describe('nested relations', function() { describe('nested relations', function() {
before(function defineModels() { before(function defineModels() {
var Book = app.model( var Book = app.model(
'Book', 'Book',
@ -1375,7 +1356,6 @@ describe('relations - integration', function() {
ctx.res.set('x-after', 'after'); ctx.res.set('x-after', 'after');
next(); next();
}); });
}); });
before(function createBook(done) { before(function createBook(done) {
@ -1636,5 +1616,4 @@ describe('relations - integration', function() {
}); });
}); });
}); });
}); });

View File

@ -2,8 +2,7 @@ var loopback = require('../');
var defineModelTestsWithDataSource = require('./util/model-tests'); var defineModelTestsWithDataSource = require('./util/model-tests');
describe('RemoteConnector', function() { describe('RemoteConnector', function() {
var remoteApp; var remoteApp, remote;
var remote;
defineModelTestsWithDataSource({ defineModelTestsWithDataSource({
beforeEach: function(done) { beforeEach: function(done) {
@ -14,7 +13,7 @@ describe('RemoteConnector', function() {
test.dataSource = loopback.createDataSource({ test.dataSource = loopback.createDataSource({
host: 'localhost', host: 'localhost',
port: remoteApp.get('port'), port: remoteApp.get('port'),
connector: loopback.Remote connector: loopback.Remote,
}); });
done(); done();
}); });
@ -23,10 +22,10 @@ describe('RemoteConnector', function() {
var RemoteModel = Model.extend('Remote' + Model.modelName, {}, var RemoteModel = Model.extend('Remote' + Model.modelName, {},
{ plural: Model.pluralModelName }); { plural: Model.pluralModelName });
RemoteModel.attachTo(loopback.createDataSource({ RemoteModel.attachTo(loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
})); }));
remoteApp.model(RemoteModel); remoteApp.model(RemoteModel);
} },
}); });
beforeEach(function(done) { beforeEach(function(done) {
@ -41,7 +40,7 @@ describe('RemoteConnector', function() {
test.remote = loopback.createDataSource({ test.remote = loopback.createDataSource({
host: 'localhost', host: 'localhost',
port: remoteApp.get('port'), port: remoteApp.get('port'),
connector: loopback.Remote connector: loopback.Remote,
}); });
done(); done();
}); });

View File

@ -16,14 +16,14 @@ describe('remoting coercion', function() {
}; };
TestModel.remoteMethod('test', { TestModel.remoteMethod('test', {
accepts: { arg: 'inst', type: 'TestModel', http: { source: 'body' }}, accepts: { arg: 'inst', type: 'TestModel', http: { source: 'body' }},
http: {path: '/test', verb: 'post'} http: { path: '/test', verb: 'post' },
}); });
request(app) request(app)
.post('/TestModels/test') .post('/TestModels/test')
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.send({ .send({
foo: 'bar' foo: 'bar',
}) })
.end(function(err) { .end(function(err) {
if (err) return done(err); if (err) return done(err);

View File

@ -6,7 +6,6 @@ var app = require(path.join(SIMPLE_APP, 'server/server.js'));
var assert = require('assert'); var assert = require('assert');
describe('remoting - integration', function() { describe('remoting - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
lt.beforeEach.givenModel('store'); lt.beforeEach.givenModel('store');
@ -34,7 +33,7 @@ describe('remoting - integration', function() {
} }
this.http = this.post('/api/stores'); this.http = this.post('/api/stores');
this.http.send({ this.http.send({
'name': name 'name': name,
}); });
this.http.end(function(err) { this.http.end(function(err) {
if (err) return done(err); if (err) return done(err);
@ -53,7 +52,7 @@ describe('remoting - integration', function() {
} }
this.http = this.post('/api/stores'); this.http = this.post('/api/stores');
this.http.send({ this.http.send({
'name': name 'name': name,
}); });
this.http.end(function(err) { this.http.end(function(err) {
if (err) return done(err); if (err) return done(err);
@ -88,7 +87,7 @@ describe('remoting - integration', function() {
' ', ' ',
m.getHttpMethod(), m.getHttpMethod(),
' ', ' ',
m.getFullPath() m.getFullPath(),
].join(''); ].join('');
} }
@ -121,7 +120,7 @@ describe('remoting - integration', function() {
'deleteById(id:any):object DELETE /stores/:id', 'deleteById(id:any):object DELETE /stores/:id',
'count(where:object):number GET /stores/count', 'count(where:object):number GET /stores/count',
'prototype.updateAttributes(data:object):store PUT /stores/:id', 'prototype.updateAttributes(data:object):store PUT /stores/:id',
'createChangeStream(options:object):ReadableStream POST /stores/change-stream' 'createChangeStream(options:object):ReadableStream POST /stores/change-stream',
]; ];
// The list of methods is from docs: // The list of methods is from docs:
@ -143,7 +142,7 @@ describe('remoting - integration', function() {
'__get__superStores(filter:object):store GET /stores/superStores', '__get__superStores(filter:object):store GET /stores/superStores',
'__create__superStores(data:store):store POST /stores/superStores', '__create__superStores(data:store):store POST /stores/superStores',
'__delete__superStores() DELETE /stores/superStores', '__delete__superStores() DELETE /stores/superStores',
'__count__superStores(where:object):number GET /stores/superStores/count' '__count__superStores(where:object):number GET /stores/superStores/count',
]; ];
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
@ -151,7 +150,6 @@ describe('remoting - integration', function() {
it('should have correct signatures for belongsTo methods', it('should have correct signatures for belongsTo methods',
function() { function() {
var widgetClass = findClass('widget'); var widgetClass = findClass('widget');
var methods = widgetClass.methods var methods = widgetClass.methods
.filter(function(m) { .filter(function(m) {
@ -163,14 +161,13 @@ describe('remoting - integration', function() {
var expectedMethods = [ var expectedMethods = [
'prototype.__get__store(refresh:boolean):store ' + 'prototype.__get__store(refresh:boolean):store ' +
'GET /widgets/:id/store' 'GET /widgets/:id/store',
]; ];
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
}); });
it('should have correct signatures for hasMany methods', it('should have correct signatures for hasMany methods',
function() { function() {
var physicianClass = findClass('store'); var physicianClass = findClass('store');
var methods = physicianClass.methods var methods = physicianClass.methods
.filter(function(m) { .filter(function(m) {
@ -194,14 +191,13 @@ describe('remoting - integration', function() {
'prototype.__delete__widgets() ' + 'prototype.__delete__widgets() ' +
'DELETE /stores/:id/widgets', 'DELETE /stores/:id/widgets',
'prototype.__count__widgets(where:object):number ' + 'prototype.__count__widgets(where:object):number ' +
'GET /stores/:id/widgets/count' 'GET /stores/:id/widgets/count',
]; ];
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
}); });
it('should have correct signatures for hasMany-through methods', it('should have correct signatures for hasMany-through methods',
function() { // jscs:disable validateIndentation function() { // jscs:disable validateIndentation
var physicianClass = findClass('physician'); var physicianClass = findClass('physician');
var methods = physicianClass.methods var methods = physicianClass.methods
.filter(function(m) { .filter(function(m) {
@ -231,10 +227,9 @@ describe('remoting - integration', function() {
'prototype.__delete__patients() ' + 'prototype.__delete__patients() ' +
'DELETE /physicians/:id/patients', 'DELETE /physicians/:id/patients',
'prototype.__count__patients(where:object):number ' + 'prototype.__count__patients(where:object):number ' +
'GET /physicians/:id/patients/count' 'GET /physicians/:id/patients/count',
]; ];
expect(methods).to.include.members(expectedMethods); expect(methods).to.include.members(expectedMethods);
}); });
}); });
}); });

View File

@ -10,10 +10,12 @@ describe('Replication over REST', function() {
var PETER = { id: 'p', username: 'peter', email: 'p@t.io', password: 'p' }; var PETER = { id: 'p', username: 'peter', email: 'p@t.io', password: 'p' };
var EMERY = { id: 'e', username: 'emery', email: 'e@t.io', password: 'p' }; var EMERY = { id: 'e', username: 'emery', email: 'e@t.io', password: 'p' };
/* eslint-disable one-var */
var serverApp, serverUrl, ServerUser, ServerCar, serverCars; var serverApp, serverUrl, ServerUser, ServerCar, serverCars;
var aliceId, peterId, aliceToken, peterToken, emeryToken, request; var aliceId, peterId, aliceToken, peterToken, emeryToken, request;
var clientApp, LocalUser, LocalCar, RemoteUser, RemoteCar, clientCars; var clientApp, LocalUser, LocalCar, RemoteUser, RemoteCar, clientCars;
var conflictedCarId; var conflictedCarId;
/* eslint-enable one-var */
before(setupServer); before(setupServer);
before(setupClient); before(setupClient);
@ -322,7 +324,7 @@ describe('Replication over REST', function() {
.to.have.property('fullname', 'Alice Smith'); .to.have.property('fullname', 'Alice Smith');
next(); next();
}); });
} },
], done); ], done);
}); });
@ -347,7 +349,7 @@ describe('Replication over REST', function() {
.to.not.have.property('fullname'); .to.not.have.property('fullname');
next(); next();
}); });
} },
], done); ], done);
}); });
@ -367,7 +369,7 @@ describe('Replication over REST', function() {
}); });
var USER_PROPS = { var USER_PROPS = {
id: { type: 'string', id: true } id: { type: 'string', id: true },
}; };
var USER_OPTS = { var USER_OPTS = {
@ -375,13 +377,13 @@ describe('Replication over REST', function() {
plural: 'Users', // use the same REST path in all models plural: 'Users', // use the same REST path in all models
trackChanges: true, trackChanges: true,
strict: 'throw', strict: 'throw',
persistUndefinedAsNull: true persistUndefinedAsNull: true,
}; };
var CAR_PROPS = { var CAR_PROPS = {
id: { type: 'string', id: true, defaultFn: 'guid' }, id: { type: 'string', id: true, defaultFn: 'guid' },
model: { type: 'string', required: true }, model: { type: 'string', required: true },
maker: { type: 'string' } maker: { type: 'string' },
}; };
var CAR_OPTS = { var CAR_OPTS = {
@ -395,30 +397,30 @@ describe('Replication over REST', function() {
{ {
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$everyone', principalId: '$everyone',
permission: 'DENY' permission: 'DENY',
}, },
// allow all authenticated users to read data // allow all authenticated users to read data
{ {
principalType: 'ROLE', principalType: 'ROLE',
principalId: '$authenticated', principalId: '$authenticated',
permission: 'ALLOW', permission: 'ALLOW',
accessType: 'READ' accessType: 'READ',
}, },
// allow Alice to pull changes // allow Alice to pull changes
{ {
principalType: 'USER', principalType: 'USER',
principalId: ALICE.id, principalId: ALICE.id,
permission: 'ALLOW', permission: 'ALLOW',
accessType: 'REPLICATE' accessType: 'REPLICATE',
}, },
// allow Peter to write data // allow Peter to write data
{ {
principalType: 'USER', principalType: 'USER',
principalId: PETER.id, principalId: PETER.id,
permission: 'ALLOW', permission: 'ALLOW',
accessType: 'WRITE' accessType: 'WRITE',
} },
] ],
}; };
function setupServer(done) { function setupServer(done) {
@ -435,9 +437,9 @@ describe('Replication over REST', function() {
user: { user: {
type: 'belongsTo', type: 'belongsTo',
model: 'ServerUser', model: 'ServerUser',
foreignKey: 'userId' foreignKey: 'userId',
} },
} },
}); });
serverApp.model(ServerToken, { dataSource: 'db', public: false }); serverApp.model(ServerToken, { dataSource: 'db', public: false });
serverApp.model(loopback.ACL, { dataSource: 'db', public: false }); serverApp.model(loopback.ACL, { dataSource: 'db', public: false });
@ -448,7 +450,7 @@ describe('Replication over REST', function() {
serverApp.model(ServerUser, { serverApp.model(ServerUser, {
dataSource: 'db', dataSource: 'db',
public: true, public: true,
relations: { accessTokens: { model: 'ServerToken' } } relations: { accessTokens: { model: 'ServerToken' }},
}); });
ServerCar = loopback.createModel('ServerCar', CAR_PROPS, CAR_OPTS); ServerCar = loopback.createModel('ServerCar', CAR_PROPS, CAR_OPTS);
@ -476,7 +478,7 @@ describe('Replication over REST', function() {
clientApp.dataSource('db', { connector: 'memory' }); clientApp.dataSource('db', { connector: 'memory' });
clientApp.dataSource('remote', { clientApp.dataSource('remote', {
connector: 'remote', connector: 'remote',
url: serverUrl url: serverUrl,
}); });
// NOTE(bajtos) At the moment, all models share the same Checkpoint // NOTE(bajtos) At the moment, all models share the same Checkpoint
@ -510,7 +512,7 @@ describe('Replication over REST', function() {
trackChanges: false, trackChanges: false,
// Enable remote replication in order to get remoting API metadata // Enable remote replication in order to get remoting API metadata
// used by the remoting connector // used by the remoting connector
enableRemoteReplication: true enableRemoteReplication: true,
}); });
} }
@ -548,14 +550,14 @@ describe('Replication over REST', function() {
ServerCar.create( ServerCar.create(
[ [
{ id: 'Ford-Mustang', maker: 'Ford', model: 'Mustang' }, { id: 'Ford-Mustang', maker: 'Ford', model: 'Mustang' },
{ id: 'Audi-R8', maker: 'Audi', model: 'R8' } { id: 'Audi-R8', maker: 'Audi', model: 'R8' },
], ],
function(err, cars) { function(err, cars) {
if (err) return next(err); if (err) return next(err);
serverCars = cars.map(carToString); serverCars = cars.map(carToString);
next(); next();
}); });
} },
], done); ], done);
} }
@ -600,7 +602,7 @@ describe('Replication over REST', function() {
function setAccessToken(token) { function setAccessToken(token) {
clientApp.dataSources.remote.connector.remotes.auth = { clientApp.dataSources.remote.connector.remotes.auth = {
bearer: new Buffer(token).toString('base64'), bearer: new Buffer(token).toString('base64'),
sendImmediately: true sendImmediately: true,
}; };
} }

View File

@ -8,8 +8,7 @@ var expect = require('chai').expect;
var debug = require('debug')('test'); var debug = require('debug')('test');
describe('Replication / Change APIs', function() { describe('Replication / Change APIs', function() {
var dataSource, SourceModel, TargetModel; var dataSource, SourceModel, TargetModel, useSinceFilter;
var useSinceFilter;
var tid = 0; // per-test unique id used e.g. to build unique model names var tid = 0; // per-test unique id used e.g. to build unique model names
beforeEach(function() { beforeEach(function() {
@ -17,7 +16,7 @@ describe('Replication / Change APIs', function() {
useSinceFilter = false; useSinceFilter = false;
var test = this; var test = this;
dataSource = this.dataSource = loopback.createDataSource({ dataSource = this.dataSource = loopback.createDataSource({
connector: loopback.Memory connector: loopback.Memory,
}); });
SourceModel = this.SourceModel = PersistedModel.extend( SourceModel = this.SourceModel = PersistedModel.extend(
'SourceModel-' + tid, 'SourceModel-' + tid,
@ -85,7 +84,8 @@ describe('Replication / Change APIs', function() {
}); });
}); });
it('rectifyOnDelete for Delete should call rectifyChange instead of rectifyAllChanges', function(done) { it('rectifyOnDelete for Delete should call rectifyChange instead of rectifyAllChanges',
function(done) {
var calls = mockTargetModelRectify(); var calls = mockTargetModelRectify();
async.waterfall([ async.waterfall([
function(callback) { function(callback) {
@ -94,7 +94,7 @@ describe('Replication / Change APIs', function() {
function(data, callback) { function(data, callback) {
SourceModel.replicate(TargetModel, callback); SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
} },
], function(err, results) { ], function(err, results) {
if (err) return done(err); if (err) return done(err);
expect(calls).to.eql(['rectifyChange']); expect(calls).to.eql(['rectifyChange']);
@ -102,7 +102,8 @@ describe('Replication / Change APIs', function() {
}); });
}); });
it('rectifyOnSave for Update should call rectifyChange instead of rectifyAllChanges', function(done) { it('rectifyOnSave for Update should call rectifyChange instead of rectifyAllChanges',
function(done) {
var calls = mockTargetModelRectify(); var calls = mockTargetModelRectify();
var newData = { 'name': 'Janie' }; var newData = { 'name': 'Janie' };
async.waterfall([ async.waterfall([
@ -112,7 +113,7 @@ describe('Replication / Change APIs', function() {
function(data, callback) { function(data, callback) {
SourceModel.replicate(TargetModel, callback); SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
} },
], function(err, result) { ], function(err, result) {
if (err) return done(err); if (err) return done(err);
expect(calls).to.eql(['rectifyChange']); expect(calls).to.eql(['rectifyChange']);
@ -120,7 +121,8 @@ describe('Replication / Change APIs', function() {
}); });
}); });
it('rectifyOnSave for Create should call rectifyChange instead of rectifyAllChanges', function(done) { it('rectifyOnSave for Create should call rectifyChange instead of rectifyAllChanges',
function(done) {
var calls = mockTargetModelRectify(); var calls = mockTargetModelRectify();
var newData = [{ name: 'Janie', surname: 'Doe' }]; var newData = [{ name: 'Janie', surname: 'Doe' }];
async.waterfall([ async.waterfall([
@ -130,7 +132,7 @@ describe('Replication / Change APIs', function() {
function(data, callback) { function(data, callback) {
SourceModel.replicate(TargetModel, callback); SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
} },
], function(err, result) { ], function(err, result) {
if (err) return done(err); if (err) return done(err);
expect(calls).to.eql(['rectifyChange']); expect(calls).to.eql(['rectifyChange']);
@ -192,7 +194,6 @@ describe('Replication / Change APIs', function() {
if (err) return done(err); if (err) return done(err);
SourceModel.changes(FUTURE_CHECKPOINT, {}, function(err, changes) { SourceModel.changes(FUTURE_CHECKPOINT, {}, function(err, changes) {
if (err) return done(err); if (err) return done(err);
/*jshint -W030 */
expect(changes).to.be.empty; expect(changes).to.be.empty;
done(); done();
}); });
@ -201,11 +202,9 @@ describe('Replication / Change APIs', function() {
}); });
describe('Model.replicate(since, targetModel, options, callback)', function() { describe('Model.replicate(since, targetModel, options, callback)', function() {
function assertTargetModelEqualsSourceModel(conflicts, sourceModel, function assertTargetModelEqualsSourceModel(conflicts, sourceModel,
targetModel, done) { targetModel, done) {
var sourceData; var sourceData, targetData;
var targetData;
assert(conflicts.length === 0); assert(conflicts.length === 0);
async.parallel([ async.parallel([
@ -222,7 +221,7 @@ describe('Replication / Change APIs', function() {
targetData = result; targetData = result;
cb(); cb();
}); });
} },
], function(err) { ], function(err) {
if (err) return done(err); if (err) return done(err);
@ -289,7 +288,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.eql(['2']); expect(getIds(list)).to.eql(['2']);
next(); next();
}); });
} },
], done); ], done);
}); });
@ -321,7 +320,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.eql(['2']); expect(getIds(list)).to.eql(['2']);
next(); next();
}); });
} },
], done); ], done);
}); });
@ -433,7 +432,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list), 'target ids').to.eql(['init', 'racer']); expect(getIds(list), 'target ids').to.eql(['init', 'racer']);
next(); next();
}); });
} },
], done); ], done);
}); });
@ -453,11 +452,11 @@ describe('Replication / Change APIs', function() {
expect(conflicts, 'conflicts').to.eql([]); expect(conflicts, 'conflicts').to.eql([]);
expect(newCheckpoints, 'currentCheckpoints').to.eql({ expect(newCheckpoints, 'currentCheckpoints').to.eql({
source: sourceCp + 1, source: sourceCp + 1,
target: targetCp + 1 target: targetCp + 1,
}); });
cb(); cb();
}); });
} },
], done); ], done);
function bumpSourceCheckpoint(cb) { function bumpSourceCheckpoint(cb) {
@ -494,7 +493,7 @@ describe('Replication / Change APIs', function() {
done(); done();
}); });
}); });
} },
], done); ], done);
}); });
@ -540,7 +539,7 @@ describe('Replication / Change APIs', function() {
}, },
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1') verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done); ], done);
}); });
@ -581,7 +580,7 @@ describe('Replication / Change APIs', function() {
}, },
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1') verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done); ], done);
}); });
@ -626,7 +625,7 @@ describe('Replication / Change APIs', function() {
}, },
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1') verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done); ], done);
}); });
@ -657,7 +656,7 @@ describe('Replication / Change APIs', function() {
next(); next();
}, },
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1') verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done); ], done);
}); });
}); });
@ -686,7 +685,7 @@ describe('Replication / Change APIs', function() {
inst.name = 'target update'; inst.name = 'target update';
inst.save(cb); inst.save(cb);
}); });
} },
], function(err) { ], function(err) {
if (err) return done(err); if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) { SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -724,11 +723,11 @@ describe('Replication / Change APIs', function() {
this.conflict.models(function(err, source, target) { this.conflict.models(function(err, source, target) {
assert.deepEqual(source.toJSON(), { assert.deepEqual(source.toJSON(), {
id: test.model.id, id: test.model.id,
name: 'source update' name: 'source update',
}); });
assert.deepEqual(target.toJSON(), { assert.deepEqual(target.toJSON(), {
id: test.model.id, id: test.model.id,
name: 'target update' name: 'target update',
}); });
done(); done();
}); });
@ -758,7 +757,7 @@ describe('Replication / Change APIs', function() {
inst.name = 'target update'; inst.name = 'target update';
inst.save(cb); inst.save(cb);
}); });
} },
], function(err) { ], function(err) {
if (err) return done(err); if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) { SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -797,7 +796,7 @@ describe('Replication / Change APIs', function() {
assert.equal(source, null); assert.equal(source, null);
assert.deepEqual(target.toJSON(), { assert.deepEqual(target.toJSON(), {
id: test.model.id, id: test.model.id,
name: 'target update' name: 'target update',
}); });
done(); done();
}); });
@ -827,7 +826,7 @@ describe('Replication / Change APIs', function() {
if (err) return cb(err); if (err) return cb(err);
inst.remove(cb); inst.remove(cb);
}); });
} },
], function(err) { ], function(err) {
if (err) return done(err); if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) { SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -866,7 +865,7 @@ describe('Replication / Change APIs', function() {
assert.equal(target, null); assert.equal(target, null);
assert.deepEqual(source.toJSON(), { assert.deepEqual(source.toJSON(), {
id: test.model.id, id: test.model.id,
name: 'source update' name: 'source update',
}); });
done(); done();
}); });
@ -895,7 +894,7 @@ describe('Replication / Change APIs', function() {
if (err) return cb(err); if (err) return cb(err);
inst.remove(cb); inst.remove(cb);
}); });
} },
], function(err) { ], function(err) {
if (err) return done(err); if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) { SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -1070,7 +1069,7 @@ describe('Replication / Change APIs', function() {
}); });
}, },
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifySourceWasReplicated() verifySourceWasReplicated(),
], done); ], done);
}); });
@ -1107,7 +1106,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.not.contain(sourceInstance.id); expect(getIds(list)).to.not.contain(sourceInstance.id);
next(); next();
}); });
} },
], done); ], done);
}); });
@ -1119,7 +1118,7 @@ describe('Replication / Change APIs', function() {
updateSourceInstanceNameTo('updated'), updateSourceInstanceNameTo('updated'),
updateSourceInstanceNameTo('again'), updateSourceInstanceNameTo('again'),
replicateExpectingSuccess(), replicateExpectingSuccess(),
verifySourceWasReplicated() verifySourceWasReplicated(),
], done); ], done);
}); });
@ -1141,7 +1140,7 @@ describe('Replication / Change APIs', function() {
async.series([ async.series([
// Note that ClientA->Server was already replicated during setup // Note that ClientA->Server was already replicated during setup
replicateExpectingSuccess(Server, ClientB), replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB) verifySourceWasReplicated(ClientB),
], done); ], done);
}); });
@ -1158,7 +1157,7 @@ describe('Replication / Change APIs', function() {
replicateExpectingSuccess(ClientA, Server), replicateExpectingSuccess(ClientA, Server),
replicateExpectingSuccess(Server, ClientB), replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB) verifySourceWasReplicated(ClientB),
], done); ], done);
}); });
@ -1167,7 +1166,7 @@ describe('Replication / Change APIs', function() {
deleteSourceInstance(), deleteSourceInstance(),
replicateExpectingSuccess(ClientA, Server), replicateExpectingSuccess(ClientA, Server),
replicateExpectingSuccess(Server, ClientB), replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB) verifySourceWasReplicated(ClientB),
], done); ], done);
}); });
@ -1183,7 +1182,7 @@ describe('Replication / Change APIs', function() {
it('propagates CREATE', function(done) { it('propagates CREATE', function(done) {
async.series([ async.series([
sync(ClientA, Server), sync(ClientA, Server),
sync(ClientB, Server) sync(ClientB, Server),
], done); ], done);
}); });
@ -1207,7 +1206,6 @@ describe('Replication / Change APIs', function() {
// ClientB fetches the created & updated instance from the server // ClientB fetches the created & updated instance from the server
sync(ClientB, Server), sync(ClientB, Server),
], done); ], done);
}); });
it('does not report false conflicts', function(done) { it('does not report false conflicts', function(done) {
@ -1227,7 +1225,7 @@ describe('Replication / Change APIs', function() {
sync(ClientB, Server), sync(ClientB, Server),
// client A fetches the changes // client A fetches the changes
sync(ClientA, Server) sync(ClientA, Server),
], done); ], done);
}); });
@ -1325,7 +1323,7 @@ describe('Replication / Change APIs', function() {
// and sync back to ClientA too // and sync back to ClientA too
sync(ClientA, Server), sync(ClientA, Server),
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId) verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),
], cb); ], cb);
} }
@ -1363,7 +1361,7 @@ describe('Replication / Change APIs', function() {
// and sync back to ClientA too // and sync back to ClientA too
sync(ClientA, Server), sync(ClientA, Server),
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId) verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),
], cb); ], cb);
} }
@ -1383,11 +1381,10 @@ describe('Replication / Change APIs', function() {
// NOTE(bajtos) It's important to replicate from the client to the // NOTE(bajtos) It's important to replicate from the client to the
// server first, so that we can resolve any conflicts at the client // server first, so that we can resolve any conflicts at the client
replicateExpectingSuccess(client, server), replicateExpectingSuccess(client, server),
replicateExpectingSuccess(server, client) replicateExpectingSuccess(server, client),
], next); ], next);
}; };
} }
}); });
function updateSourceInstanceNameTo(value) { function updateSourceInstanceNameTo(value) {

View File

@ -120,7 +120,7 @@ describe('loopback.rest', function() {
var ds = app.dataSource('db', { connector: loopback.Memory }); var ds = app.dataSource('db', { connector: loopback.Memory });
var CustomModel = ds.createModel('CustomModel', var CustomModel = ds.createModel('CustomModel',
{ name: String }, { name: String },
{ http: { 'path': 'domain1/CustomModelPath' } { http: { 'path': 'domain1/CustomModelPath' },
}); });
app.model(CustomModel); app.model(CustomModel);
@ -133,7 +133,7 @@ describe('loopback.rest', function() {
var ds = app.dataSource('db', { connector: loopback.Memory }); var ds = app.dataSource('db', { connector: loopback.Memory });
var CustomModel = ds.createModel('CustomModel', var CustomModel = ds.createModel('CustomModel',
{ name: String }, { name: String },
{ http: { path: 'domain%20one/CustomModelPath' } { http: { path: 'domain%20one/CustomModelPath' },
}); });
app.model(CustomModel); app.model(CustomModel);
@ -164,7 +164,7 @@ describe('loopback.rest', function() {
}; };
loopback.remoteMethod(User.getToken, { loopback.remoteMethod(User.getToken, {
accepts: [{ type: 'object', http: { source: 'req' }}], accepts: [{ type: 'object', http: { source: 'req' }}],
returns: [{ type: 'object', name: 'id' }] returns: [{ type: 'object', name: 'id' }],
}); });
app.use(loopback.rest()); app.use(loopback.rest());
@ -247,8 +247,8 @@ describe('loopback.rest', function() {
loopback.remoteMethod(User.getToken, { loopback.remoteMethod(User.getToken, {
accepts: [], accepts: [],
returns: [ returns: [
{ type: 'object', name: 'id' } { type: 'object', name: 'id' },
] ],
}); });
}); });
@ -312,8 +312,8 @@ describe('loopback.rest', function() {
loopback.remoteMethod(User.getToken, { loopback.remoteMethod(User.getToken, {
accepts: [], accepts: [],
returns: [ returns: [
{ type: 'object', name: 'id' } { type: 'object', name: 'id' },
] ],
}); });
invokeGetToken(done); invokeGetToken(done);
@ -326,9 +326,9 @@ describe('loopback.rest', function() {
// the global model registry // the global model registry
app.model('accessToken', { app.model('accessToken', {
options: { options: {
base: 'AccessToken' base: 'AccessToken',
}, },
dataSource: 'db' dataSource: 'db',
}); });
return app.model('user', { return app.model('user', {
options: { options: {
@ -337,11 +337,11 @@ describe('loopback.rest', function() {
accessTokens: { accessTokens: {
model: 'accessToken', model: 'accessToken',
type: 'hasMany', type: 'hasMany',
foreignKey: 'userId' foreignKey: 'userId',
}
}
}, },
dataSource: 'db' },
},
dataSource: 'db',
}); });
} }
function givenLoggedInUser(cb, done) { function givenLoggedInUser(cb, done) {
@ -391,7 +391,7 @@ describe('loopback.rest', function() {
app.models.Todo.create([ app.models.Todo.create([
{ content: 'a' }, { content: 'a' },
{ content: 'b' }, { content: 'b' },
{content: 'c'} { content: 'c' },
], function() { ], function() {
request(app) request(app)
.del('/todos') .del('/todos')
@ -437,7 +437,7 @@ describe('loopback.rest', function() {
app.models.Todo.create([ app.models.Todo.create([
{ content: 'a' }, { content: 'a' },
{ content: 'b' }, { content: 'b' },
{content: 'c'} { content: 'c' },
], function() { ], function() {
request(app) request(app)
.del('/todos') .del('/todos')

View File

@ -38,7 +38,8 @@ describe('role model', function() {
it('should define role/role relations', function() { it('should define role/role relations', function() {
Role.create({ name: 'user' }, function(err, userRole) { Role.create({ name: 'user' }, function(err, userRole) {
Role.create({ name: 'admin' }, function(err, adminRole) { Role.create({ name: 'admin' }, function(err, adminRole) {
userRole.principals.create({principalType: RoleMapping.ROLE, principalId: adminRole.id}, function(err, mapping) { userRole.principals.create({ principalType: RoleMapping.ROLE, principalId: adminRole.id },
function(err, mapping) {
Role.find(function(err, roles) { Role.find(function(err, roles) {
assert.equal(roles.length, 2); assert.equal(roles.length, 2);
}); });
@ -56,15 +57,14 @@ describe('role model', function() {
}); });
}); });
}); });
}); });
it('should define role/user relations', function() { it('should define role/user relations', function() {
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
// console.log('User: ', user.id); // console.log('User: ', user.id);
Role.create({ name: 'userRole' }, function(err, role) { Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
Role.find(function(err, roles) { Role.find(function(err, roles) {
assert(!err); assert(!err);
assert.equal(roles.length, 1); assert.equal(roles.length, 1);
@ -85,16 +85,15 @@ describe('role model', function() {
}); });
}); });
}); });
}); });
it('should automatically generate role id', function() { it('should automatically generate role id', function() {
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
// console.log('User: ', user.id); // console.log('User: ', user.id);
Role.create({ name: 'userRole' }, function(err, role) { Role.create({ name: 'userRole' }, function(err, role) {
assert(role.id); assert(role.id);
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
assert(p.id); assert(p.id);
assert.equal(p.roleId, role.id); assert.equal(p.roleId, role.id);
Role.find(function(err, roles) { Role.find(function(err, roles) {
@ -117,45 +116,52 @@ describe('role model', function() {
}); });
}); });
}); });
}); });
it('should support getRoles() and isInRole()', function() { it('should support getRoles() and isInRole()', function() {
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
// console.log('User: ', user.id); // console.log('User: ', user.id);
Role.create({ name: 'userRole' }, function(err, role) { Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
// Role.find(console.log); // Role.find(console.log);
// role.principals(console.log); // role.principals(console.log);
Role.isInRole('userRole', {principalType: RoleMapping.USER, principalId: user.id}, function(err, exists) { Role.isInRole('userRole', { principalType: RoleMapping.USER, principalId: user.id },
function(err, exists) {
assert(!err && exists === true); assert(!err && exists === true);
}); });
Role.isInRole('userRole', {principalType: RoleMapping.APP, principalId: user.id}, function(err, exists) { Role.isInRole('userRole', { principalType: RoleMapping.APP, principalId: user.id },
function(err, exists) {
assert(!err && exists === false); assert(!err && exists === false);
}); });
Role.isInRole('userRole', {principalType: RoleMapping.USER, principalId: 100}, function(err, exists) { Role.isInRole('userRole', { principalType: RoleMapping.USER, principalId: 100 },
function(err, exists) {
assert(!err && exists === false); assert(!err && exists === false);
}); });
Role.getRoles({principalType: RoleMapping.USER, principalId: user.id}, function(err, roles) { Role.getRoles({ principalType: RoleMapping.USER, principalId: user.id },
function(err, roles) {
assert.equal(roles.length, 3); // everyone, authenticated, userRole assert.equal(roles.length, 3); // everyone, authenticated, userRole
assert(roles.indexOf(role.id) >= 0); assert(roles.indexOf(role.id) >= 0);
assert(roles.indexOf(Role.EVERYONE) >= 0); assert(roles.indexOf(Role.EVERYONE) >= 0);
assert(roles.indexOf(Role.AUTHENTICATED) >= 0); assert(roles.indexOf(Role.AUTHENTICATED) >= 0);
}); });
Role.getRoles({principalType: RoleMapping.APP, principalId: user.id}, function(err, roles) { Role.getRoles({ principalType: RoleMapping.APP, principalId: user.id },
function(err, roles) {
assert.equal(roles.length, 2); assert.equal(roles.length, 2);
assert(roles.indexOf(Role.EVERYONE) >= 0); assert(roles.indexOf(Role.EVERYONE) >= 0);
assert(roles.indexOf(Role.AUTHENTICATED) >= 0); assert(roles.indexOf(Role.AUTHENTICATED) >= 0);
}); });
Role.getRoles({principalType: RoleMapping.USER, principalId: 100}, function(err, roles) { Role.getRoles({ principalType: RoleMapping.USER, principalId: 100 },
function(err, roles) {
assert.equal(roles.length, 2); assert.equal(roles.length, 2);
assert(roles.indexOf(Role.EVERYONE) >= 0); assert(roles.indexOf(Role.EVERYONE) >= 0);
assert(roles.indexOf(Role.AUTHENTICATED) >= 0); assert(roles.indexOf(Role.AUTHENTICATED) >= 0);
}); });
Role.getRoles({principalType: RoleMapping.USER, principalId: null}, function(err, roles) { Role.getRoles({ principalType: RoleMapping.USER, principalId: null },
function(err, roles) {
assert.equal(roles.length, 2); assert.equal(roles.length, 2);
assert(roles.indexOf(Role.EVERYONE) >= 0); assert(roles.indexOf(Role.EVERYONE) >= 0);
assert(roles.indexOf(Role.UNAUTHENTICATED) >= 0); assert(roles.indexOf(Role.UNAUTHENTICATED) >= 0);
@ -163,54 +169,60 @@ describe('role model', function() {
}); });
}); });
}); });
}); });
it('should support owner role resolver', function() { it('should support owner role resolver', function() {
var Album = ds.createModel('Album', { var Album = ds.createModel('Album', {
name: String, name: String,
userId: Number userId: Number,
}, { }, {
relations: { relations: {
user: { user: {
type: 'belongsTo', type: 'belongsTo',
model: 'User', model: 'User',
foreignKey: 'userId' foreignKey: 'userId',
} },
} },
}); });
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: user.id}, function(err, yes) { Role.isInRole(Role.AUTHENTICATED, { principalType: ACL.USER, principalId: user.id },
function(err, yes) {
assert(!err && yes); assert(!err && yes);
}); });
Role.isInRole(Role.AUTHENTICATED, {principalType: ACL.USER, principalId: null}, function(err, yes) { Role.isInRole(Role.AUTHENTICATED, { principalType: ACL.USER, principalId: null },
function(err, yes) {
assert(!err && !yes); assert(!err && !yes);
}); });
Role.isInRole(Role.UNAUTHENTICATED, {principalType: ACL.USER, principalId: user.id}, function(err, yes) { Role.isInRole(Role.UNAUTHENTICATED, { principalType: ACL.USER, principalId: user.id },
function(err, yes) {
assert(!err && !yes); assert(!err && !yes);
}); });
Role.isInRole(Role.UNAUTHENTICATED, {principalType: ACL.USER, principalId: null}, function(err, yes) { Role.isInRole(Role.UNAUTHENTICATED, { principalType: ACL.USER, principalId: null },
function(err, yes) {
assert(!err && yes); assert(!err && yes);
}); });
Role.isInRole(Role.EVERYONE, {principalType: ACL.USER, principalId: user.id}, function(err, yes) { Role.isInRole(Role.EVERYONE, { principalType: ACL.USER, principalId: user.id },
function(err, yes) {
assert(!err && yes); assert(!err && yes);
}); });
Role.isInRole(Role.EVERYONE, {principalType: ACL.USER, principalId: null}, function(err, yes) { Role.isInRole(Role.EVERYONE, { principalType: ACL.USER, principalId: null },
function(err, yes) {
assert(!err && yes); assert(!err && yes);
}); });
// console.log('User: ', user.id); // console.log('User: ', user.id);
Album.create({ name: 'Album 1', userId: user.id }, function(err, album1) { Album.create({ name: 'Album 1', userId: user.id }, function(err, album1) {
Role.isInRole(Role.OWNER, {principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id}, function(err, yes) { var role = { principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id };
Role.isInRole(Role.OWNER, role, function(err, yes) {
assert(!err && yes); assert(!err && yes);
}); });
Album.create({ name: 'Album 2' }, function(err, album2) { Album.create({ name: 'Album 2' }, function(err, album2) {
Role.isInRole(Role.OWNER, {principalType: ACL.USER, principalId: user.id, model: Album, id: album2.id}, function(err, yes) { role = { principalType: ACL.USER, principalId: user.id, model: Album, id: album2.id };
Role.isInRole(Role.OWNER, role, function(err, yes) {
assert(!err && !yes); assert(!err && !yes);
}); });
}); });
@ -225,35 +237,35 @@ describe('role model', function() {
User.create({ User.create({
username: 'john', username: 'john',
email: 'john@gmail.com', email: 'john@gmail.com',
password: 'jpass' password: 'jpass',
}, function(err, u) { }, function(err, u) {
if (err) return done(err); if (err) return done(err);
user = u; user = u;
User.create({ User.create({
username: 'mary', username: 'mary',
email: 'mary@gmail.com', email: 'mary@gmail.com',
password: 'mpass' password: 'mpass',
}, function(err, u) { }, function(err, u) {
if (err) return done(err); if (err) return done(err);
Application.create({ Application.create({
name: 'demo' name: 'demo',
}, function(err, a) { }, function(err, a) {
if (err) return done(err); if (err) return done(err);
app = a; app = a;
Role.create({ Role.create({
name: 'admin' name: 'admin',
}, function(err, r) { }, function(err, r) {
if (err) return done(err); if (err) return done(err);
role = r; role = r;
var principals = [ var principals = [
{ {
principalType: ACL.USER, principalType: ACL.USER,
principalId: user.id principalId: user.id,
}, },
{ {
principalType: ACL.APP, principalType: ACL.APP,
principalId: app.id principalId: app.id,
} },
]; ];
async.each(principals, function(p, done) { async.each(principals, function(p, done) {
role.principals.create(p, done); role.principals.create(p, done);
@ -344,7 +356,6 @@ describe('role model', function() {
done(); done();
}); });
}); });
}); });
describe('listByPrincipalType', function() { describe('listByPrincipalType', function() {
@ -373,7 +384,8 @@ describe('role model', function() {
var Model = principalTypesToModels[principalType]; var Model = principalTypesToModels[principalType];
Model.create({ name: 'test', email: 'x@y.com', password: 'foobar' }, function(err, model) { Model.create({ name: 'test', email: 'x@y.com', password: 'foobar' }, function(err, model) {
Role.create({ name: 'testRole' }, function(err, role) { Role.create({ name: 'testRole' }, function(err, role) {
role.principals.create({principalType: principalType, principalId: model.id}, function(err, p) { role.principals.create({ principalType: principalType, principalId: model.id },
function(err, p) {
var pluralName = Model.pluralModelName.toLowerCase(); var pluralName = Model.pluralModelName.toLowerCase();
role[pluralName](function(err, models) { role[pluralName](function(err, models) {
assert(!err); assert(!err);
@ -391,7 +403,8 @@ describe('role model', function() {
it('should apply query', function(done) { it('should apply query', function(done) {
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
Role.create({ name: 'userRole' }, function(err, role) { Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) { role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
var query = { fields: ['id', 'name'] }; var query = { fields: ['id', 'name'] };
sandbox.spy(User, 'find'); sandbox.spy(User, 'find');
role.users(query, function(err, users) { role.users(query, function(err, users) {
@ -406,5 +419,4 @@ describe('role model', function() {
}); });
}); });
}); });
}); });

View File

@ -21,14 +21,14 @@ beforeEach(function() {
// setup default data sources // setup default data sources
loopback.setDefaultDataSourceForType('db', { loopback.setDefaultDataSourceForType('db', {
connector: loopback.Memory connector: loopback.Memory,
}); });
loopback.setDefaultDataSourceForType('mail', { loopback.setDefaultDataSourceForType('mail', {
connector: loopback.Mail, connector: loopback.Mail,
transports: [ transports: [
{type: 'STUB'} { type: 'STUB' },
] ],
}); });
}); });

View File

@ -1,4 +1,3 @@
/*jshint -W030 */
var loopback = require('../'); var loopback = require('../');
var lt = require('./helpers/loopback-testing-helper'); var lt = require('./helpers/loopback-testing-helper');
var path = require('path'); var path = require('path');
@ -7,7 +6,6 @@ var app = require(path.join(SIMPLE_APP, 'server/server.js'));
var expect = require('chai').expect; var expect = require('chai').expect;
describe('users - integration', function() { describe('users - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
before(function(done) { before(function(done) {
@ -30,8 +28,7 @@ describe('users - integration', function() {
}); });
describe('base-user', function() { describe('base-user', function() {
var userId; var userId, accessToken;
var accessToken;
it('should create a new user', function(done) { it('should create a new user', function(done) {
this.post('/api/users') this.post('/api/users')
@ -93,8 +90,7 @@ describe('users - integration', function() {
}); });
describe('sub-user', function() { describe('sub-user', function() {
var userId; var userId, accessToken;
var accessToken;
it('should create a new user', function(done) { it('should create a new user', function(done) {
var url = '/api/myUsers'; var url = '/api/myUsers';
@ -157,6 +153,4 @@ describe('users - integration', function() {
}); });
}); });
}); });
}); });

View File

@ -1,20 +1,22 @@
require('./support'); require('./support');
var loopback = require('../'); var loopback = require('../');
var User; var User, AccessToken;
var AccessToken;
var MailConnector = require('../lib/connectors/mail'); var MailConnector = require('../lib/connectors/mail');
var userMemory = loopback.createDataSource({ var userMemory = loopback.createDataSource({
connector: 'memory' connector: 'memory',
}); });
describe('User', function() { describe('User', function() {
var validCredentialsEmail = 'foo@bar.com'; var validCredentialsEmail = 'foo@bar.com';
var validCredentials = { email: validCredentialsEmail, password: 'bar' }; var validCredentials = { email: validCredentialsEmail, password: 'bar' };
var validCredentialsEmailVerified = {email: 'foo1@bar.com', password: 'bar1', emailVerified: true}; var validCredentialsEmailVerified = {
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true}; email: 'foo1@bar.com', password: 'bar1', emailVerified: true };
var validCredentialsEmailVerifiedOverREST = {
email: 'foo2@bar.com', password: 'bar2', emailVerified: true };
var validCredentialsWithTTL = { email: 'foo@bar.com', password: 'bar', ttl: 3600 }; var validCredentialsWithTTL = { email: 'foo@bar.com', password: 'bar', ttl: 3600 };
var validCredentialsWithTTLAndScope = {email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'}; var validCredentialsWithTTLAndScope = {
email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all' };
var validMixedCaseEmailCredentials = { email: 'Foo@bar.com', password: 'bar' }; var validMixedCaseEmailCredentials = { email: 'Foo@bar.com', password: 'bar' };
var invalidCredentials = { email: 'foo1@bar.com', password: 'invalid' }; var invalidCredentials = { email: 'foo1@bar.com', password: 'invalid' };
var incompleteCredentials = { password: 'bar1' }; var incompleteCredentials = { password: 'bar1' };
@ -37,7 +39,6 @@ describe('User', function() {
// allow many User.afterRemote's to be called // allow many User.afterRemote's to be called
User.setMaxListeners(0); User.setMaxListeners(0);
}); });
beforeEach(function(done) { beforeEach(function(done) {
@ -91,7 +92,7 @@ describe('User', function() {
it('credentials/challenges are object types', function(done) { it('credentials/challenges are object types', function(done) {
User.create({ email: 'f1@b.com', password: 'bar1', User.create({ email: 'f1@b.com', password: 'bar1',
credentials: { cert: 'xxxxx', key: '111' }, credentials: { cert: 'xxxxx', key: '111' },
challenges: {x: 'X', a: 1} challenges: { x: 'X', a: 1 },
}, function(err, user) { }, function(err, user) {
assert(!err); assert(!err);
User.findById(user.id, function(err, user) { User.findById(user.id, function(err, user) {
@ -112,7 +113,7 @@ describe('User', function() {
assert.equal(err.details.context, User.modelName); assert.equal(err.details.context, User.modelName);
assert.deepEqual(err.details.codes.email, [ assert.deepEqual(err.details.codes.email, [
'presence', 'presence',
'format.null' 'format.null',
]); ]);
done(); done();
@ -199,8 +200,7 @@ describe('User', function() {
}); });
describe('custom password hash', function() { describe('custom password hash', function() {
var defaultHashPassword; var defaultHashPassword, defaultValidatePassword;
var defaultValidatePassword;
beforeEach(function() { beforeEach(function() {
defaultHashPassword = User.hashPassword; defaultHashPassword = User.hashPassword;
@ -575,14 +575,17 @@ describe('User', function() {
it('Require valid and complete credentials for email verification error', function(done) { it('Require valid and complete credentials for email verification error', function(done) {
User.login({ email: validCredentialsEmail }, function(err, accessToken) { User.login({ email: validCredentialsEmail }, function(err, accessToken) {
// strongloop/loopback#931 // strongloop/loopback#931
// error message should be "login failed" and not "login failed as the email has not been verified" // error message should be "login failed"
assert(err && !/verified/.test(err.message), ('expecting "login failed" error message, received: "' + err.message + '"')); // and not "login failed as the email has not been verified"
assert(err && !/verified/.test(err.message),
'expecting "login failed" error message, received: "' + err.message + '"');
assert.equal(err.code, 'LOGIN_FAILED'); assert.equal(err.code, 'LOGIN_FAILED');
done(); done();
}); });
}); });
it('Require valid and complete credentials for email verification error - promise variant', function(done) { it('Require valid and complete credentials for email verification error - promise variant',
function(done) {
User.login({ email: validCredentialsEmail }) User.login({ email: validCredentialsEmail })
.then(function(accessToken) { .then(function(accessToken) {
done(); done();
@ -590,7 +593,8 @@ describe('User', function() {
.catch(function(err) { .catch(function(err) {
// strongloop/loopback#931 // strongloop/loopback#931
// error message should be "login failed" and not "login failed as the email has not been verified" // error message should be "login failed" and not "login failed as the email has not been verified"
assert(err && !/verified/.test(err.message), ('expecting "login failed" error message, received: "' + err.message + '"')); assert(err && !/verified/.test(err.message),
'expecting "login failed" error message, received: "' + err.message + '"');
assert.equal(err.code, 'LOGIN_FAILED'); assert.equal(err.code, 'LOGIN_FAILED');
done(); done();
}); });
@ -653,7 +657,9 @@ describe('User', function() {
}); });
}); });
it('Login a user over REST require complete and valid credentials for email verification error message', function(done) { it('Login user over REST require complete and valid credentials ' +
'for email verification error message',
function(done) {
request(app) request(app)
.post('/test-users/login') .post('/test-users/login')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
@ -664,9 +670,11 @@ describe('User', function() {
return done(err); return done(err);
} }
// strongloop/loopback#931 // strongloop/loopback#931
// error message should be "login failed" and not "login failed as the email has not been verified" // error message should be "login failed"
// and not "login failed as the email has not been verified"
var errorResponse = res.body.error; var errorResponse = res.body.error;
assert(errorResponse && !/verified/.test(errorResponse.message), ('expecting "login failed" error message, received: "' + errorResponse.message + '"')); assert(errorResponse && !/verified/.test(errorResponse.message),
'expecting "login failed" error message, received: "' + errorResponse.message + '"');
assert.equal(errorResponse.code, 'LOGIN_FAILED'); assert.equal(errorResponse.code, 'LOGIN_FAILED');
done(); done();
}); });
@ -687,12 +695,10 @@ describe('User', function() {
done(); done();
}); });
}); });
}); });
describe('User.login requiring realm', function() { describe('User.login requiring realm', function() {
var User; var User, AccessToken;
var AccessToken;
before(function() { before(function() {
User = loopback.User.extend('RealmUser', {}, User = loopback.User.extend('RealmUser', {},
@ -713,53 +719,53 @@ describe('User', function() {
realm: 'realm1', realm: 'realm1',
username: 'foo100', username: 'foo100',
email: 'foo100@bar.com', email: 'foo100@bar.com',
password: 'pass100' password: 'pass100',
}; };
var realm2User = { var realm2User = {
realm: 'realm2', realm: 'realm2',
username: 'foo100', username: 'foo100',
email: 'foo100@bar.com', email: 'foo100@bar.com',
password: 'pass200' password: 'pass200',
}; };
var credentialWithoutRealm = { var credentialWithoutRealm = {
username: 'foo100', username: 'foo100',
email: 'foo100@bar.com', email: 'foo100@bar.com',
password: 'pass100' password: 'pass100',
}; };
var credentialWithBadPass = { var credentialWithBadPass = {
realm: 'realm1', realm: 'realm1',
username: 'foo100', username: 'foo100',
email: 'foo100@bar.com', email: 'foo100@bar.com',
password: 'pass001' password: 'pass001',
}; };
var credentialWithBadRealm = { var credentialWithBadRealm = {
realm: 'realm3', realm: 'realm3',
username: 'foo100', username: 'foo100',
email: 'foo100@bar.com', email: 'foo100@bar.com',
password: 'pass100' password: 'pass100',
}; };
var credentialWithRealm = { var credentialWithRealm = {
realm: 'realm1', realm: 'realm1',
username: 'foo100', username: 'foo100',
password: 'pass100' password: 'pass100',
}; };
var credentialRealmInUsername = { var credentialRealmInUsername = {
username: 'realm1:foo100', username: 'realm1:foo100',
password: 'pass100' password: 'pass100',
}; };
var credentialRealmInEmail = { var credentialRealmInEmail = {
email: 'realm1:foo100@bar.com', email: 'realm1:foo100@bar.com',
password: 'pass100' password: 'pass100',
}; };
var user1; var user1 = null;
beforeEach(function(done) { beforeEach(function(done) {
User.create(realm1User, function(err, u) { User.create(realm1User, function(err, u) {
if (err) { if (err) {
@ -868,7 +874,8 @@ describe('User', function() {
} }
}); });
it('Logout a user by providing the current accessToken id (using node) - promise variant', function(done) { it('Logout a user by providing the current accessToken id (using node) - promise variant',
function(done) {
login(logout); login(logout);
function login(fn) { function login(fn) {
@ -989,7 +996,6 @@ describe('User', function() {
}); });
describe('Verification', function() { describe('Verification', function() {
describe('user.verify(options, fn)', function() { describe('user.verify(options, fn)', function() {
it('Verify a user\'s email address', function(done) { it('Verify a user\'s email address', function(done) {
User.afterRemote('create', function(ctx, user, next) { User.afterRemote('create', function(ctx, user, next) {
@ -1001,7 +1007,7 @@ describe('User', function() {
from: 'noreply@myapp.org', from: 'noreply@myapp.org',
redirect: '/', redirect: '/',
protocol: ctx.req.protocol, protocol: ctx.req.protocol,
host: ctx.req.get('host') host: ctx.req.get('host'),
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1037,7 +1043,7 @@ describe('User', function() {
from: 'noreply@myapp.org', from: 'noreply@myapp.org',
redirect: '/', redirect: '/',
protocol: ctx.req.protocol, protocol: ctx.req.protocol,
host: ctx.req.get('host') host: ctx.req.get('host'),
}; };
user.verify(options) user.verify(options)
@ -1078,7 +1084,7 @@ describe('User', function() {
redirect: '/', redirect: '/',
protocol: ctx.req.protocol, protocol: ctx.req.protocol,
host: ctx.req.get('host'), host: ctx.req.get('host'),
headers: {'message-id':'custom-header-value'} headers: { 'message-id': 'custom-header-value' },
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1120,7 +1126,7 @@ describe('User', function() {
process.nextTick(function() { process.nextTick(function() {
cb(null, 'token-123456'); cb(null, 'token-123456');
}); });
} },
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1162,7 +1168,7 @@ describe('User', function() {
process.nextTick(function() { process.nextTick(function() {
cb(new Error('Fake error')); cb(new Error('Fake error'));
}); });
} },
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1197,7 +1203,7 @@ describe('User', function() {
redirect: '/', redirect: '/',
protocol: 'http', protocol: 'http',
host: 'myapp.org', host: 'myapp.org',
port: 3000 port: 3000,
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1230,7 +1236,7 @@ describe('User', function() {
redirect: '/', redirect: '/',
protocol: 'http', protocol: 'http',
host: 'myapp.org', host: 'myapp.org',
port: 80 port: 80,
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1263,7 +1269,7 @@ describe('User', function() {
redirect: '/', redirect: '/',
protocol: 'https', protocol: 'https',
host: 'myapp.org', host: 'myapp.org',
port: 3000 port: 3000,
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1296,7 +1302,7 @@ describe('User', function() {
redirect: '/', redirect: '/',
protocol: 'https', protocol: 'https',
host: 'myapp.org', host: 'myapp.org',
port: 443 port: 443,
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1320,7 +1326,11 @@ describe('User', function() {
}); });
it('should hide verification tokens from user JSON', function(done) { it('should hide verification tokens from user JSON', function(done) {
var user = new User({email: 'bar@bat.com', password: 'bar', verificationToken: 'a-token' }); var user = new User({
email: 'bar@bat.com',
password: 'bar',
verificationToken: 'a-token',
});
var data = user.toJSON(); var data = user.toJSON();
assert(!('verificationToken' in data)); assert(!('verificationToken' in data));
done(); done();
@ -1340,7 +1350,7 @@ describe('User', function() {
from: 'noreply@myapp.org', from: 'noreply@myapp.org',
redirect: 'http://foo.com/bar', redirect: 'http://foo.com/bar',
protocol: ctx.req.protocol, protocol: ctx.req.protocol,
host: ctx.req.get('host') host: ctx.req.get('host'),
}; };
user.verify(options, function(err, result) { user.verify(options, function(err, result) {
@ -1478,7 +1488,7 @@ describe('User', function() {
var calledBack = false; var calledBack = false;
User.resetPassword({ User.resetPassword({
email: email email: email,
}, function() { }, function() {
calledBack = true; calledBack = true;
}); });

View File

@ -7,11 +7,8 @@ var PersistedModel = loopback.PersistedModel;
var RemoteObjects = require('strong-remoting'); var RemoteObjects = require('strong-remoting');
module.exports = function defineModelTestsWithDataSource(options) { module.exports = function defineModelTestsWithDataSource(options) {
describe('Model Tests', function() { describe('Model Tests', function() {
var User, dataSource;
var User;
var dataSource;
if (options.beforeEach) { if (options.beforeEach) {
beforeEach(options.beforeEach); beforeEach(options.beforeEach);
@ -44,9 +41,9 @@ module.exports = function defineModelTestsWithDataSource(options) {
'password': String, 'password': String,
'gender': String, 'gender': String,
'domain': String, 'domain': String,
'email': String 'email': String,
}, { }, {
trackChanges: true trackChanges: true,
}); });
User.attachTo(dataSource); User.attachTo(dataSource);
@ -131,7 +128,8 @@ module.exports = function defineModelTestsWithDataSource(options) {
}); });
describe('Model.create([data], [callback])', function() { describe('Model.create([data], [callback])', function() {
it('Create an instance of Model with given data and save to the attached data source', function(done) { it('Create an instance of Model with given data and save to the attached data source',
function(done) {
User.create({ first: 'Joe', last: 'Bob' }, function(err, user) { User.create({ first: 'Joe', last: 'Bob' }, function(err, user) {
assert(user instanceof User); assert(user instanceof User);
done(); done();
@ -159,7 +157,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
user.updateAttributes({ user.updateAttributes({
first: 'updatedFirst', first: 'updatedFirst',
last: 'updatedLast' last: 'updatedLast',
}, function(err, updatedUser) { }, function(err, updatedUser) {
assert(!err); assert(!err);
assert.equal(updatedUser.first, 'updatedFirst'); assert.equal(updatedUser.first, 'updatedFirst');
@ -247,7 +245,5 @@ module.exports = function defineModelTestsWithDataSource(options) {
}); });
}); });
}); });
}); });
}; };