Merge pull request #1845 from overleaf/ta-invite-self

Don't Send Invite when Group Manager Invite Self

GitOrigin-RevId: 35d7aecc82cf1124fbdac7fe986081b9556c23f9
This commit is contained in:
Timothée Alby 2019-06-11 15:14:08 +02:00 committed by sharelatex
parent f6c2cfe727
commit 8caea13193
2 changed files with 160 additions and 165 deletions

View file

@ -1,16 +1,3 @@
/* eslint-disable
handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let TeamInvitesHandler let TeamInvitesHandler
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const crypto = require('crypto') const crypto = require('crypto')
@ -19,7 +6,6 @@ const async = require('async')
const settings = require('settings-sharelatex') const settings = require('settings-sharelatex')
const { ObjectId } = require('mongojs') const { ObjectId } = require('mongojs')
const { TeamInvite } = require('../../models/TeamInvite')
const { Subscription } = require('../../models/Subscription') const { Subscription } = require('../../models/Subscription')
const UserGetter = require('../User/UserGetter') const UserGetter = require('../User/UserGetter')
@ -38,54 +24,45 @@ module.exports = TeamInvitesHandler = {
err, err,
subscription subscription
) { ) {
if (err != null) { if (err) {
return callback(err) return callback(err)
} }
if (subscription == null) { if (!subscription) {
return callback(new Errors.NotFoundError('team not found')) return callback(new Errors.NotFoundError('team not found'))
} }
const invite = subscription.teamInvites.find(i => i.token === token) const invite = subscription.teamInvites.find(i => i.token === token)
return callback(null, invite, subscription) callback(null, invite, subscription)
}) })
}, },
createInvite(teamManagerId, subscription, email, callback) { createInvite(teamManagerId, subscription, email, callback) {
email = EmailHelper.parseEmail(email) email = EmailHelper.parseEmail(email)
if (email == null) { if (!email) {
return callback(new Error('invalid email')) return callback(new Error('invalid email'))
} }
logger.log({ teamManagerId, email }, 'Creating manager team invite') logger.log({ teamManagerId, email }, 'Creating manager team invite')
return UserGetter.getUser(teamManagerId, function(error, teamManager) { return UserGetter.getUser(teamManagerId, function(error, teamManager) {
let inviterName if (error) {
if (error != null) {
return callback(error) return callback(error)
} }
if (teamManager.first_name && teamManager.last_name) { removeLegacyInvite(subscription.id, email, function(error) {
inviterName = `${teamManager.first_name} ${teamManager.last_name} (${ if (error) {
teamManager.email
})`
} else {
inviterName = teamManager.email
}
return removeLegacyInvite(subscription.id, email, function(error) {
if (error != null) {
return callback(error) return callback(error)
} }
return createInvite(subscription, email, inviterName, callback) createInvite(subscription, email, teamManager, callback)
}) })
}) })
}, },
importInvite(subscription, inviterName, email, token, sentAt, callback) { importInvite(subscription, inviterName, email, token, sentAt, callback) {
return checkIfInviteIsPossible(subscription, email, function( checkIfInviteIsPossible(subscription, email, function(
error, error,
possible, possible,
reason reason
) { ) {
if (error != null) { if (error) {
return callback(error) return callback(error)
} }
if (!possible) { if (!possible) {
@ -99,60 +76,51 @@ module.exports = TeamInvitesHandler = {
sentAt sentAt
}) })
return subscription.save(callback) subscription.save(callback)
}) })
}, },
acceptInvite(token, userId, callback) { acceptInvite(token, userId, callback) {
logger.log({ userId }, 'Accepting invite') logger.log({ userId }, 'Accepting invite')
return TeamInvitesHandler.getInvite(token, function( TeamInvitesHandler.getInvite(token, function(err, invite, subscription) {
err, if (err) {
invite,
subscription
) {
if (err != null) {
return callback(err) return callback(err)
} }
if (invite == null) { if (!invite) {
return callback(new Errors.NotFoundError('invite not found')) return callback(new Errors.NotFoundError('invite not found'))
} }
return SubscriptionUpdater.addUserToGroup( SubscriptionUpdater.addUserToGroup(subscription._id, userId, function(
subscription._id, err
userId, ) {
function(err) { if (err) {
if (err != null) {
return callback(err) return callback(err)
} }
return removeInviteFromTeam(subscription.id, invite.email, callback) removeInviteFromTeam(subscription.id, invite.email, callback)
} })
)
}) })
}, },
revokeInvite(teamManagerId, subscription, email, callback) { revokeInvite(teamManagerId, subscription, email, callback) {
email = EmailHelper.parseEmail(email) email = EmailHelper.parseEmail(email)
if (email == null) { if (!email) {
return callback(new Error('invalid email')) return callback(new Error('invalid email'))
} }
logger.log({ teamManagerId, email }, 'Revoking invite') logger.log({ teamManagerId, email }, 'Revoking invite')
return removeInviteFromTeam(subscription.id, email, callback) removeInviteFromTeam(subscription.id, email, callback)
}, },
// Legacy method to allow a user to receive a confirmation email if their // Legacy method to allow a user to receive a confirmation email if their
// email is in Subscription.invited_emails when they join. We'll remove this // email is in Subscription.invited_emails when they join. We'll remove this
// after a short while. // after a short while.
createTeamInvitesForLegacyInvitedEmail(email, callback) { createTeamInvitesForLegacyInvitedEmail(email, callback) {
return SubscriptionLocator.getGroupsWithEmailInvite(email, function( SubscriptionLocator.getGroupsWithEmailInvite(email, function(err, teams) {
err, if (err) {
teams
) {
if (err != null) {
return callback(err) return callback(err)
} }
return async.map( async.map(
teams, teams,
(team, cb) => (team, cb) =>
TeamInvitesHandler.createInvite(team.admin_id, team, email, cb), TeamInvitesHandler.createInvite(team.admin_id, team, email, cb),
@ -162,26 +130,48 @@ module.exports = TeamInvitesHandler = {
} }
} }
var createInvite = function(subscription, email, inviterName, callback) { var createInvite = function(subscription, email, inviter, callback) {
logger.log( logger.log(
{ subscriptionId: subscription.id, email, inviterName }, { subscriptionId: subscription.id, email, inviterId: inviter._id },
'Creating invite' 'Creating invite'
) )
return checkIfInviteIsPossible(subscription, email, function( checkIfInviteIsPossible(subscription, email, function(
error, error,
possible, possible,
reason reason
) { ) {
if (error != null) { if (error) {
return callback(error) return callback(error)
} }
if (!possible) { if (!possible) {
return callback(reason) return callback(reason)
} }
// don't send invites when inviting self; add user directly to the group
const isInvitingSelf = inviter.emails.some(
emailData => emailData.email === email
)
if (isInvitingSelf) {
return SubscriptionUpdater.addUserToGroup(
subscription._id,
inviter._id,
err => {
if (err) {
return callback(err)
}
// legacy: remove any invite that might have been created in the past
removeInviteFromTeam(subscription._id, email, callback)
}
)
}
const inviterName = getInviterName(inviter)
let invite = subscription.teamInvites.find(invite => invite.email === email) let invite = subscription.teamInvites.find(invite => invite.email === email)
if (invite == null) { if (invite) {
invite.sentAt = new Date()
} else {
invite = { invite = {
email, email,
inviterName, inviterName,
@ -189,12 +179,10 @@ var createInvite = function(subscription, email, inviterName, callback) {
sentAt: new Date() sentAt: new Date()
} }
subscription.teamInvites.push(invite) subscription.teamInvites.push(invite)
} else {
invite.sentAt = new Date()
} }
return subscription.save(function(error) { subscription.save(function(error) {
if (error != null) { if (error) {
return callback(error) return callback(error)
} }
@ -206,7 +194,7 @@ var createInvite = function(subscription, email, inviterName, callback) {
}/`, }/`,
appName: settings.appName appName: settings.appName
} }
return EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error => EmailHandler.sendEmail('verifyEmailToJoinTeam', opts, error =>
callback(error, invite) callback(error, invite)
) )
}) })
@ -221,7 +209,7 @@ var removeInviteFromTeam = function(subscriptionId, email, callback) {
'removeInviteFromTeam' 'removeInviteFromTeam'
) )
return async.series( async.series(
[ [
cb => Subscription.update(searchConditions, removeInvite, cb), cb => Subscription.update(searchConditions, removeInvite, cb),
cb => removeLegacyInvite(subscriptionId, email, cb) cb => removeLegacyInvite(subscriptionId, email, cb)
@ -244,9 +232,6 @@ var removeLegacyInvite = (subscriptionId, email, callback) =>
) )
var checkIfInviteIsPossible = function(subscription, email, callback) { var checkIfInviteIsPossible = function(subscription, email, callback) {
if (callback == null) {
callback = function(error, possible, reason) {}
}
if (!subscription.groupPlan) { if (!subscription.groupPlan) {
logger.log( logger.log(
{ subscriptionId: subscription.id }, { subscriptionId: subscription.id },
@ -263,11 +248,11 @@ var checkIfInviteIsPossible = function(subscription, email, callback) {
return callback(null, false, { limitReached: true }) return callback(null, false, { limitReached: true })
} }
return UserGetter.getUserByAnyEmail(email, function(error, existingUser) { UserGetter.getUserByAnyEmail(email, function(error, existingUser) {
if (error != null) { if (error) {
return callback(error) return callback(error)
} }
if (existingUser == null) { if (!existingUser) {
return callback(null, true) return callback(null, true)
} }
@ -280,9 +265,22 @@ var checkIfInviteIsPossible = function(subscription, email, callback) {
{ subscriptionId: subscription.id, email }, { subscriptionId: subscription.id, email },
'user already in team' 'user already in team'
) )
return callback(null, false, { alreadyInTeam: true }) callback(null, false, { alreadyInTeam: true })
} else { } else {
return callback(null, true) callback(null, true)
} }
}) })
} }
var getInviterName = function(inviter) {
let inviterName
if (inviter.first_name && inviter.last_name) {
inviterName = `${inviter.first_name} ${inviter.last_name} (${
inviter.email
})`
} else {
inviterName = inviter.email
}
return inviterName
}

View file

@ -1,21 +1,6 @@
/* eslint-disable
handle-callback-err,
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module') const SandboxedModule = require('sandboxed-module')
const should = require('chai').should()
const sinon = require('sinon') const sinon = require('sinon')
const { expect } = require('chai') const { expect } = require('chai')
const querystring = require('querystring')
const modulePath = const modulePath =
'../../../../app/src/Features/Subscription/TeamInvitesHandler' '../../../../app/src/Features/Subscription/TeamInvitesHandler'
@ -25,10 +10,11 @@ const Errors = require('../../../../app/src/Features/Errors/Errors')
describe('TeamInvitesHandler', function() { describe('TeamInvitesHandler', function() {
beforeEach(function() { beforeEach(function() {
this.manager = { this.manager = {
id: '666666', _id: '666666',
first_name: 'Daenerys', first_name: 'Daenerys',
last_name: 'Targaryen', last_name: 'Targaryen',
email: 'daenerys@example.com' email: 'daenerys@example.com',
emails: [{ email: 'daenerys@example.com' }]
} }
this.token = 'aaaaaaaaaaaaaaaaaaaaaa' this.token = 'aaaaaaaaaaaaaaaaaaaaaa'
@ -41,7 +27,7 @@ describe('TeamInvitesHandler', function() {
this.subscription = { this.subscription = {
id: '55153a8014829a865bbf700d', id: '55153a8014829a865bbf700d',
_id: new ObjectId('55153a8014829a865bbf700d'), _id: new ObjectId('55153a8014829a865bbf700d'),
admin_id: this.manager.id, admin_id: this.manager._id,
groupPlan: true, groupPlan: true,
member_ids: [], member_ids: [],
teamInvites: [this.teamInvite], teamInvites: [this.teamInvite],
@ -83,7 +69,9 @@ describe('TeamInvitesHandler', function() {
} }
} }
this.UserGetter.getUser.withArgs(this.manager.id).yields(null, this.manager) this.UserGetter.getUser
.withArgs(this.manager._id)
.yields(null, this.manager)
this.UserGetter.getUserByAnyEmail this.UserGetter.getUserByAnyEmail
.withArgs(this.manager.email) .withArgs(this.manager.email)
.yields(null, this.manager) .yields(null, this.manager)
@ -94,7 +82,7 @@ describe('TeamInvitesHandler', function() {
) )
this.Subscription.findOne.yields(null, this.subscription) this.Subscription.findOne.yields(null, this.subscription)
return (this.TeamInvitesHandler = SandboxedModule.require(modulePath, { this.TeamInvitesHandler = SandboxedModule.require(modulePath, {
requires: { requires: {
'logger-sharelatex': { log() {} }, 'logger-sharelatex': { log() {} },
crypto: this.crypto, crypto: this.crypto,
@ -108,40 +96,40 @@ describe('TeamInvitesHandler', function() {
'../Email/EmailHandler': this.EmailHandler, '../Email/EmailHandler': this.EmailHandler,
'../Errors/Errors': Errors '../Errors/Errors': Errors
} }
})) })
}) })
describe('getInvite', function() { describe('getInvite', function() {
it("returns the invite if there's one", function(done) { it("returns the invite if there's one", function(done) {
return this.TeamInvitesHandler.getInvite( this.TeamInvitesHandler.getInvite(
this.token, this.token,
(err, invite, subscription) => { (err, invite, subscription) => {
expect(err).to.eq(null) expect(err).to.eq(null)
expect(invite).to.deep.eq(this.teamInvite) expect(invite).to.deep.eq(this.teamInvite)
expect(subscription).to.deep.eq(this.subscription) expect(subscription).to.deep.eq(this.subscription)
return done() done()
} }
) )
}) })
return it("returns teamNotFound if there's none", function(done) { it("returns teamNotFound if there's none", function(done) {
this.Subscription.findOne = sinon.stub().yields(null, null) this.Subscription.findOne = sinon.stub().yields(null, null)
return this.TeamInvitesHandler.getInvite(this.token, function( this.TeamInvitesHandler.getInvite(this.token, function(
err, err,
invite, invite,
subscription subscription
) { ) {
expect(err).to.be.instanceof(Errors.NotFoundError) expect(err).to.be.instanceof(Errors.NotFoundError)
return done() done()
}) })
}) })
}) })
describe('createInvite', function() { describe('createInvite', function() {
it('adds the team invite to the subscription', function(done) { it('adds the team invite to the subscription', function(done) {
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'John.Snow@example.com', 'John.Snow@example.com',
(err, invite) => { (err, invite) => {
@ -152,14 +140,14 @@ describe('TeamInvitesHandler', function() {
'Daenerys Targaryen (daenerys@example.com)' 'Daenerys Targaryen (daenerys@example.com)'
) )
expect(this.subscription.teamInvites).to.deep.include(invite) expect(this.subscription.teamInvites).to.deep.include(invite)
return done() done()
} }
) )
}) })
it('sends an email', function(done) { it('sends an email', function(done) {
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'John.Snow@example.com', 'John.Snow@example.com',
(err, invite) => { (err, invite) => {
@ -175,7 +163,7 @@ describe('TeamInvitesHandler', function() {
}) })
) )
.should.equal(true) .should.equal(true)
return done() done(err)
} }
) )
}) })
@ -183,8 +171,8 @@ describe('TeamInvitesHandler', function() {
it('refreshes the existing invite if the email has already been invited', function(done) { it('refreshes the existing invite if the email has already been invited', function(done) {
const originalInvite = Object.assign({}, this.teamInvite) const originalInvite = Object.assign({}, this.teamInvite)
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
originalInvite.email, originalInvite.email,
(err, invite) => { (err, invite) => {
@ -198,14 +186,14 @@ describe('TeamInvitesHandler', function() {
this.subscription.save.calledOnce.should.eq(true) this.subscription.save.calledOnce.should.eq(true)
return done() done()
} }
) )
}) })
return it('removes any legacy invite from the subscription', function(done) { it('removes any legacy invite from the subscription', function(done) {
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'John.Snow@example.com', 'John.Snow@example.com',
(err, invite) => { (err, invite) => {
@ -215,7 +203,24 @@ describe('TeamInvitesHandler', function() {
{ $pull: { invited_emails: 'john.snow@example.com' } } { $pull: { invited_emails: 'john.snow@example.com' } }
) )
.should.eq(true) .should.eq(true)
return done() done(err)
}
)
})
it('add user to subscription if inviting self', function(done) {
this.TeamInvitesHandler.createInvite(
this.manager._id,
this.subscription,
this.manager.email,
(err, invite) => {
sinon.assert.calledWith(
this.SubscriptionUpdater.addUserToGroup,
this.subscription._id,
this.manager._id
)
sinon.assert.notCalled(this.subscription.save)
done(err)
} }
) )
}) })
@ -223,11 +228,11 @@ describe('TeamInvitesHandler', function() {
describe('importInvite', function() { describe('importInvite', function() {
beforeEach(function() { beforeEach(function() {
return (this.sentAt = new Date()) this.sentAt = new Date()
}) })
return it('can imports an invite from v1', function() { it('can imports an invite from v1', function() {
return this.TeamInvitesHandler.importInvite( this.TeamInvitesHandler.importInvite(
this.subscription, this.subscription,
'A-Team', 'A-Team',
'hannibal@a-team.org', 'hannibal@a-team.org',
@ -242,7 +247,7 @@ describe('TeamInvitesHandler', function() {
i => i.email === 'hannibal@a-team.org' i => i.email === 'hannibal@a-team.org'
) )
expect(invite.token).to.eq('secret') expect(invite.token).to.eq('secret')
return expect(invite.sentAt).to.eq(this.sentAt) expect(invite.sentAt).to.eq(this.sentAt)
} }
) )
}) })
@ -261,7 +266,7 @@ describe('TeamInvitesHandler', function() {
.withArgs(this.user.email) .withArgs(this.user.email)
.yields(null, this.user) .yields(null, this.user)
return this.subscription.teamInvites.push({ this.subscription.teamInvites.push({
email: 'john.snow@example.com', email: 'john.snow@example.com',
token: 'dddddddd', token: 'dddddddd',
inviterName: 'Daenerys Targaryen (daenerys@example.com)' inviterName: 'Daenerys Targaryen (daenerys@example.com)'
@ -269,39 +274,31 @@ describe('TeamInvitesHandler', function() {
}) })
it('adds the user to the team', function(done) { it('adds the user to the team', function(done) {
return this.TeamInvitesHandler.acceptInvite( this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
'dddddddd',
this.user.id,
() => {
this.SubscriptionUpdater.addUserToGroup this.SubscriptionUpdater.addUserToGroup
.calledWith(this.subscription._id, this.user.id) .calledWith(this.subscription._id, this.user.id)
.should.eq(true) .should.eq(true)
return done() done()
} })
)
}) })
return it('removes the invite from the subscription', function(done) { it('removes the invite from the subscription', function(done) {
return this.TeamInvitesHandler.acceptInvite( this.TeamInvitesHandler.acceptInvite('dddddddd', this.user.id, () => {
'dddddddd',
this.user.id,
() => {
this.Subscription.update this.Subscription.update
.calledWith( .calledWith(
{ _id: new ObjectId('55153a8014829a865bbf700d') }, { _id: new ObjectId('55153a8014829a865bbf700d') },
{ $pull: { teamInvites: { email: 'john.snow@example.com' } } } { $pull: { teamInvites: { email: 'john.snow@example.com' } } }
) )
.should.eq(true) .should.eq(true)
return done() done()
} })
)
}) })
}) })
describe('revokeInvite', () => describe('revokeInvite', () =>
it('removes the team invite from the subscription', function(done) { it('removes the team invite from the subscription', function(done) {
return this.TeamInvitesHandler.revokeInvite( this.TeamInvitesHandler.revokeInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'jorah@example.com', 'jorah@example.com',
() => { () => {
@ -318,7 +315,7 @@ describe('TeamInvitesHandler', function() {
{ $pull: { invited_emails: 'jorah@example.com' } } { $pull: { invited_emails: 'jorah@example.com' } }
) )
.should.eq(true) .should.eq(true)
return done() done()
} }
) )
})) }))
@ -330,13 +327,13 @@ describe('TeamInvitesHandler', function() {
'robert@example.com' 'robert@example.com'
] ]
this.TeamInvitesHandler.createInvite = sinon.stub().yields(null) this.TeamInvitesHandler.createInvite = sinon.stub().yields(null)
return (this.SubscriptionLocator.getGroupsWithEmailInvite = sinon this.SubscriptionLocator.getGroupsWithEmailInvite = sinon
.stub() .stub()
.yields(null, [this.subscription])) .yields(null, [this.subscription])
}) })
return it('sends an invitation email to addresses in the legacy invited_emails field', function(done) { it('sends an invitation email to addresses in the legacy invited_emails field', function(done) {
return this.TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail( this.TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail(
'eddard@example.com', 'eddard@example.com',
(err, invite) => { (err, invite) => {
expect(err).not.to.exist expect(err).not.to.exist
@ -351,42 +348,42 @@ describe('TeamInvitesHandler', function() {
this.TeamInvitesHandler.createInvite.callCount.should.eq(1) this.TeamInvitesHandler.createInvite.callCount.should.eq(1)
return done() done()
} }
) )
}) })
}) })
return describe('validation', function() { describe('validation', function() {
it("doesn't create an invite if the team limit has been reached", function(done) { it("doesn't create an invite if the team limit has been reached", function(done) {
this.LimitationsManager.teamHasReachedMemberLimit = sinon this.LimitationsManager.teamHasReachedMemberLimit = sinon
.stub() .stub()
.returns(true) .returns(true)
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'John.Snow@example.com', 'John.Snow@example.com',
(err, invite) => { (err, invite) => {
expect(err).to.deep.equal({ limitReached: true }) expect(err).to.deep.equal({ limitReached: true })
return done() done()
} }
) )
}) })
it("doesn't create an invite if the subscription is not in a group plan", function(done) { it("doesn't create an invite if the subscription is not in a group plan", function(done) {
this.subscription.groupPlan = false this.subscription.groupPlan = false
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'John.Snow@example.com', 'John.Snow@example.com',
(err, invite) => { (err, invite) => {
expect(err).to.deep.equal({ wrongPlan: true }) expect(err).to.deep.equal({ wrongPlan: true })
return done() done()
} }
) )
}) })
return it("doesn't create an invite if the user is already part of the team", function(done) { it("doesn't create an invite if the user is already part of the team", function(done) {
const member = { const member = {
id: '1a2b', id: '1a2b',
_id: '1a2b', _id: '1a2b',
@ -398,14 +395,14 @@ describe('TeamInvitesHandler', function() {
.withArgs(member.email) .withArgs(member.email)
.yields(null, member) .yields(null, member)
return this.TeamInvitesHandler.createInvite( this.TeamInvitesHandler.createInvite(
this.manager.id, this.manager._id,
this.subscription, this.subscription,
'tyrion@example.com', 'tyrion@example.com',
(err, invite) => { (err, invite) => {
expect(err).to.deep.equal({ alreadyInTeam: true }) expect(err).to.deep.equal({ alreadyInTeam: true })
expect(invite).not.to.exist expect(invite).not.to.exist
return done() done()
} }
) )
}) })