From 74018019b48295075ce5a0a7fd69e0b8c3ffc184 Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Thu, 12 Mar 2015 14:55:39 -0400 Subject: [PATCH 1/6] fix implementation of Role methods: users,roles, and applications --- common/models/role.js | 49 +++++++++++++++++++++++++++---------------- test/role.test.js | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index 2c005cde..c3b996a1 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -29,42 +29,55 @@ module.exports = function(Role) { // Set up the connection to users/applications/roles once the model Role.once('dataSourceAttached', function() { var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping); + + /** + * Fetch the ids of all users assigned to this role + * @param {Function} callback + */ Role.prototype.users = function(callback) { - roleMappingModel.find({where: {roleId: this.id, - principalType: RoleMapping.USER}}, function(err, mappings) { + roleMappingModel.find({ + where: {roleId: this.id, principalType: RoleMapping.USER} + }, function(err, mappings) { if (err) { - if (callback) callback(err); - return; + return callback(err); } - return mappings.map(function(m) { + callback(null, mappings.map(function(m) { return m.principalId; - }); + })); }); }; + /** + * Fetch the ids of all applications assigned to this role + * @param {Function} callback + */ Role.prototype.applications = function(callback) { - roleMappingModel.find({where: {roleId: this.id, - principalType: RoleMapping.APPLICATION}}, function(err, mappings) { + roleMappingModel.find({ + where: {roleId: this.id, principalType: RoleMapping.APPLICATION} + }, function(err, mappings) { if (err) { - if (callback) callback(err); - return; + return callback(err); } - return mappings.map(function(m) { + callback(null, mappings.map(function(m) { return m.principalId; - }); + })); }); }; + /** + * Fetch the ids of all roles assigned to this role + * @param {Function} callback + */ Role.prototype.roles = function(callback) { - roleMappingModel.find({where: {roleId: this.id, - principalType: RoleMapping.ROLE}}, function(err, mappings) { + roleMappingModel.find({ + where: {roleId: this.id, principalType: RoleMapping.ROLE} + }, function(err, mappings) { if (err) { - if (callback) callback(err); - return; + return callback(err); } - return mappings.map(function(m) { + callback(null, mappings.map(function(m) { return m.principalId; - }); + })); }); }; diff --git a/test/role.test.js b/test/role.test.js index dfedc6cc..f10a1c33 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -3,6 +3,7 @@ var loopback = require('../index'); var Role = loopback.Role; var RoleMapping = loopback.RoleMapping; var User = loopback.User; +var Application = loopback.Application; var ACL = loopback.ACL; function checkResult(err, result) { @@ -208,4 +209,49 @@ describe('role model', function() { }); + it('should fetch all user ids assigned to the role', 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) { + role.users(function(err, users) { + assert(!err); + assert.equal(users.length, 1); + assert.equal(users[0], user.id); + done(); + }); + }); + }); + }); + }); + + it('should fetch all application ids assigned to the role', function(done) { + Application.create({name: 'New App'}, function(err, application) { + Role.create({name: 'applicationRole'}, function(err, role) { + role.principals.create({principalType: RoleMapping.APPLICATION, principalId: application.id}, function(err, p) { + role.applications(function(err, applications) { + assert(!err); + assert.equal(applications.length, 1); + assert.equal(applications[0], application.id); + done(); + }); + }); + }); + }); + }); + + it('should fetch all role ids assigned to the role', function(done) { + Role.create({name: 'New Role'}, function(err, newRole) { + Role.create({name: 'applicationRole'}, function(err, role) { + role.principals.create({principalType: RoleMapping.ROLE, principalId: newRole.id}, function(err, p) { + role.roles(function(err, roles) { + assert(!err); + assert.equal(roles.length, 1); + assert.equal(roles[0], newRole.id); + done(); + }); + }); + }); + }); + }); + }); From 8cc558a991d915be8483c7c61abc7d91cf1d53bc Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Fri, 13 Mar 2015 11:50:30 -0400 Subject: [PATCH 2/6] consolidate Role methods roles, applications, and users into one, add query param to allow for pagination and restricting fields --- common/models/role.js | 95 ++++++++++++++++++++++++------------------- lib/builtin-models.js | 8 ++-- package.json | 1 + test/role.test.js | 90 +++++++++++++++++++++++----------------- 4 files changed, 112 insertions(+), 82 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index c3b996a1..b8f36317 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -6,6 +6,10 @@ var async = require('async'); var AccessContext = require('../../lib/access-context').AccessContext; var RoleMapping = loopback.RoleMapping; +var Role = loopback.Role; +var User = loopback.User; +var Application = loopback.Application; + assert(RoleMapping, 'RoleMapping model must be defined before Role model'); /** @@ -29,57 +33,66 @@ module.exports = function(Role) { // Set up the connection to users/applications/roles once the model Role.once('dataSourceAttached', function() { var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping); + var principalTypesToModels = {}; + + principalTypesToModels[RoleMapping.USER] = User; + principalTypesToModels[RoleMapping.APPLICATION] = Application; + principalTypesToModels[RoleMapping.ROLE] = Role; + + Object.keys(principalTypesToModels).forEach(function(principalType){ + var model = principalTypesToModels[principalType]; + var pluralName = model.pluralModelName.toLowerCase(); + /** + * Fetch all users assigned to this role + * @function Role.prototype#users + * @param {object} [query] query object passed to model find call + * @param {Function} [callback] + */ + /** + * Fetch all applications assigned to this role + * @function Role.prototype#applications + * @param {object} [query] query object passed to model find call + * @param {Function} [callback] + */ + /** + * Fetch all roles assigned to this role + * @function Role.prototype#roles + * @param {object} [query] query object passed to model find call + * @param {Function} [callback] + */ + Role.prototype[pluralName] = function(query, callback) { + listByPrincipalType(model, principalType, query, callback); + } + }); /** - * Fetch the ids of all users assigned to this role - * @param {Function} callback + * Fetch all models assigned to this role + * @param {*} model model type to fetch + * @param {String} [principalType] principalType used in the rolemapping for model + * @param {object} [query] query object passed to model find call + * @param {Function} [callback] */ - Role.prototype.users = function(callback) { + function listByPrincipalType(model, principalType, query, callback) { + if (callback === undefined) { + callback = query; + query = {}; + } + roleMappingModel.find({ - where: {roleId: this.id, principalType: RoleMapping.USER} + where: {roleId: this.id, principalType: principalType} }, function(err, mappings) { + var ids; if (err) { return callback(err); } - callback(null, mappings.map(function(m) { + ids = mappings.map(function(m) { return m.principalId; - })); + }); + query.where = query.where || {}; + query.where.id = {inq: ids}; + model.find(query, callback); }); - }; - - /** - * Fetch the ids of all applications assigned to this role - * @param {Function} callback - */ - Role.prototype.applications = function(callback) { - roleMappingModel.find({ - where: {roleId: this.id, principalType: RoleMapping.APPLICATION} - }, function(err, mappings) { - if (err) { - return callback(err); - } - callback(null, mappings.map(function(m) { - return m.principalId; - })); - }); - }; - - /** - * Fetch the ids of all roles assigned to this role - * @param {Function} callback - */ - Role.prototype.roles = function(callback) { - roleMappingModel.find({ - where: {roleId: this.id, principalType: RoleMapping.ROLE} - }, function(err, mappings) { - if (err) { - return callback(err); - } - callback(null, mappings.map(function(m) { - return m.principalId; - })); - }); - }; + } }); diff --git a/lib/builtin-models.js b/lib/builtin-models.js index 2a346d3e..4a986e8f 100644 --- a/lib/builtin-models.js +++ b/lib/builtin-models.js @@ -13,6 +13,10 @@ module.exports = function(loopback) { require('../common/models/access-token.json'), require('../common/models/access-token.js')); + loopback.User = createModel( + require('../common/models/user.json'), + require('../common/models/user.js')); + loopback.RoleMapping = createModel( require('../common/models/role-mapping.json'), require('../common/models/role-mapping.js')); @@ -29,10 +33,6 @@ module.exports = function(loopback) { require('../common/models/scope.json'), require('../common/models/scope.js')); - loopback.User = createModel( - require('../common/models/user.json'), - require('../common/models/user.js')); - loopback.Change = createModel( require('../common/models/change.json'), require('../common/models/change.js')); diff --git a/package.json b/package.json index 2e065bad..b932f7f8 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "loopback-datasource-juggler": "^2.19.1", "loopback-testing": "^1.1.0", "mocha": "^2.1.0", + "sinon": "^1.13.0", "strong-task-emitter": "^0.0.6", "supertest": "^0.15.0" }, diff --git a/test/role.test.js b/test/role.test.js index f10a1c33..d0cc3586 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -1,4 +1,5 @@ var assert = require('assert'); +var sinon = require('sinon'); var loopback = require('../index'); var Role = loopback.Role; var RoleMapping = loopback.RoleMapping; @@ -209,49 +210,64 @@ describe('role model', function() { }); - it('should fetch all user ids assigned to the role', 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) { - role.users(function(err, users) { - assert(!err); - assert.equal(users.length, 1); - assert.equal(users[0], user.id); - done(); - }); - }); - }); - }); - }); + describe('listByPrincipalType', function(){ + var sandbox; - it('should fetch all application ids assigned to the role', function(done) { - Application.create({name: 'New App'}, function(err, application) { - Role.create({name: 'applicationRole'}, function(err, role) { - role.principals.create({principalType: RoleMapping.APPLICATION, principalId: application.id}, function(err, p) { - role.applications(function(err, applications) { - assert(!err); - assert.equal(applications.length, 1); - assert.equal(applications[0], application.id); - done(); - }); - }); - }); + beforeEach(function(){ + sandbox = sinon.sandbox.create(); }); - }); - it('should fetch all role ids assigned to the role', function(done) { - Role.create({name: 'New Role'}, function(err, newRole) { - Role.create({name: 'applicationRole'}, function(err, role) { - role.principals.create({principalType: RoleMapping.ROLE, principalId: newRole.id}, function(err, p) { - role.roles(function(err, roles) { - assert(!err); - assert.equal(roles.length, 1); - assert.equal(roles[0], newRole.id); - done(); + afterEach(function(){ + sandbox.restore(); + }); + + it('should fetch all models assigned to the role', function(done) { + var principalTypesToModels = {}; + var runs = 0; + var mappings; + + principalTypesToModels[RoleMapping.USER] = User; + principalTypesToModels[RoleMapping.APPLICATION] = Application; + principalTypesToModels[RoleMapping.ROLE] = Role; + + mappings = Object.keys(principalTypesToModels); + + 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){ + var pluralName = Model.pluralModelName.toLowerCase(); + role[pluralName](function(err, models){ + assert(!err); + assert.equal(models.length, 1); + if (++runs === mappings.length) { + done(); + } + }); + }); + }) + }) + }); + }); + + 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']}; + sandbox.spy(User, 'find'); + role.users(query, function(err, users) { + assert(!err); + assert.equal(users.length, 1); + assert.equal(users[0].id, user.id); + assert(User.find.calledWith(query)); + done(); + }); }); }); }); }); - }); + }) }); From c764c09837b1a71d6ef107e34244f4406ea4399e Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Fri, 13 Mar 2015 16:53:26 -0400 Subject: [PATCH 3/6] fix lint erros --- common/models/role.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index daa7505f..1108060f 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -40,27 +40,27 @@ module.exports = function(Role) { principalTypesToModels[RoleMapping.APPLICATION] = Application; principalTypesToModels[RoleMapping.ROLE] = Role; - Object.keys(principalTypesToModels).forEach(function(principalType){ + Object.keys(principalTypesToModels).forEach(function(principalType) { var model = principalTypesToModels[principalType]; var pluralName = model.pluralModelName.toLowerCase(); - /** + /** * Fetch all users assigned to this role * @function Role.prototype#users * @param {object} [query] query object passed to model find call * @param {Function} [callback] - */ - /** + */ + /** * Fetch all applications assigned to this role * @function Role.prototype#applications * @param {object} [query] query object passed to model find call * @param {Function} [callback] */ - /** + /** * Fetch all roles assigned to this role * @function Role.prototype#roles * @param {object} [query] query object passed to model find call * @param {Function} [callback] - */ + */ Role.prototype[pluralName] = function(query, callback) { listByPrincipalType(model, principalType, query, callback); } From 551261ec16c3fd2bee3226b27c67f56327d0f3e5 Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Fri, 13 Mar 2015 18:30:53 -0400 Subject: [PATCH 4/6] fix linting errors --- common/models/role.js | 2 +- test/role.test.js | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index 1108060f..0d8880fa 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -63,7 +63,7 @@ module.exports = function(Role) { */ Role.prototype[pluralName] = function(query, callback) { listByPrincipalType(model, principalType, query, callback); - } + }; }); /** diff --git a/test/role.test.js b/test/role.test.js index d0cc3586..882bf0f4 100644 --- a/test/role.test.js +++ b/test/role.test.js @@ -210,14 +210,14 @@ describe('role model', function() { }); - describe('listByPrincipalType', function(){ + describe('listByPrincipalType', function() { var sandbox; - beforeEach(function(){ + beforeEach(function() { sandbox = sinon.sandbox.create(); }); - afterEach(function(){ + afterEach(function() { sandbox.restore(); }); @@ -232,13 +232,13 @@ describe('role model', function() { mappings = Object.keys(principalTypesToModels); - mappings.forEach(function(principalType){ + 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){ + role[pluralName](function(err, models) { assert(!err); assert.equal(models.length, 1); if (++runs === mappings.length) { @@ -246,8 +246,8 @@ describe('role model', function() { } }); }); - }) - }) + }); + }); }); }); @@ -255,7 +255,7 @@ describe('role model', function() { 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']}; + var query = {fields:['id', 'name']}; sandbox.spy(User, 'find'); role.users(query, function(err, users) { assert(!err); @@ -268,6 +268,6 @@ describe('role model', function() { }); }); }); - }) + }); }); From 7923d036f89b8c6fd99766b5a593d2f6b9b93fef Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Wed, 25 Mar 2015 10:10:34 -0400 Subject: [PATCH 5/6] mark utiltiy function as private --- common/models/role.js | 1 + 1 file changed, 1 insertion(+) diff --git a/common/models/role.js b/common/models/role.js index 0d8880fa..7cf3ed45 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -68,6 +68,7 @@ module.exports = function(Role) { /** * Fetch all models assigned to this role + * @private * @param {*} model model type to fetch * @param {String} [principalType] principalType used in the rolemapping for model * @param {object} [query] query object passed to model find call From 957f84e989c6a6540a96a23cf769ec901c3e5693 Mon Sep 17 00:00:00 2001 From: Esco Obong <menzoic@gmail.com> Date: Thu, 26 Mar 2015 10:10:13 -0400 Subject: [PATCH 6/6] add callback args for listByPrincipalType to jsdoc comment, pass explicit arguments to callback --- common/models/role.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/models/role.js b/common/models/role.js index 7cf3ed45..340874ff 100644 --- a/common/models/role.js +++ b/common/models/role.js @@ -72,7 +72,7 @@ module.exports = function(Role) { * @param {*} model model type to fetch * @param {String} [principalType] principalType used in the rolemapping for model * @param {object} [query] query object passed to model find call - * @param {Function} [callback] + * @param {Function} [callback] callback function called with `(err, models)` arguments. */ function listByPrincipalType(model, principalType, query, callback) { if (callback === undefined) { @@ -92,7 +92,9 @@ module.exports = function(Role) { }); query.where = query.where || {}; query.where.id = {inq: ids}; - model.find(query, callback); + model.find(query, function(err, models) { + callback(err, models); + }); }); }