diff --git a/package.json b/package.json index 8a763df4..2aca27d0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "test": "grunt mocha-and-karma" }, "dependencies": { - "async": "^0.9.0", + "async": "^2.0.1", "bcryptjs": "^2.1.0", "bluebird": "^3.1.1", "body-parser": "^1.12.0", @@ -49,8 +49,8 @@ "loopback-connector-remote": "^1.0.3", "loopback-datasource-juggler": "^3.0.0-alpha.1", "loopback-phase": "^1.2.0", - "nodemailer": "^1.3.1", - "nodemailer-stub-transport": "^0.1.5", + "nodemailer": "^2.5.0", + "nodemailer-stub-transport": "^1.0.0", "serve-favicon": "^2.2.0", "stable": "^0.1.5", "strong-remoting": "^3.0.0-alpha.1", @@ -58,36 +58,36 @@ "underscore.string": "^3.0.3" }, "devDependencies": { - "strong-error-handler": "^1.0.1", - "browserify": "^10.0.0", - "chai": "^2.1.1", + "browserify": "^13.1.0", + "chai": "^3.5.0", "cookie-parser": "^1.3.4", "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-uglify": "^0.9.1", - "grunt-contrib-watch": "^0.6.1", - "grunt-eslint": "^18.0.0", - "grunt-karma": "^0.10.1", + "grunt": "^1.0.1", + "grunt-browserify": "^5.0.0", + "grunt-cli": "^1.2.0", + "grunt-contrib-uglify": "^2.0.0", + "grunt-contrib-watch": "^1.0.0", + "grunt-eslint": "^18.1.0", + "grunt-karma": "^2.0.0", "grunt-mocha-test": "^0.12.7", - "karma": "^0.12.31", - "karma-browserify": "^4.0.0", - "karma-chrome-launcher": "^0.1.7", - "karma-firefox-launcher": "^0.1.4", - "karma-html2js-preprocessor": "^0.1.0", - "karma-junit-reporter": "^0.2.2", - "karma-mocha": "^0.1.10", + "karma": "^1.1.2", + "karma-browserify": "^4.4.2", + "karma-chrome-launcher": "^1.0.1", + "karma-firefox-launcher": "^1.0.0", + "karma-html2js-preprocessor": "^1.0.0", + "karma-junit-reporter": "~1.0.0", + "karma-mocha": "^1.1.1", "karma-phantomjs-launcher": "^1.0.0", - "karma-script-launcher": "^0.1.0", + "karma-script-launcher": "^1.0.0", "loopback-boot": "^2.7.0", - "mocha": "^2.1.0", + "mocha": "^3.0.0", "phantomjs-prebuilt": "^2.1.7", "sinon": "^1.13.0", "sinon-chai": "^2.8.0", + "strong-error-handler": "^1.0.1", "strong-task-emitter": "^0.0.6", - "supertest": "^0.15.0" + "supertest": "^2.0.0" }, "repository": { "type": "git", diff --git a/test/app.test.js b/test/app.test.js index 0cf517c4..0afb23c2 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -12,6 +12,7 @@ var loopback = require('../'); var PersistedModel = loopback.PersistedModel; var describe = require('./util/describe'); +var expect = require('chai').expect; var it = require('./util/it'); describe('app', function() { @@ -937,18 +938,14 @@ describe('app', function() { .end(function(err, res) { if (err) return done(err); - assert.equal(typeof res.body, 'object'); - assert(res.body.started); - // The number can be 0 - assert(res.body.uptime !== undefined); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('started'); + expect(res.body.uptime, 'uptime').to.be.gte(0); var elapsed = Date.now() - Number(new Date(res.body.started)); - // elapsed should be a positive number... - assert(elapsed >= 0); - - // less than 100 milliseconds - assert(elapsed < 100); + // elapsed should be a small positive number... + expect(elapsed, 'elapsed').to.be.within(0, 300); done(); }); @@ -1040,8 +1037,18 @@ describe('app', function() { }); function executeMiddlewareHandlers(app, urlPath, callback) { + var handlerError = undefined; var server = http.createServer(function(req, res) { - app.handle(req, res, callback); + app.handle(req, res, function(err) { + if (err) { + handlerError = err; + res.statusCode = err.status || err.statusCode || 500; + res.end(err.stack || err); + } else { + res.statusCode = 204; + res.end(); + } + }); }); if (callback === undefined && typeof urlPath === 'function') { @@ -1052,6 +1059,6 @@ function executeMiddlewareHandlers(app, urlPath, callback) { request(server) .get(urlPath) .end(function(err) { - if (err) return callback(err); + callback(handlerError || err); }); } diff --git a/test/role.test.js b/test/role.test.js index 50643161..98ff89f0 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -6,11 +6,6 @@ var assert = require('assert'); var sinon = require('sinon'); var loopback = require('../index'); -var Role = loopback.Role; -var RoleMapping = loopback.RoleMapping; -var User = loopback.User; -var Application = loopback.Application; -var ACL = loopback.ACL; var async = require('async'); var expect = require('chai').expect; var Promise = require('bluebird'); @@ -20,17 +15,29 @@ function checkResult(err, result) { } describe('role model', function() { - var ds; + var app, Role, RoleMapping, User, Application, ACL; beforeEach(function() { - ds = loopback.createDataSource({ connector: 'memory' }); - // Re-attach the models so that they can have isolated store to avoid + // Use local app registry to ensure models are isolated to avoid // pollutions from other tests - ACL.attachTo(ds); - User.attachTo(ds); - Role.attachTo(ds); - RoleMapping.attachTo(ds); - Application.attachTo(ds); + app = loopback({ localRegistry: true, loadBuiltinModels: true }); + app.dataSource('db', { connector: 'memory' }); + + ACL = app.registry.getModel('ACL'); + app.model(ACL, { dataSource: 'db' }); + + User = app.registry.getModel('User'); + app.model(User, { dataSource: 'db' }); + + Role = app.registry.getModel('Role'); + app.model(Role, { dataSource: 'db' }); + + RoleMapping = app.registry.getModel('RoleMapping'); + app.model(RoleMapping, { dataSource: 'db' }); + + Application = app.registry.getModel('Application'); + app.model(Application, { dataSource: 'db' }); + ACL.roleModel = Role; ACL.roleMappingModel = RoleMapping; ACL.userModel = User; @@ -40,51 +47,88 @@ describe('role model', function() { Role.applicationModel = Application; }); - it('should define role/role relations', function() { + it('should define role/role relations', function(done) { Role.create({ name: 'user' }, function(err, userRole) { + if (err) return done(err); 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); + if (err) return done(err); + userRole.principals.create( + { principalType: RoleMapping.ROLE, principalId: adminRole.id }, + function(err, mapping) { + if (err) return done(err); + + async.parallel([ + function(next) { + Role.find(function(err, roles) { + if (err) return next(err); + assert.equal(roles.length, 2); + next(); + }); + }, + function(next) { + RoleMapping.find(function(err, mappings) { + if (err) return next(err); + assert.equal(mappings.length, 1); + assert.equal(mappings[0].principalType, RoleMapping.ROLE); + assert.equal(mappings[0].principalId, adminRole.id); + next(); + }); + }, + function(next) { + userRole.principals(function(err, principals) { + if (err) return next(err); + assert.equal(principals.length, 1); + next(); + }); + }, + function(next) { + userRole.roles(function(err, roles) { + if (err) return next(err); + assert.equal(roles.length, 1); + next(); + }); + }, + ], done); }); - RoleMapping.find(function(err, mappings) { - assert.equal(mappings.length, 1); - assert.equal(mappings[0].principalType, RoleMapping.ROLE); - assert.equal(mappings[0].principalId, adminRole.id); - }); - userRole.principals(function(err, principals) { - assert.equal(principals.length, 1); - }); - userRole.roles(function(err, roles) { - assert.equal(roles.length, 1); - }); - }); }); }); }); - it('should define role/user relations', function() { + it('should define role/user relations', function(done) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + if (err) return done(err); Role.create({ name: 'userRole' }, function(err, role) { + if (err) return done(err); role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, function(err, p) { - Role.find(function(err, roles) { - assert(!err); - assert.equal(roles.length, 1); - assert.equal(roles[0].name, 'userRole'); - }); - role.principals(function(err, principals) { - assert(!err); - assert.equal(principals.length, 1); - assert.equal(principals[0].principalType, RoleMapping.USER); - assert.equal(principals[0].principalId, user.id); - }); - role.users(function(err, users) { - assert(!err); - assert.equal(users.length, 1); - assert.equal(users[0].id, user.id); - }); + if (err) return done(err); + async.parallel([ + function(next) { + Role.find(function(err, roles) { + if (err) return next(err); + assert.equal(roles.length, 1); + assert.equal(roles[0].name, 'userRole'); + next(); + }); + }, + function(next) { + role.principals(function(err, principals) { + if (err) return next(err); + assert.equal(principals.length, 1); + assert.equal(principals[0].principalType, RoleMapping.USER); + assert.equal(principals[0].principalId, user.id); + next(); + }); + }, + function(next) { + role.users(function(err, users) { + if (err) return next(err); + assert.equal(users.length, 1); + assert.equal(users[0].id, user.id); + next(); + }); + }, + ], done); }); }); }); @@ -106,86 +150,146 @@ describe('role model', function() { }); }); - it('should automatically generate role id', function() { + it('should automatically generate role id', function(done) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + if (err) return done(err); Role.create({ name: 'userRole' }, function(err, role) { + if (err) return done(err); assert(role.id); role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, function(err, p) { + if (err) return done(err); assert(p.id); assert.equal(p.roleId, role.id); - Role.find(function(err, roles) { - assert(!err); - assert.equal(roles.length, 1); - assert.equal(roles[0].name, 'userRole'); - }); - role.principals(function(err, principals) { - assert(!err); - assert.equal(principals.length, 1); - assert.equal(principals[0].principalType, RoleMapping.USER); - assert.equal(principals[0].principalId, user.id); - }); - role.users(function(err, users) { - assert(!err); - assert.equal(users.length, 1); - assert.equal(users[0].id, user.id); - }); + async.parallel([ + function(next) { + Role.find(function(err, roles) { + if (err) return next(err); + assert.equal(roles.length, 1); + assert.equal(roles[0].name, 'userRole'); + next(); + }); + }, + function(next) { + role.principals(function(err, principals) { + if (err) return next(err); + assert.equal(principals.length, 1); + assert.equal(principals[0].principalType, RoleMapping.USER); + assert.equal(principals[0].principalId, user.id); + next(); + }); + }, + function(next) { + role.users(function(err, users) { + if (err) return next(err); + assert.equal(users.length, 1); + assert.equal(users[0].id, user.id); + }); + next(); + }, + ], done); }); }); }); }); - it('should support getRoles() and isInRole()', function() { + it('should support getRoles() and isInRole()', function(done) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + if (err) return done(err); Role.create({ name: 'userRole' }, function(err, role) { + if (err) return done(err); role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, function(err, p) { - 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) { - assert(!err && exists === false); - }); - - 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) { - 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) { - 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) { - 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) { - assert.equal(roles.length, 2); - assert(roles.indexOf(Role.EVERYONE) >= 0); - assert(roles.indexOf(Role.UNAUTHENTICATED) >= 0); - }); + if (err) return done(err); + async.series([ + function(next) { + Role.isInRole( + 'userRole', + { principalType: RoleMapping.USER, principalId: user.id }, + function(err, inRole) { + if (err) return next(err); + // NOTE(bajtos) Apparently isRole is not a boolean, + // but the matchin role object instead + assert(!!inRole); + next(); + }); + }, + function(next) { + Role.isInRole( + 'userRole', + { principalType: RoleMapping.APP, principalId: user.id }, + function(err, inRole) { + if (err) return next(err); + assert(!inRole); + next(); + }); + }, + function(next) { + Role.isInRole( + 'userRole', + { principalType: RoleMapping.USER, principalId: 100 }, + function(err, inRole) { + if (err) return next(err); + assert(!inRole); + next(); + }); + }, + function(next) { + Role.getRoles( + { principalType: RoleMapping.USER, principalId: user.id }, + function(err, roles) { + if (err) return next(err); + expect(roles).to.eql([ + Role.AUTHENTICATED, + Role.EVERYONE, + role.id, + ]); + next(); + }); + }, + function(next) { + Role.getRoles( + { principalType: RoleMapping.APP, principalId: user.id }, + function(err, roles) { + if (err) return next(err); + expect(roles).to.eql([ + Role.AUTHENTICATED, + Role.EVERYONE, + ]); + next(); + }); + }, + function(next) { + Role.getRoles( + { principalType: RoleMapping.USER, principalId: 100 }, + function(err, roles) { + if (err) return next(err); + expect(roles).to.eql([ + Role.AUTHENTICATED, + Role.EVERYONE, + ]); + next(); + }); + }, + function(next) { + Role.getRoles( + { principalType: RoleMapping.USER, principalId: null }, + function(err, roles) { + if (err) return next(err); + expect(roles).to.eql([ + Role.UNAUTHENTICATED, + Role.EVERYONE, + ]); + next(); + }); + }, + ], done); }); }); }); }); - it('should support owner role resolver', function() { + it('should support owner role resolver', function(done) { Role.registerResolver('returnPromise', function(role, context) { return new Promise(function(resolve) { process.nextTick(function() { @@ -194,7 +298,7 @@ describe('role model', function() { }); }); - var Album = ds.createModel('Album', { + var Album = app.registry.createModel('Album', { name: String, userId: Number, }, { @@ -206,54 +310,108 @@ describe('role model', function() { }, }, }); + app.model(Album, { dataSource: 'db' }); User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { - Role.isInRole('returnPromise', { principalType: ACL.USER, principalId: user.id }, - function(err, yes) { - assert(!err && yes); - }); + if (err) return done(err); + async.parallel([ + function(next) { + Role.isInRole( + 'returnPromise', + { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { + if (err) return next(err); + assert(yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.AUTHENTICATED, + { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { + if (err) next(err); + assert(yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.AUTHENTICATED, + { principalType: ACL.USER, principalId: null }, + function(err, yes) { + if (err) next(err); + assert(!yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.UNAUTHENTICATED, + { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { + if (err) return next(err); + assert(!yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.UNAUTHENTICATED, + { principalType: ACL.USER, principalId: null }, + function(err, yes) { + if (err) return next(err); + assert(yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.EVERYONE, + { principalType: ACL.USER, principalId: user.id }, + function(err, yes) { + if (err) return next(err); + assert(yes); + next(); + }); + }, + function(next) { + Role.isInRole( + Role.EVERYONE, + { principalType: ACL.USER, principalId: null }, + function(err, yes) { + if (err) return next(err); + assert(yes); + next(); + }); + }, + function(next) { + Album.create({ name: 'Album 1', userId: user.id }, function(err, album1) { + if (err) return done(err); + var role = { + principalType: ACL.USER, principalId: user.id, + model: Album, id: album1.id, + }; + Role.isInRole(Role.OWNER, role, function(err, yes) { + if (err) return next(err); + assert(yes); - 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) { - assert(!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) { - assert(!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) { - assert(!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 = { principalType: ACL.USER, principalId: user.id, model: Album, id: album2.id }; - Role.isInRole(Role.OWNER, role, function(err, yes) { - assert(!err && !yes); + Album.create({ name: 'Album 2' }, function(err, album2) { + if (err) return next(err); + role = { + principalType: ACL.USER, principalId: user.id, + model: Album, id: album2.id, + }; + Role.isInRole(Role.OWNER, role, function(err, yes) { + if (err) return next(err); + assert(!yes); + next(); + }); + }); + }); }); - }); - }); + }, + ], done); }); }); @@ -261,6 +419,7 @@ describe('role model', function() { var user, app, role; beforeEach(function(done) { + this.timeout(5000); User.create({ username: 'john', email: 'john@gmail.com', @@ -434,12 +593,16 @@ 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) { + if (err) return done(err); + var uniqueRoleName = 'testRoleFor' + principalType; + Role.create({ name: uniqueRoleName }, function(err, role) { + if (err) return done(err); role.principals.create({ principalType: principalType, principalId: model.id }, function(err, p) { + if (err) return done(err); var pluralName = Model.pluralModelName.toLowerCase(); role[pluralName](function(err, models) { - assert(!err); + if (err) return done(err); assert.equal(models.length, 1); if (++runs === mappings.length) { @@ -454,13 +617,16 @@ describe('role model', function() { it('should apply query', function(done) { User.create({ name: 'Raymond', email: 'x@y.com', password: 'foobar' }, function(err, user) { + if (err) return done(err); Role.create({ name: 'userRole' }, function(err, role) { + if (err) return done(err); role.principals.create({ principalType: RoleMapping.USER, principalId: user.id }, function(err, p) { + if (err) return done(err); var query = { fields: ['id', 'name'] }; sandbox.spy(User, 'find'); role.users(query, function(err, users) { - assert(!err); + if (err) return done(err); assert.equal(users.length, 1); assert.equal(users[0].id, user.id); assert(User.find.calledWith(query)); diff --git a/test/user.test.js b/test/user.test.js index e6d85d07..26a5fa02 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1676,7 +1676,7 @@ describe('User', function() { .end(function(err, res) { if (err) return done(err); - assert.deepEqual(res.body, { }); + assert.deepEqual(res.body, ''); done(); });