From 7546ee531d9cffea3cceeffe66c0b0de3766b132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 2 Aug 2016 10:59:21 +0200 Subject: [PATCH 1/5] Update dependencies to their latest versions --- package.json | 46 +++++++++++++++++++++++----------------------- test/user.test.js | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) 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/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(); }); From 48205fb2bdc3396ded4c8d7efbfc268f78dbec44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Wed, 3 Aug 2016 15:55:29 +0200 Subject: [PATCH 2/5] test: fix broken Role tests Rework the test suite to always report errors and correctly signal when async tests are done. This should prevent spurious test failures on CI servers that are difficult to troubleshoot, because the error is reported for different test case. --- test/role.test.js | 473 +++++++++++++++++++++++++++++++--------------- 1 file changed, 319 insertions(+), 154 deletions(-) diff --git a/test/role.test.js b/test/role.test.js index 50643161..83df23ab 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); }); }); @@ -434,12 +592,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 +616,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)); From caaa296a82539f390c0d506ba03d28a9d80effdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 4 Aug 2016 11:00:00 +0200 Subject: [PATCH 3/5] test: make status test more robust Rework assertions to report helpful messages on failure. Increase the "elapsed" limit from 100ms to 300ms to support our slow CI machines. --- test/app.test.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/app.test.js b/test/app.test.js index 0cf517c4..9df0d1cd 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(); }); From 5d18d41b28f776c0b4b648849c4b2bb1b373fcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 4 Aug 2016 13:32:47 +0200 Subject: [PATCH 4/5] test: increate timeout in Role test --- test/role.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/role.test.js b/test/role.test.js index 83df23ab..98ff89f0 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -419,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', From 39da31bb5a1fec92c6ab43a86107ca474e6ee239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 4 Aug 2016 14:41:33 +0200 Subject: [PATCH 5/5] test: fix "socket hang up" error in app.test Rework the test to always wait for the client request to finish before calling the test done. --- test/app.test.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/app.test.js b/test/app.test.js index 9df0d1cd..0afb23c2 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -1037,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') { @@ -1049,6 +1059,6 @@ function executeMiddlewareHandlers(app, urlPath, callback) { request(server) .get(urlPath) .end(function(err) { - if (err) return callback(err); + callback(handlerError || err); }); }