Use eslint with loopback config

Drop jshint and jscs in favour of eslint.

Fix style violations.

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

2
.eslintignore Normal file
View File

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

10
.eslintrc Normal file
View File

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

View File

@ -1 +0,0 @@
node_modules

View File

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

View File

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

View File

@ -27,7 +27,6 @@ var DEFAULT_TOKEN_LEN = 64;
*/
module.exports = function(AccessToken) {
// Workaround for https://github.com/strongloop/loopback/issues/292
AccessToken.definition.rawProperties.created.default =
AccessToken.definition.properties.created.default = function() {
@ -42,7 +41,7 @@ module.exports = function(AccessToken) {
* ```
*/
AccessToken.ANONYMOUS = new AccessToken({id: '$anonymous'});
AccessToken.ANONYMOUS = new AccessToken({ id: '$anonymous' });
/**
* Create a cryptographically random access token id.
@ -165,8 +164,7 @@ module.exports = function(AccessToken) {
var headers = options.headers || [];
var cookies = options.cookies || [];
var i = 0;
var length;
var id;
var length, id;
// https://github.com/strongloop/loopback/issues/1326
if (options.searchDefaultTokenKeys !== false) {

View File

@ -78,7 +78,6 @@ assert(Role, 'Role model must be defined before ACL model');
*/
module.exports = function(ACL) {
ACL.ALL = AccessContext.ALL;
ACL.DEFAULT = AccessContext.DEFAULT; // Not specified
@ -112,7 +111,8 @@ module.exports = function(ACL) {
score = score * 4;
var ruleValue = rule[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;
if (props[i] === 'accessType' && !isMatchingAccessType) {
@ -278,7 +278,7 @@ module.exports = function(ACL) {
principalType: acl.principalType,
principalId: acl.principalId, // TODO: Should it be a name?
accessType: acl.accessType || ACL.ALL,
permission: acl.permission
permission: acl.permission,
}));
}
});
@ -300,7 +300,7 @@ module.exports = function(ACL) {
principalType: acl.principalType,
principalId: acl.principalId,
accessType: acl.accessType,
permission: acl.permission
permission: acl.permission,
}));
});
}
@ -325,9 +325,10 @@ module.exports = function(ACL) {
principalId = principalId.toString();
}
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;
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);
@ -345,8 +346,8 @@ module.exports = function(ACL) {
}
var self = this;
this.find({where: {principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery}},
this.find({ where: { principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery }},
function(err, dynACLs) {
if (err) {
if (callback) callback(err);
@ -399,13 +400,13 @@ module.exports = function(ACL) {
var modelName = context.modelName;
var methodNames = context.methodNames;
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
var propertyQuery = (property === ACL.ALL) ? undefined : { inq: methodNames.concat([ACL.ALL]) };
var accessTypeQuery = (accessType === ACL.ALL) ?
undefined :
(accessType === ACL.REPLICATE) ?
{inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} :
{inq: [accessType, ACL.ALL]};
{ inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL] } :
{ inq: [accessType, ACL.ALL] };
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
@ -414,8 +415,8 @@ module.exports = function(ACL) {
var self = this;
var roleModel = registry.getModelByType(Role);
this.find({where: {model: model.modelName, property: propertyQuery,
accessType: accessTypeQuery}}, function(err, acls) {
this.find({ where: { model: model.modelName, property: propertyQuery,
accessType: accessTypeQuery }}, function(err, acls) {
if (err) {
if (callback) callback(err);
return;
@ -485,7 +486,7 @@ module.exports = function(ACL) {
model: model,
property: method,
method: method,
modelId: modelId
modelId: modelId,
});
this.checkAccessForContext(context, function(err, access) {
@ -518,15 +519,15 @@ module.exports = function(ACL) {
this.resolveRelatedModels();
switch (type) {
case ACL.ROLE:
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
this.roleModel.findOne({ where: { or: [{ name: id }, { id: id }] }}, cb);
break;
case ACL.USER:
this.userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb);
{ where: { or: [{ username: id }, { email: id }, { id: id }] }}, cb);
break;
case ACL.APP:
this.applicationModel.findOne(
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
{ where: { or: [{ name: id }, { email: id }, { id: id }] }}, cb);
break;
default:
process.nextTick(function() {
@ -559,8 +560,8 @@ module.exports = function(ACL) {
where: {
roleId: role.id,
principalType: principalType,
principalId: String(principalId)
}
principalId: String(principalId),
},
}, function(err, result) {
if (err) return cb(err);
return cb(null, !!result);

View File

@ -65,7 +65,6 @@ function generateKey(hmacKey, algorithm, encoding) {
*/
module.exports = function(Application) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Application.definition.rawProperties.created.default =
Application.definition.properties.created.default = function() {
@ -122,7 +121,7 @@ module.exports = function(Application) {
}
cb = cb || utils.createPromiseCallback();
var props = {owner: owner, name: name};
var props = { owner: owner, name: name };
for (var p in options) {
if (!(p in props)) {
props[p] = options[p];
@ -195,7 +194,7 @@ module.exports = function(Application) {
if (app[keyNames[i]] === key) {
result = {
application: app,
keyType: keyNames[i]
keyType: keyNames[i],
};
break;
}

View File

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

View File

@ -16,7 +16,6 @@ var assert = require('assert');
*/
module.exports = function(Checkpoint) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Checkpoint.definition.rawProperties.time.default =
Checkpoint.definition.properties.time.default = function() {
@ -37,8 +36,8 @@ module.exports = function(Checkpoint) {
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
var query = { limit: 1 }; // match all instances, return only one
var initialData = { seq: 1 };
this.findOrCreate(query, initialData, cb);
};
@ -55,7 +54,7 @@ module.exports = function(Checkpoint) {
var originalSeq = cp.seq;
cp.seq++;
// Update the checkpoint but only if it was not changed under our hands
Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) {
Checkpoint.updateAll({ id: cp.id, seq: originalSeq }, { seq: cp.seq }, function(err, info) {
if (err) return cb(err);
// possible outcomes
// 1) seq was updated to seq+1 - exactly what we wanted!

View File

@ -11,7 +11,6 @@
*/
module.exports = function(Email) {
/**
* 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
*/
module.exports = function(Role) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.created.default =
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
Role.once('dataSourceAttached', function(roleModel) {
['users', 'applications', 'roles'].forEach(function(rel) {
/**
* Fetch all users assigned to this role
@ -64,14 +62,14 @@ module.exports = function(Role) {
var relsToModels = {
users: roleModel.userModel,
applications: roleModel.applicationModel,
roles: roleModel
roles: roleModel,
};
var ACL = loopback.ACL;
var relsToTypes = {
users: ACL.USER,
applications: ACL.APP,
roles: ACL.ROLE
roles: ACL.ROLE,
};
var model = relsToModels[rel];
@ -94,7 +92,7 @@ module.exports = function(Role) {
}
roleModel.roleMappingModel.find({
where: {roleId: this.id, principalType: principalType}
where: { roleId: this.id, principalType: principalType },
}, function(err, mappings) {
var ids;
if (err) {
@ -104,13 +102,12 @@ module.exports = function(Role) {
return m.principalId;
});
query.where = query.where || {};
query.where.id = {inq: ids};
query.where.id = { inq: ids };
model.find(query, function(err, models) {
callback(err, models);
});
});
}
});
// Special roles
@ -305,7 +302,6 @@ module.exports = function(Role) {
}
var inRole = context.principals.some(function(p) {
var principalType = p.type || undefined;
var principalId = p.id || undefined;
@ -322,7 +318,7 @@ module.exports = function(Role) {
}
var roleMappingModel = this.roleMappingModel;
this.findOne({where: {name: role}}, function(err, result) {
this.findOne({ where: { name: role }}, function(err, result) {
if (err) {
if (callback) callback(err);
return;
@ -338,14 +334,15 @@ module.exports = function(Role) {
var principalType = p.type || undefined;
var principalId = p.id || undefined;
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();
}
if (principalType && principalId) {
roleMappingModel.findOne({where: {roleId: roleId,
principalType: principalType, principalId: principalId}},
roleMappingModel.findOne({ where: { roleId: roleId,
principalType: principalType, principalId: principalId }},
function(err, result) {
debug('Role mapping found: %j', result);
done(!err && result); // The only arg is the result
@ -360,7 +357,6 @@ module.exports = function(Role) {
if (callback) callback(null, inRole);
});
});
};
/**
@ -421,8 +417,8 @@ module.exports = function(Role) {
if (principalType && principalId) {
// Please find() treat undefined matches all values
inRoleTasks.push(function(done) {
roleMappingModel.find({where: {principalType: principalType,
principalId: principalId}}, function(err, mappings) {
roleMappingModel.find({ where: { principalType: principalType,
principalId: principalId }}, function(err, mappings) {
debug('Role mappings found: %s %j', err, mappings);
if (err) {
if (done) done(err);

View File

@ -36,7 +36,7 @@ module.exports = function(Scope) {
assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called');
this.findOne({where: {name: scope}}, function(err, scope) {
this.findOne({ where: { name: scope }}, function(err, scope) {
if (err) {
if (callback) callback(err);
} else {

View File

@ -69,7 +69,6 @@ var debug = require('debug')('loopback:user');
*/
module.exports = function(User) {
/**
* Create access token for the logged in user. This method can be overridden to
* customize how access tokens are generated
@ -99,7 +98,7 @@ module.exports = function(User) {
var userModel = this.constructor;
ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL);
this.accessTokens.create({
ttl: ttl
ttl: ttl,
}, cb);
return cb.promise;
};
@ -216,7 +215,7 @@ module.exports = function(User) {
return fn.promise;
}
self.findOne({where: query}, function(err, user) {
self.findOne({ where: query }, function(err, user) {
var defaultError = new Error('login failed');
defaultError.statusCode = 401;
defaultError.code = 'LOGIN_FAILED';
@ -369,11 +368,14 @@ module.exports = function(User) {
assert(typeof options === 'object', 'options required when calling user.verify()');
assert(options.type, 'You must supply a verification type (options.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()');
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.protocol = options.protocol || 'http';
@ -423,7 +425,8 @@ module.exports = function(User) {
function sendEmail(user) {
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);
@ -440,7 +443,7 @@ module.exports = function(User) {
if (err) {
fn(err);
} else {
fn(null, {email: email, token: user.verificationToken, uid: user.id});
fn(null, { email: email, token: user.verificationToken, uid: user.id });
}
});
}
@ -531,7 +534,7 @@ module.exports = function(User) {
return cb.promise;
}
UserModel.findOne({ where: {email: options.email} }, function(err, user) {
UserModel.findOne({ where: { email: options.email }}, function(err, user) {
if (err) {
return cb(err);
}
@ -543,7 +546,7 @@ module.exports = function(User) {
}
// create a short lived access token for temp login to change password
// TODO(ritch) - eventually this should only allow password change
user.accessTokens.create({ttl: ttl}, function(err, accessToken) {
user.accessTokens.create({ ttl: ttl }, function(err, accessToken) {
if (err) {
return cb(err);
}
@ -551,7 +554,7 @@ module.exports = function(User) {
UserModel.emit('resetPasswordRequest', {
email: options.email,
accessToken: accessToken,
user: user
user: user,
});
});
});
@ -633,10 +636,10 @@ module.exports = function(User) {
{
description: 'Login a user with username/email and password.',
accepts: [
{arg: 'credentials', type: 'object', required: true, http: {source: 'body'}},
{arg: 'include', type: ['string'], http: {source: 'query' },
{ arg: 'credentials', type: 'object', required: true, http: { source: 'body' }},
{ arg: 'include', type: ['string'], http: { source: 'query' },
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: {
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' +
'Depending on the value of `include` parameter, the body may contain ' +
'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' },
}
);
@ -655,17 +658,17 @@ module.exports = function(User) {
{
description: 'Logout a user with access token.',
accepts: [
{arg: 'access_token', type: 'string', required: true, http: function(ctx) {
{ arg: 'access_token', type: 'string', required: true, http: function(ctx) {
var req = ctx && ctx.req;
var accessToken = req && req.accessToken;
var tokenID = accessToken && accessToken.id;
return tokenID;
}, description: 'Do not supply this argument, it is automatically extracted ' +
'from request headers.'
}
'from request headers.',
},
],
http: {verb: 'all'}
http: { verb: 'all' },
}
);
@ -674,11 +677,11 @@ module.exports = function(User) {
{
description: 'Confirm a user registration with email verification token.',
accepts: [
{arg: 'uid', type: 'string', required: true},
{arg: 'token', type: 'string', required: true},
{arg: 'redirect', type: 'string'}
{ arg: 'uid', type: 'string', required: true },
{ arg: 'token', type: 'string', required: true },
{ 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.',
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' },
}
);
@ -714,12 +717,12 @@ module.exports = function(User) {
// email validation regex
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'});
UserModel.validatesFormatOf('email', { with: re, message: 'Must provide a valid email' });
// FIXME: We need to add support for uniqueness of composite keys in juggler
if (!(UserModel.settings.realmRequired || UserModel.settings.realmDelimiter)) {
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
UserModel.validatesUniquenessOf('email', { message: 'Email already exists' });
UserModel.validatesUniquenessOf('username', { message: 'User already exists' });
}
return UserModel;
@ -730,5 +733,4 @@ module.exports = function(User) {
*/
User.setup();
};

View File

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

View File

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

View File

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

View File

@ -4,16 +4,16 @@ var app = loopback();
app.use(loopback.rest());
var schema = {
name: String
name: String,
};
var Color = app.model('color', schema);
app.dataSource('db', {adapter: 'memory'}).attach(Color);
app.dataSource('db', { adapter: 'memory' }).attach(Color);
Color.create({name: 'red'});
Color.create({name: 'green'});
Color.create({name: 'blue'});
Color.create({ name: 'red' });
Color.create({ name: 'green' });
Color.create({ name: 'blue' });
app.listen(3000);

View File

@ -14,7 +14,7 @@ app.use(function saveHostToContext(req, res, next) {
app.use(loopback.rest());
var Color = loopback.createModel('color', { 'name': String });
Color.beforeRemote('**', function (ctx, unused, next) {
Color.beforeRemote('**', function(ctx, unused, next) {
// Inside LoopBack code, you can read the property from the context
var ns = loopback.getCurrentContext();
console.log('Request to host', ns && ns.get('host'));

View File

@ -5,41 +5,42 @@ var app = loopback();
app.use(loopback.rest());
var dataSource = loopback.createDataSource('db', {connector: loopback.Memory});
var dataSource = loopback.createDataSource('db', { connector: loopback.Memory });
var Application = models.Application(dataSource);
app.model(Application);
var data = {pushSettings: [
{ "platform": "apns",
"apns": {
"pushOptions": {
"gateway": "gateway.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem"
var data = { pushSettings: [
{ 'platform': 'apns',
'apns': {
'pushOptions': {
'gateway': 'gateway.sandbox.push.apple.com',
'cert': 'credentials/apns_cert_dev.pem',
'key': 'credentials/apns_key_dev.pem',
},
"feedbackOptions": {
"gateway": "feedback.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem",
"batchFeedback": true,
"interval": 300
}
}}
]}
'feedbackOptions': {
'gateway': 'feedback.sandbox.push.apple.com',
'cert': 'credentials/apns_cert_dev.pem',
'key': 'credentials/apns_key_dev.pem',
'batchFeedback': true,
'interval': 300,
},
}},
] };
Application.create(data, function(err, data) {
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());
result.resetKeys(function (err, result) {
result.resetKeys(function(err, result) {
console.log(result.toObject());
});
});

View File

@ -1,8 +1,8 @@
var loopback = require('../../');
var app = loopback();
var db = app.dataSource('db', {connector: loopback.Memory});
var Color = app.model('color', {dataSource: 'db', options: {trackChanges: true}});
var Color2 = app.model('color2', {dataSource: 'db', options: {trackChanges: true}});
var db = app.dataSource('db', { connector: loopback.Memory });
var Color = app.model('color', { dataSource: 'db', options: { trackChanges: true }});
var Color2 = app.model('color2', { dataSource: 'db', options: { trackChanges: true }});
var target = Color2;
var source = Color;
var SPEED = process.env.SPEED || 100;
@ -56,16 +56,16 @@ var steps = [
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data')
list.bind(this, target, 'current TARGET data'),
];
run(steps);
function createSomeInitialSourceData() {
Color.create([
{name: 'red'},
{name: 'blue'},
{name: 'green'}
{ name: 'red' },
{ name: 'blue' },
{ name: 'green' },
]);
}
@ -76,7 +76,7 @@ function replicateSourceToTarget() {
}
function resolveAllConflicts() {
if(conflicts.length) {
if (conflicts.length) {
conflicts.forEach(function(conflict) {
conflict.resolve();
});
@ -91,11 +91,11 @@ function updateSomeTargetData() {
}
function createMoreSourceData() {
Color.create({name: 'orange'});
Color.create({ name: 'orange' });
}
function createEvenMoreSourceData() {
Color.create({name: 'black'});
Color.create({ name: 'black' });
}
function updateSomeSourceDataCausingAConflict() {
@ -111,9 +111,9 @@ function deleteAllSourceData() {
function createSomeNewSourceData() {
Color.create([
{name: 'violet'},
{name: 'amber'},
{name: 'olive'}
{ name: 'violet' },
{ name: 'amber' },
{ name: 'olive' },
]);
}
@ -130,7 +130,7 @@ function list(model, msg) {
function run(steps) {
setInterval(function() {
var step = steps.shift();
if(step) {
if (step) {
console.log(step.name);
step();
}

View File

@ -4,17 +4,17 @@ var app = loopback();
app.use(loopback.rest());
var dataSource = app.dataSource('db', {adapter: 'memory'});
var dataSource = app.dataSource('db', { adapter: 'memory' });
var Color = dataSource.define('color', {
'name': String
'name': String,
});
Color.create({name: 'red'});
Color.create({name: 'green'});
Color.create({name: 'blue'});
Color.create({ name: 'red' });
Color.create({ name: 'green' });
Color.create({ name: 'blue' });
Color.all(function () {
Color.all(function() {
console.log(arguments);
});

View File

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

View File

@ -326,7 +326,7 @@ app.enableAuth = function(options) {
app.model(Model, {
dataSource: options.dataSource,
public: m === 'User'
public: m === 'User',
});
});
}
@ -365,20 +365,19 @@ app.enableAuth = function(options) {
} else if (allowed) {
next();
} else {
var messages = {
403: {
message: 'Access Denied',
code: 'ACCESS_DENIED'
code: 'ACCESS_DENIED',
},
404: {
message: ('could not find ' + modelName + ' with id ' + modelId),
code: 'MODEL_NOT_FOUND'
code: 'MODEL_NOT_FOUND',
},
401: {
message: 'Authorization Required',
code: 'AUTHORIZATION_REQUIRED'
}
code: 'AUTHORIZATION_REQUIRED',
},
};
var e = new Error(messages[errStatusCode].message || messages[403].message);
@ -480,7 +479,7 @@ function setSharedMethodSharedProperties(model, app, modelConfigs) {
});
// set sharedMethod.shared using the merged settings
var sharedMethods = model.sharedClass.methods({includeDisabled: true});
var sharedMethods = model.sharedClass.methods({ includeDisabled: true });
sharedMethods.forEach(function(sharedMethod) {
// use the specific setting if it exists
var hasSpecificSetting = settings.hasOwnProperty(sharedMethod.name);

View File

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

View File

@ -18,7 +18,6 @@ module.exports = MailConnector;
*/
function MailConnector(settings) {
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
var transports = settings.transports;
@ -156,7 +155,8 @@ Mailer.send = function(options, fn) {
}
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);
} else {
console.warn('Warning: No email transport specified for sending email.' +

View File

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

View File

@ -52,17 +52,17 @@ loopback.registry = new Registry();
Object.defineProperties(loopback, {
Model: {
get: function() { return this.registry.getModel('Model'); }
get: function() { return this.registry.getModel('Model'); },
},
PersistedModel: {
get: function() { return this.registry.getModel('PersistedModel'); }
get: function() { return this.registry.getModel('PersistedModel'); },
},
defaultDataSources: {
get: function() { return this.registry.defaultDataSources; }
get: function() { return this.registry.defaultDataSources; },
},
modelBuilder: {
get: function() { return this.registry.modelBuilder; }
}
get: function() { return this.registry.modelBuilder; },
},
});
/*!
@ -196,7 +196,7 @@ loopback.remoteMethod = function(fn, options) {
fn[key] = options[key];
});
}
fn.http = fn.http || {verb: 'get'};
fn.http = fn.http || { verb: 'get' };
};
/**
@ -213,7 +213,7 @@ loopback.template = function(file) {
var templates = this._templates || (this._templates = {});
var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
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;
module.exports = function(registry) {
/**
* The base class for **all models**.
*
@ -176,16 +175,16 @@ module.exports = function(registry) {
var idDesc = ModelCtor.modelName + ' id';
ModelCtor.sharedCtor.accepts = [
{arg: 'id', type: 'any', required: true, http: {source: 'path'},
description: idDesc}
{ arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: idDesc },
// {arg: 'instance', type: 'object', http: {source: 'body'}}
];
ModelCtor.sharedCtor.http = [
{path: '/:id'}
{ path: '/:id' },
];
ModelCtor.sharedCtor.returns = {root: true};
ModelCtor.sharedCtor.returns = { root: true };
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
@ -227,7 +226,6 @@ module.exports = function(registry) {
// resolve relation functions
sharedClass.resolve(function resolver(define) {
var relations = ModelCtor.relations || {};
// get the relations
@ -250,9 +248,11 @@ module.exports = function(registry) {
// handle scopes
var scopes = ModelCtor.scopes || {};
/* eslint-disable one-var */
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
/* eslint-enable one-var */
});
return ModelCtor;
@ -307,7 +307,7 @@ module.exports = function(registry) {
sharedMethod: sharedMethod,
modelId: modelId,
accessType: this._getAccessTypeForMethod(sharedMethod),
remotingContext: ctx
remotingContext: ctx,
}, function(err, accessRequest) {
if (err) return callback(err);
callback(null, accessRequest.isAllowed());
@ -323,7 +323,7 @@ module.exports = function(registry) {
Model._getAccessTypeForMethod = function(method) {
if (typeof method === 'string') {
method = {name: method};
method = { name: method };
}
assert(
typeof method === 'object',
@ -353,7 +353,7 @@ module.exports = function(registry) {
}
switch (method.name) {
case'create':
case 'create':
return ACL.WRITE;
case 'updateOrCreate':
return ACL.WRITE;
@ -442,11 +442,11 @@ module.exports = function(registry) {
var pathName = (relation.options.http && relation.options.http.path) || relationName;
define('__get__' + relationName, {
isStatic: false,
http: {verb: 'get', path: '/' + pathName},
accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
accessType: 'READ',
description: 'Fetches belongsTo relation ' + relationName + '.',
returns: {arg: relationName, type: modelName, root: true}
returns: { arg: relationName, type: modelName, root: true },
}, fn);
};
@ -467,37 +467,37 @@ module.exports = function(registry) {
define('__get__' + relationName, {
isStatic: false,
http: {verb: 'get', path: '/' + pathName},
accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
description: 'Fetches hasOne relation ' + relationName + '.',
accessType: 'READ',
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
returns: { arg: relationName, type: relation.modelTo.modelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
});
define('__create__' + relationName, {
isStatic: false,
http: {verb: 'post', path: '/' + pathName},
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
http: { verb: 'post', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Creates a new instance in ' + relationName + ' of this model.',
accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}
returns: { arg: 'data', type: toModelName, root: true },
});
define('__update__' + relationName, {
isStatic: false,
http: {verb: 'put', path: '/' + pathName},
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
http: { verb: 'put', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Update ' + relationName + ' of this model.',
accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}
returns: { arg: 'data', type: toModelName, root: true },
});
define('__destroy__' + relationName, {
isStatic: false,
http: {verb: 'delete', path: '/' + pathName},
http: { verb: 'delete', path: '/' + pathName },
description: 'Deletes ' + relationName + ' of this model.',
accessType: 'WRITE'
accessType: 'WRITE',
});
};
@ -508,41 +508,41 @@ module.exports = function(registry) {
var findByIdFunc = this.prototype['__findById__' + relationName];
define('__findById__' + relationName, {
isStatic: false,
http: {verb: 'get', path: '/' + pathName + '/:fk'},
accepts: {arg: 'fk', type: 'any',
http: { verb: 'get', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
http: { source: 'path' }},
description: 'Find a related item by id for ' + relationName + '.',
accessType: 'READ',
returns: {arg: 'result', type: toModelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}
returns: { arg: 'result', type: toModelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
}, findByIdFunc);
var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
define('__destroyById__' + relationName, {
isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/:fk'},
accepts: {arg: 'fk', type: 'any',
http: { verb: 'delete', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
http: { source: 'path' }},
description: 'Delete a related item by id for ' + relationName + '.',
accessType: 'WRITE',
returns: []
returns: [],
}, destroyByIdFunc);
var updateByIdFunc = this.prototype['__updateById__' + relationName];
define('__updateById__' + relationName, {
isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/:fk'},
http: { verb: 'put', path: '/' + pathName + '/:fk' },
accepts: [
{arg: 'fk', type: 'any',
{ arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
{arg: 'data', type: toModelName, http: {source: 'body'}}
http: { source: 'path' }},
{ arg: 'data', type: toModelName, http: { source: 'body' }},
],
description: 'Update a related item by id for ' + relationName + '.',
accessType: 'WRITE',
returns: {arg: 'result', type: toModelName, root: true}
returns: { arg: 'result', type: toModelName, root: true },
}, updateByIdFunc);
if (relation.modelThrough || relation.type === 'referencesMany') {
@ -551,31 +551,31 @@ module.exports = function(registry) {
var accepts = [];
if (relation.type === 'hasMany' && relation.modelThrough) {
// Restrict: only hasManyThrough relation can have additional properties
accepts.push({arg: 'data', type: modelThrough.modelName, http: {source: 'body'}});
accepts.push({ arg: 'data', type: modelThrough.modelName, http: { source: 'body' }});
}
var addFunc = this.prototype['__link__' + relationName];
define('__link__' + relationName, {
isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
accepts: [{arg: 'fk', type: 'any',
http: { verb: 'put', path: '/' + pathName + '/rel/:fk' },
accepts: [{ arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}}].concat(accepts),
http: { source: 'path' }}].concat(accepts),
description: 'Add a related item by id for ' + relationName + '.',
accessType: 'WRITE',
returns: {arg: relationName, type: modelThrough.modelName, root: true}
returns: { arg: relationName, type: modelThrough.modelName, root: true },
}, addFunc);
var removeFunc = this.prototype['__unlink__' + relationName];
define('__unlink__' + relationName, {
isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},
accepts: {arg: 'fk', type: 'any',
http: { verb: 'delete', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
http: { source: 'path' }},
description: 'Remove the ' + relationName + ' relation to an item by id.',
accessType: 'WRITE',
returns: []
returns: [],
}, removeFunc);
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
@ -583,13 +583,13 @@ module.exports = function(registry) {
var existsFunc = this.prototype['__exists__' + relationName];
define('__exists__' + relationName, {
isStatic: false,
http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},
accepts: {arg: 'fk', type: 'any',
http: { verb: 'head', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any',
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
http: { source: 'path' }},
description: 'Check the existence of ' + relationName + ' relation to an item by id.',
accessType: 'READ',
returns: {arg: 'exists', type: 'boolean', root: true},
returns: { arg: 'exists', type: 'boolean', root: true },
rest: {
// After hook to map exists to 200/404 for HEAD
after: function(ctx, cb) {
@ -604,8 +604,8 @@ module.exports = function(registry) {
} else {
cb();
}
}
}
},
},
}, existsFunc);
}
};
@ -628,38 +628,37 @@ module.exports = function(registry) {
define('__get__' + scopeName, {
isStatic: isStatic,
http: {verb: 'get', path: '/' + pathName},
accepts: {arg: 'filter', type: 'object'},
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'filter', type: 'object' },
description: 'Queries ' + scopeName + ' of ' + this.modelName + '.',
accessType: 'READ',
returns: {arg: scopeName, type: [toModelName], root: true}
returns: { arg: scopeName, type: [toModelName], root: true },
});
define('__create__' + scopeName, {
isStatic: isStatic,
http: {verb: 'post', path: '/' + pathName},
accepts: {arg: 'data', type: toModelName, http: {source: 'body'}},
http: { verb: 'post', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: 'Creates a new instance in ' + scopeName + ' of this model.',
accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}
returns: { arg: 'data', type: toModelName, root: true },
});
define('__delete__' + scopeName, {
isStatic: isStatic,
http: {verb: 'delete', path: '/' + pathName},
http: { verb: 'delete', path: '/' + pathName },
description: 'Deletes all ' + scopeName + ' of this model.',
accessType: 'WRITE'
accessType: 'WRITE',
});
define('__count__' + scopeName, {
isStatic: isStatic,
http: {verb: 'get', path: '/' + pathName + '/count'},
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
http: { verb: 'get', path: '/' + pathName + '/count' },
accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' },
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
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 http = [].concat(sharedToClass.http || [])[0];
var httpPath;
var acceptArgs;
var httpPath, acceptArgs;
if (relation.multiple) {
httpPath = pathName + '/:' + paramName;
@ -705,8 +703,8 @@ module.exports = function(registry) {
{
arg: paramName, type: 'any', http: { source: 'path' },
description: 'Foreign key for ' + relation.name + '.',
required: true
}
required: true,
},
];
} else {
httpPath = pathName;
@ -830,9 +828,10 @@ module.exports = function(registry) {
}
});
});
} 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,28 +555,30 @@ module.exports = function(registry) {
setRemoting(PersistedModel, 'create', {
description: 'Create a new instance of the model and persist it into the data source.',
accessType: 'WRITE',
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
returns: {arg: 'data', type: typeName, root: true},
http: {verb: 'post', path: '/'}
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
'Model instance data' },
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'post', path: '/' },
});
setRemoting(PersistedModel, 'upsert', {
aliases: ['updateOrCreate'],
description: 'Update an existing model instance or insert a new one into the data source.',
accessType: 'WRITE',
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
returns: {arg: 'data', type: typeName, root: true},
http: {verb: 'put', path: '/'}
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description:
'Model instance data' },
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'put', path: '/' },
});
setRemoting(PersistedModel, 'exists', {
description: 'Check whether a model instance exists in the data source.',
accessType: 'READ',
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true},
returns: {arg: 'exists', type: 'boolean'},
accepts: { arg: 'id', type: 'any', description: 'Model id', required: true },
returns: { arg: 'exists', type: 'boolean' },
http: [
{verb: 'get', path: '/:id/exists'},
{verb: 'head', path: '/:id'}
{ verb: 'get', path: '/:id/exists' },
{ verb: 'head', path: '/:id' },
],
rest: {
// After hook to map exists to 200/404 for HEAD
@ -596,8 +598,8 @@ module.exports = function(registry) {
} else {
cb();
}
}
}
},
},
});
setRemoting(PersistedModel, 'findById', {
@ -605,44 +607,46 @@ module.exports = function(registry) {
accessType: 'READ',
accepts: [
{ arg: 'id', type: 'any', description: 'Model id', required: true,
http: {source: 'path'}},
http: { source: 'path' }},
{ arg: 'filter', type: 'object',
description: 'Filter defining fields and include'}
description: 'Filter defining fields and include' },
],
returns: {arg: 'data', type: typeName, root: true},
http: {verb: 'get', path: '/:id'},
rest: {after: convertNullToNotFoundError}
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'get', path: '/:id' },
rest: { after: convertNullToNotFoundError },
});
setRemoting(PersistedModel, 'find', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'},
returns: {arg: 'data', type: [typeName], root: true},
http: {verb: 'get', path: '/'}
accepts: { arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit' },
returns: { arg: 'data', type: [typeName], root: true },
http: { verb: 'get', path: '/' },
});
setRemoting(PersistedModel, 'findOne', {
description: 'Find first instance of the model matched by filter from the data source.',
accessType: 'READ',
accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit'},
returns: {arg: 'data', type: typeName, root: true},
http: {verb: 'get', path: '/findOne'},
rest: {after: convertNullToNotFoundError}
accepts: { arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit' },
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'get', path: '/findOne' },
rest: { after: convertNullToNotFoundError },
});
setRemoting(PersistedModel, 'destroyAll', {
description: 'Delete all matching records.',
accessType: 'WRITE',
accepts: {arg: 'where', type: 'object', description: 'filter.where object'},
accepts: { arg: 'where', type: 'object', description: 'filter.where object' },
returns: {
arg: 'count',
type: 'object',
description: 'The number of instances deleted',
root: true
root: true,
},
http: {verb: 'del', path: '/'},
shared: false
http: { verb: 'del', path: '/' },
shared: false,
});
setRemoting(PersistedModel, 'updateAll', {
@ -650,44 +654,44 @@ module.exports = function(registry) {
description: 'Update instances of the model matched by where from the data source.',
accessType: 'WRITE',
accepts: [
{arg: 'where', type: 'object', http: {source: 'query'},
description: 'Criteria to match model instances'},
{arg: 'data', type: 'object', http: {source: 'body'},
description: 'An object of model property name/value pairs'},
{ arg: 'where', type: 'object', http: { source: 'query' },
description: 'Criteria to match model instances' },
{ arg: 'data', type: 'object', http: { source: 'body' },
description: 'An object of model property name/value pairs' },
],
returns: {
arg: 'count',
description: 'The number of instances updated',
type: 'object',
root: true
root: true,
},
http: {verb: 'post', path: '/update'}
http: { verb: 'post', path: '/update' },
});
setRemoting(PersistedModel, 'deleteById', {
aliases: ['destroyById', 'removeById'],
description: 'Delete a model instance by id from the data source.',
accessType: 'WRITE',
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true,
http: {source: 'path'}},
http: {verb: 'del', path: '/:id'},
returns: {arg: 'count', type: 'object', root: true}
accepts: { arg: 'id', type: 'any', description: 'Model id', required: true,
http: { source: 'path' }},
http: { verb: 'del', path: '/:id' },
returns: { arg: 'count', type: 'object', root: true },
});
setRemoting(PersistedModel, 'count', {
description: 'Count instances of the model matched by where from the data source.',
accessType: 'READ',
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
returns: {arg: 'count', type: 'number'},
http: {verb: 'get', path: '/count'}
accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' },
returns: { arg: 'count', type: 'number' },
http: { verb: 'get', path: '/count' },
});
setRemoting(PersistedModel.prototype, 'updateAttributes', {
description: 'Update attributes for a model instance and persist it into the data source.',
accessType: 'WRITE',
accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'},
returns: {arg: 'data', type: typeName, root: true},
http: {verb: 'put', path: '/'}
accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'An object of model property name/value pairs' },
returns: { arg: 'data', type: typeName, root: true },
http: { verb: 'put', path: '/' },
});
if (options.trackChanges || options.enableRemoteReplication) {
@ -695,12 +699,12 @@ module.exports = function(registry) {
description: 'Get a set of deltas and conflicts since the given checkpoint.',
accessType: 'READ',
accepts: [
{arg: 'since', type: 'number', description: 'Find deltas since this checkpoint'},
{arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
http: {source: 'body'}}
{ arg: 'since', type: 'number', description: 'Find deltas since this checkpoint' },
{ arg: 'remoteChanges', type: 'array', description: 'an array of change objects',
http: { source: 'body' }},
],
returns: {arg: 'result', type: 'object', root: true},
http: {verb: 'post', path: '/diff'}
returns: { arg: 'result', type: 'object', root: true },
http: { verb: 'post', path: '/diff' },
});
setRemoting(PersistedModel, 'changes', {
@ -708,11 +712,13 @@ module.exports = function(registry) {
'Provide a filter object to reduce the number of results returned.',
accessType: 'READ',
accepts: [
{arg: 'since', type: 'number', description: 'Only return changes since this checkpoint'},
{arg: 'filter', type: 'object', description: 'Only include changes that match this filter'}
{ arg: 'since', type: 'number', description:
'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},
http: {verb: 'get', path: '/changes'}
returns: { arg: 'changes', type: 'array', root: true },
http: { verb: 'get', path: '/changes' },
});
setRemoting(PersistedModel, 'checkpoint', {
@ -722,15 +728,15 @@ module.exports = function(registry) {
// We need to allow this method for users that don't have full
// WRITE permissions.
accessType: 'REPLICATE',
returns: {arg: 'checkpoint', type: 'object', root: true},
http: {verb: 'post', path: '/checkpoint'}
returns: { arg: 'checkpoint', type: 'object', root: true },
http: { verb: 'post', path: '/checkpoint' },
});
setRemoting(PersistedModel, 'currentCheckpoint', {
description: 'Get the current checkpoint.',
accessType: 'READ',
returns: {arg: 'checkpoint', type: 'object', root: true},
http: {verb: 'get', path: '/checkpoint'}
returns: { arg: 'checkpoint', type: 'object', root: true },
http: { verb: 'get', path: '/checkpoint' },
});
setRemoting(PersistedModel, 'createUpdates', {
@ -739,16 +745,16 @@ module.exports = function(registry) {
// It is called by the replication algorithm to compile a list
// of changes to apply on the target.
accessType: 'READ',
accepts: {arg: 'deltas', type: 'array', http: {source: 'body'}},
returns: {arg: 'updates', type: 'array', root: true},
http: {verb: 'post', path: '/create-updates'}
accepts: { arg: 'deltas', type: 'array', http: { source: 'body' }},
returns: { arg: 'updates', type: 'array', root: true },
http: { verb: 'post', path: '/create-updates' },
});
setRemoting(PersistedModel, 'bulkUpdate', {
description: 'Run multiple updates at once. Note: this is not atomic.',
accessType: 'WRITE',
accepts: {arg: 'updates', type: 'array'},
http: {verb: 'post', path: '/bulk-update'}
accepts: { arg: 'updates', type: 'array' },
http: { verb: 'post', path: '/bulk-update' },
});
setRemoting(PersistedModel, 'findLastChange', {
@ -756,30 +762,30 @@ module.exports = function(registry) {
accessType: 'READ',
accepts: {
arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: 'Model id'
description: 'Model id',
},
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', {
description: [
'Update the properties of the most recent change record',
'kept for this instance.'
'kept for this instance.',
],
accessType: 'WRITE',
accepts: [
{
arg: 'id', type: 'any', required: true, http: { source: 'path' },
description: 'Model id'
description: 'Model id',
},
{
arg: 'data', type: 'object', http: {source: 'body'},
description: 'An object of Change property name/value pairs'
arg: 'data', type: 'object', http: { source: 'body' },
description: 'An object of Change property name/value pairs',
},
],
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', {
description: 'Rectify all Model changes.',
accessType: 'WRITE',
http: {verb: 'post', path: '/rectify-all'}
http: { verb: 'post', path: '/rectify-all' },
});
setRemoting(PersistedModel, 'rectifyChange', {
description: 'Tell loopback that a change to the model with the given id has occurred.',
accessType: 'WRITE',
accepts: {arg: 'id', type: 'any', http: {source: 'path'}},
http: {verb: 'post', path: '/:id/rectify-change'}
accepts: { arg: 'id', type: 'any', http: { source: 'path' }},
http: { verb: 'post', path: '/:id/rectify-change' },
});
}
@ -804,18 +810,18 @@ module.exports = function(registry) {
description: 'Create a change stream.',
accessType: 'READ',
http: [
{verb: 'post', path: '/change-stream'},
{verb: 'get', path: '/change-stream'}
{ verb: 'post', path: '/change-stream' },
{ verb: 'get', path: '/change-stream' },
],
accepts: {
arg: 'options',
type: 'object'
type: 'object',
},
returns: {
arg: 'changes',
type: 'ReadableStream',
json: true
}
json: true,
},
});
};
@ -870,14 +876,14 @@ module.exports = function(registry) {
// TODO(ritch) this whole thing could be optimized a bit more
Change.find({ where: {
checkpoint: { gte: since },
modelName: this.modelName
modelName: this.modelName,
}}, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
filter.where[idName] = { inq: ids };
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
@ -1004,16 +1010,14 @@ module.exports = function(registry) {
'You must enable change tracking before replicating'
);
var diff;
var updates;
var newSourceCp, newTargetCp;
var diff, updates, newSourceCp, newTargetCp;
var tasks = [
checkpoints,
getSourceChanges,
getDiffFromTarget,
createSourceUpdates,
bulkUpdate
bulkUpdate,
];
async.waterfall(tasks, done);
@ -1136,7 +1140,7 @@ module.exports = function(registry) {
deltas.forEach(function(change) {
change = new Change(change);
var type = change.type();
var update = {type: type, change: change};
var update = { type: type, change: change };
switch (type) {
case Change.CREATE:
case Change.UPDATE:
@ -1521,7 +1525,7 @@ module.exports = function(registry) {
this.Change = BaseChangeModel.extend(this.modelName + '-change',
{},
{
trackModel: this
trackModel: this,
}
);
@ -1575,7 +1579,7 @@ module.exports = function(registry) {
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({ where: { modelId: id } }, cb);
Change.findOne({ where: { modelId: id }}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
@ -1611,7 +1615,7 @@ module.exports = function(registry) {
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var changes = new PassThrough({ objectMode: true });
var writeable = true;
changes.destroy = function() {
@ -1661,7 +1665,7 @@ module.exports = function(registry) {
var change = {
target: target,
where: where,
data: data
data: data,
};
switch (type) {

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ function context(options) {
// Run the code in the context of the namespace
if (enableHttpContext) {
// Set up the transport context
ns.set('http', {req: req, res: res});
ns.set('http', { req: req, res: res });
}
next();
});

View File

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

View File

@ -1,17 +1,14 @@
/*jshint -W030 */
var loopback = require('../');
var lt = require('./helpers/loopback-testing-helper');
var path = require('path');
var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
var app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));
var assert = require('assert');
var USER = {email: 'test@test.test', password: 'test'};
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
var USER = { email: 'test@test.test', password: 'test' };
var CURRENT_USER = { email: 'current@test.test', password: 'test' };
var debug = require('debug')('loopback:test:access-control.integration');
describe('access control - integration', function() {
lt.beforeEach.withApp(app);
/*
@ -61,7 +58,6 @@ describe('access control - integration', function() {
*/
describe('/users', function() {
lt.beforeEach.givenModel('user', USER, 'randomUser');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');
@ -128,7 +124,7 @@ describe('access control - integration', function() {
userCounter = userCounter ? ++userCounter : 1;
return {
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
app.models.account.create({
userId: self.user.id,
balance: 100
balance: 100,
}, function(err, act) {
self.url = '/api/accounts/' + act.id;
done();
});
});
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
lt.it.shouldBeAllowed();
});
@ -226,5 +222,4 @@ describe('access control - integration', function() {
return '/api/accounts/' + this.account.id;
}
});
});

View File

@ -1,7 +1,7 @@
var loopback = require('../');
var extend = require('util')._extend;
var Token = loopback.AccessToken.extend('MyToken');
var ds = loopback.createDataSource({connector: loopback.Memory});
var ds = loopback.createDataSource({ connector: loopback.Memory });
Token.attachTo(ds);
var ACL = loopback.ACL;
@ -36,7 +36,7 @@ describe('loopback.token(options)', function() {
var tokenId = this.token.id;
var app = createTestApp(
this.token,
{ token: { searchDefaultTokenKeys: false } },
{ token: { searchDefaultTokenKeys: false }},
done);
var agent = request.agent(app);
@ -144,7 +144,7 @@ describe('loopback.token(options)', function() {
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId});
assert.deepEqual(res.body, { userId: userId });
done();
});
});
@ -159,7 +159,7 @@ describe('loopback.token(options)', function() {
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId, state: 1});
assert.deepEqual(res.body, { userId: userId, state: 1 });
done();
});
});
@ -174,7 +174,7 @@ describe('loopback.token(options)', function() {
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId, state: 1});
assert.deepEqual(res.body, { userId: userId, state: 1 });
done();
});
});
@ -227,7 +227,7 @@ describe('AccessToken', function() {
it('supports two-arg variant with no options', function(done) {
var expectedTokenId = this.token.id;
var req = mockRequest({
headers: { 'authorization': expectedTokenId }
headers: { 'authorization': expectedTokenId },
});
Token.findForRequest(req, function(err, token) {
@ -247,7 +247,7 @@ describe('AccessToken', function() {
// express helpers
param: function(name) { return this._params[name]; },
header: function(name) { return this.headers[name]; }
header: function(name) { return this.headers[name]; },
},
opts);
}
@ -274,7 +274,7 @@ describe('app.enableAuth()', function() {
});
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, {app:{aclErrorStatus:403}}, done)
createTestAppAndRequest(this.token, { app: { aclErrorStatus: 403 }}, done)
.del('/tests/123')
.expect(403)
.set('authorization', this.token.id)
@ -290,7 +290,7 @@ describe('app.enableAuth()', function() {
});
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, {model:{aclErrorStatus:404}}, done)
createTestAppAndRequest(this.token, { model: { aclErrorStatus: 404 }}, done)
.del('/tests/123')
.expect(404)
.set('authorization', this.token.id)
@ -328,7 +328,7 @@ describe('app.enableAuth()', function() {
};
TestModel.remoteMethod('getToken', {
returns: { arg: 'token', type: 'object' },
http: { verb: 'GET', path: '/token' }
http: { verb: 'GET', path: '/token' },
});
var app = loopback();
@ -355,7 +355,7 @@ describe('app.enableAuth()', function() {
function createTestingToken(done) {
var test = this;
Token.create({userId: '123'}, function(err, token) {
Token.create({ userId: '123' }, function(err, token) {
if (err) return done(err);
test.token = token;
done();
@ -376,7 +376,7 @@ function createTestApp(testToken, settings, done) {
var modelSettings = settings.model || {};
var tokenSettings = extend({
model: Token,
currentUserLiteral: 'me'
currentUserLiteral: 'me',
}, settings.token);
var app = loopback();
@ -384,8 +384,8 @@ function createTestApp(testToken, settings, done) {
app.use(loopback.cookieParser('secret'));
app.use(loopback.token(tokenSettings));
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, {signed: true});
res.cookie('access_token', testToken.id, {signed: true});
res.cookie('authorization', testToken.id, { signed: true });
res.cookie('access_token', testToken.id, { signed: true });
res.end();
});
app.get('/', function(req, res) {
@ -401,7 +401,7 @@ function createTestApp(testToken, settings, done) {
res.status(req.accessToken ? 200 : 401).end();
});
app.use('/users/:uid', function(req, res) {
var result = {userId: req.params.uid};
var result = { userId: req.params.uid };
if (req.query.state) {
result.state = req.query.state;
} else if (req.url !== '/') {
@ -423,9 +423,9 @@ function createTestApp(testToken, settings, done) {
principalId: '$everyone',
accessType: ACL.ALL,
permission: ACL.DENY,
property: 'deleteById'
}
]
property: 'deleteById',
},
],
};
Object.keys(modelSettings).forEach(function(key) {

View File

@ -14,12 +14,12 @@ function checkResult(err, result) {
var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
ds = loopback.createDataSource({ connector: loopback.Memory });
});
describe('security scopes', function() {
beforeEach(function() {
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
var ds = this.ds = loopback.createDataSource({ connector: loopback.Memory });
testModel = loopback.PersistedModel.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
@ -30,45 +30,53 @@ describe('security scopes', 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) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW},
function(err, resource) {
Scope.create({ name: 'userScope', description: 'access user information' },
function(err, scope) {
ACL.create({
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', 'name', ACL.ALL, checkResult);
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
});
});
});
it('should allow access to models for the given scope', function() {
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,
model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
function(err, resource) {
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,
model: 'testModel', property: 'name',
accessType: ACL.WRITE, permission: ACL.DENY,
}, function(err, 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
});
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
});
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);
});
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);
});
});
});
});
});
});
describe('security ACLs', function() {
@ -79,26 +87,26 @@ describe('security ACLs', function() {
'accessType': '*',
'permission': 'DENY',
'principalType': 'ROLE',
'principalId': '$everyone'
'principalId': '$everyone',
},
{
'model': 'account',
'accessType': '*',
'permission': 'ALLOW',
'principalType': 'ROLE',
'principalId': '$owner'
'principalId': '$owner',
},
{
'model': 'account',
'accessType': 'READ',
'permission': 'ALLOW',
'principalType': 'ROLE',
'principalId': '$everyone'
'principalId': '$everyone',
}];
var req = {
model: 'account',
property: 'find',
accessType: 'WRITE'
accessType: 'WRITE',
};
acls = acls.map(function(a) { return new ACL(a); });
@ -108,17 +116,19 @@ describe('security ACLs', function() {
property: 'find',
accessType: 'WRITE',
permission: 'ALLOW',
methodNames: []});
methodNames: [] });
});
it('should allow access to models for the given principal by wildcard', function() {
// jscs:disable validateIndentation
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, 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.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, 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) {
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) {
assert(perm.permission === ACL.DENY);
});
});
});
});
it('should allow access to models by exception', function() {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY}, function(err, acl) {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.ALLOW}, function(err, acl) {
ACL.create({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) {
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY,
}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.ALLOW,
}, function(err, acl) {
ACL.create({
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);
});
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);
});
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);
});
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);
});
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);
});
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);
});
});
});
});
});
it('should honor defaultPermission from the model', function() {
@ -179,19 +192,23 @@ describe('security ACLs', function() {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY },
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, {
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;
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);
});
@ -202,7 +219,6 @@ describe('security ACLs', function() {
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
});
it('should honor static ACLs from the model', function() {
@ -210,16 +226,21 @@ describe('security ACLs', function() {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY },
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u002', accessType: ACL.EXECUTE, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u003', accessType: ACL.EXECUTE, permission: ACL.DENY}
]
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW },
{ 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);
});
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);
});
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);
});
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);
});
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);
});
});
it('should filter static ACLs by model/property', function() {
@ -255,21 +280,21 @@ describe('security ACLs', function() {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY },
{ principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', property: 'name',
accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u002', property: 'findOne',
accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],
accessType: ACL.ALL, permission: ACL.ALLOW}
]
{ principalType: ACL.USER, principalId: 'u001', property: 'name',
accessType: ACL.ALL, permission: ACL.ALLOW },
{ principalType: ACL.USER, principalId: 'u002', property: 'findOne',
accessType: ACL.ALL, permission: ACL.ALLOW },
{ principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],
accessType: ACL.ALL, permission: ACL.ALLOW },
],
});
var staticACLs = ACL.getStaticACLs('Model1', 'name');
@ -288,8 +313,7 @@ describe('security ACLs', function() {
var log = function() {};
// 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());
var userId = user.id;
@ -299,56 +323,62 @@ describe('security ACLs', function() {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{ principalType: ACL.USER, principalId: userId,
accessType: ACL.WRITE, permission: ACL.DENY },
{ principalType: ACL.USER, principalId: userId,
accessType: ACL.ALL, permission: ACL.ALLOW },
],
},
}, {
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,
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: userId,
model: 'Customer', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, acl) {
log('ACL 1: ', acl.toObject());
Role.create({name: 'MyRole'}, function(err, myRole) {
Role.create({ name: 'MyRole' }, function(err, myRole) {
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());
ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) {
ACL.create({
principalType: ACL.ROLE, principalId: 'MyRole',
model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) {
log('ACL 2: ', acl.toObject());
ACL.checkAccessForContext({
principals: [
{type: ACL.USER, id: userId}
{ type: ACL.USER, id: userId },
],
model: 'Customer',
property: 'name',
accessType: ACL.READ
accessType: ACL.READ,
}, function(err, access) {
assert(!err && access.permission === ACL.ALLOW);
});
ACL.checkAccessForContext({
principals: [
{type: ACL.ROLE, id: Role.EVERYONE}
{ type: ACL.ROLE, id: Role.EVERYONE },
],
model: 'Customer',
property: 'name',
accessType: ACL.READ
accessType: ACL.READ,
}, function(err, access) {
assert(!err && access.permission === ACL.DENY);
});
});
});
});
@ -363,11 +393,11 @@ describe('access check', function() {
app = loopback();
app.use(loopback.rest());
app.enableAuth();
app.dataSource('test', {connector: 'memory'});
app.dataSource('test', { connector: 'memory' });
});
it('should occur before other remote hooks', function(done) {
var MyTestModel = app.model('MyTestModel', {base: 'PersistedModel', dataSource: 'test'});
var MyTestModel = app.model('MyTestModel', { base: 'PersistedModel', dataSource: 'test' });
var checkAccessCalled = false;
var beforeHookCalled = false;

View File

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

View File

@ -2,11 +2,11 @@ describe('PersistedModel.createChangeStream()', function() {
describe('configured to source changes locally', function() {
before(function() {
var test = this;
var app = loopback({localRegistry: true});
var ds = app.dataSource('ds', {connector: 'memory'});
var app = loopback({ localRegistry: true });
var ds = app.dataSource('ds', { connector: 'memory' });
this.Score = app.model('Score', {
dataSource: 'ds',
changeDataSource: false // use only local observers
changeDataSource: false, // use only local observers
});
});
@ -20,13 +20,13 @@ describe('PersistedModel.createChangeStream()', function() {
done();
});
Score.create({team: 'foo'});
Score.create({ team: 'foo' });
});
});
it('should detect update', function(done) {
var Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) {
Score.create({ team: 'foo' }, function(err, newScore) {
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {
expect(change.type).to.equal('update');
@ -34,7 +34,7 @@ describe('PersistedModel.createChangeStream()', function() {
done();
});
newScore.updateAttributes({
bat: 'baz'
bat: 'baz',
});
});
});
@ -42,7 +42,7 @@ describe('PersistedModel.createChangeStream()', function() {
it('should detect delete', function(done) {
var Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) {
Score.create({ team: 'foo' }, function(err, newScore) {
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {
expect(change.type).to.equal('remove');
@ -60,17 +60,17 @@ describe('PersistedModel.createChangeStream()', function() {
describe.skip('configured to source changes using pubsub', function() {
before(function() {
var test = this;
var app = loopback({localRegistry: true});
var db = app.dataSource('ds', {connector: 'memory'});
var app = loopback({ localRegistry: true });
var db = app.dataSource('ds', { connector: 'memory' });
var ps = app.dataSource('ps', {
host: 'localhost',
port: '12345',
connector: 'pubsub',
pubsubAdapter: 'mqtt'
pubsubAdapter: 'mqtt',
});
this.Score = app.model('Score', {
dataSource: 'db',
changeDataSource: 'ps'
changeDataSource: 'ps',
});
});

View File

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

View File

@ -8,7 +8,7 @@ describe('Checkpoint', function() {
describe('bumpLastSeq() and current()', function() {
beforeEach(function() {
var memory = loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
});
Checkpoint.attachTo(memory);
});
@ -23,14 +23,14 @@ describe('Checkpoint', function() {
expect(seq).to.equal(3);
next();
});
}
},
], done);
});
it('Should be no race condition for current() when calling in parallel', function(done) {
async.parallel([
function(next) { Checkpoint.current(next); },
function(next) { Checkpoint.current(next); }
function(next) { Checkpoint.current(next); },
], function(err, list) {
if (err) return done(err);
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) {
async.parallel([
function(next) { Checkpoint.bumpLastSeq(next); },
function(next) { Checkpoint.bumpLastSeq(next); }
function(next) { Checkpoint.bumpLastSeq(next); },
], function(err, list) {
if (err) return done(err);
Checkpoint.find(function(err, data) {
@ -57,14 +57,15 @@ describe('Checkpoint', function() {
expect(data[0].seq).to.equal(2);
// In this particular case, since the new last seq is always 2, both results
// should be 2.
expect(list.map(function(it) {return it.seq;}))
expect(list.map(function(it) { return it.seq; }))
.to.eql([2, 2]);
done();
});
});
});
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) {
expect(seq).to.equal(1);
done(err);

View File

@ -3,7 +3,7 @@ describe('DataSource', function() {
beforeEach(function() {
memory = loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
});
assertValidDataSource(memory);
@ -11,7 +11,7 @@ describe('DataSource', function() {
describe('dataSource.createModel(name, properties, settings)', function() {
it('Define a model and attach it to a `DataSource`', function() {
var Color = memory.createModel('color', {name: String});
var Color = memory.createModel('color', { name: String });
assert.isFunc(Color, 'find');
assert.isFunc(Color, 'findById');
assert.isFunc(Color, 'findOne');
@ -36,13 +36,13 @@ describe('DataSource', function() {
it('should honor settings.base', function() {
var Base = memory.createModel('base');
var Color = memory.createModel('color', {name: String}, {base: Base});
var Color = memory.createModel('color', { name: String }, { base: Base });
assert(Color.prototype instanceof Base);
assert.equal(Color.base, Base);
});
it('should use loopback.PersistedModel as the base for DBs', function() {
var Color = memory.createModel('color', {name: String});
var Color = memory.createModel('color', { name: String });
assert(Color.prototype instanceof loopback.PersistedModel);
assert.equal(Color.base, loopback.PersistedModel);
});
@ -56,14 +56,13 @@ describe('DataSource', function() {
};
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.equal(Color.base.modelName, 'PersistedModel');
});
});
describe.skip('PersistedModel Methods', function() {
@ -103,7 +102,9 @@ describe('DataSource', function() {
var fn = scope[name];
var actuallyEnabled = Model.getRemoteMethod(name);
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
var ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote
connector: loopback.Remote,
});
TestModel.attachTo(ds);
});
it('should be able to call create', function(done) {
TestModel.create({
foo: 'bar'
foo: 'bar',
}, function(err, inst) {
if (err) return done(err);
assert(inst.id);
@ -26,7 +26,7 @@ describe('RemoteConnector', function() {
it('should be able to call save', function(done) {
var m = new TestModel({
foo: 'bar'
foo: 'bar',
});
m.save(function(err, data) {
if (err) return done(err);

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ var app = module.exports = loopback();
boot(app, __dirname);
var apiPath = '/api';
app.use(loopback.token({model: app.models.accessToken}));
app.use(loopback.token({ model: app.models.accessToken }));
app.use(apiPath, loopback.rest());
app.use(loopback.urlNotFound());

View File

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

View File

@ -3,7 +3,7 @@ var boot = require('loopback-boot');
var app = module.exports = loopback();
app.enableAuth();
boot(app, __dirname);
app.use(loopback.token({model: app.models.AccessToken}));
app.use(loopback.token({ model: app.models.AccessToken }));
var apiPath = '/api';
app.use(apiPath, loopback.rest());
app.use(loopback.urlNotFound());

View File

@ -1,9 +1,9 @@
describe('GeoPoint', function() {
describe('geoPoint.distanceTo(geoPoint, options)', function() {
it('Get the distance to another `GeoPoint`', function() {
var here = new GeoPoint({lat: 10, lng: 10});
var there = new GeoPoint({lat: 5, lng: 5});
var distance = here.distanceTo(there, {type: 'meters'});
var here = new GeoPoint({ lat: 10, lng: 10 });
var there = new GeoPoint({ lat: 5, lng: 5 });
var distance = here.distanceTo(there, { type: 'meters' });
assert.equal(Math.floor(distance), 782777);
});
@ -11,9 +11,9 @@ describe('GeoPoint', function() {
describe('GeoPoint.distanceBetween(a, b, options)', function() {
it('Get the distance between two points', function() {
var here = new GeoPoint({lat: 10, lng: 10});
var there = new GeoPoint({lat: 5, lng: 5});
var distance = GeoPoint.distanceBetween(here, there, {type: 'feet'});
var here = new GeoPoint({ lat: 10, lng: 10 });
var there = new GeoPoint({ lat: 5, lng: 5 });
var distance = GeoPoint.distanceBetween(here, there, { type: 'feet' });
assert.equal(Math.floor(distance), 2568169);
});
@ -43,11 +43,11 @@ describe('GeoPoint', function() {
});
it('Create as Model property', function() {
var Model = loopback.createModel('geo-model', {
geo: {type: 'GeoPoint'}
geo: { type: 'GeoPoint' },
});
var m = new Model({
geo: '1.222,3.444'
geo: '1.222,3.444',
});
assert(m.geo instanceof GeoPoint);

View File

@ -4,7 +4,7 @@ var _beforeEach = {};
var helpers = {
describe: _describe,
it: _it,
beforeEach: _beforeEach
beforeEach: _beforeEach,
};
module.exports = helpers;
@ -119,7 +119,7 @@ _beforeEach.givenAnUnauthenticatedToken = function(attrs, optionalHandler) {
};
_beforeEach.givenAnAnonymousToken = function(attrs, optionalHandler) {
_beforeEach.givenModel('accessToken', {id: '$anonymous'}, optionalHandler);
_beforeEach.givenModel('accessToken', { id: '$anonymous' }, optionalHandler);
};
_describe.whenCalledRemotely = function(verb, url, data, cb) {
@ -145,7 +145,9 @@ _describe.whenCalledRemotely = function(verb, url, data, cb) {
if (methodForVerb === 'delete') methodForVerb = 'del';
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);

View File

@ -5,7 +5,7 @@ describe('hidden properties', function() {
var app = this.app = loopback();
var Product = this.Product = loopback.PersistedModel.extend('product',
{},
{hidden: ['secret']}
{ hidden: ['secret'] }
);
Product.attachTo(loopback.memory());
@ -18,11 +18,11 @@ describe('hidden properties', function() {
app.use(loopback.rest());
Category.create({
name: 'my category'
name: 'my category',
}, function(err, category) {
category.products.create({
name: 'pencil',
secret: 'a secret'
secret: 'a secret',
}, done);
});
});

View File

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

View File

@ -26,7 +26,7 @@ module.exports = function(config) {
'test/replication.test.js',
'test/change.test.js',
'test/checkpoint.test.js',
'test/app.test.js'
'test/app.test.js',
],
// list of files / patterns to exclude
@ -52,7 +52,7 @@ module.exports = function(config) {
// - PhantomJS
// - IE (only Windows)
browsers: [
'Chrome'
'Chrome',
],
// Which plugins to enable
@ -61,16 +61,16 @@ module.exports = function(config) {
'karma-mocha',
'karma-phantomjs-launcher',
'karma-chrome-launcher',
'karma-junit-reporter'
'karma-junit-reporter',
],
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// to avoid DISCONNECTED messages
browserDisconnectTimeout : 10000, // default 2000
browserDisconnectTolerance : 1, // default 0
browserNoActivityTimeout : 60000, //default 10000
browserDisconnectTimeout: 10000, // default 2000
browserDisconnectTolerance: 1, // default 0
browserNoActivityTimeout: 60000, //default 10000
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
@ -97,7 +97,7 @@ module.exports = function(config) {
'passport',
'passport-local',
'superagent',
'supertest'
'supertest',
],
// transform: ['coffeeify'],
debug: true,
@ -106,6 +106,6 @@ module.exports = function(config) {
},
// Add browserify to preprocessors
preprocessors: {'test/*': ['browserify']}
preprocessors: { 'test/*': ['browserify'] },
});
};

View File

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

View File

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

View File

@ -7,7 +7,7 @@ describe('Application', function() {
it('honors `application.register` - promise variant', function(done) {
Application.register('rfeng', 'MyTestApp',
{description: 'My test application'}, function(err, result) {
{ description: 'My test application' }, function(err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyTestApp');
@ -18,7 +18,7 @@ describe('Application', function() {
it('honors `application.register` - promise variant', function(done) {
Application.register('rfeng', 'MyTestApp',
{description: 'My test application'})
{ description: 'My test application' })
.then(function(result) {
var app = result;
assert.equal(app.owner, 'rfeng');
@ -32,9 +32,9 @@ describe('Application', function() {
});
it('Create a new application', function(done) {
Application.create({owner: 'rfeng',
Application.create({ owner: 'rfeng',
name: 'MyApp1',
description: 'My first mobile application'}, function(err, result) {
description: 'My first mobile application' }, function(err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp1');
@ -52,7 +52,7 @@ describe('Application', function() {
});
it('Create a new application with push settings', function(done) {
Application.create({owner: 'rfeng',
Application.create({ owner: 'rfeng',
name: 'MyAppWithPush',
description: 'My push mobile application',
pushSettings: {
@ -62,18 +62,18 @@ describe('Application', function() {
keyData: 'key',
pushOptions: {
gateway: 'gateway.sandbox.push.apple.com',
port: 2195
port: 2195,
},
feedbackOptions: {
gateway: 'feedback.sandbox.push.apple.com',
port: 2196,
interval: 300,
batchFeedback: true
}
batchFeedback: true,
},
},
gcm: {
serverApiKey: 'serverKey'
}
serverApiKey: 'serverKey',
},
}},
function(err, result) {
var app = result;
@ -84,18 +84,18 @@ describe('Application', function() {
keyData: 'key',
pushOptions: {
gateway: 'gateway.sandbox.push.apple.com',
port: 2195
port: 2195,
},
feedbackOptions: {
gateway: 'feedback.sandbox.push.apple.com',
port: 2196,
interval: 300,
batchFeedback: true
}
batchFeedback: true,
},
},
gcm: {
serverApiKey: 'serverKey'
}
serverApiKey: 'serverKey',
},
});
done(err, result);
});
@ -103,7 +103,7 @@ describe('Application', function() {
beforeEach(function(done) {
Application.register('rfeng', 'MyApp2',
{description: 'My second mobile application'}, function(err, result) {
{ description: 'My second mobile application' }, function(err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2');
@ -280,10 +280,10 @@ describe('Application', function() {
describe('Application subclass', function() {
it('should use subclass model name', function(done) {
var MyApp = Application.extend('MyApp');
var ds = loopback.createDataSource({connector: loopback.Memory});
var ds = loopback.createDataSource({ connector: loopback.Memory });
MyApp.attachTo(ds);
MyApp.register('rfeng', 'MyApp123',
{description: 'My 123 mobile application'}, function(err, result) {
{ description: 'My 123 mobile application' }, function(err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp123');

View File

@ -10,8 +10,8 @@ var describe = require('./util/describe');
describe('Model / PersistedModel', function() {
defineModelTestsWithDataSource({
dataSource: {
connector: loopback.Memory
}
connector: loopback.Memory,
},
});
describe('Model.validatesUniquenessOf(property, options)', function() {
@ -23,19 +23,19 @@ describe('Model / PersistedModel', function() {
'password': String,
'gender': String,
'domain': String,
'email': String
'email': String,
});
var dataSource = loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
});
User.attachTo(dataSource);
User.validatesUniquenessOf('email', {message: 'email is not unique'});
User.validatesUniquenessOf('email', { message: 'email is not unique' });
var joe = new User({email: 'joe@joe.com'});
var joe2 = new User({email: 'joe@joe.com'});
var joe = new User({ email: 'joe@joe.com' });
var joe2 = new User({ email: 'joe@joe.com' });
joe.save(function() {
joe2.save(function(err) {
@ -50,25 +50,23 @@ describe('Model / PersistedModel', function() {
describe('Model.attachTo(dataSource)', 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({
connector: loopback.Memory
connector: loopback.Memory,
});
MyModel.attachTo(dataSource);
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() {
var User, Post;
var dataSource;
var app;
var User, Post, dataSource, app;
beforeEach(function() {
User = PersistedModel.extend('user', {
@ -79,21 +77,21 @@ describe.onServer('Remote Methods', function() {
'password': String,
'gender': String,
'domain': String,
'email': String
'email': String,
}, {
trackChanges: true
trackChanges: true,
});
Post = PersistedModel.extend('post', {
id: { id: true, type: String, defaultFn: 'guid' },
title: String,
content: String
content: String,
}, {
trackChanges: true
trackChanges: true,
});
dataSource = loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
});
User.attachTo(dataSource);
@ -113,11 +111,11 @@ describe.onServer('Remote Methods', function() {
User.login,
{
accepts: [
{arg: 'username', type: 'string', required: true},
{arg: 'password', type: 'string', required: true}
{ arg: 'username', type: 'string', required: true },
{ arg: 'password', type: 'string', required: true },
],
returns: {arg: 'sessionId', type: 'any', root: true},
http: {path: '/sign-in', verb: 'get'}
returns: { arg: 'sessionId', type: 'any', root: true },
http: { path: '/sign-in', verb: 'get' },
}
);
@ -129,11 +127,11 @@ describe.onServer('Remote Methods', function() {
describe('Model.destroyAll(callback)', function() {
it('Delete all Model instances from data source', function(done) {
(new TaskEmitter())
.task(User, 'create', {first: 'jill'})
.task(User, 'create', {first: 'bob'})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.task(User, 'create', { first: 'jill' })
.task(User, 'create', { first: 'bob' })
.task(User, 'create', { first: 'jan' })
.task(User, 'create', { first: 'sam' })
.task(User, 'create', { first: 'suzy' })
.on('done', function() {
User.count(function(err, count) {
User.destroyAll(function() {
@ -178,7 +176,7 @@ describe.onServer('Remote Methods', function() {
it('Call the findById with filter.fields using HTTP / REST', function(done) {
request(app)
.post('/users')
.send({first: 'x', last: 'y'})
.send({ first: 'x', last: 'y' })
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -201,7 +199,7 @@ describe.onServer('Remote Methods', function() {
it('Call the findById with filter.include using HTTP / REST', function(done) {
request(app)
.post('/users')
.send({first: 'x', last: 'y'})
.send({ first: 'x', last: 'y' })
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -210,7 +208,7 @@ describe.onServer('Remote Methods', function() {
assert(userId);
request(app)
.post('/users/' + userId + '/posts')
.send({title: 'T1', content: 'C1'})
.send({ title: 'T1', content: 'C1' })
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -230,7 +228,6 @@ describe.onServer('Remote Methods', function() {
});
});
});
});
describe('Model.beforeRemote(name, fn)', function() {
@ -245,7 +242,7 @@ describe.onServer('Remote Methods', function() {
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.send({ data: { first: 'foo', last: 'bar' }})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -275,7 +272,7 @@ describe.onServer('Remote Methods', function() {
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.send({ data: { first: 'foo', last: 'bar' }})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -324,7 +321,7 @@ describe.onServer('Remote Methods', function() {
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.send({ data: { first: 'foo', last: 'bar' }})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -353,7 +350,7 @@ describe.onServer('Remote Methods', function() {
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.send({ data: { first: 'foo', last: 'bar' }})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -367,20 +364,20 @@ describe.onServer('Remote Methods', function() {
describe('Model.hasMany(Model)', function() {
it('Define a one to many relationship', function(done) {
var Book = dataSource.createModel('book', {title: String, author: String});
var Chapter = dataSource.createModel('chapter', {title: String});
var Book = dataSource.createModel('book', { title: String, author: String });
var Chapter = dataSource.createModel('chapter', { title: String });
// by referencing model
Book.hasMany(Chapter);
Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) {
Book.create({ title: 'Into the Wild', author: 'Jon Krakauer' }, function(err, book) {
// using 'chapters' scope for build:
var c = book.chapters.build({title: 'Chapter 1'});
book.chapters.create({title: 'Chapter 2'}, function() {
var c = book.chapters.build({ title: 'Chapter 1' });
book.chapters.create({ title: 'Chapter 2' }, function() {
c.save(function() {
Chapter.count({bookId: book.id}, function(err, count) {
Chapter.count({ bookId: book.id }, function(err, count) {
assert.equal(count, 2);
book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) {
book.chapters({ where: { title: 'Chapter 1' }}, function(err, chapters) {
assert.equal(chapters.length, 1);
assert.equal(chapters[0].title, 'Chapter 1');
done();
@ -396,10 +393,10 @@ describe.onServer('Remote Methods', function() {
it('Normalized properties passed in originally by loopback.createModel()', function() {
var props = {
s: String,
n: {type: 'Number'},
o: {type: 'String', min: 10, max: 100},
n: { type: 'Number' },
o: { type: 'String', min: 10, max: 100 },
d: Date,
g: loopback.GeoPoint
g: loopback.GeoPoint,
};
var MyModel = loopback.createModel('foo', props);
@ -426,7 +423,7 @@ describe.onServer('Remote Methods', function() {
describe('Model.extend()', function() {
it('Create a new model by extending an existing model', function() {
var User = loopback.PersistedModel.extend('test-user', {
email: String
email: String,
});
User.foo = function() {
@ -439,7 +436,7 @@ describe.onServer('Remote Methods', function() {
var MyUser = User.extend('my-user', {
a: String,
b: String
b: String,
});
assert.equal(MyUser.prototype.bar, User.prototype.bar);
@ -448,7 +445,7 @@ describe.onServer('Remote Methods', function() {
var user = new MyUser({
email: 'foo@bar.com',
a: 'foo',
b: 'bar'
b: 'bar',
});
assert.equal(user.email, 'foo@bar.com');
@ -461,11 +458,11 @@ describe.onServer('Remote Methods', function() {
it('create isolated emitters for subclasses', function() {
var User1 = loopback.createModel('User1', {
'first': String,
'last': String
'last': String,
});
var User2 = loopback.createModel('User2', {
'name': String
'name': String,
});
var user1Triggered = false;
@ -486,7 +483,6 @@ describe.onServer('Remote Methods', function() {
assert(user1Triggered);
assert(!user2Triggered);
});
});
describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {
@ -506,7 +502,7 @@ describe.onServer('Remote Methods', function() {
function shouldReturn(methodName, expectedAccessType) {
describe(methodName, function() {
it('should return ' + expectedAccessType, function() {
var remoteMethod = {name: methodName};
var remoteMethod = { name: methodName };
assert.equal(
User._getAccessTypeForMethod(remoteMethod),
expectedAccessType
@ -538,10 +534,9 @@ describe.onServer('Remote Methods', function() {
var Checkpoint = User.getChangeModel().getCheckpointModel();
var tasks = [
getCurrentCheckpoint,
checkpoint
checkpoint,
];
var result;
var current;
var result, current;
async.series(tasks, function(err) {
if (err) return done(err);
@ -615,7 +610,7 @@ describe.onServer('Remote Methods', function() {
'removeById',
'count',
'prototype.updateAttributes',
'createChangeStream'
'createChangeStream',
]);
});
});

View File

@ -5,11 +5,11 @@ describe('Registry', function() {
var appBar = loopback();
var modelName = 'MyModel';
var subModelName = 'Sub' + modelName;
var settings = {base: 'PersistedModel'};
var settings = { base: 'PersistedModel' };
appFoo.set('perAppRegistries', true);
appBar.set('perAppRegistries', true);
var dsFoo = appFoo.dataSource('dsFoo', {connector: 'memory'});
var dsBar = appFoo.dataSource('dsBar', {connector: 'memory'});
var dsFoo = appFoo.dataSource('dsFoo', { connector: 'memory' });
var dsBar = appFoo.dataSource('dsBar', { connector: 'memory' });
var FooModel = appFoo.model(modelName, settings);
var FooSubModel = appFoo.model(subModelName, settings);
@ -26,9 +26,9 @@ describe('Registry', function() {
expect(appFoo.models[modelName]).to.not.equal(appBar.models[modelName]);
BarModel.create({name: 'bar'}, function(err, bar) {
BarModel.create({ name: 'bar' }, function(err, bar) {
assert(!err);
bar.subMyModels.create({parent: 'bar'}, function(err) {
bar.subMyModels.create({ parent: 'bar' }, function(err) {
assert(!err);
FooSubModel.find(function(err, foos) {
assert(!err);

View File

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

View File

@ -2,8 +2,7 @@ var loopback = require('../');
var defineModelTestsWithDataSource = require('./util/model-tests');
describe('RemoteConnector', function() {
var remoteApp;
var remote;
var remoteApp, remote;
defineModelTestsWithDataSource({
beforeEach: function(done) {
@ -14,7 +13,7 @@ describe('RemoteConnector', function() {
test.dataSource = loopback.createDataSource({
host: 'localhost',
port: remoteApp.get('port'),
connector: loopback.Remote
connector: loopback.Remote,
});
done();
});
@ -23,10 +22,10 @@ describe('RemoteConnector', function() {
var RemoteModel = Model.extend('Remote' + Model.modelName, {},
{ plural: Model.pluralModelName });
RemoteModel.attachTo(loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
}));
remoteApp.model(RemoteModel);
}
},
});
beforeEach(function(done) {
@ -41,7 +40,7 @@ describe('RemoteConnector', function() {
test.remote = loopback.createDataSource({
host: 'localhost',
port: remoteApp.get('port'),
connector: loopback.Remote
connector: loopback.Remote,
});
done();
});
@ -62,7 +61,7 @@ describe('RemoteConnector', function() {
ServerModel.setupRemoting();
var m = new RemoteModel({foo: 'bar'});
var m = new RemoteModel({ foo: 'bar' });
m.save(function(err, inst) {
if (err) return done(err);
assert(inst instanceof RemoteModel);

View File

@ -7,7 +7,7 @@ describe('remoting coercion', function() {
var app = loopback();
app.use(loopback.rest());
var TestModel = app.model('TestModel', {base: 'Model', dataSource: null, public: true});
var TestModel = app.model('TestModel', { base: 'Model', dataSource: null, public: true });
TestModel.test = function(inst, cb) {
called = true;
assert(inst instanceof TestModel);
@ -15,15 +15,15 @@ describe('remoting coercion', function() {
cb();
};
TestModel.remoteMethod('test', {
accepts: {arg: 'inst', type: 'TestModel', http: {source: 'body'}},
http: {path: '/test', verb: 'post'}
accepts: { arg: 'inst', type: 'TestModel', http: { source: 'body' }},
http: { path: '/test', verb: 'post' },
});
request(app)
.post('/TestModels/test')
.set('Content-Type', 'application/json')
.send({
foo: 'bar'
foo: 'bar',
})
.end(function(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');
describe('remoting - integration', function() {
lt.beforeEach.withApp(app);
lt.beforeEach.givenModel('store');
@ -17,8 +16,8 @@ describe('remoting - integration', function() {
describe('app.remotes.options', function() {
it('should load remoting options', function() {
var remotes = app.remotes();
assert.deepEqual(remotes.options, {'json': {'limit': '1kb', 'strict': false},
'urlencoded': {'limit': '8kb', 'extended': true}});
assert.deepEqual(remotes.options, { 'json': { 'limit': '1kb', 'strict': false },
'urlencoded': { 'limit': '8kb', 'extended': true }});
});
it('rest handler', function() {
@ -34,7 +33,7 @@ describe('remoting - integration', function() {
}
this.http = this.post('/api/stores');
this.http.send({
'name': name
'name': name,
});
this.http.end(function(err) {
if (err) return done(err);
@ -53,7 +52,7 @@ describe('remoting - integration', function() {
}
this.http = this.post('/api/stores');
this.http.send({
'name': name
'name': name,
});
this.http.end(function(err) {
if (err) return done(err);
@ -88,7 +87,7 @@ describe('remoting - integration', function() {
' ',
m.getHttpMethod(),
' ',
m.getFullPath()
m.getFullPath(),
].join('');
}
@ -121,7 +120,7 @@ describe('remoting - integration', function() {
'deleteById(id:any):object DELETE /stores/:id',
'count(where:object):number GET /stores/count',
'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:
@ -143,7 +142,7 @@ describe('remoting - integration', function() {
'__get__superStores(filter:object):store GET /stores/superStores',
'__create__superStores(data:store):store POST /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);
@ -151,7 +150,6 @@ describe('remoting - integration', function() {
it('should have correct signatures for belongsTo methods',
function() {
var widgetClass = findClass('widget');
var methods = widgetClass.methods
.filter(function(m) {
@ -163,14 +161,13 @@ describe('remoting - integration', function() {
var expectedMethods = [
'prototype.__get__store(refresh:boolean):store ' +
'GET /widgets/:id/store'
'GET /widgets/:id/store',
];
expect(methods).to.include.members(expectedMethods);
});
it('should have correct signatures for hasMany methods',
function() {
var physicianClass = findClass('store');
var methods = physicianClass.methods
.filter(function(m) {
@ -194,14 +191,13 @@ describe('remoting - integration', function() {
'prototype.__delete__widgets() ' +
'DELETE /stores/:id/widgets',
'prototype.__count__widgets(where:object):number ' +
'GET /stores/:id/widgets/count'
'GET /stores/:id/widgets/count',
];
expect(methods).to.include.members(expectedMethods);
});
it('should have correct signatures for hasMany-through methods',
function() { // jscs:disable validateIndentation
var physicianClass = findClass('physician');
var methods = physicianClass.methods
.filter(function(m) {
@ -231,10 +227,9 @@ describe('remoting - integration', function() {
'prototype.__delete__patients() ' +
'DELETE /physicians/:id/patients',
'prototype.__count__patients(where:object):number ' +
'GET /physicians/:id/patients/count'
'GET /physicians/:id/patients/count',
];
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 EMERY = { id: 'e', username: 'emery', email: 'e@t.io', password: 'p' };
/* eslint-disable one-var */
var serverApp, serverUrl, ServerUser, ServerCar, serverCars;
var aliceId, peterId, aliceToken, peterToken, emeryToken, request;
var clientApp, LocalUser, LocalCar, RemoteUser, RemoteCar, clientCars;
var conflictedCarId;
/* eslint-enable one-var */
before(setupServer);
before(setupClient);
@ -322,7 +324,7 @@ describe('Replication over REST', function() {
.to.have.property('fullname', 'Alice Smith');
next();
});
}
},
], done);
});
@ -347,7 +349,7 @@ describe('Replication over REST', function() {
.to.not.have.property('fullname');
next();
});
}
},
], done);
});
@ -367,7 +369,7 @@ describe('Replication over REST', function() {
});
var USER_PROPS = {
id: { type: 'string', id: true }
id: { type: 'string', id: true },
};
var USER_OPTS = {
@ -375,13 +377,13 @@ describe('Replication over REST', function() {
plural: 'Users', // use the same REST path in all models
trackChanges: true,
strict: 'throw',
persistUndefinedAsNull: true
persistUndefinedAsNull: true,
};
var CAR_PROPS = {
id: { type: 'string', id: true, defaultFn: 'guid' },
model: { type: 'string', required: true },
maker: { type: 'string' }
maker: { type: 'string' },
};
var CAR_OPTS = {
@ -395,30 +397,30 @@ describe('Replication over REST', function() {
{
principalType: 'ROLE',
principalId: '$everyone',
permission: 'DENY'
permission: 'DENY',
},
// allow all authenticated users to read data
{
principalType: 'ROLE',
principalId: '$authenticated',
permission: 'ALLOW',
accessType: 'READ'
accessType: 'READ',
},
// allow Alice to pull changes
{
principalType: 'USER',
principalId: ALICE.id,
permission: 'ALLOW',
accessType: 'REPLICATE'
accessType: 'REPLICATE',
},
// allow Peter to write data
{
principalType: 'USER',
principalId: PETER.id,
permission: 'ALLOW',
accessType: 'WRITE'
}
]
accessType: 'WRITE',
},
],
};
function setupServer(done) {
@ -435,9 +437,9 @@ describe('Replication over REST', function() {
user: {
type: 'belongsTo',
model: 'ServerUser',
foreignKey: 'userId'
}
}
foreignKey: 'userId',
},
},
});
serverApp.model(ServerToken, { dataSource: 'db', public: false });
serverApp.model(loopback.ACL, { dataSource: 'db', public: false });
@ -448,7 +450,7 @@ describe('Replication over REST', function() {
serverApp.model(ServerUser, {
dataSource: 'db',
public: true,
relations: { accessTokens: { model: 'ServerToken' } }
relations: { accessTokens: { model: 'ServerToken' }},
});
ServerCar = loopback.createModel('ServerCar', CAR_PROPS, CAR_OPTS);
@ -476,7 +478,7 @@ describe('Replication over REST', function() {
clientApp.dataSource('db', { connector: 'memory' });
clientApp.dataSource('remote', {
connector: 'remote',
url: serverUrl
url: serverUrl,
});
// NOTE(bajtos) At the moment, all models share the same Checkpoint
@ -510,7 +512,7 @@ describe('Replication over REST', function() {
trackChanges: false,
// Enable remote replication in order to get remoting API metadata
// used by the remoting connector
enableRemoteReplication: true
enableRemoteReplication: true,
});
}
@ -548,14 +550,14 @@ describe('Replication over REST', function() {
ServerCar.create(
[
{ id: 'Ford-Mustang', maker: 'Ford', model: 'Mustang' },
{ id: 'Audi-R8', maker: 'Audi', model: 'R8' }
{ id: 'Audi-R8', maker: 'Audi', model: 'R8' },
],
function(err, cars) {
if (err) return next(err);
serverCars = cars.map(carToString);
next();
});
}
},
], done);
}
@ -600,7 +602,7 @@ describe('Replication over REST', function() {
function setAccessToken(token) {
clientApp.dataSources.remote.connector.remotes.auth = {
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');
describe('Replication / Change APIs', function() {
var dataSource, SourceModel, TargetModel;
var useSinceFilter;
var dataSource, SourceModel, TargetModel, useSinceFilter;
var tid = 0; // per-test unique id used e.g. to build unique model names
beforeEach(function() {
@ -17,18 +16,18 @@ describe('Replication / Change APIs', function() {
useSinceFilter = false;
var test = this;
dataSource = this.dataSource = loopback.createDataSource({
connector: loopback.Memory
connector: loopback.Memory,
});
SourceModel = this.SourceModel = PersistedModel.extend(
'SourceModel-' + tid,
{ id: { id: true, type: String, defaultFn: 'guid' } },
{ id: { id: true, type: String, defaultFn: 'guid' }},
{ trackChanges: true });
SourceModel.attachTo(dataSource);
TargetModel = this.TargetModel = PersistedModel.extend(
'TargetModel-' + tid,
{ id: { id: true, type: String, defaultFn: 'guid' } },
{ id: { id: true, type: String, defaultFn: 'guid' }},
{ trackChanges: true });
// NOTE(bajtos) At the moment, all models share the same Checkpoint
@ -44,7 +43,7 @@ describe('Replication / Change APIs', function() {
test.startingCheckpoint = -1;
this.createInitalData = function(cb) {
SourceModel.create({name: 'foo'}, function(err, inst) {
SourceModel.create({ name: 'foo' }, function(err, inst) {
if (err) return cb(err);
test.model = inst;
SourceModel.replicate(TargetModel, cb);
@ -54,7 +53,7 @@ describe('Replication / Change APIs', function() {
describe('optimization check rectifyChange Vs rectifyAllChanges', function() {
beforeEach(function initialData(done) {
var data = [{name: 'John', surname: 'Doe'}, {name: 'Jane', surname: 'Roe'}];
var data = [{ name: 'John', surname: 'Doe' }, { name: 'Jane', surname: 'Roe' }];
async.waterfall([
function(callback) {
SourceModel.create(data, callback);
@ -68,7 +67,7 @@ describe('Replication / Change APIs', function() {
it('should call rectifyAllChanges if no id is passed for rectifyOnDelete', function(done) {
var calls = mockSourceModelRectify();
SourceModel.destroyAll({name: 'John'}, function(err, data) {
SourceModel.destroyAll({ name: 'John' }, function(err, data) {
if (err) return done(err);
expect(calls).to.eql(['rectifyAllChanges']);
done();
@ -77,24 +76,25 @@ describe('Replication / Change APIs', function() {
it('should call rectifyAllChanges if no id is passed for rectifyOnSave', function(done) {
var calls = mockSourceModelRectify();
var newData = {'name': 'Janie'};
SourceModel.update({name: 'Jane'}, newData, function(err, data) {
var newData = { 'name': 'Janie' };
SourceModel.update({ name: 'Jane' }, newData, function(err, data) {
if (err) return done(err);
expect(calls).to.eql(['rectifyAllChanges']);
done();
});
});
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();
async.waterfall([
function(callback) {
SourceModel.destroyAll({name: 'John'}, callback);
SourceModel.destroyAll({ name: 'John' }, callback);
},
function(data, callback) {
SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
}
},
], function(err, results) {
if (err) return done(err);
expect(calls).to.eql(['rectifyChange']);
@ -102,17 +102,18 @@ 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 newData = {'name': 'Janie'};
var newData = { 'name': 'Janie' };
async.waterfall([
function(callback) {
SourceModel.update({name: 'Jane'}, newData, callback);
SourceModel.update({ name: 'Jane' }, newData, callback);
},
function(data, callback) {
SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
}
},
], function(err, result) {
if (err) return done(err);
expect(calls).to.eql(['rectifyChange']);
@ -120,9 +121,10 @@ 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 newData = [{name: 'Janie', surname: 'Doe'}];
var newData = [{ name: 'Janie', surname: 'Doe' }];
async.waterfall([
function(callback) {
SourceModel.create(newData, callback);
@ -130,7 +132,7 @@ describe('Replication / Change APIs', function() {
function(data, callback) {
SourceModel.replicate(TargetModel, callback);
// replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation
}
},
], function(err, result) {
if (err) return done(err);
expect(calls).to.eql(['rectifyChange']);
@ -174,7 +176,7 @@ describe('Replication / Change APIs', function() {
describe('Model.changes(since, filter, callback)', function() {
it('Get changes since the given checkpoint', function(done) {
var test = this;
this.SourceModel.create({name: 'foo'}, function(err) {
this.SourceModel.create({ name: 'foo' }, function(err) {
if (err) return done(err);
setTimeout(function() {
test.SourceModel.changes(test.startingCheckpoint, {}, function(err, changes) {
@ -192,7 +194,6 @@ describe('Replication / Change APIs', function() {
if (err) return done(err);
SourceModel.changes(FUTURE_CHECKPOINT, {}, function(err, changes) {
if (err) return done(err);
/*jshint -W030 */
expect(changes).to.be.empty;
done();
});
@ -201,11 +202,9 @@ describe('Replication / Change APIs', function() {
});
describe('Model.replicate(since, targetModel, options, callback)', function() {
function assertTargetModelEqualsSourceModel(conflicts, sourceModel,
targetModel, done) {
var sourceData;
var targetData;
var sourceData, targetData;
assert(conflicts.length === 0);
async.parallel([
@ -222,7 +221,7 @@ describe('Replication / Change APIs', function() {
targetData = result;
cb();
});
}
},
], function(err) {
if (err) return done(err);
@ -235,7 +234,7 @@ describe('Replication / Change APIs', function() {
var test = this;
var options = {};
this.SourceModel.create({name: 'foo'}, function(err) {
this.SourceModel.create({ name: 'foo' }, function(err) {
if (err) return done(err);
test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,
options, function(err, conflicts) {
@ -251,7 +250,7 @@ describe('Replication / Change APIs', function() {
var test = this;
var options = {};
this.SourceModel.create({name: 'foo'}, function(err) {
this.SourceModel.create({ name: 'foo' }, function(err) {
if (err) return done(err);
test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,
options)
@ -289,7 +288,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.eql(['2']);
next();
});
}
},
], done);
});
@ -321,7 +320,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.eql(['2']);
next();
});
}
},
], done);
});
@ -433,7 +432,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list), 'target ids').to.eql(['init', 'racer']);
next();
});
}
},
], done);
});
@ -453,11 +452,11 @@ describe('Replication / Change APIs', function() {
expect(conflicts, 'conflicts').to.eql([]);
expect(newCheckpoints, 'currentCheckpoints').to.eql({
source: sourceCp + 1,
target: targetCp + 1
target: targetCp + 1,
});
cb();
});
}
},
], done);
function bumpSourceCheckpoint(cb) {
@ -487,14 +486,14 @@ describe('Replication / Change APIs', function() {
TargetModel.currentCheckpoint(function(err, cp) {
if (err) return next(err);
TargetModel.getChangeModel().find(
{ where: { checkpoint: { gte: cp } } },
{ where: { checkpoint: { gte: cp }}},
function(err, changes) {
if (err) return done(err);
expect(changes).to.have.length(0);
done();
});
});
}
},
], done);
});
@ -513,14 +512,14 @@ describe('Replication / Change APIs', function() {
connector.updateAttributes(
TargetModel.modelName,
'1',
{name: '3rd-party'},
{ name: '3rd-party' },
cb);
} else {
// 2.x connectors require `options`
connector.updateAttributes(
TargetModel.modelName,
'1',
{name: '3rd-party'},
{ name: '3rd-party' },
{}, // options
cb);
}
@ -540,7 +539,7 @@ describe('Replication / Change APIs', function() {
},
replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1')
verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done);
});
@ -555,13 +554,13 @@ describe('Replication / Change APIs', function() {
if (connector.create.length <= 3) {
connector.create(
TargetModel.modelName,
{id: '1', name: '3rd-party'},
{ id: '1', name: '3rd-party' },
cb);
} else {
// 2.x connectors require `options`
connector.create(
TargetModel.modelName,
{id: '1', name: '3rd-party'},
{ id: '1', name: '3rd-party' },
{}, // options
cb);
}
@ -581,7 +580,7 @@ describe('Replication / Change APIs', function() {
},
replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1')
verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done);
});
@ -599,14 +598,14 @@ describe('Replication / Change APIs', function() {
connector.updateAttributes(
TargetModel.modelName,
'1',
{name: '3rd-party'},
{ name: '3rd-party' },
cb);
} else {
// 2.x connectors require `options`
connector.updateAttributes(
TargetModel.modelName,
'1',
{name: '3rd-party'},
{ name: '3rd-party' },
{}, // options
cb);
}
@ -626,7 +625,7 @@ describe('Replication / Change APIs', function() {
},
replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1')
verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done);
});
@ -657,7 +656,7 @@ describe('Replication / Change APIs', function() {
next();
},
replicateExpectingSuccess(),
verifyInstanceWasReplicated(SourceModel, TargetModel, '1')
verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),
], done);
});
});
@ -686,7 +685,7 @@ describe('Replication / Change APIs', function() {
inst.name = 'target update';
inst.save(cb);
});
}
},
], function(err) {
if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -724,11 +723,11 @@ describe('Replication / Change APIs', function() {
this.conflict.models(function(err, source, target) {
assert.deepEqual(source.toJSON(), {
id: test.model.id,
name: 'source update'
name: 'source update',
});
assert.deepEqual(target.toJSON(), {
id: test.model.id,
name: 'target update'
name: 'target update',
});
done();
});
@ -758,7 +757,7 @@ describe('Replication / Change APIs', function() {
inst.name = 'target update';
inst.save(cb);
});
}
},
], function(err) {
if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -797,7 +796,7 @@ describe('Replication / Change APIs', function() {
assert.equal(source, null);
assert.deepEqual(target.toJSON(), {
id: test.model.id,
name: 'target update'
name: 'target update',
});
done();
});
@ -827,7 +826,7 @@ describe('Replication / Change APIs', function() {
if (err) return cb(err);
inst.remove(cb);
});
}
},
], function(err) {
if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -866,7 +865,7 @@ describe('Replication / Change APIs', function() {
assert.equal(target, null);
assert.deepEqual(source.toJSON(), {
id: test.model.id,
name: 'source update'
name: 'source update',
});
done();
});
@ -895,7 +894,7 @@ describe('Replication / Change APIs', function() {
if (err) return cb(err);
inst.remove(cb);
});
}
},
], function(err) {
if (err) return done(err);
SourceModel.replicate(TargetModel, function(err, conflicts) {
@ -958,7 +957,7 @@ describe('Replication / Change APIs', function() {
};
SourceModel.findOrCreate(
{ where: { name: 'does-not-exist' } },
{ where: { name: 'does-not-exist' }},
{ name: 'created' },
function(err, inst) {
if (err) return done(err);
@ -1070,14 +1069,14 @@ describe('Replication / Change APIs', function() {
});
},
replicateExpectingSuccess(),
verifySourceWasReplicated()
verifySourceWasReplicated(),
], done);
});
beforeEach(function setupThirdModel() {
AnotherModel = this.AnotherModel = PersistedModel.extend(
'AnotherModel-' + tid,
{ id: { id: true, type: String, defaultFn: 'guid' } },
{ id: { id: true, type: String, defaultFn: 'guid' }},
{ trackChanges: true });
// NOTE(bajtos) At the moment, all models share the same Checkpoint
@ -1107,7 +1106,7 @@ describe('Replication / Change APIs', function() {
expect(getIds(list)).to.not.contain(sourceInstance.id);
next();
});
}
},
], done);
});
@ -1119,7 +1118,7 @@ describe('Replication / Change APIs', function() {
updateSourceInstanceNameTo('updated'),
updateSourceInstanceNameTo('again'),
replicateExpectingSuccess(),
verifySourceWasReplicated()
verifySourceWasReplicated(),
], done);
});
@ -1141,7 +1140,7 @@ describe('Replication / Change APIs', function() {
async.series([
// Note that ClientA->Server was already replicated during setup
replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB)
verifySourceWasReplicated(ClientB),
], done);
});
@ -1158,7 +1157,7 @@ describe('Replication / Change APIs', function() {
replicateExpectingSuccess(ClientA, Server),
replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB)
verifySourceWasReplicated(ClientB),
], done);
});
@ -1167,7 +1166,7 @@ describe('Replication / Change APIs', function() {
deleteSourceInstance(),
replicateExpectingSuccess(ClientA, Server),
replicateExpectingSuccess(Server, ClientB),
verifySourceWasReplicated(ClientB)
verifySourceWasReplicated(ClientB),
], done);
});
@ -1183,7 +1182,7 @@ describe('Replication / Change APIs', function() {
it('propagates CREATE', function(done) {
async.series([
sync(ClientA, Server),
sync(ClientB, Server)
sync(ClientB, Server),
], done);
});
@ -1207,7 +1206,6 @@ describe('Replication / Change APIs', function() {
// ClientB fetches the created & updated instance from the server
sync(ClientB, Server),
], done);
});
it('does not report false conflicts', function(done) {
@ -1227,7 +1225,7 @@ describe('Replication / Change APIs', function() {
sync(ClientB, Server),
// client A fetches the changes
sync(ClientA, Server)
sync(ClientA, Server),
], done);
});
@ -1325,7 +1323,7 @@ describe('Replication / Change APIs', function() {
// and sync back to ClientA too
sync(ClientA, Server),
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId)
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),
], cb);
}
@ -1363,7 +1361,7 @@ describe('Replication / Change APIs', function() {
// and sync back to ClientA too
sync(ClientA, Server),
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId)
verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),
], cb);
}
@ -1383,11 +1381,10 @@ describe('Replication / Change APIs', function() {
// NOTE(bajtos) It's important to replicate from the client to the
// server first, so that we can resolve any conflicts at the client
replicateExpectingSuccess(client, server),
replicateExpectingSuccess(server, client)
replicateExpectingSuccess(server, client),
], next);
};
}
});
function updateSourceInstanceNameTo(value) {

View File

@ -4,7 +4,7 @@ describe('loopback.rest', function() {
var MyModel;
beforeEach(function() {
var ds = app.dataSource('db', { connector: loopback.Memory });
MyModel = ds.createModel('MyModel', {name: String});
MyModel = ds.createModel('MyModel', { name: String });
loopback.autoAttach();
});
@ -20,7 +20,7 @@ describe('loopback.rest', function() {
app.set('legacyExplorer', false);
app.model(MyModel);
app.use(loopback.rest());
MyModel.create({name: 'm1'}, function(err, inst) {
MyModel.create({ name: 'm1' }, function(err, inst) {
request(app)
.del('/mymodels/' + inst.id)
.expect(200, function(err, res) {
@ -61,7 +61,7 @@ describe('loopback.rest', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({exists: false});
expect(res.body).to.eql({ exists: false });
done();
});
});
@ -69,7 +69,7 @@ describe('loopback.rest', function() {
it('should report 200 for GET /:id found', function(done) {
app.model(MyModel);
app.use(loopback.rest());
MyModel.create({name: 'm1'}, function(err, inst) {
MyModel.create({ name: 'm1' }, function(err, inst) {
request(app).get('/mymodels/' + inst.id)
.expect(200)
.end(done);
@ -79,7 +79,7 @@ describe('loopback.rest', function() {
it('should report 200 for HEAD /:id found', function(done) {
app.model(MyModel);
app.use(loopback.rest());
MyModel.create({name: 'm2'}, function(err, inst) {
MyModel.create({ name: 'm2' }, function(err, inst) {
request(app).head('/mymodels/' + inst.id)
.expect(200)
.end(done);
@ -89,12 +89,12 @@ describe('loopback.rest', function() {
it('should report 200 for GET /:id/exists found', function(done) {
app.model(MyModel);
app.use(loopback.rest());
MyModel.create({name: 'm2'}, function(err, inst) {
MyModel.create({ name: 'm2' }, function(err, inst) {
request(app).get('/mymodels/' + inst.id + '/exists')
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({exists: true});
expect(res.body).to.eql({ exists: true });
done();
});
});
@ -105,7 +105,7 @@ describe('loopback.rest', function() {
// NOTE it is crucial to set `remoting` before creating any models
var supportedTypes = ['json', 'application/javascript', 'text/javascript'];
app.set('remoting', { rest: { supportedTypes: supportedTypes } });
app.set('remoting', { rest: { supportedTypes: supportedTypes }});
app.model(MyModel);
app.use(loopback.rest());
@ -120,7 +120,7 @@ describe('loopback.rest', function() {
var ds = app.dataSource('db', { connector: loopback.Memory });
var CustomModel = ds.createModel('CustomModel',
{ name: String },
{ http: { 'path': 'domain1/CustomModelPath' }
{ http: { 'path': 'domain1/CustomModelPath' },
});
app.model(CustomModel);
@ -133,7 +133,7 @@ describe('loopback.rest', function() {
var ds = app.dataSource('db', { connector: loopback.Memory });
var CustomModel = ds.createModel('CustomModel',
{ name: String },
{ http: { path: 'domain%20one/CustomModelPath' }
{ http: { path: 'domain%20one/CustomModelPath' },
});
app.model(CustomModel);
@ -163,8 +163,8 @@ describe('loopback.rest', function() {
cb(null, req.accessToken ? req.accessToken.id : null);
};
loopback.remoteMethod(User.getToken, {
accepts: [{ type: 'object', http: { source: 'req' } }],
returns: [{ type: 'object', name: 'id' }]
accepts: [{ type: 'object', http: { source: 'req' }}],
returns: [{ type: 'object', name: 'id' }],
});
app.use(loopback.rest());
@ -240,15 +240,15 @@ describe('loopback.rest', function() {
cb(null, req && req.accessToken ? req.accessToken.id : null);
};
// Set up the ACL
User.settings.acls.push({principalType: 'ROLE',
User.settings.acls.push({ principalType: 'ROLE',
principalId: '$authenticated', permission: 'ALLOW',
property: 'getToken'});
property: 'getToken' });
loopback.remoteMethod(User.getToken, {
accepts: [],
returns: [
{ type: 'object', name: 'id' }
]
{ type: 'object', name: 'id' },
],
});
});
@ -276,7 +276,7 @@ describe('loopback.rest', function() {
it('should enable context with loopback.rest', function(done) {
app.enableAuth();
app.set('remoting', { context: { enableHttpContext: true } });
app.set('remoting', { context: { enableHttpContext: true }});
app.use(loopback.rest());
invokeGetToken(done);
@ -312,8 +312,8 @@ describe('loopback.rest', function() {
loopback.remoteMethod(User.getToken, {
accepts: [],
returns: [
{ type: 'object', name: 'id' }
]
{ type: 'object', name: 'id' },
],
});
invokeGetToken(done);
@ -326,9 +326,9 @@ describe('loopback.rest', function() {
// the global model registry
app.model('accessToken', {
options: {
base: 'AccessToken'
base: 'AccessToken',
},
dataSource: 'db'
dataSource: 'db',
});
return app.model('user', {
options: {
@ -337,11 +337,11 @@ describe('loopback.rest', function() {
accessTokens: {
model: 'accessToken',
type: 'hasMany',
foreignKey: 'userId'
}
}
foreignKey: 'userId',
},
dataSource: 'db'
},
},
dataSource: 'db',
});
}
function givenLoggedInUser(cb, done) {
@ -389,9 +389,9 @@ describe('loopback.rest', function() {
it('should be exposed when the definition value is true', function(done) {
var app = require(getFixturePath('model-config-default-true'));
app.models.Todo.create([
{content: 'a'},
{content: 'b'},
{content: 'c'}
{ content: 'a' },
{ content: 'b' },
{ content: 'c' },
], function() {
request(app)
.del('/todos')
@ -435,9 +435,9 @@ describe('loopback.rest', function() {
it('should be exposed when the definition value is true', function(done) {
var app = require(getFixturePath('config-default-true'));
app.models.Todo.create([
{content: 'a'},
{content: 'b'},
{content: 'c'}
{ content: 'a' },
{ content: 'b' },
{ content: 'c' },
], function() {
request(app)
.del('/todos')

View File

@ -18,7 +18,7 @@ describe('role model', function() {
var ds;
beforeEach(function() {
ds = loopback.createDataSource({connector: 'memory'});
ds = loopback.createDataSource({ connector: 'memory' });
// Re-attach the models so that they can have isolated store to avoid
// pollutions from other tests
ACL.attachTo(ds);
@ -36,9 +36,10 @@ describe('role model', function() {
});
it('should define role/role relations', function() {
Role.create({name: 'user'}, function(err, userRole) {
Role.create({name: 'admin'}, function(err, adminRole) {
userRole.principals.create({principalType: RoleMapping.ROLE, principalId: adminRole.id}, function(err, mapping) {
Role.create({ name: 'user' }, function(err, userRole) {
Role.create({ name: 'admin' }, function(err, adminRole) {
userRole.principals.create({ principalType: RoleMapping.ROLE, principalId: adminRole.id },
function(err, mapping) {
Role.find(function(err, roles) {
assert.equal(roles.length, 2);
});
@ -56,15 +57,14 @@ describe('role model', 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);
Role.create({name: 'userRole'}, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) {
Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
Role.find(function(err, roles) {
assert(!err);
assert.equal(roles.length, 1);
@ -85,16 +85,15 @@ describe('role model', 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);
Role.create({name: 'userRole'}, function(err, role) {
Role.create({ name: 'userRole' }, function(err, role) {
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.equal(p.roleId, role.id);
Role.find(function(err, roles) {
@ -117,45 +116,52 @@ describe('role model', 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);
Role.create({name: 'userRole'}, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) {
Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
// Role.find(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);
});
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);
});
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);
});
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(roles.indexOf(role.id) >= 0);
assert(roles.indexOf(Role.EVERYONE) >= 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(roles.indexOf(Role.EVERYONE) >= 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(roles.indexOf(Role.EVERYONE) >= 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(roles.indexOf(Role.EVERYONE) >= 0);
assert(roles.indexOf(Role.UNAUTHENTICATED) >= 0);
@ -163,54 +169,60 @@ describe('role model', function() {
});
});
});
});
it('should support owner role resolver', function() {
var Album = ds.createModel('Album', {
name: String,
userId: Number
userId: Number,
}, {
relations: {
user: {
type: 'belongsTo',
model: 'User',
foreignKey: 'userId'
}
}
foreignKey: 'userId',
},
},
});
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) {
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) {
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);
});
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);
});
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);
});
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);
});
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);
});
// console.log('User: ', user.id);
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) {
Album.create({ name: 'Album 1', userId: user.id }, function(err, album1) {
var role = { principalType: ACL.USER, principalId: user.id, model: Album, id: album1.id };
Role.isInRole(Role.OWNER, role, function(err, yes) {
assert(!err && yes);
});
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) {
Album.create({ name: 'Album 2' }, function(err, album2) {
role = { principalType: ACL.USER, principalId: user.id, model: Album, id: album2.id };
Role.isInRole(Role.OWNER, role, function(err, yes) {
assert(!err && !yes);
});
});
@ -225,35 +237,35 @@ describe('role model', function() {
User.create({
username: 'john',
email: 'john@gmail.com',
password: 'jpass'
password: 'jpass',
}, function(err, u) {
if (err) return done(err);
user = u;
User.create({
username: 'mary',
email: 'mary@gmail.com',
password: 'mpass'
password: 'mpass',
}, function(err, u) {
if (err) return done(err);
Application.create({
name: 'demo'
name: 'demo',
}, function(err, a) {
if (err) return done(err);
app = a;
Role.create({
name: 'admin'
name: 'admin',
}, function(err, r) {
if (err) return done(err);
role = r;
var principals = [
{
principalType: ACL.USER,
principalId: user.id
principalId: user.id,
},
{
principalType: ACL.APP,
principalId: app.id
}
principalId: app.id,
},
];
async.each(principals, function(p, done) {
role.principals.create(p, done);
@ -344,7 +356,6 @@ describe('role model', function() {
done();
});
});
});
describe('listByPrincipalType', function() {
@ -371,9 +382,10 @@ describe('role model', function() {
mappings.forEach(function(principalType) {
var Model = principalTypesToModels[principalType];
Model.create({name:'test', email:'x@y.com', password: 'foobar'}, function(err, model) {
Role.create({name:'testRole'}, function(err, role) {
role.principals.create({principalType: principalType, principalId: model.id}, function(err, p) {
Model.create({ name: 'test', email: 'x@y.com', password: 'foobar' }, function(err, model) {
Role.create({ name: 'testRole' }, function(err, role) {
role.principals.create({ principalType: principalType, principalId: model.id },
function(err, p) {
var pluralName = Model.pluralModelName.toLowerCase();
role[pluralName](function(err, models) {
assert(!err);
@ -389,10 +401,11 @@ describe('role model', function() {
});
it('should apply query', function(done) {
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {
Role.create({name: 'userRole'}, function(err, role) {
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function(err, p) {
var query = {fields:['id', 'name']};
User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) {
Role.create({ name: 'userRole' }, function(err, role) {
role.principals.create({ principalType: RoleMapping.USER, principalId: user.id },
function(err, p) {
var query = { fields: ['id', 'name'] };
sandbox.spy(User, 'find');
role.users(query, function(err, users) {
assert(!err);
@ -406,5 +419,4 @@ describe('role model', function() {
});
});
});
});

View File

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

View File

@ -1,4 +1,3 @@
/*jshint -W030 */
var loopback = require('../');
var lt = require('./helpers/loopback-testing-helper');
var path = require('path');
@ -7,7 +6,6 @@ var app = require(path.join(SIMPLE_APP, 'server/server.js'));
var expect = require('chai').expect;
describe('users - integration', function() {
lt.beforeEach.withApp(app);
before(function(done) {
@ -15,7 +13,7 @@ describe('users - integration', function() {
// other tests
app.models.User.hasMany(app.models.post);
app.models.User.hasMany(app.models.AccessToken,
{options: {disableInclude: true}});
{ options: { disableInclude: true }});
app.models.AccessToken.belongsTo(app.models.User);
app.models.User.destroyAll(function(err) {
if (err) return done(err);
@ -30,12 +28,11 @@ describe('users - integration', function() {
});
describe('base-user', function() {
var userId;
var accessToken;
var userId, accessToken;
it('should create a new user', function(done) {
this.post('/api/users')
.send({username: 'x', email: 'x@y.com', password: 'x'})
.send({ username: 'x', email: 'x@y.com', password: 'x' })
.expect(200, function(err, res) {
if (err) return done(err);
expect(res.body.id).to.exist;
@ -48,7 +45,7 @@ describe('users - integration', function() {
var url = '/api/users/login';
this.post(url)
.send({username: 'x', email: 'x@y.com', password: 'x'})
.send({ username: 'x', email: 'x@y.com', password: 'x' })
.expect(200, function(err, res) {
if (err) {
return done(err);
@ -62,7 +59,7 @@ describe('users - integration', function() {
it('should create post for a given user', function(done) {
var url = '/api/users/' + userId + '/posts?access_token=' + accessToken;
this.post(url)
.send({title: 'T1', content: 'C1'})
.send({ title: 'T1', content: 'C1' })
.expect(200, function(err, res) {
if (err) {
return done(err);
@ -93,14 +90,13 @@ describe('users - integration', function() {
});
describe('sub-user', function() {
var userId;
var accessToken;
var userId, accessToken;
it('should create a new user', function(done) {
var url = '/api/myUsers';
this.post(url)
.send({username: 'x', email: 'x@y.com', password: 'x'})
.send({ username: 'x', email: 'x@y.com', password: 'x' })
.expect(200, function(err, res) {
if (err) {
return done(err);
@ -115,7 +111,7 @@ describe('users - integration', function() {
var url = '/api/myUsers/login';
this.post(url)
.send({username: 'x', email: 'x@y.com', password: 'x'})
.send({ username: 'x', email: 'x@y.com', password: 'x' })
.expect(200, function(err, res) {
if (err) {
return done(err);
@ -129,7 +125,7 @@ describe('users - integration', function() {
it('should create blog for a given user', function(done) {
var url = '/api/myUsers/' + userId + '/blogs?access_token=' + accessToken;
this.post(url)
.send({title: 'T1', content: 'C1'})
.send({ title: 'T1', content: 'C1' })
.expect(200, function(err, res) {
if (err) {
console.error(err);
@ -157,6 +153,4 @@ describe('users - integration', function() {
});
});
});
});

View File

@ -1,23 +1,25 @@
require('./support');
var loopback = require('../');
var User;
var AccessToken;
var User, AccessToken;
var MailConnector = require('../lib/connectors/mail');
var userMemory = loopback.createDataSource({
connector: 'memory'
connector: 'memory',
});
describe('User', function() {
var validCredentialsEmail = 'foo@bar.com';
var validCredentials = {email: validCredentialsEmail, password: 'bar'};
var validCredentialsEmailVerified = {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 validCredentialsWithTTLAndScope = {email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'};
var validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};
var invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};
var incompleteCredentials = {password: 'bar1'};
var validCredentials = { email: validCredentialsEmail, password: 'bar' };
var validCredentialsEmailVerified = {
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 validCredentialsWithTTLAndScope = {
email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all' };
var validMixedCaseEmailCredentials = { email: 'Foo@bar.com', password: 'bar' };
var invalidCredentials = { email: 'foo1@bar.com', password: 'invalid' };
var incompleteCredentials = { password: 'bar1' };
var defaultApp;
@ -26,23 +28,22 @@ describe('User', function() {
// to the wrong app instance
defaultApp = loopback.User.app;
loopback.User.app = null;
User = loopback.User.extend('TestUser', {}, {http: {path: 'test-users'}});
User = loopback.User.extend('TestUser', {}, { http: { path: 'test-users' }});
AccessToken = loopback.AccessToken.extend('TestAccessToken');
User.email = loopback.Email.extend('email');
loopback.autoAttach();
// Update the AccessToken relation to use the subclass of User
AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
AccessToken.belongsTo(User, { as: 'user', foreignKey: 'userId' });
User.hasMany(AccessToken, { as: 'accessTokens', foreignKey: 'userId' });
// allow many User.afterRemote's to be called
User.setMaxListeners(0);
});
beforeEach(function(done) {
app.enableAuth();
app.use(loopback.token({model: AccessToken}));
app.use(loopback.token({ model: AccessToken }));
app.use(loopback.rest());
app.model(User);
@ -60,7 +61,7 @@ describe('User', function() {
describe('User.create', function() {
it('Create a new user', function(done) {
User.create({email: 'f@b.com', password: 'bar'}, function(err, user) {
User.create({ email: 'f@b.com', password: 'bar' }, function(err, user) {
assert(!err);
assert(user.id);
assert(user.email);
@ -70,7 +71,7 @@ describe('User', function() {
it('Create a new user (email case-sensitivity off)', function(done) {
User.settings.caseSensitiveEmail = false;
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
User.create({ email: 'F@b.com', password: 'bar' }, function(err, user) {
if (err) return done(err);
assert(user.id);
assert.equal(user.email, user.email.toLowerCase());
@ -79,7 +80,7 @@ describe('User', function() {
});
it('Create a new user (email case-sensitive)', function(done) {
User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {
User.create({ email: 'F@b.com', password: 'bar' }, function(err, user) {
if (err) return done(err);
assert(user.id);
assert(user.email);
@ -89,30 +90,30 @@ describe('User', function() {
});
it('credentials/challenges are object types', function(done) {
User.create({email: 'f1@b.com', password: 'bar1',
credentials: {cert: 'xxxxx', key: '111'},
challenges: {x: 'X', a: 1}
User.create({ email: 'f1@b.com', password: 'bar1',
credentials: { cert: 'xxxxx', key: '111' },
challenges: { x: 'X', a: 1 },
}, function(err, user) {
assert(!err);
User.findById(user.id, function(err, user) {
assert(user.id);
assert(user.email);
assert.deepEqual(user.credentials, {cert: 'xxxxx', key: '111'});
assert.deepEqual(user.challenges, {x: 'X', a: 1});
assert.deepEqual(user.credentials, { cert: 'xxxxx', key: '111' });
assert.deepEqual(user.challenges, { x: 'X', a: 1 });
done();
});
});
});
it('Email is required', function(done) {
User.create({password: '123'}, function(err) {
User.create({ password: '123' }, function(err) {
assert(err);
assert.equal(err.name, 'ValidationError');
assert.equal(err.statusCode, 422);
assert.equal(err.details.context, User.modelName);
assert.deepEqual(err.details.codes.email, [
'presence',
'format.null'
'format.null',
]);
done();
@ -121,24 +122,24 @@ describe('User', function() {
// will change in future versions where password will be optional by default
it('Password is required', function(done) {
var u = new User({email: '123@456.com'});
var u = new User({ email: '123@456.com' });
User.create({email: 'c@d.com'}, function(err) {
User.create({ email: 'c@d.com' }, function(err) {
assert(err);
done();
});
});
it('Requires a valid email', function(done) {
User.create({email: 'foo@', password: '123'}, function(err) {
User.create({ email: 'foo@', password: '123' }, function(err) {
assert(err);
done();
});
});
it('Requires a unique email', function(done) {
User.create({email: 'a@b.com', password: 'foobar'}, function() {
User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {
User.create({ email: 'a@b.com', password: 'foobar' }, function() {
User.create({ email: 'a@b.com', password: 'batbaz' }, function(err) {
assert(err, 'should error because the email is not unique!');
done();
});
@ -147,9 +148,9 @@ describe('User', function() {
it('Requires a unique email (email case-sensitivity off)', function(done) {
User.settings.caseSensitiveEmail = false;
User.create({email: 'A@b.com', password: 'foobar'}, function(err) {
User.create({ email: 'A@b.com', password: 'foobar' }, function(err) {
if (err) return done(err);
User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {
User.create({ email: 'a@b.com', password: 'batbaz' }, function(err) {
assert(err, 'should error because the email is not unique!');
done();
});
@ -157,8 +158,8 @@ describe('User', function() {
});
it('Requires a unique email (email case-sensitive)', function(done) {
User.create({email: 'A@b.com', password: 'foobar'}, function(err, user1) {
User.create({email: 'a@b.com', password: 'batbaz'}, function(err, user2) {
User.create({ email: 'A@b.com', password: 'foobar' }, function(err, user1) {
User.create({ email: 'a@b.com', password: 'batbaz' }, function(err, user2) {
if (err) return done(err);
assert.notEqual(user1.email, user2.email);
done();
@ -167,8 +168,8 @@ describe('User', function() {
});
it('Requires a unique username', function(done) {
User.create({email: 'a@b.com', username: 'abc', password: 'foobar'}, function() {
User.create({email: 'b@b.com', username: 'abc', password: 'batbaz'}, function(err) {
User.create({ email: 'a@b.com', username: 'abc', password: 'foobar' }, function() {
User.create({ email: 'b@b.com', username: 'abc', password: 'batbaz' }, function(err) {
assert(err, 'should error because the username is not unique!');
done();
});
@ -176,8 +177,8 @@ describe('User', function() {
});
it('Requires a password to login with basic auth', function(done) {
User.create({email: 'b@c.com'}, function(err) {
User.login({email: 'b@c.com'}, function(err, accessToken) {
User.create({ email: 'b@c.com' }, function(err) {
User.login({ email: 'b@c.com' }, function(err, accessToken) {
assert(!accessToken, 'should not create a accessToken without a valid password');
assert(err, 'should not login without a password');
assert.equal(err.code, 'LOGIN_FAILED');
@ -187,20 +188,19 @@ describe('User', function() {
});
it('Hashes the given password', function() {
var u = new User({username: 'foo', password: 'bar'});
var u = new User({ username: 'foo', password: 'bar' });
assert(u.password !== 'bar');
});
it('does not hash the password if it\'s already hashed', function() {
var u1 = new User({username: 'foo', password: 'bar'});
var u1 = new User({ username: 'foo', password: 'bar' });
assert(u1.password !== 'bar');
var u2 = new User({username: 'foo', password: u1.password});
var u2 = new User({ username: 'foo', password: u1.password });
assert(u2.password === u1.password);
});
describe('custom password hash', function() {
var defaultHashPassword;
var defaultValidatePassword;
var defaultHashPassword, defaultValidatePassword;
beforeEach(function() {
defaultHashPassword = User.hashPassword;
@ -225,7 +225,7 @@ describe('User', function() {
it('Reports invalid password', function() {
try {
var u = new User({username: 'foo', password: 'aa'});
var u = new User({ username: 'foo', password: 'aa' });
assert(false, 'Error should have been thrown');
} catch (e) {
// Ignore
@ -233,7 +233,7 @@ describe('User', function() {
});
it('Hashes the given password', function() {
var u = new User({username: 'foo', password: 'bar'});
var u = new User({ username: 'foo', password: 'bar' });
assert(u.password === 'BAR');
});
});
@ -264,7 +264,7 @@ describe('User', function() {
it('Should be able to find lowercase email with mixed-case email query', function(done) {
User.settings.caseSensitiveEmail = false;
User.find({where:{email: validMixedCaseEmailCredentials.email}}, function(err, result) {
User.find({ where: { email: validMixedCaseEmailCredentials.email }}, function(err, result) {
if (err) done(err);
assert(result[0], 'The query did not find the user');
assert.equal(result[0].email, validCredentialsEmail);
@ -355,7 +355,7 @@ describe('User', function() {
// Override createAccessToken
User.prototype.createAccessToken = function(ttl, cb) {
// Reduce the ttl by half for testing purpose
this.accessTokens.create({ttl: ttl / 2 }, cb);
this.accessTokens.create({ ttl: ttl / 2 }, cb);
};
User.login(validCredentialsWithTTL, function(err, accessToken) {
assert(accessToken.userId);
@ -383,7 +383,7 @@ describe('User', function() {
// Override createAccessToken
User.prototype.createAccessToken = function(ttl, options, cb) {
// Reduce the ttl by half for testing purpose
this.accessTokens.create({ttl: ttl / 2, scopes: options.scope}, cb);
this.accessTokens.create({ ttl: ttl / 2, scopes: options.scope }, cb);
};
User.login(validCredentialsWithTTLAndScope, function(err, accessToken) {
assert(accessToken.userId);
@ -393,7 +393,7 @@ describe('User', function() {
assert.equal(accessToken.scopes, 'all');
User.findById(accessToken.userId, function(err, user) {
user.createAccessToken(120, {scope: 'default'}, function(err, accessToken) {
user.createAccessToken(120, { scope: 'default' }, function(err, accessToken) {
assert(accessToken.userId);
assert(accessToken.id);
assert.equal(accessToken.ttl, 60);
@ -575,14 +575,17 @@ describe('User', function() {
it('Require valid and complete credentials for email verification error', function(done) {
User.login({ email: validCredentialsEmail }, function(err, accessToken) {
// strongloop/loopback#931
// 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 + '"'));
// 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.equal(err.code, 'LOGIN_FAILED');
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 })
.then(function(accessToken) {
done();
@ -590,7 +593,8 @@ describe('User', function() {
.catch(function(err) {
// strongloop/loopback#931
// 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');
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)
.post('/test-users/login')
.expect('Content-Type', /json/)
@ -664,9 +670,11 @@ describe('User', function() {
return done(err);
}
// 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;
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');
done();
});
@ -687,23 +695,21 @@ describe('User', function() {
done();
});
});
});
describe('User.login requiring realm', function() {
var User;
var AccessToken;
var User, AccessToken;
before(function() {
User = loopback.User.extend('RealmUser', {},
{realmRequired: true, realmDelimiter: ':'});
{ realmRequired: true, realmDelimiter: ':' });
AccessToken = loopback.AccessToken.extend('RealmAccessToken');
loopback.autoAttach();
// Update the AccessToken relation to use the subclass of User
AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
AccessToken.belongsTo(User, { as: 'user', foreignKey: 'userId' });
User.hasMany(AccessToken, { as: 'accessTokens', foreignKey: 'userId' });
// allow many User.afterRemote's to be called
User.setMaxListeners(0);
@ -713,53 +719,53 @@ describe('User', function() {
realm: 'realm1',
username: 'foo100',
email: 'foo100@bar.com',
password: 'pass100'
password: 'pass100',
};
var realm2User = {
realm: 'realm2',
username: 'foo100',
email: 'foo100@bar.com',
password: 'pass200'
password: 'pass200',
};
var credentialWithoutRealm = {
username: 'foo100',
email: 'foo100@bar.com',
password: 'pass100'
password: 'pass100',
};
var credentialWithBadPass = {
realm: 'realm1',
username: 'foo100',
email: 'foo100@bar.com',
password: 'pass001'
password: 'pass001',
};
var credentialWithBadRealm = {
realm: 'realm3',
username: 'foo100',
email: 'foo100@bar.com',
password: 'pass100'
password: 'pass100',
};
var credentialWithRealm = {
realm: 'realm1',
username: 'foo100',
password: 'pass100'
password: 'pass100',
};
var credentialRealmInUsername = {
username: 'realm1:foo100',
password: 'pass100'
password: 'pass100',
};
var credentialRealmInEmail = {
email: 'realm1:foo100@bar.com',
password: 'pass100'
password: 'pass100',
};
var user1;
var user1 = null;
beforeEach(function(done) {
User.create(realm1User, function(err, u) {
if (err) {
@ -771,11 +777,11 @@ describe('User', function() {
});
afterEach(function(done) {
User.deleteAll({realm: 'realm1'}, function(err) {
User.deleteAll({ realm: 'realm1' }, function(err) {
if (err) {
return done(err);
}
User.deleteAll({realm: 'realm2'}, done);
User.deleteAll({ realm: 'realm2' }, done);
});
});
@ -860,7 +866,7 @@ describe('User', function() {
login(logout);
function login(fn) {
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
User.login({ email: 'foo@bar.com', password: 'bar' }, fn);
}
function logout(err, accessToken) {
@ -868,11 +874,12 @@ 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);
function login(fn) {
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
User.login({ email: 'foo@bar.com', password: 'bar' }, fn);
}
function logout(err, accessToken) {
@ -891,7 +898,7 @@ describe('User', function() {
.post('/test-users/login')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'foo@bar.com', password: 'bar'})
.send({ email: 'foo@bar.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -932,7 +939,7 @@ describe('User', function() {
describe('user.hasPassword(plain, fn)', function() {
it('Determine if the password matches the stored password', function(done) {
var u = new User({username: 'foo', password: 'bar'});
var u = new User({ username: 'foo', password: 'bar' });
u.hasPassword('bar', function(err, isMatch) {
assert(isMatch, 'password doesnt match');
done();
@ -940,7 +947,7 @@ describe('User', function() {
});
it('Determine if the password matches the stored password - promise variant', function(done) {
var u = new User({username: 'foo', password: 'bar'});
var u = new User({ username: 'foo', password: 'bar' });
u.hasPassword('bar')
.then(function(isMatch) {
assert(isMatch, 'password doesnt match');
@ -952,7 +959,7 @@ describe('User', function() {
});
it('should match a password when saved', function(done) {
var u = new User({username: 'a', password: 'b', email: 'z@z.net'});
var u = new User({ username: 'a', password: 'b', email: 'z@z.net' });
u.save(function(err, user) {
User.findById(user.id, function(err, uu) {
@ -965,7 +972,7 @@ describe('User', function() {
});
it('should match a password after it is changed', function(done) {
User.create({email: 'foo@baz.net', username: 'bat', password: 'baz'}, function(err, user) {
User.create({ email: 'foo@baz.net', username: 'bat', password: 'baz' }, function(err, user) {
User.findById(user.id, function(err, foundUser) {
assert(foundUser);
foundUser.hasPassword('baz', function(err, isMatch) {
@ -989,7 +996,6 @@ describe('User', function() {
});
describe('Verification', function() {
describe('user.verify(options, fn)', function() {
it('Verify a user\'s email address', function(done) {
User.afterRemote('create', function(ctx, user, next) {
@ -1001,7 +1007,7 @@ describe('User', function() {
from: 'noreply@myapp.org',
redirect: '/',
protocol: ctx.req.protocol,
host: ctx.req.get('host')
host: ctx.req.get('host'),
};
user.verify(options, function(err, result) {
@ -1019,7 +1025,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1037,7 +1043,7 @@ describe('User', function() {
from: 'noreply@myapp.org',
redirect: '/',
protocol: ctx.req.protocol,
host: ctx.req.get('host')
host: ctx.req.get('host'),
};
user.verify(options)
@ -1057,7 +1063,7 @@ describe('User', function() {
request(app)
.post('/test-users')
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
@ -1078,7 +1084,7 @@ describe('User', function() {
redirect: '/',
protocol: ctx.req.protocol,
host: ctx.req.get('host'),
headers: {'message-id':'custom-header-value'}
headers: { 'message-id': 'custom-header-value' },
};
user.verify(options, function(err, result) {
@ -1092,7 +1098,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1120,7 +1126,7 @@ describe('User', function() {
process.nextTick(function() {
cb(null, 'token-123456');
});
}
},
};
user.verify(options, function(err, result) {
@ -1138,7 +1144,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1162,7 +1168,7 @@ describe('User', function() {
process.nextTick(function() {
cb(new Error('Fake error'));
});
}
},
};
user.verify(options, function(err, result) {
@ -1177,7 +1183,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1197,7 +1203,7 @@ describe('User', function() {
redirect: '/',
protocol: 'http',
host: 'myapp.org',
port: 3000
port: 3000,
};
user.verify(options, function(err, result) {
@ -1211,7 +1217,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1230,7 +1236,7 @@ describe('User', function() {
redirect: '/',
protocol: 'http',
host: 'myapp.org',
port: 80
port: 80,
};
user.verify(options, function(err, result) {
@ -1244,7 +1250,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1263,7 +1269,7 @@ describe('User', function() {
redirect: '/',
protocol: 'https',
host: 'myapp.org',
port: 3000
port: 3000,
};
user.verify(options, function(err, result) {
@ -1277,7 +1283,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1296,7 +1302,7 @@ describe('User', function() {
redirect: '/',
protocol: 'https',
host: 'myapp.org',
port: 443
port: 443,
};
user.verify(options, function(err, result) {
@ -1310,7 +1316,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(200)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1320,7 +1326,11 @@ describe('User', function() {
});
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();
assert(!('verificationToken' in data));
done();
@ -1340,7 +1350,7 @@ describe('User', function() {
from: 'noreply@myapp.org',
redirect: 'http://foo.com/bar',
protocol: ctx.req.protocol,
host: ctx.req.get('host')
host: ctx.req.get('host'),
};
user.verify(options, function(err, result) {
@ -1355,7 +1365,7 @@ describe('User', function() {
.post('/test-users')
.expect('Content-Type', /json/)
.expect(302)
.send({email: 'bar@bat.com', password: 'bar'})
.send({ email: 'bar@bat.com', password: 'bar' })
.end(function(err, res) {
if (err) {
return done(err);
@ -1478,7 +1488,7 @@ describe('User', function() {
var calledBack = false;
User.resetPassword({
email: email
email: email,
}, function() {
calledBack = true;
});

View File

@ -7,11 +7,8 @@ var PersistedModel = loopback.PersistedModel;
var RemoteObjects = require('strong-remoting');
module.exports = function defineModelTestsWithDataSource(options) {
describe('Model Tests', function() {
var User;
var dataSource;
var User, dataSource;
if (options.beforeEach) {
beforeEach(options.beforeEach);
@ -44,9 +41,9 @@ module.exports = function defineModelTestsWithDataSource(options) {
'password': String,
'gender': String,
'domain': String,
'email': String
'email': String,
}, {
trackChanges: true
trackChanges: true,
});
User.attachTo(dataSource);
@ -59,7 +56,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.validatesPresenceOf(properties...)', function() {
it('Require a model to include a property to be considered valid', function() {
User.validatesPresenceOf('first', 'last', 'age');
var joe = new User({first: 'joe'});
var joe = new User({ first: 'joe' });
assert(joe.isValid() === false, 'model should not validate');
assert(joe.errors.last, 'should have a missing last error');
assert(joe.errors.age, 'should have a missing age error');
@ -68,8 +65,8 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.validatesLengthOf(property, options)', function() {
it('Require a property length to be within a specified range', function() {
User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});
var joe = new User({password: '1234'});
User.validatesLengthOf('password', { min: 5, message: { min: 'Password is too short' }});
var joe = new User({ password: '1234' });
assert(joe.isValid() === false, 'model should not be valid');
assert(joe.errors.password, 'should have password error');
});
@ -77,8 +74,8 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.validatesInclusionOf(property, options)', function() {
it('Require a value for `property` to be in the specified array', function() {
User.validatesInclusionOf('gender', {in: ['male', 'female']});
var foo = new User({gender: 'bar'});
User.validatesInclusionOf('gender', { in: ['male', 'female'] });
var foo = new User({ gender: 'bar' });
assert(foo.isValid() === false, 'model should not be valid');
assert(foo.errors.gender, 'should have gender error');
});
@ -86,10 +83,10 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.validatesExclusionOf(property, options)', function() {
it('Require a value for `property` to not exist in the specified array', function() {
User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});
var foo = new User({domain: 'www'});
var bar = new User({domain: 'billing'});
var bat = new User({domain: 'admin'});
User.validatesExclusionOf('domain', { in: ['www', 'billing', 'admin'] });
var foo = new User({ domain: 'www' });
var bar = new User({ domain: 'billing' });
var bat = new User({ domain: 'admin' });
assert(foo.isValid() === false);
assert(bar.isValid() === false);
assert(bat.isValid() === false);
@ -101,10 +98,10 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.validatesNumericalityOf(property, options)', function() {
it('Require a value for `property` to be a specific type of `Number`', function() {
User.validatesNumericalityOf('age', {int: true});
var joe = new User({age: 10.2});
User.validatesNumericalityOf('age', { int: true });
var joe = new User({ age: 10.2 });
assert(joe.isValid() === false);
var bob = new User({age: 0});
var bob = new User({ age: 0 });
assert(bob.isValid() === true);
assert(joe.errors.age, 'model should have an age error');
});
@ -112,16 +109,16 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('myModel.isValid()', function() {
it('Validate the model instance', function() {
User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'});
User.validatesNumericalityOf('age', { int: true });
var user = new User({ first: 'joe', age: 'flarg' });
var valid = user.isValid();
assert(valid === false);
assert(user.errors.age, 'model should have age error');
});
it('Asynchronously validate the model', function(done) {
User.validatesNumericalityOf('age', {int: true});
var user = new User({first: 'joe', age: 'flarg'});
User.validatesNumericalityOf('age', { int: true });
var user = new User({ first: 'joe', age: 'flarg' });
user.isValid(function(valid) {
assert(valid === false);
assert(user.errors.age, 'model should have age error');
@ -131,8 +128,9 @@ module.exports = function defineModelTestsWithDataSource(options) {
});
describe('Model.create([data], [callback])', function() {
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) {
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) {
assert(user instanceof User);
done();
});
@ -141,7 +139,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('model.save([options], [callback])', function() {
it('Save an instance of a Model to the attached data source', function(done) {
var joe = new User({first: 'Joe', last: 'Bob'});
var joe = new User({ first: 'Joe', last: 'Bob' });
joe.save(function(err, user) {
assert(user.id);
assert(!err);
@ -153,13 +151,13 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('model.updateAttributes(data, [callback])', function() {
it('Save specified attributes to the attached data source', function(done) {
User.create({first: 'joe', age: 100}, function(err, user) {
User.create({ first: 'joe', age: 100 }, function(err, user) {
assert(!err);
assert.equal(user.first, 'joe');
user.updateAttributes({
first: 'updatedFirst',
last: 'updatedLast'
last: 'updatedLast',
}, function(err, updatedUser) {
assert(!err);
assert.equal(updatedUser.first, 'updatedFirst');
@ -173,11 +171,11 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.upsert(data, callback)', function() {
it('Update when record with id=data.id found, insert otherwise', function(done) {
User.upsert({first: 'joe', id: 7}, function(err, user) {
User.upsert({ first: 'joe', id: 7 }, function(err, user) {
assert(!err);
assert.equal(user.first, 'joe');
User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {
User.upsert({ first: 'bob', id: 7 }, function(err, updatedUser) {
assert(!err);
assert.equal(updatedUser.first, 'bob');
done();
@ -188,13 +186,13 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('model.destroy([callback])', function() {
it('Remove a model from the attached data source', function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
User.create({ first: 'joe', last: 'bob' }, function(err, user) {
User.findById(user.id, function(err, foundUser) {
if (err) return done(err);
assert.equal(user.id, foundUser.id);
User.deleteById(foundUser.id, function(err) {
if (err) return done(err);
User.find({ where: { id: user.id } }, function(err, found) {
User.find({ where: { id: user.id }}, function(err, found) {
if (err) return done(err);
assert.equal(found.length, 0);
done();
@ -207,7 +205,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.deleteById(id, [callback])', function() {
it('Delete a model instance from the attached data source', function(done) {
User.create({first: 'joe', last: 'bob'}, function(err, user) {
User.create({ first: 'joe', last: 'bob' }, function(err, user) {
User.deleteById(user.id, function(err) {
User.findById(user.id, function(err, notFound) {
assert.equal(notFound, null);
@ -220,7 +218,7 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.findById(id, callback)', function() {
it('Find an instance by id', function(done) {
User.create({first: 'michael', last: 'jordan', id: 23}, function() {
User.create({ first: 'michael', last: 'jordan', id: 23 }, function() {
User.findById(23, function(err, user) {
assert.equal(user.id, 23);
assert.equal(user.first, 'michael');
@ -234,20 +232,18 @@ module.exports = function defineModelTestsWithDataSource(options) {
describe('Model.count([query], callback)', function() {
it('Query count of Model instances in data source', function(done) {
(new TaskEmitter())
.task(User, 'create', {first: 'jill', age: 100})
.task(User, 'create', {first: 'bob', age: 200})
.task(User, 'create', {first: 'jan'})
.task(User, 'create', {first: 'sam'})
.task(User, 'create', {first: 'suzy'})
.task(User, 'create', { first: 'jill', age: 100 })
.task(User, 'create', { first: 'bob', age: 200 })
.task(User, 'create', { first: 'jan' })
.task(User, 'create', { first: 'sam' })
.task(User, 'create', { first: 'suzy' })
.on('done', function() {
User.count({age: {gt: 99}}, function(err, count) {
User.count({ age: { gt: 99 }}, function(err, count) {
assert.equal(count, 2);
done();
});
});
});
});
});
};