Merge pull request #2193 from strongloop/feature/eslint

Use eslint with loopback config
This commit is contained in:
Miroslav Bajtoš 2016-04-06 13:05:01 +02:00
commit 1e7adb21ae
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) {
}); });
}); });
}); });
}); });
}; };