mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #20034 from overleaf/tm-collab-limit-edit-invites
Enforce collaborator limit when accepting project invites GitOrigin-RevId: 94f281113fe7c7b6d0a5ef43e11ab579400d9e56
This commit is contained in:
parent
5e2662adc4
commit
98a914bb94
12 changed files with 575 additions and 266 deletions
|
@ -101,7 +101,8 @@ async function addUserIdToProject(
|
|||
projectId,
|
||||
addingUserId,
|
||||
userId,
|
||||
privilegeLevel
|
||||
privilegeLevel,
|
||||
{ pendingEditor } = {}
|
||||
) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
|
@ -124,7 +125,13 @@ async function addUserIdToProject(
|
|||
)
|
||||
} else if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
|
||||
level = { readOnly_refs: userId }
|
||||
logger.debug({ privileges: 'readOnly', userId, projectId }, 'adding user')
|
||||
if (pendingEditor) {
|
||||
level.pendingEditor_refs = userId
|
||||
}
|
||||
logger.debug(
|
||||
{ privileges: 'readOnly', userId, projectId, pendingEditor },
|
||||
'adding user'
|
||||
)
|
||||
} else {
|
||||
throw new Error(`unknown privilegeLevel: ${privilegeLevel}`)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ const LimitationsManager = require('../Subscription/LimitationsManager')
|
|||
const UserGetter = require('../User/UserGetter')
|
||||
const CollaboratorsGetter = require('./CollaboratorsGetter')
|
||||
const CollaboratorsInviteHandler = require('./CollaboratorsInviteHandler')
|
||||
const CollaboratorsInviteGetter = require('./CollaboratorsInviteGetter')
|
||||
const logger = require('@overleaf/logger')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const EmailHelper = require('../Helpers/EmailHelper')
|
||||
|
@ -38,7 +39,7 @@ const CollaboratorsInviteController = {
|
|||
const projectId = req.params.Project_id
|
||||
logger.debug({ projectId }, 'getting all active invites for project')
|
||||
const invites =
|
||||
await CollaboratorsInviteHandler.promises.getAllInvites(projectId)
|
||||
await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
|
||||
res.json({ invites })
|
||||
},
|
||||
|
||||
|
@ -291,7 +292,7 @@ const CollaboratorsInviteController = {
|
|||
}
|
||||
|
||||
// get the invite
|
||||
const invite = await CollaboratorsInviteHandler.promises.getInviteByToken(
|
||||
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
|
@ -351,7 +352,7 @@ const CollaboratorsInviteController = {
|
|||
'got request to accept invite'
|
||||
)
|
||||
|
||||
const invite = await CollaboratorsInviteHandler.promises.getInviteByToken(
|
||||
const invite = await CollaboratorsInviteGetter.promises.getInviteByToken(
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
const logger = require('@overleaf/logger')
|
||||
const { ProjectInvite } = require('../../models/ProjectInvite')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const CollaboratorsInviteHelper = require('./CollaboratorsInviteHelper')
|
||||
|
||||
async function getAllInvites(projectId) {
|
||||
logger.debug({ projectId }, 'fetching invites for project')
|
||||
const invites = await ProjectInvite.find({ projectId })
|
||||
.select('_id email privileges')
|
||||
.exec()
|
||||
logger.debug(
|
||||
{ projectId, count: invites.length },
|
||||
'found invites for project'
|
||||
)
|
||||
return invites
|
||||
}
|
||||
|
||||
async function getInviteCount(projectId) {
|
||||
logger.debug({ projectId }, 'counting invites for project')
|
||||
const count = await ProjectInvite.countDocuments({ projectId }).exec()
|
||||
return count
|
||||
}
|
||||
|
||||
async function getEditInviteCount(projectId) {
|
||||
logger.debug({ projectId }, 'counting edit invites for project')
|
||||
const count = await ProjectInvite.countDocuments({
|
||||
projectId,
|
||||
privileges: { $ne: PrivilegeLevels.READ_ONLY },
|
||||
}).exec()
|
||||
return count
|
||||
}
|
||||
|
||||
async function getInviteByToken(projectId, tokenString) {
|
||||
logger.debug({ projectId }, 'fetching invite by token')
|
||||
const invite = await ProjectInvite.findOne({
|
||||
projectId,
|
||||
tokenHmac: CollaboratorsInviteHelper.hashInviteToken(tokenString),
|
||||
}).exec()
|
||||
|
||||
if (invite == null) {
|
||||
logger.err({ projectId }, 'no invite found')
|
||||
return null
|
||||
}
|
||||
|
||||
return invite
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
promises: {
|
||||
getAllInvites,
|
||||
getInviteCount,
|
||||
getEditInviteCount,
|
||||
getInviteByToken,
|
||||
},
|
||||
}
|
|
@ -8,36 +8,12 @@ const UserGetter = require('../User/UserGetter')
|
|||
const ProjectGetter = require('../Project/ProjectGetter')
|
||||
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||
const LimitationsManager = require('../Subscription/LimitationsManager')
|
||||
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
|
||||
const _ = require('lodash')
|
||||
|
||||
const CollaboratorsInviteHandler = {
|
||||
async getAllInvites(projectId) {
|
||||
logger.debug({ projectId }, 'fetching invites for project')
|
||||
const invites = await ProjectInvite.find({ projectId })
|
||||
.select('_id email privileges')
|
||||
.exec()
|
||||
logger.debug(
|
||||
{ projectId, count: invites.length },
|
||||
'found invites for project'
|
||||
)
|
||||
return invites
|
||||
},
|
||||
|
||||
async getInviteCount(projectId) {
|
||||
logger.debug({ projectId }, 'counting invites for project')
|
||||
const count = await ProjectInvite.countDocuments({ projectId }).exec()
|
||||
return count
|
||||
},
|
||||
|
||||
async getEditInviteCount(projectId) {
|
||||
logger.debug({ projectId }, 'counting edit invites for project')
|
||||
const count = await ProjectInvite.countDocuments({
|
||||
projectId,
|
||||
privileges: { $ne: PrivilegeLevels.READ_ONLY },
|
||||
}).exec()
|
||||
return count
|
||||
},
|
||||
|
||||
async _trySendInviteNotification(projectId, sendingUser, invite) {
|
||||
const { email } = invite
|
||||
const existingUser = await UserGetter.promises.getUserByAnyEmail(email, {
|
||||
|
@ -152,27 +128,43 @@ const CollaboratorsInviteHandler = {
|
|||
)
|
||||
},
|
||||
|
||||
async getInviteByToken(projectId, tokenString) {
|
||||
logger.debug({ projectId }, 'fetching invite by token')
|
||||
const invite = await ProjectInvite.findOne({
|
||||
projectId,
|
||||
tokenHmac: CollaboratorsInviteHelper.hashInviteToken(tokenString),
|
||||
}).exec()
|
||||
|
||||
if (invite == null) {
|
||||
logger.err({ projectId }, 'no invite found')
|
||||
return null
|
||||
async acceptInvite(invite, projectId, user) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
owner_ref: 1,
|
||||
})
|
||||
const linkSharingEnforcement =
|
||||
await SplitTestHandler.promises.getAssignmentForUser(
|
||||
project.owner_ref,
|
||||
'link-sharing-enforcement'
|
||||
)
|
||||
const pendingEditor =
|
||||
invite.privileges === PrivilegeLevels.READ_AND_WRITE &&
|
||||
linkSharingEnforcement?.variant === 'active' &&
|
||||
!(await LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
project._id
|
||||
))
|
||||
if (pendingEditor) {
|
||||
logger.debug(
|
||||
{ projectId, userId: user._id },
|
||||
'no collaborator slots available, user added as read only (pending editor)'
|
||||
)
|
||||
await ProjectAuditLogHandler.promises.addEntry(
|
||||
projectId,
|
||||
'editor-moved-to-pending', // controller already logged accept-invite
|
||||
null,
|
||||
null,
|
||||
{
|
||||
userId: user._id.toString(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return invite
|
||||
},
|
||||
|
||||
async acceptInvite(invite, projectId, user) {
|
||||
await CollaboratorsHandler.promises.addUserIdToProject(
|
||||
projectId,
|
||||
invite.sendingUserId,
|
||||
user._id,
|
||||
invite.privileges
|
||||
pendingEditor ? PrivilegeLevels.READ_ONLY : invite.privileges,
|
||||
{ pendingEditor }
|
||||
)
|
||||
|
||||
// Remove invite
|
||||
|
@ -192,12 +184,9 @@ const CollaboratorsInviteHandler = {
|
|||
|
||||
module.exports = {
|
||||
promises: CollaboratorsInviteHandler,
|
||||
getAllInvites: callbackify(CollaboratorsInviteHandler.getAllInvites),
|
||||
getInviteCount: callbackify(CollaboratorsInviteHandler.getInviteCount),
|
||||
inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject),
|
||||
revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite),
|
||||
generateNewInvite: callbackify(CollaboratorsInviteHandler.generateNewInvite),
|
||||
getInviteByToken: callbackify(CollaboratorsInviteHandler.getInviteByToken),
|
||||
acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite),
|
||||
_trySendInviteNotification: callbackify(
|
||||
CollaboratorsInviteHandler._trySendInviteNotification
|
||||
|
|
|
@ -5,7 +5,7 @@ const AuthorizationManager = require('../Authorization/AuthorizationManager')
|
|||
const ProjectEditorHandler = require('../Project/ProjectEditorHandler')
|
||||
const Metrics = require('@overleaf/metrics')
|
||||
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||
const CollaboratorsInviteHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
||||
const CollaboratorsInviteGetter = require('../Collaborators/CollaboratorsInviteGetter')
|
||||
const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler')
|
||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||
const SessionManager = require('../Authentication/SessionManager')
|
||||
|
@ -128,7 +128,7 @@ async function _buildJoinProjectView(req, projectId, userId) {
|
|||
return { project: null, privilegeLevel: null, isRestrictedUser: false }
|
||||
}
|
||||
const invites =
|
||||
await CollaboratorsInviteHandler.promises.getAllInvites(projectId)
|
||||
await CollaboratorsInviteGetter.promises.getAllInvites(projectId)
|
||||
const isTokenMember = await CollaboratorsHandler.promises.userIsTokenMember(
|
||||
userId,
|
||||
projectId
|
||||
|
|
|
@ -4,7 +4,7 @@ const UserGetter = require('../User/UserGetter')
|
|||
const SubscriptionLocator = require('./SubscriptionLocator')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||
const CollaboratorsInvitesHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
||||
const CollaboratorsInvitesGetter = require('../Collaborators/CollaboratorsInviteGetter')
|
||||
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
||||
const { V1ConnectionError } = require('../Errors/Errors')
|
||||
const {
|
||||
|
@ -28,6 +28,18 @@ async function allowedNumberOfCollaboratorsForUser(userId) {
|
|||
}
|
||||
}
|
||||
|
||||
async function canAcceptEditCollaboratorInvite(projectId) {
|
||||
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
||||
if (allowedNumber < 0) {
|
||||
return true // -1 means unlimited
|
||||
}
|
||||
const currentEditors =
|
||||
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
||||
projectId
|
||||
)
|
||||
return currentEditors + 1 <= allowedNumber
|
||||
}
|
||||
|
||||
async function canAddXCollaborators(projectId, numberOfNewCollaborators) {
|
||||
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
||||
if (allowedNumber < 0) {
|
||||
|
@ -36,7 +48,7 @@ async function canAddXCollaborators(projectId, numberOfNewCollaborators) {
|
|||
const currentNumber =
|
||||
await CollaboratorsGetter.promises.getInvitedCollaboratorCount(projectId)
|
||||
const inviteCount =
|
||||
await CollaboratorsInvitesHandler.promises.getInviteCount(projectId)
|
||||
await CollaboratorsInvitesGetter.promises.getInviteCount(projectId)
|
||||
return currentNumber + inviteCount + numberOfNewCollaborators <= allowedNumber
|
||||
}
|
||||
|
||||
|
@ -53,7 +65,7 @@ async function canAddXEditCollaborators(
|
|||
projectId
|
||||
)
|
||||
const editInviteCount =
|
||||
await CollaboratorsInvitesHandler.promises.getEditInviteCount(projectId)
|
||||
await CollaboratorsInvitesGetter.promises.getEditInviteCount(projectId)
|
||||
return (
|
||||
currentEditors + editInviteCount + numberOfNewEditCollaborators <=
|
||||
allowedNumber
|
||||
|
@ -179,6 +191,7 @@ const LimitationsManager = {
|
|||
promises: {
|
||||
allowedNumberOfCollaboratorsInProject,
|
||||
allowedNumberOfCollaboratorsForUser,
|
||||
canAcceptEditCollaboratorInvite,
|
||||
canAddXCollaborators,
|
||||
canAddXEditCollaborators,
|
||||
hasPaidSubscription,
|
||||
|
|
|
@ -246,6 +246,32 @@ describe('CollaboratorsHandler', function () {
|
|||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
describe('and with pendingEditor flag', function () {
|
||||
it('should add them to the pending editor refs', async function () {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
.withArgs(
|
||||
{
|
||||
_id: this.project._id,
|
||||
},
|
||||
{
|
||||
$addToSet: {
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
},
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
await this.CollaboratorsHandler.promises.addUserIdToProject(
|
||||
this.project._id,
|
||||
this.addingUserId,
|
||||
this.userId,
|
||||
'readOnly',
|
||||
{ pendingEditor: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('as readAndWrite', function () {
|
||||
|
|
|
@ -83,15 +83,20 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
this.CollaboratorsInviteHandler = {
|
||||
promises: {
|
||||
getAllInvites: sinon.stub(),
|
||||
inviteToProject: sinon.stub().resolves(this.inviteReducedData),
|
||||
getInviteByToken: sinon.stub().resolves(this.invite),
|
||||
generateNewInvite: sinon.stub().resolves(this.invite),
|
||||
revokeInvite: sinon.stub().resolves(this.invite),
|
||||
acceptInvite: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteGetter = {
|
||||
promises: {
|
||||
getAllInvites: sinon.stub(),
|
||||
getInviteByToken: sinon.stub().resolves(this.invite),
|
||||
},
|
||||
}
|
||||
|
||||
this.EditorRealTimeController = {
|
||||
emitToRoom: sinon.stub(),
|
||||
}
|
||||
|
@ -123,6 +128,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
'../User/UserGetter': this.UserGetter,
|
||||
'./CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'./CollaboratorsInviteHandler': this.CollaboratorsInviteHandler,
|
||||
'./CollaboratorsInviteGetter': this.CollaboratorsInviteGetter,
|
||||
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
|
||||
'../Analytics/AnalyticsManager': this.AnalyticsManger,
|
||||
'../Authentication/SessionManager': this.SessionManager,
|
||||
|
@ -150,7 +156,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
describe('when all goes well', function () {
|
||||
beforeEach(function (done) {
|
||||
this.CollaboratorsInviteHandler.promises.getAllInvites.resolves(
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.resolves(
|
||||
this.fakeInvites
|
||||
)
|
||||
this.res.callback = () => done()
|
||||
|
@ -173,10 +179,10 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should have called CollaboratorsInviteHandler.getAllInvites', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getAllInvites.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
this.CollaboratorsInviteHandler.promises.getAllInvites
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites
|
||||
.calledWith(this.projectId)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
@ -184,7 +190,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
describe('when CollaboratorsInviteHandler.getAllInvites produces an error', function () {
|
||||
beforeEach(function (done) {
|
||||
this.CollaboratorsInviteHandler.promises.getAllInvites.rejects(
|
||||
this.CollaboratorsInviteGetter.promises.getAllInvites.rejects(
|
||||
new Error('woops')
|
||||
)
|
||||
this.next.callsFake(() => done())
|
||||
|
@ -802,7 +808,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
|
||||
false
|
||||
)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.resolves(
|
||||
this.invite
|
||||
)
|
||||
this.ProjectGetter.promises.getProject.resolves(this.fakeProject)
|
||||
|
@ -838,10 +844,10 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken
|
||||
.calledWith(this.fakeProject._id, this.invite.token)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
@ -924,7 +930,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should not call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
0
|
||||
)
|
||||
})
|
||||
|
@ -966,7 +972,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should not call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
0
|
||||
)
|
||||
})
|
||||
|
@ -982,7 +988,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
describe('when the getInviteByToken produces an error', function () {
|
||||
beforeEach(function (done) {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.rejects(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.rejects(
|
||||
new Error('woops')
|
||||
)
|
||||
this.next.callsFake(() => done())
|
||||
|
@ -1008,7 +1014,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
|
||||
|
@ -1027,7 +1033,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
describe('when the getInviteByToken does not produce an invite', function () {
|
||||
beforeEach(function (done) {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(null)
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.resolves(null)
|
||||
this.res.callback = () => done()
|
||||
this.CollaboratorsInviteController.viewInvite(
|
||||
this.req,
|
||||
|
@ -1057,7 +1063,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
|
||||
|
@ -1100,7 +1106,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
@ -1149,7 +1155,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
@ -1192,7 +1198,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
@ -1241,7 +1247,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
})
|
||||
|
||||
it('should call getInviteByToken', function () {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.callCount.should.equal(
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.callCount.should.equal(
|
||||
1
|
||||
)
|
||||
})
|
||||
|
@ -1513,7 +1519,7 @@ describe('CollaboratorsInviteController', function () {
|
|||
|
||||
describe('when the invite is not found', function () {
|
||||
beforeEach(function (done) {
|
||||
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(null)
|
||||
this.CollaboratorsInviteGetter.promises.getInviteByToken.resolves(null)
|
||||
this.next.callsFake(() => done())
|
||||
this.CollaboratorsInviteController.acceptInvite(
|
||||
this.req,
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const Crypto = require('crypto')
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/Collaborators/CollaboratorsInviteGetter.js'
|
||||
|
||||
describe('CollaboratorsInviteGetter', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite = class ProjectInvite {
|
||||
constructor(options) {
|
||||
if (options == null) {
|
||||
options = {}
|
||||
}
|
||||
this._id = new ObjectId()
|
||||
for (const k in options) {
|
||||
const v = options[k]
|
||||
this[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
this.ProjectInvite.prototype.save = sinon.stub()
|
||||
this.ProjectInvite.findOne = sinon.stub()
|
||||
this.ProjectInvite.find = sinon.stub()
|
||||
this.ProjectInvite.deleteOne = sinon.stub()
|
||||
this.ProjectInvite.findOneAndDelete = sinon.stub()
|
||||
this.ProjectInvite.countDocuments = sinon.stub()
|
||||
|
||||
this.Crypto = {
|
||||
randomBytes: sinon.stub().callsFake(Crypto.randomBytes),
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteHelper = {
|
||||
generateToken: sinon.stub().returns(this.Crypto.randomBytes(24)),
|
||||
hashInviteToken: sinon.stub().returns(this.tokenHmac),
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteGetter = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../../models/ProjectInvite': { ProjectInvite: this.ProjectInvite },
|
||||
'./CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
|
||||
},
|
||||
})
|
||||
|
||||
this.projectId = new ObjectId()
|
||||
this.sendingUserId = new ObjectId()
|
||||
this.email = 'user@example.com'
|
||||
this.userId = new ObjectId()
|
||||
this.inviteId = new ObjectId()
|
||||
this.token = 'hnhteaosuhtaeosuahs'
|
||||
this.privileges = 'readAndWrite'
|
||||
this.fakeInvite = {
|
||||
_id: this.inviteId,
|
||||
email: this.email,
|
||||
token: this.token,
|
||||
tokenHmac: this.tokenHmac,
|
||||
sendingUserId: this.sendingUserId,
|
||||
projectId: this.projectId,
|
||||
privileges: this.privileges,
|
||||
createdAt: new Date(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('getInviteCount', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().resolves(2),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteGetter.promises.getInviteCount(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should produce the count of documents', async function () {
|
||||
const count = await this.call()
|
||||
expect(count).to.equal(2)
|
||||
})
|
||||
|
||||
describe('when model.countDocuments produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEditInviteCount', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().resolves(2),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteGetter.promises.getEditInviteCount(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should produce the count of documents', async function () {
|
||||
const count = await this.call()
|
||||
expect(this.ProjectInvite.countDocuments).to.be.calledWith({
|
||||
projectId: this.projectId,
|
||||
privileges: { $ne: 'readOnly' },
|
||||
})
|
||||
expect(count).to.equal(2)
|
||||
})
|
||||
|
||||
describe('when model.countDocuments produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAllInvites', function () {
|
||||
beforeEach(function () {
|
||||
this.fakeInvites = [
|
||||
{ _id: new ObjectId(), one: 1 },
|
||||
{ _id: new ObjectId(), two: 2 },
|
||||
]
|
||||
this.ProjectInvite.find.returns({
|
||||
select: sinon.stub().returnsThis(),
|
||||
exec: sinon.stub().resolves(this.fakeInvites),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteGetter.promises.getAllInvites(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
beforeEach(function () {})
|
||||
|
||||
it('should produce a list of invite objects', async function () {
|
||||
const invites = await this.call()
|
||||
expect(invites).to.not.be.oneOf([null, undefined])
|
||||
expect(invites).to.deep.equal(this.fakeInvites)
|
||||
})
|
||||
|
||||
it('should have called ProjectInvite.find', async function () {
|
||||
await this.call()
|
||||
this.ProjectInvite.find.callCount.should.equal(1)
|
||||
this.ProjectInvite.find
|
||||
.calledWith({ projectId: this.projectId })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when ProjectInvite.find produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.find.returns({
|
||||
select: sinon.stub().returnsThis(),
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInviteByToken', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().resolves(this.fakeInvite),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteGetter.promises.getInviteByToken(
|
||||
this.projectId,
|
||||
this.token
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
it('should produce the invite object', async function () {
|
||||
const invite = await this.call()
|
||||
expect(invite).to.deep.equal(this.fakeInvite)
|
||||
})
|
||||
|
||||
it('should call ProjectInvite.findOne', async function () {
|
||||
await this.call()
|
||||
this.ProjectInvite.findOne.callCount.should.equal(1)
|
||||
this.ProjectInvite.findOne
|
||||
.calledWith({ projectId: this.projectId, tokenHmac: this.tokenHmac })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when findOne produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when findOne does not find an invite', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().resolves(null),
|
||||
})
|
||||
})
|
||||
|
||||
it('should not produce an invite object', async function () {
|
||||
const invite = await this.call()
|
||||
expect(invite).to.be.oneOf([null, undefined])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -39,7 +39,7 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
},
|
||||
}
|
||||
this.UserGetter = { promises: { getUser: sinon.stub() } }
|
||||
this.ProjectGetter = { promises: {} }
|
||||
this.ProjectGetter = { promises: { getProject: sinon.stub().resolves() } }
|
||||
this.NotificationsBuilder = { promises: {} }
|
||||
this.tokenHmac = 'jkhajkefhaekjfhkfg'
|
||||
this.CollaboratorsInviteHelper = {
|
||||
|
@ -47,6 +47,25 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
hashInviteToken: sinon.stub().returns(this.tokenHmac),
|
||||
}
|
||||
|
||||
this.SplitTestHandler = {
|
||||
promises: {
|
||||
getAssignmentForUser: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.LimitationsManager = {
|
||||
promises: {
|
||||
canAcceptEditCollaboratorInvite: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
|
||||
this.ProjectAuditLogHandler = {
|
||||
promises: {
|
||||
addEntry: sinon.stub().resolves(),
|
||||
},
|
||||
addEntryInBackground: sinon.stub(),
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteHandler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'@overleaf/settings': this.settings,
|
||||
|
@ -57,7 +76,10 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
'../Project/ProjectGetter': this.ProjectGetter,
|
||||
'../Notifications/NotificationsBuilder': this.NotificationsBuilder,
|
||||
'./CollaboratorsInviteHelper': this.CollaboratorsInviteHelper,
|
||||
crypto: this.Crypto,
|
||||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||
'../Subscription/LimitationsManager': this.LimitationsManager,
|
||||
'../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
|
||||
crypto: this.CryptogetAssignmentForUser,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -88,119 +110,6 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
}
|
||||
})
|
||||
|
||||
describe('getInviteCount', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().resolves(2),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteHandler.promises.getInviteCount(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should produce the count of documents', async function () {
|
||||
const count = await this.call()
|
||||
expect(count).to.equal(2)
|
||||
})
|
||||
|
||||
describe('when model.countDocuments produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEditInviteCount', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().resolves(2),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteHandler.promises.getEditInviteCount(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should produce the count of documents', async function () {
|
||||
const count = await this.call()
|
||||
expect(this.ProjectInvite.countDocuments).to.be.calledWith({
|
||||
projectId: this.projectId,
|
||||
privileges: { $ne: 'readOnly' },
|
||||
})
|
||||
expect(count).to.equal(2)
|
||||
})
|
||||
|
||||
describe('when model.countDocuments produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.countDocuments.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAllInvites', function () {
|
||||
beforeEach(function () {
|
||||
this.fakeInvites = [
|
||||
{ _id: new ObjectId(), one: 1 },
|
||||
{ _id: new ObjectId(), two: 2 },
|
||||
]
|
||||
this.ProjectInvite.find.returns({
|
||||
select: sinon.stub().returnsThis(),
|
||||
exec: sinon.stub().resolves(this.fakeInvites),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteHandler.promises.getAllInvites(
|
||||
this.projectId
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
beforeEach(function () {})
|
||||
|
||||
it('should produce a list of invite objects', async function () {
|
||||
const invites = await this.call()
|
||||
expect(invites).to.not.be.oneOf([null, undefined])
|
||||
expect(invites).to.deep.equal(this.fakeInvites)
|
||||
})
|
||||
|
||||
it('should have called ProjectInvite.find', async function () {
|
||||
await this.call()
|
||||
this.ProjectInvite.find.callCount.should.equal(1)
|
||||
this.ProjectInvite.find
|
||||
.calledWith({ projectId: this.projectId })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when ProjectInvite.find produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.find.returns({
|
||||
select: sinon.stub().returnsThis(),
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('inviteToProject', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.prototype.save.callsFake(async function () {
|
||||
|
@ -484,67 +393,15 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('getInviteByToken', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().resolves(this.fakeInvite),
|
||||
})
|
||||
this.call = async () => {
|
||||
return await this.CollaboratorsInviteHandler.promises.getInviteByToken(
|
||||
this.projectId,
|
||||
this.token
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('when all goes well', function () {
|
||||
it('should produce the invite object', async function () {
|
||||
const invite = await this.call()
|
||||
expect(invite).to.deep.equal(this.fakeInvite)
|
||||
})
|
||||
|
||||
it('should call ProjectInvite.findOne', async function () {
|
||||
await this.call()
|
||||
this.ProjectInvite.findOne.callCount.should.equal(1)
|
||||
this.ProjectInvite.findOne
|
||||
.calledWith({ projectId: this.projectId, tokenHmac: this.tokenHmac })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when findOne produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().rejects(new Error('woops')),
|
||||
})
|
||||
})
|
||||
|
||||
it('should produce an error', async function () {
|
||||
await expect(this.call()).to.be.rejectedWith(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when findOne does not find an invite', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectInvite.findOne.returns({
|
||||
exec: sinon.stub().resolves(null),
|
||||
})
|
||||
})
|
||||
|
||||
it('should not produce an invite object', async function () {
|
||||
const invite = await this.call()
|
||||
expect(invite).to.be.oneOf([null, undefined])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('acceptInvite', function () {
|
||||
beforeEach(function () {
|
||||
this.fakeProject = {
|
||||
_id: this.projectId,
|
||||
collaberator_refs: [],
|
||||
readOnly_refs: [],
|
||||
owner_ref: this.sendingUserId,
|
||||
}
|
||||
this.ProjectGetter.promises.getProject = sinon
|
||||
.stub()
|
||||
.resolves(this.fakeProject)
|
||||
this.CollaboratorsHandler.promises.addUserIdToProject.resolves()
|
||||
this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification =
|
||||
sinon.stub().resolves()
|
||||
|
@ -602,6 +459,58 @@ describe('CollaboratorsInviteHandler', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('when link-sharing-enforcement is active', function () {
|
||||
beforeEach(function () {
|
||||
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
|
||||
variant: 'active',
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has no more edit collaborator slots', function () {
|
||||
beforeEach(function () {
|
||||
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should add readAndWrite invitees to the project as readOnly (pendingEditor) users', async function () {
|
||||
await this.call()
|
||||
this.ProjectAuditLogHandler.promises.addEntry.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
'editor-moved-to-pending',
|
||||
null,
|
||||
null,
|
||||
{ userId: this.userId.toString() }
|
||||
)
|
||||
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.sendingUserId,
|
||||
this.userId,
|
||||
'readOnly',
|
||||
{ pendingEditor: true }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has available edit collaborator slots', function () {
|
||||
beforeEach(function () {
|
||||
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should add readAndWrite invitees to the project as normal', async function () {
|
||||
await this.call()
|
||||
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.sendingUserId,
|
||||
this.userId,
|
||||
this.fakeInvite.privileges
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when addUserIdToProject produces an error', function () {
|
||||
beforeEach(function () {
|
||||
this.CollaboratorsHandler.promises.addUserIdToProject.callsArgWith(
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('EditorHttpController', function () {
|
|||
userIsTokenMember: sinon.stub().resolves(false),
|
||||
},
|
||||
}
|
||||
this.CollaboratorsInviteHandler = {
|
||||
this.CollaboratorsInviteGetter = {
|
||||
promises: {
|
||||
getAllInvites: sinon.stub().resolves([
|
||||
{
|
||||
|
@ -147,8 +147,8 @@ describe('EditorHttpController', function () {
|
|||
'@overleaf/metrics': this.Metrics,
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../Collaborators/CollaboratorsInviteHandler':
|
||||
this.CollaboratorsInviteHandler,
|
||||
'../Collaborators/CollaboratorsInviteGetter':
|
||||
this.CollaboratorsInviteGetter,
|
||||
'../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
|
||||
'../Authentication/SessionManager': this.SessionManager,
|
||||
'../../infrastructure/FileWriter': this.FileWriter,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const SandboxedModule = require('sandboxed-module')
|
||||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/Subscription/LimitationsManager'
|
||||
|
@ -60,7 +61,7 @@ describe('LimitationsManager', function () {
|
|||
},
|
||||
}
|
||||
|
||||
this.CollaboratorsInviteHandler = {
|
||||
this.CollaboratorsInviteGetter = {
|
||||
promises: {
|
||||
getInviteCount: sinon.stub().resolves(),
|
||||
getEditInviteCount: sinon.stub().resolves(),
|
||||
|
@ -74,8 +75,8 @@ describe('LimitationsManager', function () {
|
|||
'./SubscriptionLocator': this.SubscriptionLocator,
|
||||
'@overleaf/settings': (this.Settings = {}),
|
||||
'../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
|
||||
'../Collaborators/CollaboratorsInviteHandler':
|
||||
this.CollaboratorsInviteHandler,
|
||||
'../Collaborators/CollaboratorsInviteGetter':
|
||||
this.CollaboratorsInviteGetter,
|
||||
'./V1SubscriptionManager': this.V1SubscriptionManager,
|
||||
},
|
||||
})
|
||||
|
@ -157,6 +158,76 @@ describe('LimitationsManager', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('canAcceptEditCollaboratorInvite', function () {
|
||||
describe('when the project has fewer collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 1
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe('when accepting the invite would exceed the collaborator limit', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 2
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
)
|
||||
expect(result).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has more collaborators than allowed', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 3
|
||||
this.user.features.collaborators = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
})
|
||||
|
||||
it('should return false', async function () {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
)
|
||||
expect(result).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has infinite collaborators', function () {
|
||||
beforeEach(function () {
|
||||
this.current_number = 100
|
||||
this.user.features.collaborators = -1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
})
|
||||
|
||||
it('should return true', async function () {
|
||||
const result =
|
||||
await this.LimitationsManager.promises.canAcceptEditCollaboratorInvite(
|
||||
this.projectId
|
||||
)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('canAddXCollaborators', function () {
|
||||
describe('when the project has fewer collaborators than allowed', function () {
|
||||
beforeEach(function (done) {
|
||||
|
@ -166,7 +237,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -190,7 +261,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -214,7 +285,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -238,7 +309,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -262,7 +333,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -286,7 +357,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -310,7 +381,7 @@ describe('LimitationsManager', function () {
|
|||
this.CollaboratorsGetter.promises.getInvitedCollaboratorCount = sinon
|
||||
.stub()
|
||||
.resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -334,7 +405,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -357,7 +428,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -380,7 +451,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -403,7 +474,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -426,7 +497,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 0
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -449,7 +520,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 2
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
@ -472,7 +543,7 @@ describe('LimitationsManager', function () {
|
|||
this.invite_count = 1
|
||||
this.CollaboratorsGetter.promises.getInvitedEditCollaboratorCount =
|
||||
sinon.stub().resolves(this.current_number)
|
||||
this.CollaboratorsInviteHandler.promises.getEditInviteCount = sinon
|
||||
this.CollaboratorsInviteGetter.promises.getEditInviteCount = sinon
|
||||
.stub()
|
||||
.resolves(this.invite_count)
|
||||
this.callback = sinon.stub().callsFake(() => done())
|
||||
|
|
Loading…
Reference in a new issue