Skip to content

Commit fbf1761

Browse files
author
vikasrohit
authored
Merge pull request #62 from topcoder-platform/feature/allow_managers_to_be_added
Feature/allow managers to be added
2 parents abc8017 + e75f5d0 commit fbf1761

File tree

4 files changed

+275
-3
lines changed

4 files changed

+275
-3
lines changed

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const USER_ROLE = {
3535

3636
export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN];
3737

38+
export const MANAGER_ROLES = [...ADMIN_ROLES, USER_ROLE.MANAGER];
3839

3940
export const EVENT = {
4041
ROUTING_KEY: {

src/routes/projectMembers/create.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Joi from 'joi';
66
import { middleware as tcMiddleware } from 'tc-core-library-js';
77
import models from '../../models';
88
import util from '../../util';
9-
import { PROJECT_MEMBER_ROLE, EVENT } from '../../constants';
9+
import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, EVENT } from '../../constants';
1010

1111
/**
1212
* API to add a project member.
@@ -53,10 +53,21 @@ module.exports = [
5353
if (_.isUndefined(member.isPrimary)) {
5454
member.isPrimary = _.isUndefined(_.find(members, m => m.isPrimary && m.role === member.role));
5555
}
56+
let promise = Promise.resolve();
57+
if (member.role === PROJECT_MEMBER_ROLE.MANAGER) {
58+
promise = util.getUserRoles(member.userId, req.log, req.id);
59+
}
5660
req.log.debug('creating member', member);
5761
let newMember = null;
5862
// register member
59-
return models.ProjectMember.create(member)
63+
return promise.then((memberRoles) => {
64+
if (member.role === PROJECT_MEMBER_ROLE.MANAGER
65+
&& (!memberRoles || !util.hasIntersection(MANAGER_ROLES, memberRoles))) {
66+
const err = new Error('This user can\'t be added as a Manager to the project');
67+
err.status = 400;
68+
return next(err);
69+
}
70+
return models.ProjectMember.create(member)
6071
.then((_newMember) => {
6172
newMember = _newMember.get({ plain: true });
6273
// publish event
@@ -72,5 +83,6 @@ module.exports = [
7283
req.log.error('Unable to register ', err);
7384
next(err);
7485
});
86+
});
7587
},
7688
];

src/routes/projectMembers/create.spec.js

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import models from '../../models';
88
import util from '../../util';
99
import server from '../../app';
1010
import testUtil from '../../tests/util';
11+
import { USER_ROLE } from '../../constants';
1112

1213
const should = chai.should();
1314

1415
describe('Project Members create', () => {
1516
let project1;
1617
let project2;
17-
before((done) => {
18+
beforeEach((done) => {
1819
testUtil.clearDb()
1920
.then(() => {
2021
models.Project.create({
@@ -246,5 +247,232 @@ describe('Project Members create', () => {
246247
}
247248
});
248249
});
250+
251+
it('should return 400 for trying to add customers as manager', (done) => {
252+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
253+
get: () => Promise.resolve({
254+
status: 200,
255+
data: {
256+
id: 'requesterId',
257+
version: 'v3',
258+
result: {
259+
success: true,
260+
status: 200,
261+
content: [{
262+
roleName: 'Topcoder User',
263+
}],
264+
},
265+
},
266+
}),
267+
});
268+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
269+
request(server)
270+
.post(`/v4/projects/${project1.id}/members/`)
271+
.set({
272+
Authorization: `Bearer ${testUtil.jwts.manager}`,
273+
})
274+
.send({
275+
param: {
276+
userId: 3,
277+
role: 'manager',
278+
},
279+
})
280+
.expect('Content-Type', /json/)
281+
.expect(400)
282+
.end((err, res) => {
283+
if (err) {
284+
done(err);
285+
} else {
286+
const resJson = res.body.result.content;
287+
should.exist(resJson);
288+
const errorMessage = _.get(resJson, 'message', '');
289+
sinon.assert.match(errorMessage, /.*can't be added as a Manager/);
290+
done();
291+
}
292+
});
293+
});
294+
295+
it('should return 400 for trying to add copilot as manager', (done) => {
296+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
297+
get: () => Promise.resolve({
298+
status: 200,
299+
data: {
300+
id: 'requesterId',
301+
version: 'v3',
302+
result: {
303+
success: true,
304+
status: 200,
305+
content: [{
306+
roleName: USER_ROLE.COPILOT,
307+
}],
308+
},
309+
},
310+
}),
311+
});
312+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
313+
request(server)
314+
.post(`/v4/projects/${project1.id}/members/`)
315+
.set({
316+
Authorization: `Bearer ${testUtil.jwts.manager}`,
317+
})
318+
.send({
319+
param: {
320+
userId: 3,
321+
role: 'manager',
322+
},
323+
})
324+
.expect('Content-Type', /json/)
325+
.expect(400)
326+
.end((err, res) => {
327+
if (err) {
328+
done(err);
329+
} else {
330+
const resJson = res.body.result.content;
331+
should.exist(resJson);
332+
done();
333+
}
334+
});
335+
});
336+
337+
it('should return 201 and register Connect Manager as manager', (done) => {
338+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
339+
get: () => Promise.resolve({
340+
status: 200,
341+
data: {
342+
id: 'requesterId',
343+
version: 'v3',
344+
result: {
345+
success: true,
346+
status: 200,
347+
content: [{
348+
roleName: USER_ROLE.MANAGER,
349+
}],
350+
},
351+
},
352+
}),
353+
});
354+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
355+
request(server)
356+
.post(`/v4/projects/${project1.id}/members/`)
357+
.set({
358+
Authorization: `Bearer ${testUtil.jwts.manager}`,
359+
})
360+
.send({
361+
param: {
362+
userId: 3,
363+
role: 'manager',
364+
},
365+
})
366+
.expect('Content-Type', /json/)
367+
.expect(201)
368+
.end((err, res) => {
369+
if (err) {
370+
done(err);
371+
} else {
372+
const resJson = res.body.result.content;
373+
should.exist(resJson);
374+
resJson.role.should.equal('manager');
375+
resJson.isPrimary.should.be.truthy;
376+
resJson.projectId.should.equal(project1.id);
377+
resJson.userId.should.equal(3);
378+
server.services.pubsub.publish.calledWith('project.member.added').should.be.true;
379+
done();
380+
}
381+
});
382+
});
383+
384+
it('should return 201 and register Connect Admin as manager', (done) => {
385+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
386+
get: () => Promise.resolve({
387+
status: 200,
388+
data: {
389+
id: 'requesterId',
390+
version: 'v3',
391+
result: {
392+
success: true,
393+
status: 200,
394+
content: [{
395+
roleName: USER_ROLE.CONNECT_ADMIN,
396+
}],
397+
},
398+
},
399+
}),
400+
});
401+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
402+
request(server)
403+
.post(`/v4/projects/${project1.id}/members/`)
404+
.set({
405+
Authorization: `Bearer ${testUtil.jwts.manager}`,
406+
})
407+
.send({
408+
param: {
409+
userId: 3,
410+
role: 'manager',
411+
},
412+
})
413+
.expect('Content-Type', /json/)
414+
.expect(201)
415+
.end((err, res) => {
416+
if (err) {
417+
done(err);
418+
} else {
419+
const resJson = res.body.result.content;
420+
should.exist(resJson);
421+
resJson.role.should.equal('manager');
422+
resJson.isPrimary.should.be.truthy;
423+
resJson.projectId.should.equal(project1.id);
424+
resJson.userId.should.equal(3);
425+
server.services.pubsub.publish.calledWith('project.member.added').should.be.true;
426+
done();
427+
}
428+
});
429+
});
430+
431+
it('should return 201 and register Topcoder Admin as manager', (done) => {
432+
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
433+
get: () => Promise.resolve({
434+
status: 200,
435+
data: {
436+
id: 'requesterId',
437+
version: 'v3',
438+
result: {
439+
success: true,
440+
status: 200,
441+
content: [{
442+
roleName: USER_ROLE.TOPCODER_ADMIN,
443+
}],
444+
},
445+
},
446+
}),
447+
});
448+
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
449+
request(server)
450+
.post(`/v4/projects/${project1.id}/members/`)
451+
.set({
452+
Authorization: `Bearer ${testUtil.jwts.manager}`,
453+
})
454+
.send({
455+
param: {
456+
userId: 3,
457+
role: 'manager',
458+
},
459+
})
460+
.expect('Content-Type', /json/)
461+
.expect(201)
462+
.end((err, res) => {
463+
if (err) {
464+
done(err);
465+
} else {
466+
const resJson = res.body.result.content;
467+
should.exist(resJson);
468+
resJson.role.should.equal('manager');
469+
resJson.isPrimary.should.be.truthy;
470+
resJson.projectId.should.equal(project1.id);
471+
resJson.userId.should.equal(3);
472+
server.services.pubsub.publish.calledWith('project.member.added').should.be.true;
473+
done();
474+
}
475+
});
476+
});
249477
});
250478
});

src/util.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ _.assignIn(util, {
8585
authRoles = authRoles.map(s => s.toLowerCase());
8686
return _.intersection(authRoles, roles.map(r => r.toLowerCase())).length > 0;
8787
},
88+
/**
89+
* Helper funtion to find intersection (case insensitive) between two arrays
90+
* @param {Array} array1 first array of strings
91+
* @param {Array} array2 second array of strings
92+
* @return {boolean} true/false
93+
*/
94+
hasIntersection: (array1, array2) => {
95+
const lowercased = array1.map(s => s.toLowerCase());
96+
return _.intersection(lowercased, array2.map(r => r.toLowerCase())).length > 0;
97+
},
8898
/**
8999
* Helper funtion to verify if user has admin roles
90100
* @param {object} req Request object that should contain authUser
@@ -307,6 +317,27 @@ _.assignIn(util, {
307317
return Promise.reject(err);
308318
}
309319
}),
320+
321+
/**
322+
* Retrieve member details from userIds
323+
*/
324+
getUserRoles: Promise.coroutine(function* (userId, logger, requestId) { // eslint-disable-line func-names
325+
try {
326+
const token = yield this.getSystemUserToken(logger);
327+
const httpClient = this.getHttpClient({ id: requestId, log: logger });
328+
return httpClient.get(`${config.identityServiceEndpoint}roles`, {
329+
params: {
330+
filter: `subjectID=${userId}`,
331+
},
332+
headers: {
333+
'Content-Type': 'application/json',
334+
Authorization: `Bearer ${token}`,
335+
},
336+
}).then(res => _.get(res, 'data.result.content', []).map(r => r.roleName));
337+
} catch (err) {
338+
return Promise.reject(err);
339+
}
340+
}),
310341
});
311342

312343
export default util;

0 commit comments

Comments
 (0)