mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #13367 from overleaf/ab-group-invite-halfway-out-of-login
[web] Move the group invite page half-way outside the login wall GitOrigin-RevId: 8d846df6e248a08433ab2ca991644c78cf9ff330
This commit is contained in:
parent
480ec139ab
commit
4991f9cdc7
6 changed files with 286 additions and 227 deletions
|
@ -64,7 +64,6 @@ module.exports = {
|
|||
// Team invites
|
||||
webRouter.get(
|
||||
'/subscription/invites/:token/',
|
||||
AuthenticationController.requireLogin(),
|
||||
TeamInvitesController.viewInvite
|
||||
)
|
||||
webRouter.put(
|
||||
|
|
|
@ -4,122 +4,129 @@ const SessionManager = require('../Authentication/SessionManager')
|
|||
const SubscriptionLocator = require('./SubscriptionLocator')
|
||||
const ErrorController = require('../Errors/ErrorController')
|
||||
const EmailHelper = require('../Helpers/EmailHelper')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const { expressify } = require('../../util/promises')
|
||||
|
||||
module.exports = {
|
||||
createInvite(req, res, next) {
|
||||
const teamManagerId = SessionManager.getLoggedInUserId(req.session)
|
||||
const subscription = req.entity
|
||||
const email = EmailHelper.parseEmail(req.body.email)
|
||||
if (!email) {
|
||||
return res.status(422).json({
|
||||
error: {
|
||||
code: 'invalid_email',
|
||||
message: req.i18n.translate('invalid_email'),
|
||||
},
|
||||
})
|
||||
function createInvite(req, res, next) {
|
||||
const teamManagerId = SessionManager.getLoggedInUserId(req.session)
|
||||
const subscription = req.entity
|
||||
const email = EmailHelper.parseEmail(req.body.email)
|
||||
if (!email) {
|
||||
return res.status(422).json({
|
||||
error: {
|
||||
code: 'invalid_email',
|
||||
message: req.i18n.translate('invalid_email'),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
TeamInvitesHandler.createInvite(
|
||||
teamManagerId,
|
||||
subscription,
|
||||
email,
|
||||
function (err, inviteUserData) {
|
||||
if (err) {
|
||||
if (err.alreadyInTeam) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'user_already_added',
|
||||
message: req.i18n.translate('user_already_added'),
|
||||
},
|
||||
})
|
||||
}
|
||||
if (err.limitReached) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'group_full',
|
||||
message: req.i18n.translate('group_full'),
|
||||
},
|
||||
})
|
||||
}
|
||||
return next(err)
|
||||
}
|
||||
res.json({ user: inviteUserData })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
TeamInvitesHandler.createInvite(
|
||||
teamManagerId,
|
||||
subscription,
|
||||
email,
|
||||
function (err, inviteUserData) {
|
||||
if (err) {
|
||||
if (err.alreadyInTeam) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'user_already_added',
|
||||
message: req.i18n.translate('user_already_added'),
|
||||
},
|
||||
})
|
||||
}
|
||||
if (err.limitReached) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'group_full',
|
||||
message: req.i18n.translate('group_full'),
|
||||
},
|
||||
})
|
||||
}
|
||||
return next(err)
|
||||
}
|
||||
res.json({ user: inviteUserData })
|
||||
}
|
||||
async function viewInvite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
|
||||
const { invite } = await TeamInvitesHandler.promises.getInvite(token)
|
||||
if (!invite) {
|
||||
return ErrorController.notFound(req, res)
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
const personalSubscription =
|
||||
await SubscriptionLocator.promises.getUsersSubscription(userId)
|
||||
|
||||
const hasIndividualRecurlySubscription =
|
||||
personalSubscription &&
|
||||
personalSubscription.groupPlan === false &&
|
||||
personalSubscription.recurlyStatus?.state !== 'canceled' &&
|
||||
personalSubscription.recurlySubscription_id &&
|
||||
personalSubscription.recurlySubscription_id !== ''
|
||||
|
||||
res.render('subscriptions/team/invite', {
|
||||
inviterName: invite.inviterName,
|
||||
inviteToken: invite.token,
|
||||
hasIndividualRecurlySubscription,
|
||||
appName: settings.appName,
|
||||
expired: req.query.expired,
|
||||
})
|
||||
} else {
|
||||
const userByEmail = await UserGetter.promises.getUserByMainEmail(
|
||||
invite.email
|
||||
)
|
||||
},
|
||||
|
||||
viewInvite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
res.render('subscriptions/team/invite_logged_out', {
|
||||
inviterName: invite.inviterName,
|
||||
inviteToken: invite.token,
|
||||
appName: settings.appName,
|
||||
accountExists: userByEmail != null,
|
||||
emailAddress: invite.email,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
TeamInvitesHandler.getInvite(
|
||||
token,
|
||||
function (err, invite, teamSubscription) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
function acceptInvite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
|
||||
if (!invite) {
|
||||
return ErrorController.notFound(req, res, next)
|
||||
}
|
||||
TeamInvitesHandler.acceptInvite(token, userId, function (err, results) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
res.sendStatus(204)
|
||||
})
|
||||
}
|
||||
|
||||
SubscriptionLocator.getUsersSubscription(
|
||||
userId,
|
||||
function (err, personalSubscription) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
function revokeInvite(req, res, next) {
|
||||
const subscription = req.entity
|
||||
const email = EmailHelper.parseEmail(req.params.email)
|
||||
const teamManagerId = SessionManager.getLoggedInUserId(req.session)
|
||||
if (!email) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
const hasIndividualRecurlySubscription =
|
||||
personalSubscription &&
|
||||
personalSubscription.groupPlan === false &&
|
||||
personalSubscription.recurlyStatus?.state !== 'canceled' &&
|
||||
personalSubscription.recurlySubscription_id &&
|
||||
personalSubscription.recurlySubscription_id !== ''
|
||||
|
||||
res.render('subscriptions/team/invite', {
|
||||
inviterName: invite.inviterName,
|
||||
inviteToken: invite.token,
|
||||
hasIndividualRecurlySubscription,
|
||||
appName: settings.appName,
|
||||
expired: req.query.expired,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
acceptInvite(req, res, next) {
|
||||
const { token } = req.params
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
|
||||
TeamInvitesHandler.acceptInvite(token, userId, function (err, results) {
|
||||
TeamInvitesHandler.revokeInvite(
|
||||
teamManagerId,
|
||||
subscription,
|
||||
email,
|
||||
function (err, results) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
res.sendStatus(204)
|
||||
})
|
||||
},
|
||||
|
||||
revokeInvite(req, res, next) {
|
||||
const subscription = req.entity
|
||||
const email = EmailHelper.parseEmail(req.params.email)
|
||||
const teamManagerId = SessionManager.getLoggedInUserId(req.session)
|
||||
if (!email) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
TeamInvitesHandler.revokeInvite(
|
||||
teamManagerId,
|
||||
subscription,
|
||||
email,
|
||||
function (err, results) {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
res.sendStatus(204)
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createInvite,
|
||||
viewInvite: expressify(viewInvite),
|
||||
acceptInvite,
|
||||
revokeInvite,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
let TeamInvitesHandler
|
||||
const logger = require('@overleaf/logger')
|
||||
const crypto = require('crypto')
|
||||
const async = require('async')
|
||||
|
@ -17,120 +16,125 @@ const EmailHandler = require('../Email/EmailHandler')
|
|||
const EmailHelper = require('../Helpers/EmailHelper')
|
||||
|
||||
const Errors = require('../Errors/Errors')
|
||||
const { promisifyMultiResult, promisify } = require('../../util/promises')
|
||||
|
||||
module.exports = TeamInvitesHandler = {
|
||||
getInvite(token, callback) {
|
||||
Subscription.findOne(
|
||||
{ 'teamInvites.token': token },
|
||||
function (err, subscription) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (!subscription) {
|
||||
return callback(new Errors.NotFoundError('team not found'))
|
||||
}
|
||||
|
||||
const invite = subscription.teamInvites.find(i => i.token === token)
|
||||
callback(null, invite, subscription)
|
||||
function getInvite(token, callback) {
|
||||
Subscription.findOne(
|
||||
{ 'teamInvites.token': token },
|
||||
function (err, subscription) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (!subscription) {
|
||||
return callback(new Errors.NotFoundError('team not found'))
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
createInvite(teamManagerId, subscription, email, callback) {
|
||||
email = EmailHelper.parseEmail(email)
|
||||
if (!email) {
|
||||
return callback(new Error('invalid email'))
|
||||
const invite = subscription.teamInvites.find(i => i.token === token)
|
||||
callback(null, invite, subscription)
|
||||
}
|
||||
UserGetter.getUser(teamManagerId, function (error, teamManager) {
|
||||
)
|
||||
}
|
||||
|
||||
function createInvite(teamManagerId, subscription, email, callback) {
|
||||
email = EmailHelper.parseEmail(email)
|
||||
if (!email) {
|
||||
return callback(new Error('invalid email'))
|
||||
}
|
||||
UserGetter.getUser(teamManagerId, function (error, teamManager) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
_removeLegacyInvite(subscription.id, email, function (error) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
|
||||
removeLegacyInvite(subscription.id, email, function (error) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
createInvite(subscription, email, teamManager, callback)
|
||||
})
|
||||
_createInvite(subscription, email, teamManager, callback)
|
||||
})
|
||||
},
|
||||
|
||||
importInvite(subscription, inviterName, email, token, sentAt, callback) {
|
||||
checkIfInviteIsPossible(
|
||||
subscription,
|
||||
email,
|
||||
function (error, possible, reason) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
if (!possible) {
|
||||
return callback(reason)
|
||||
}
|
||||
|
||||
subscription.teamInvites.push({
|
||||
email,
|
||||
inviterName,
|
||||
token,
|
||||
sentAt,
|
||||
})
|
||||
|
||||
subscription.save(callback)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
acceptInvite(token, userId, callback) {
|
||||
TeamInvitesHandler.getInvite(token, function (err, invite, subscription) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (!invite) {
|
||||
return callback(new Errors.NotFoundError('invite not found'))
|
||||
}
|
||||
|
||||
SubscriptionUpdater.addUserToGroup(
|
||||
subscription._id,
|
||||
userId,
|
||||
function (err) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
removeInviteFromTeam(subscription.id, invite.email, callback)
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
revokeInvite(teamManagerId, subscription, email, callback) {
|
||||
email = EmailHelper.parseEmail(email)
|
||||
if (!email) {
|
||||
return callback(new Error('invalid email'))
|
||||
}
|
||||
removeInviteFromTeam(subscription.id, email, callback)
|
||||
},
|
||||
|
||||
// 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
|
||||
// after a short while.
|
||||
createTeamInvitesForLegacyInvitedEmail(email, callback) {
|
||||
SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
async.map(
|
||||
teams,
|
||||
(team, cb) =>
|
||||
TeamInvitesHandler.createInvite(team.admin_id, team, email, cb),
|
||||
callback
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function createInvite(subscription, email, inviter, callback) {
|
||||
checkIfInviteIsPossible(
|
||||
function importInvite(
|
||||
subscription,
|
||||
inviterName,
|
||||
email,
|
||||
token,
|
||||
sentAt,
|
||||
callback
|
||||
) {
|
||||
_checkIfInviteIsPossible(
|
||||
subscription,
|
||||
email,
|
||||
function (error, possible, reason) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
if (!possible) {
|
||||
return callback(reason)
|
||||
}
|
||||
|
||||
subscription.teamInvites.push({
|
||||
email,
|
||||
inviterName,
|
||||
token,
|
||||
sentAt,
|
||||
})
|
||||
|
||||
subscription.save(callback)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function acceptInvite(token, userId, callback) {
|
||||
getInvite(token, function (err, invite, subscription) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
if (!invite) {
|
||||
return callback(new Errors.NotFoundError('invite not found'))
|
||||
}
|
||||
|
||||
SubscriptionUpdater.addUserToGroup(
|
||||
subscription._id,
|
||||
userId,
|
||||
function (err) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
_removeInviteFromTeam(subscription.id, invite.email, callback)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function revokeInvite(teamManagerId, subscription, email, callback) {
|
||||
email = EmailHelper.parseEmail(email)
|
||||
if (!email) {
|
||||
return callback(new Error('invalid email'))
|
||||
}
|
||||
_removeInviteFromTeam(subscription.id, email, callback)
|
||||
}
|
||||
|
||||
// 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
|
||||
// after a short while.
|
||||
function createTeamInvitesForLegacyInvitedEmail(email, callback) {
|
||||
SubscriptionLocator.getGroupsWithEmailInvite(email, function (err, teams) {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
async.map(
|
||||
teams,
|
||||
(team, cb) => createInvite(team.admin_id, team, email, cb),
|
||||
callback
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function _createInvite(subscription, email, inviter, callback) {
|
||||
_checkIfInviteIsPossible(
|
||||
subscription,
|
||||
email,
|
||||
function (error, possible, reason) {
|
||||
|
@ -155,7 +159,7 @@ function createInvite(subscription, email, inviter, callback) {
|
|||
}
|
||||
|
||||
// legacy: remove any invite that might have been created in the past
|
||||
removeInviteFromTeam(subscription._id, email, error => {
|
||||
_removeInviteFromTeam(subscription._id, email, error => {
|
||||
const inviteUserData = {
|
||||
email: inviter.email,
|
||||
first_name: inviter.first_name,
|
||||
|
@ -168,7 +172,7 @@ function createInvite(subscription, email, inviter, callback) {
|
|||
)
|
||||
}
|
||||
|
||||
const inviterName = getInviterName(inviter)
|
||||
const inviterName = _getInviterName(inviter)
|
||||
let invite = subscription.teamInvites.find(
|
||||
invite => invite.email === email
|
||||
)
|
||||
|
@ -206,20 +210,20 @@ function createInvite(subscription, email, inviter, callback) {
|
|||
)
|
||||
}
|
||||
|
||||
function removeInviteFromTeam(subscriptionId, email, callback) {
|
||||
function _removeInviteFromTeam(subscriptionId, email, callback) {
|
||||
const searchConditions = { _id: new ObjectId(subscriptionId.toString()) }
|
||||
const removeInvite = { $pull: { teamInvites: { email } } }
|
||||
|
||||
async.series(
|
||||
[
|
||||
cb => Subscription.updateOne(searchConditions, removeInvite, cb),
|
||||
cb => removeLegacyInvite(subscriptionId, email, cb),
|
||||
cb => _removeLegacyInvite(subscriptionId, email, cb),
|
||||
],
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
const removeLegacyInvite = (subscriptionId, email, callback) =>
|
||||
const _removeLegacyInvite = (subscriptionId, email, callback) =>
|
||||
Subscription.updateOne(
|
||||
{
|
||||
_id: new ObjectId(subscriptionId.toString()),
|
||||
|
@ -232,7 +236,7 @@ const removeLegacyInvite = (subscriptionId, email, callback) =>
|
|||
callback
|
||||
)
|
||||
|
||||
function checkIfInviteIsPossible(subscription, email, callback) {
|
||||
function _checkIfInviteIsPossible(subscription, email, callback) {
|
||||
if (!subscription.groupPlan) {
|
||||
logger.debug(
|
||||
{ subscriptionId: subscription.id },
|
||||
|
@ -273,7 +277,7 @@ function checkIfInviteIsPossible(subscription, email, callback) {
|
|||
})
|
||||
}
|
||||
|
||||
function getInviterName(inviter) {
|
||||
function _getInviterName(inviter) {
|
||||
let inviterName
|
||||
if (inviter.first_name && inviter.last_name) {
|
||||
inviterName = `${inviter.first_name} ${inviter.last_name} (${inviter.email})`
|
||||
|
@ -283,3 +287,22 @@ function getInviterName(inviter) {
|
|||
|
||||
return inviterName
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getInvite,
|
||||
createInvite,
|
||||
importInvite,
|
||||
acceptInvite,
|
||||
revokeInvite,
|
||||
createTeamInvitesForLegacyInvitedEmail,
|
||||
promises: {
|
||||
getInvite: promisifyMultiResult(getInvite, ['invite', 'subscription']),
|
||||
createInvite: promisify(createInvite),
|
||||
importInvite: promisify(importInvite),
|
||||
acceptInvite: promisify(acceptInvite),
|
||||
revokeInvite: promisify(revokeInvite),
|
||||
createTeamInvitesForLegacyInvitedEmail: promisify(
|
||||
createTeamInvitesForLegacyInvitedEmail
|
||||
),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
extends ../../layout
|
||||
|
||||
block content
|
||||
main.content.content-alt.team-invite#main-content
|
||||
.container
|
||||
.row
|
||||
.col-md-8.col-md-offset-2.text-center
|
||||
.card
|
||||
.page-header
|
||||
h1.text-centered #{translate("invited_to_group", {inviterName: inviterName, appName: appName})}
|
||||
|
||||
if (accountExists)
|
||||
div
|
||||
p #{translate("invited_to_group_login_benefits", {appName: appName})}
|
||||
p #{translate("invited_to_group_login", {emailAddress: emailAddress})}
|
||||
p
|
||||
a.btn.btn.btn-primary(href=`/login?redir=/subscription/invites/${inviteToken}`) #{translate("login_to_accept_invitation")}
|
||||
else
|
||||
div
|
||||
p #{translate("invited_to_group_register_benefits", {appName: appName})}
|
||||
p #{translate("invited_to_group_register", {inviterName: inviterName})}
|
||||
p
|
||||
a.btn.btn.btn-primary(href=`/register?redir=/subscription/invites/${inviteToken}`) #{translate("register_to_accept_invitation")}
|
||||
|
|
@ -798,6 +798,10 @@
|
|||
"invite_not_valid": "This is not a valid project invite",
|
||||
"invite_not_valid_description": "The invite may have expired. Please contact the project owner",
|
||||
"invited_to_group": "__inviterName__ has invited you to join a group subscription on __appName__",
|
||||
"invited_to_group_login": "To accept this invitation you need to log in as __emailAddress__.",
|
||||
"invited_to_group_login_benefits": "As part of this group, you’ll have access to __appName__ premium features such as additional collaborators, greater maximum compile time, and real-time track changes.",
|
||||
"invited_to_group_register": "To accept __inviterName__’s invitation you’ll need to create an account.",
|
||||
"invited_to_group_register_benefits": "__appName__ is a collaborative online LaTeX editor, with thousands of ready-to-use templates and an array of LaTeX learning resources to help you get started.",
|
||||
"invited_to_join": "You have been invited to join",
|
||||
"ip_address": "IP Address",
|
||||
"is_email_affiliated": "Is your email affiliated with an institution? ",
|
||||
|
@ -911,6 +915,7 @@
|
|||
"login_here": "Login here",
|
||||
"login_or_password_wrong_try_again": "Your login or password is incorrect. Please try again",
|
||||
"login_register_or": "or",
|
||||
"login_to_accept_invitation": "Login to accept invitation",
|
||||
"login_to_overleaf": "Log in to Overleaf",
|
||||
"login_with_email": "Log in with your email",
|
||||
"login_with_service": "Log in with __service__",
|
||||
|
@ -1268,6 +1273,7 @@
|
|||
"register": "Register",
|
||||
"register_error": "Registration error",
|
||||
"register_intercept_sso": "You can link your __authProviderName__ account from the Account Settings page after logging in.",
|
||||
"register_to_accept_invitation": "Register to accept invitation",
|
||||
"register_to_edit_template": "Please register to edit the __templateName__ template",
|
||||
"register_using_email": "Register using your email",
|
||||
"register_using_service": "Register using __service__",
|
||||
|
|
|
@ -340,18 +340,18 @@ describe('TeamInvitesHandler', function () {
|
|||
it('sends an invitation email to addresses in the legacy invited_emails field', function (done) {
|
||||
this.TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail(
|
||||
'eddard@example.com',
|
||||
(err, invite) => {
|
||||
(err, invites) => {
|
||||
expect(err).not.to.exist
|
||||
expect(invites.length).to.eq(1)
|
||||
|
||||
this.TeamInvitesHandler.createInvite
|
||||
.calledWith(
|
||||
this.subscription.admin_id,
|
||||
this.subscription,
|
||||
'eddard@example.com'
|
||||
)
|
||||
.should.eq(true)
|
||||
|
||||
this.TeamInvitesHandler.createInvite.callCount.should.eq(1)
|
||||
const [invite] = invites
|
||||
expect(invite.token).to.eq(this.newToken)
|
||||
expect(invite.email).to.eq('eddard@example.com')
|
||||
expect(invite.inviterName).to.eq(
|
||||
'Daenerys Targaryen (daenerys@example.com)'
|
||||
)
|
||||
expect(invite.invite).to.be.true
|
||||
expect(this.subscription.teamInvites).to.deep.include(invite)
|
||||
|
||||
done()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue