Skip to content

Feature/individual invitation errors #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion postman.json
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,31 @@
},
"response": []
},
{
"name": "Invite with userIds and emails - both success and failed",
"request": {
"url": "{{api-url}}/v4/projects/1/members/invite",
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token-manager-40051334}}",
"description": ""
},
{
"key": "Content-Type",
"value": "application/json",
"description": ""
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"param\": {\n\t\t\"userIds\": [40051331, 40051334],\n\t\t\"emails\": [\"divyalife526@gmail.com\"],\n\t\t\"role\": \"manager\"\n\t}\n}"
},
"description": ""
},
"response": []
},
{
"name": "Update invite status with userId",
"request": {
Expand Down Expand Up @@ -5497,4 +5522,4 @@
]
}
]
}
}
90 changes: 45 additions & 45 deletions src/routes/projectMemberInvites/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ const addMemberValidations = {
* @param {Object} invite invite to process
* @param {Array} invites existent invites from DB
* @param {Object} data template for new invites to be put in DB
* @param {Array} failed failed invites error message
*
* @returns {Promise<Promise[]>} list of promises
*/
const buildCreateInvitePromises = (req, invite, invites, data) => {
const buildCreateInvitePromises = (req, invite, invites, data, failed) => {
const invitePromises = [];

if (invite.userIds) {
// remove invites for users that are invited already
_.remove(invite.userIds, u => _.some(invites, i => i.userId === u));
Expand Down Expand Up @@ -91,15 +91,15 @@ const buildCreateInvitePromises = (req, invite, invites, data) => {

invitePromises.push(models.ProjectMemberInvite.create(dataNew));
});

return Promise.resolve(invitePromises);
return invitePromises;
}).catch((error) => {
req.log.error(error);
return Promise.reject(invitePromises);
_.forEach(invite.emails, email => failed.push(_.assign({}, { email, message: error.statusText })));
return invitePromises;
});
}

return Promise.resolve(invitePromises);
return invitePromises;
};

const sendInviteEmail = (req, projectId, invite) => {
Expand Down Expand Up @@ -157,6 +157,7 @@ module.exports = [
validate(addMemberValidations),
permissions('projectMemberInvite.create'),
(req, res, next) => {
let failed = [];
const invite = req.body.param;

if (!invite.userIds && !invite.emails) {
Expand Down Expand Up @@ -192,12 +193,11 @@ module.exports = [
if (invite.emails) {
// email invites can only be used for CUSTOMER role
if (invite.role !== PROJECT_MEMBER_ROLE.CUSTOMER) { // eslint-disable-line no-lonely-if
const err = new Error(`Emails can only be used for ${PROJECT_MEMBER_ROLE.CUSTOMER}`);
err.status = 400;
return next(err);
const message = `Emails can only be used for ${PROJECT_MEMBER_ROLE.CUSTOMER}`;
failed = _.concat(failed, _.map(invite.emails, email => _.assign({}, { email, message })));
delete invite.emails;
}
}

if (promises.length === 0) {
promises.push(Promise.resolve());
}
Expand All @@ -209,14 +209,14 @@ module.exports = [
const [userId, roles] = data;
req.log.debug(roles);

if (!util.hasIntersection(MANAGER_ROLES, roles)) {
if (roles && !util.hasIntersection(MANAGER_ROLES, roles)) {
forbidUserList.push(userId);
}
});
if (forbidUserList.length > 0) {
const err = new Error(`${forbidUserList.join()} cannot be added with a Manager role to the project`);
err.status = 403;
return next(err);
const message = 'cannot be added with a Manager role to the project';
failed = _.concat(failed, _.map(forbidUserList, id => _.assign({}, { userId: id, message })));
invite.userIds = _.filter(invite.userIds, userId => !_.includes(forbidUserList, userId));
}
}
return models.ProjectMemberInvite.getPendingInvitesForProject(projectId)
Expand All @@ -233,39 +233,39 @@ module.exports = [
updatedBy: req.authUser.userId,
};

return buildCreateInvitePromises(req, invite, invites, data)
.then((invitePromises) => {
if (invitePromises.length === 0) {
return [];
}

req.log.debug('Creating invites');
return models.sequelize.Promise.all(invitePromises)
.then((values) => {
values.forEach((v) => {
req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, {
req,
userId: v.userId,
email: v.email,
status: v.status,
role: v.role,
});
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED,
v,
{ correlationId: req.id },
);
// send email invite (async)
if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) {
sendInviteEmail(req, projectId, v);
}
});
return values;
}); // models.sequelize.Promise.all
}); // buildCreateInvitePromises
req.log.debug('Creating invites');
return models.sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed))
.then((values) => {
values.forEach((v) => {
req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, {
req,
userId: v.userId,
email: v.email,
status: v.status,
role: v.role,
});
req.app.services.pubsub.publish(
EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED,
v,
{ correlationId: req.id },
);
// send email invite (async)
if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) {
sendInviteEmail(req, projectId, v);
}
});
return values;
}); // models.sequelize.Promise.all
}); // models.ProjectMemberInvite.getPendingInvitesForProject
})
.then(values => res.status(201).json(util.wrapResponse(req.id, values, null, 201)))
.then((values) => {
const success = _.assign({}, { success: values });
if (failed.length) {
res.status(403).json(util.wrapResponse(req.id, _.assign({}, success, { failed }), null, 403));
} else {
res.status(201).json(util.wrapResponse(req.id, success, null, 201));
}
})
.catch(err => next(err));
},
];
109 changes: 85 additions & 24 deletions src/routes/projectMemberInvites/create.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ describe('Project Member Invite create', () => {
createdBy: 1,
updatedBy: 1,
});

