From 98a7a22d10486918af7b5c6c18c0847394dd6696 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 14 Jan 2019 18:56:35 +0800 Subject: [PATCH 1/2] fix connect app issue 2741 - [Team Management 2.0] Handle the case when email entered to be invited is already topcoder user --- src/routes/projectMemberInvites/create.js | 30 ++++++++++- .../projectMemberInvites/create.spec.js | 53 +++++++++++++++++++ src/util.js | 38 +++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 98c3eb2d..d9f92598 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -118,9 +118,35 @@ module.exports = [ data.userId = null; if (invite.emails) { + // if for some emails there are already existent users, we will invite them by userId, + // to avoid sending them registration email + const existentUsers = util.lookupUserEmails(req, invite.emails).map((user) => { + const userWithNumberId = {}; + _.assign(userWithNumberId, user, { + id: parseInt(user.id, 10), + }); + return userWithNumberId; + }); + // for existent users - invite by ids + const existentUserIds = _.map(existentUsers, 'id'); + // the rest of email of non-existent users, so we will invite them by email + const nonExistentUserEmails = invite.emails.filter(inviteEmail => + !_.find(existentUsers, { email: inviteEmail }), + ); + + // remove invites for users that are invited already + _.remove(existentUserIds, userId => _.some(invites, i => i.userId === userId)); + existentUserIds.forEach((userId) => { + const dataNew = _.clone(data); + _.assign(dataNew, { + userId, + }); + invitePromises.push(models.ProjectMemberInvite.create(dataNew)); + }); + // remove invites for users that are invited already - _.remove(invite.emails, u => _.some(invites, i => i.email === u)); - invite.emails.forEach((email) => { + _.remove(nonExistentUserEmails, email => _.some(invites, i => i.email === email)); + nonExistentUserEmails.forEach((email) => { const dataNew = _.clone(data); _.assign(dataNew, { email, diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index 382469f9..41ceb687 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -86,6 +86,8 @@ describe('Project Member Invite create', () => { server.services.pubsub.publish.restore(); 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, 'lookupUserEmails', () => []); }); afterEach(() => { sandbox.restore(); @@ -275,6 +277,57 @@ describe('Project Member Invite create', () => { }); }); + it('should return 201 and add new userId invite as customer for existent user when invite by email', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: [{ + roleName: USER_ROLE.COPILOT, + }], + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + util.lookupUserEmails.restore(); + sandbox.stub(util, 'lookupUserEmails', () => [{ + id: '12345', + email: 'hello@world.com', + }]); + request(server) + .post(`/v4/projects/${project2.id}/members/invite`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + param: { + emails: ['hello@world.com'], + role: 'customer', + }, + }) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content[0]; + should.exist(resJson); + resJson.role.should.equal('customer'); + resJson.projectId.should.equal(project2.id); + resJson.userId.should.equal(12345); + server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; + done(); + } + }); + }); + it('should return 201 and add new user invite as customer', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ diff --git a/src/util.js b/src/util.js index 3909fd95..a3f1fefb 100644 --- a/src/util.js +++ b/src/util.js @@ -423,6 +423,44 @@ _.assignIn(util, { return Promise.reject(err); }); }), + + /** + * Lookup user handles from emails + * @param {Object} req request + * @param {Array} userEmails user emails + * @param {Boolean} isPattern flag to indicate that pattern matching is required or not + * @return {Promise} promise + */ + lookupUserEmails: (req, userEmails, isPattern = false) => { + req.log.debug(`identityServiceEndpoint: ${config.get('identityServiceEndpoint')}`); + let filter = _.map(userEmails, i => `email=${i}`).join(' OR '); + if (isPattern) { + filter += '&like=true'; + } + req.log.trace('filter for users api call', filter); + return util.getSystemUserToken(req.log) + .then((token) => { + req.log.debug(`Bearer ${token}`); + const httpClient = this.getHttpClient({ id: req.id, log: req.log }); + return httpClient.get(`${config.get('identityServiceEndpoint')}users`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + params: { + fields: 'handle,id,email', + filter, + }, + }) + .then((response) => { + const data = _.get(response, 'data.result.content', null); + if (!data) { throw new Error('Response does not have result.content'); } + req.log.debug('UserHandle response', data); + return data; + }); + }); + }, }); export default util; From c52e099fa9c784bd4d10512f6d073976b201439b Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 15 Jan 2019 10:08:10 +0800 Subject: [PATCH 2/2] improved unit test --- src/routes/projectMemberInvites/create.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index 41ceb687..2f95e344 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -322,6 +322,7 @@ describe('Project Member Invite create', () => { resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); resJson.userId.should.equal(12345); + should.not.exist(resJson.email); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); }