Skip to content

Feature/fix email duplicated invites #303

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
merged 6 commits into from
May 10, 2019
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
3 changes: 2 additions & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@
"inviteEmailSectionTitle": "Project Invitation",
"connectUrl":"https://connect.topcoder-dev.com",
"accountsAppUrl": "https://accounts.topcoder-dev.com",
"MAX_REVISION_NUMBER": 100
"MAX_REVISION_NUMBER": 100,
"UNIQUE_GMAIL_VALIDATION": true
}
31 changes: 29 additions & 2 deletions src/routes/projectMemberInvites/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ const addMemberValidations = {
},
};

/**
* Helper method to check the uniqueness of two emails
*
* @param {String} email1 first email to compare
* @param {String} email2 second email to compare
* @param {Object} options the options
*
* @returns {Boolean} true if two emails are same
*/
const compareEmail = (email1, email2, options = { UNIQUE_GMAIL_VALIDATION: false }) => {
if (options.UNIQUE_GMAIL_VALIDATION) {
// email is gmail
const emailSplit = /(^[\w.+-]+)(@gmail\.com|@googlemail\.com)$/g.exec(_.toLower(email1));
if (emailSplit) {
const address = emailSplit[1].replace('.', '');
const emailDomain = emailSplit[2].replace('.', '\\.');
const regexAddress = address.split('').join('\\.?');
const regex = new RegExp(`${regexAddress}${emailDomain}`);
return regex.test(_.toLower(email2));
}
}
return _.toLower(email1) === _.toLower(email2);
};

/**
* Helper method to build promises for creating new invites in DB
*
Expand Down Expand Up @@ -68,7 +92,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => {
});
// non-existent users we will invite them by email only
const nonExistentUserEmails = invite.emails.filter(inviteEmail =>
!_.find(existentUsers, { email: inviteEmail }),
!_.find(existentUsers, existentUser =>
compareEmail(existentUser.email, inviteEmail, { UNIQUE_GMAIL_VALIDATION: false })),
);

// remove invites for users that are invited already
Expand All @@ -83,7 +108,9 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => {
});

// remove invites for users that are invited already
_.remove(nonExistentUserEmails, email => _.some(invites, i => i.email === email));
_.remove(nonExistentUserEmails, email =>
_.some(invites, i =>
compareEmail(i.email, email, { UNIQUE_GMAIL_VALIDATION: config.get('UNIQUE_GMAIL_VALIDATION') })));
nonExistentUserEmails.forEach((email) => {
const dataNew = _.clone(data);

Expand Down
171 changes: 160 additions & 11 deletions src/routes/projectMemberInvites/create.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,60 @@ describe('Project Member Invite create', () => {
createdBy: 1,
updatedBy: 1,
}).then(() => {
models.ProjectMemberInvite.create({
projectId: project1.id,
userId: 40051335,
email: null,
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}).then(() => {
const promises = [
models.ProjectMemberInvite.create({
projectId: project1.id,
userId: 40051335,
email: null,
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}),
models.ProjectMemberInvite.create({
projectId: project1.id,
email: 'duplicate_lowercase@test.com',
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}),
models.ProjectMemberInvite.create({
projectId: project1.id,
email: 'DUPLICATE_UPPERCASE@test.com',
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}),
models.ProjectMemberInvite.create({
projectId: project1.id,
email: 'with.dot@gmail.com',
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}),
models.ProjectMemberInvite.create({
projectId: project1.id,
email: 'withoutdot@gmail.com',
role: PROJECT_MEMBER_ROLE.MANAGER,
status: INVITE_STATUS.PENDING,
createdBy: 1,
updatedBy: 1,
createdAt: '2016-06-30 00:33:07+00',
updatedAt: '2016-06-30 00:33:07+00',
}),
];
Promise.all(promises).then(() => {
done();
});
});
Expand Down Expand Up @@ -640,6 +683,112 @@ describe('Project Member Invite create', () => {
});
});

it('should return 201 and empty response when trying add already invited member by lowercase email', (done) => {
request(server)
.post(`/v4/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
param: {
emails: ['DUPLICATE_LOWERCASE@test.com'],
role: 'customer',
},
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content.success;
should.exist(resJson);
resJson.length.should.equal(0);
done();
}
});
});

it('should return 201 and empty response when trying add already invited member by uppercase email', (done) => {
request(server)
.post(`/v4/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
param: {
emails: ['duplicate_uppercase@test.com'],
role: 'customer',
},
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content.success;
should.exist(resJson);
resJson.length.should.equal(0);
done();
}
});
});

it('should return 201 and empty response when trying add already invited member by gmail email with dot',
(done) => {
request(server)
.post(`/v4/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
param: {
emails: ['WITHdot@gmail.com'],
role: 'customer',
},
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content.success;
should.exist(resJson);
resJson.length.should.equal(0);
done();
}
});
});

it('should return 201 and empty response when trying add already invited member by gmail email without dot',
(done) => {
request(server)
.post(`/v4/projects/${project1.id}/members/invite`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
})
.send({
param: {
emails: ['WITHOUT.dot@gmail.com'],
role: 'customer',
},
})
.expect('Content-Type', /json/)
.expect(201)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body.result.content.success;
should.exist(resJson);
resJson.length.should.equal(0);
done();
}
});
});

describe('Bus api', () => {
let createEventSpy;

Expand Down