models.ProjectMember.create({
userId: 40051334,
projectId: project1.id,
role: 'manager',
isPrimary: true,
createdBy: 1,
updatedBy: 1,
});
}).then(() =>
models.Project.create({
type: 'generic',
Expand Down Expand Up @@ -87,6 +96,7 @@ describe('Project Member Invite create', () => {
sinon.stub(server.services.pubsub, 'init', () => {});
sinon.stub(server.services.pubsub, 'publish', () => {});
// by default mock lookupUserEmails return nothing so all the cases are not broken
sandbox.stub(util, 'getUserRoles', () => Promise.resolve([]));
sandbox.stub(util, 'lookupUserEmails', () => Promise.resolve([]));
sandbox.stub(util, 'getMemberDetailsByUserIds', () => Promise.resolve([{
userId: 40051333,
Expand Down Expand Up @@ -246,9 +256,11 @@ describe('Project Member Invite create', () => {
result: {
success: true,
status: 200,
content: [{
roleName: USER_ROLE.COPILOT,
}],
content: {
success: [{
roleName: USER_ROLE.COPILOT,
}],
},
},
},
}),
Expand All @@ -271,7 +283,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content[0];
const resJson = res.body.result.content.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
Expand All @@ -292,9 +304,11 @@ describe('Project Member Invite create', () => {
result: {
success: true,
status: 200,
content: [{
roleName: USER_ROLE.COPILOT,
}],
content: {
success: [{
roleName: USER_ROLE.COPILOT,
}],
},
},
},
}),
Expand Down Expand Up @@ -322,7 +336,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content[0];
const resJson = res.body.result.content.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
Expand All @@ -344,9 +358,11 @@ describe('Project Member Invite create', () => {
result: {
success: true,
status: 200,
content: [{
roleName: USER_ROLE.COPILOT,
}],
content: {
success: [{
roleName: USER_ROLE.COPILOT,
}],
},
},
},
}),
Expand All @@ -369,7 +385,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content[0];
const resJson = res.body.result.content.success[0];
should.exist(resJson);
resJson.role.should.equal('customer');
resJson.projectId.should.equal(project2.id);
Expand All @@ -390,9 +406,11 @@ describe('Project Member Invite create', () => {
result: {
success: true,
status: 200,
content: [{
roleName: USER_ROLE.COPILOT,
}],
content: {
success: [{
roleName: USER_ROLE.COPILOT,
}],
},
},
},
}),
Expand All @@ -415,7 +433,7 @@ describe('Project Member Invite create', () => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content;
const resJson = res.body.result.content.success;
should.exist(resJson);
resJson.length.should.equal(0);
server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true;
Expand Down Expand Up @@ -484,16 +502,18 @@ describe('Project Member Invite create', () => {
it('should return 201 if try to create manager with MANAGER_ROLES', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
status: 403,
data: {
id: 'requesterId',
version: 'v3',
result: {
success: true,
status: 200,
content: [{
roleName: USER_ROLE.MANAGER,
}],
status: 403,
content: {
failed: [{
message: 'cannot be added with a Manager role to the project',
}],
},
},
},
}),
Expand All @@ -511,16 +531,57 @@ describe('Project Member Invite create', () => {
},
})
.expect('Content-Type', /json/)
.expect(403)
.end((err, res) => {
const failed = res.body.result.content.failed[0];
should.exist(failed);
failed.message.should.equal('cannot be added with a Manager role to the project');
done();
});
});

it('should return 201 if try to create customer with COPILOT', (done) => {
const mockHttpClient = _.merge(testUtil.mockHttpClient, {
get: () => Promise.resolve({
status: 200,
data: {
id: 'requesterId',
version: 'v3',
result: {
success: true,
status: 200,
content: {
success: [{
roleName: USER_ROLE.COPILOT,
}],
},
},
},
}),
});
sandbox.stub(util, 'getHttpClient', () => mockHttpClient);
request(server)
.post(`/v4/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
param: {
userIds: [40051331],
role: 'copilot',
},
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content[0];
const resJson = res.body.result.content.success[0];
should.exist(resJson);
resJson.role.should.equal('manager');
resJson.role.should.equal('copilot');
resJson.projectId.should.equal(project1.id);
resJson.userId.should.equal(40152855);
resJson.userId.should.equal(40051331);
server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true;
done();
}
Expand Down
Loading