mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[web] Enforce collaborator limit (#19619)
* Enables collaborator limit enforcement * Add pendingEditor_refs for editors downgraded during limit enforcement * Add isPendingEditor to useEditorContext --------- Co-authored-by: Thomas Mees <thomas.mees@overleaf.com> GitOrigin-RevId: b622213f6282ccd8ee85a23ceb29b8c6f8ff6a96
This commit is contained in:
parent
18ee2bc87a
commit
5e2662adc4
13 changed files with 227 additions and 28 deletions
|
@ -40,6 +40,7 @@ module.exports = {
|
|||
getMemberIdPrivilegeLevel,
|
||||
getInvitedCollaboratorCount,
|
||||
getInvitedEditCollaboratorCount,
|
||||
getInvitedPendingEditorCount,
|
||||
getProjectsUserIsMemberOf,
|
||||
dangerouslyGetAllProjectsUserIsMemberOf,
|
||||
isUserInvitedMemberOfProject,
|
||||
|
@ -59,6 +60,7 @@ async function getMemberIdsWithPrivilegeLevels(projectId) {
|
|||
tokenAccessReadOnly_refs: 1,
|
||||
tokenAccessReadAndWrite_refs: 1,
|
||||
publicAccesLevel: 1,
|
||||
pendingEditor_refs: 1,
|
||||
})
|
||||
if (!project) {
|
||||
throw new Errors.NotFoundError(`no project found with id ${projectId}`)
|
||||
|
@ -69,7 +71,8 @@ async function getMemberIdsWithPrivilegeLevels(projectId) {
|
|||
project.readOnly_refs,
|
||||
project.tokenAccessReadAndWrite_refs,
|
||||
project.tokenAccessReadOnly_refs,
|
||||
project.publicAccesLevel
|
||||
project.publicAccesLevel,
|
||||
project.pendingEditor_refs
|
||||
)
|
||||
return memberIds
|
||||
}
|
||||
|
@ -136,6 +139,17 @@ async function getInvitedEditCollaboratorCount(projectId) {
|
|||
).length
|
||||
}
|
||||
|
||||
async function getInvitedPendingEditorCount(projectId) {
|
||||
// Only counts invited members that are readonly pending editors
|
||||
const members = await getMemberIdsWithPrivilegeLevels(projectId)
|
||||
return members.filter(
|
||||
m =>
|
||||
m.source === Sources.INVITE &&
|
||||
m.privilegeLevel === PrivilegeLevels.READ_ONLY &&
|
||||
m.pendingEditor === true
|
||||
).length
|
||||
}
|
||||
|
||||
async function isUserInvitedMemberOfProject(userId, projectId) {
|
||||
if (!userId) {
|
||||
return false
|
||||
|
@ -309,7 +323,8 @@ function _getMemberIdsWithPrivilegeLevelsFromFields(
|
|||
readOnlyIds,
|
||||
tokenAccessIds,
|
||||
tokenAccessReadOnlyIds,
|
||||
publicAccessLevel
|
||||
publicAccessLevel,
|
||||
pendingEditorIds
|
||||
) {
|
||||
const members = []
|
||||
members.push({
|
||||
|
@ -329,6 +344,9 @@ function _getMemberIdsWithPrivilegeLevelsFromFields(
|
|||
id: memberId.toString(),
|
||||
privilegeLevel: PrivilegeLevels.READ_ONLY,
|
||||
source: Sources.INVITE,
|
||||
...(pendingEditorIds?.some(pe => memberId.equals(pe)) && {
|
||||
pendingEditor: true,
|
||||
}),
|
||||
})
|
||||
}
|
||||
if (publicAccessLevel === PublicAccessLevels.TOKEN_BASED) {
|
||||
|
@ -364,7 +382,11 @@ async function _loadMembers(members) {
|
|||
signUpDate: 1,
|
||||
})
|
||||
if (user != null) {
|
||||
return { user, privilegeLevel: member.privilegeLevel }
|
||||
return {
|
||||
user,
|
||||
privilegeLevel: member.privilegeLevel,
|
||||
...(member.pendingEditor && { pendingEditor: true }),
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ async function removeUserFromProject(projectId, userId) {
|
|||
$pull: {
|
||||
collaberator_refs: userId,
|
||||
readOnly_refs: userId,
|
||||
pendingEditor_refs: userId,
|
||||
tokenAccessReadOnly_refs: userId,
|
||||
tokenAccessReadAndWrite_refs: userId,
|
||||
trashed: userId,
|
||||
|
@ -62,6 +63,7 @@ async function removeUserFromProject(projectId, userId) {
|
|||
$pull: {
|
||||
collaberator_refs: userId,
|
||||
readOnly_refs: userId,
|
||||
pendingEditor_refs: userId,
|
||||
tokenAccessReadOnly_refs: userId,
|
||||
tokenAccessReadAndWrite_refs: userId,
|
||||
archived: userId,
|
||||
|
@ -196,6 +198,19 @@ async function transferProjects(fromUserId, toUserId) {
|
|||
}
|
||||
).exec()
|
||||
|
||||
await Project.updateMany(
|
||||
{ pendingEditor_refs: fromUserId },
|
||||
{
|
||||
$addToSet: { pendingEditor_refs: toUserId },
|
||||
}
|
||||
).exec()
|
||||
await Project.updateMany(
|
||||
{ pendingEditor_refs: fromUserId },
|
||||
{
|
||||
$pull: { pendingEditor_refs: fromUserId },
|
||||
}
|
||||
).exec()
|
||||
|
||||
// Flush in background, no need to block on this
|
||||
_flushProjects(projectIds).catch(err => {
|
||||
logger.err(
|
||||
|
@ -220,14 +235,14 @@ async function setCollaboratorPrivilegeLevel(
|
|||
switch (privilegeLevel) {
|
||||
case PrivilegeLevels.READ_AND_WRITE: {
|
||||
update = {
|
||||
$pull: { readOnly_refs: userId },
|
||||
$pull: { readOnly_refs: userId, pendingEditor_refs: userId },
|
||||
$addToSet: { collaberator_refs: userId },
|
||||
}
|
||||
break
|
||||
}
|
||||
case PrivilegeLevels.READ_ONLY: {
|
||||
update = {
|
||||
$pull: { collaberator_refs: userId },
|
||||
$pull: { collaberator_refs: userId, pendingEditor_refs: userId },
|
||||
$addToSet: { readOnly_refs: userId },
|
||||
}
|
||||
break
|
||||
|
|
|
@ -415,6 +415,7 @@ const _ProjectController = {
|
|||
tokens: 1,
|
||||
tokenAccessReadAndWrite_refs: 1, // used for link sharing analytics
|
||||
collaberator_refs: 1, // used for link sharing analytics
|
||||
pendingEditor_refs: 1, // used for link sharing analytics
|
||||
}),
|
||||
userIsMemberOfGroupSubscription: sessionUser
|
||||
? (async () =>
|
||||
|
@ -488,12 +489,24 @@ const _ProjectController = {
|
|||
anonRequestToken
|
||||
)
|
||||
|
||||
const linkSharingChanges =
|
||||
await SplitTestHandler.promises.getAssignmentForUser(
|
||||
const [linkSharingChanges, linkSharingEnforcement] = await Promise.all([
|
||||
SplitTestHandler.promises.getAssignmentForUser(
|
||||
project.owner_ref,
|
||||
'link-sharing-warning'
|
||||
)
|
||||
),
|
||||
SplitTestHandler.promises.getAssignmentForUser(
|
||||
project.owner_ref,
|
||||
'link-sharing-enforcement'
|
||||
),
|
||||
])
|
||||
|
||||
if (linkSharingChanges?.variant === 'active') {
|
||||
if (linkSharingEnforcement?.variant === 'active') {
|
||||
await Modules.promises.hooks.fire(
|
||||
'enforceCollaboratorLimit',
|
||||
projectId
|
||||
)
|
||||
}
|
||||
if (isTokenMember) {
|
||||
// Check explicitly that the user is in read write token refs, while this could be inferred
|
||||
// from the privilege level, the privilege level of token members might later be restricted
|
||||
|
@ -570,12 +583,14 @@ const _ProjectController = {
|
|||
)
|
||||
const planLimit = ownerFeatures?.collaborators || 0
|
||||
const namedEditors = project.collaberator_refs?.length || 0
|
||||
const pendingEditors = project.pendingEditor_refs?.length || 0
|
||||
const exceedAtLimit = planLimit > -1 && namedEditors >= planLimit
|
||||
const projectOpenedSegmentation = {
|
||||
projectId: project._id,
|
||||
// temporary link sharing segmentation:
|
||||
linkSharingWarning: linkSharingChanges?.variant,
|
||||
namedEditors,
|
||||
pendingEditors,
|
||||
tokenEditors: project.tokenAccessReadAndWrite_refs?.length || 0,
|
||||
planLimit,
|
||||
exceedAtLimit,
|
||||
|
@ -1034,7 +1049,6 @@ const ProjectController = {
|
|||
),
|
||||
updateProjectSettings: expressify(_ProjectController.updateProjectSettings),
|
||||
userProjectsJson: expressify(_ProjectController.userProjectsJson),
|
||||
|
||||
_buildProjectList: _ProjectController._buildProjectList,
|
||||
_buildProjectViewModel: _ProjectController._buildProjectViewModel,
|
||||
_injectProjectUsers: _ProjectController._injectProjectUsers,
|
||||
|
|
|
@ -83,11 +83,9 @@ module.exports = ProjectEditorHandler = {
|
|||
for (const member of members || []) {
|
||||
if (member.privilegeLevel === 'owner') {
|
||||
ownerFeatures = member.user.features
|
||||
owner = this.buildUserModelView(member.user, 'owner')
|
||||
owner = this.buildUserModelView(member)
|
||||
} else {
|
||||
filteredMembers.push(
|
||||
this.buildUserModelView(member.user, member.privilegeLevel)
|
||||
)
|
||||
filteredMembers.push(this.buildUserModelView(member))
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -97,14 +95,16 @@ module.exports = ProjectEditorHandler = {
|
|||
}
|
||||
},
|
||||
|
||||
buildUserModelView(user, privileges) {
|
||||
buildUserModelView(member) {
|
||||
const user = member.user
|
||||
return {
|
||||
_id: user._id,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
email: user.email,
|
||||
privileges,
|
||||
privileges: member.privilegeLevel,
|
||||
signUpDate: user.signUpDate,
|
||||
pendingEditor: member.pendingEditor,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -30,14 +30,14 @@ async function allowedNumberOfCollaboratorsForUser(userId) {
|
|||
|
||||
async function canAddXCollaborators(projectId, numberOfNewCollaborators) {
|
||||
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
||||
if (allowedNumber < 0) {
|
||||
return true // -1 means unlimited
|
||||
}
|
||||
const currentNumber =
|
||||
await CollaboratorsGetter.promises.getInvitedCollaboratorCount(projectId)
|
||||
const inviteCount =
|
||||
await CollaboratorsInvitesHandler.promises.getInviteCount(projectId)
|
||||
return (
|
||||
currentNumber + inviteCount + numberOfNewCollaborators <= allowedNumber ||
|
||||
allowedNumber < 0 // -1 means unlimited
|
||||
)
|
||||
return currentNumber + inviteCount + numberOfNewCollaborators <= allowedNumber
|
||||
}
|
||||
|
||||
async function canAddXEditCollaborators(
|
||||
|
@ -45,6 +45,9 @@ async function canAddXEditCollaborators(
|
|||
numberOfNewEditCollaborators
|
||||
) {
|
||||
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
||||
if (allowedNumber < 0) {
|
||||
return true // -1 means unlimited
|
||||
}
|
||||
const currentEditors =
|
||||
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
||||
projectId
|
||||
|
@ -53,7 +56,7 @@ async function canAddXEditCollaborators(
|
|||
await CollaboratorsInvitesHandler.promises.getEditInviteCount(projectId)
|
||||
return (
|
||||
currentEditors + editInviteCount + numberOfNewEditCollaborators <=
|
||||
allowedNumber || allowedNumber < 0 // -1 means unlimited
|
||||
allowedNumber
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ const ProjectSchema = new Schema(
|
|||
owner_ref: { type: ObjectId, ref: 'User' },
|
||||
collaberator_refs: [{ type: ObjectId, ref: 'User' }],
|
||||
readOnly_refs: [{ type: ObjectId, ref: 'User' }],
|
||||
pendingEditor_refs: [{ type: ObjectId, ref: 'User' }],
|
||||
rootDoc_id: { type: ObjectId },
|
||||
rootFolder: [FolderSchema],
|
||||
version: { type: Number }, // incremented for every change in the project structure (folders and filenames)
|
||||
|
|
|
@ -124,9 +124,17 @@ export default function EditMember({
|
|||
<FormGroup className="project-member">
|
||||
<Col xs={7}>
|
||||
<div className="project-member-email-icon">
|
||||
<Icon type={shouldWarnMember() ? 'warning' : 'user'} fw />
|
||||
<Icon
|
||||
type={
|
||||
shouldWarnMember() || member.pendingEditor ? 'warning' : 'user'
|
||||
}
|
||||
fw
|
||||
/>
|
||||
<div className="email-warning">
|
||||
{member.email}
|
||||
{member.pendingEditor && (
|
||||
<div className="subtitle">Pending editor</div>
|
||||
)}
|
||||
{shouldWarnMember() && (
|
||||
<div className="subtitle">
|
||||
{t('will_lose_edit_access_on_date', {
|
||||
|
|
|
@ -44,6 +44,7 @@ export const EditorContext = createContext<
|
|||
insertSymbol?: (symbol: string) => void
|
||||
isProjectOwner: boolean
|
||||
isRestrictedTokenMember?: boolean
|
||||
isPendingEditor: boolean
|
||||
permissionsLevel: 'readOnly' | 'readAndWrite' | 'owner'
|
||||
deactivateTutorial: (tutorial: string) => void
|
||||
inactiveTutorials: string[]
|
||||
|
@ -62,7 +63,7 @@ export const EditorProvider: FC = ({ children }) => {
|
|||
const { role } = useDetachContext()
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
|
||||
const { owner, features, _id: projectId } = useProjectContext()
|
||||
const { owner, features, _id: projectId, members } = useProjectContext()
|
||||
|
||||
const cobranding = useMemo(() => {
|
||||
const brandVariation = getMeta('ol-brandVariation')
|
||||
|
@ -98,6 +99,17 @@ export const EditorProvider: FC = ({ children }) => {
|
|||
|
||||
const [currentPopup, setCurrentPopup] = useState<string | null>(null)
|
||||
|
||||
const isPendingEditor = useMemo(
|
||||
() =>
|
||||
members?.some(
|
||||
member =>
|
||||
member._id === userId &&
|
||||
member.pendingEditor &&
|
||||
member.privileges === 'readAndWrite'
|
||||
),
|
||||
[members, userId]
|
||||
)
|
||||
|
||||
const deactivateTutorial = useCallback(
|
||||
tutorialKey => {
|
||||
setInactiveTutorials([...inactiveTutorials, tutorialKey])
|
||||
|
@ -174,6 +186,7 @@ export const EditorProvider: FC = ({ children }) => {
|
|||
setPermissionsLevel,
|
||||
isProjectOwner: owner?._id === userId,
|
||||
isRestrictedTokenMember: getMeta('ol-isRestrictedTokenMember'),
|
||||
isPendingEditor,
|
||||
showSymbolPalette,
|
||||
toggleSymbolPalette,
|
||||
insertSymbol,
|
||||
|
@ -194,6 +207,7 @@ export const EditorProvider: FC = ({ children }) => {
|
|||
renameProject,
|
||||
permissionsLevel,
|
||||
setPermissionsLevel,
|
||||
isPendingEditor,
|
||||
showSymbolPalette,
|
||||
toggleSymbolPalette,
|
||||
insertSymbol,
|
||||
|
|
|
@ -7,6 +7,7 @@ export type ProjectContextMember = {
|
|||
email: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
pendingEditor?: boolean
|
||||
}
|
||||
|
||||
export type ProjectContextValue = {
|
||||
|
|
|
@ -946,6 +946,10 @@ class User {
|
|||
updateOp = { $addToSet: { collaberator_refs: user._id } }
|
||||
} else if (privileges === 'readOnly') {
|
||||
updateOp = { $addToSet: { readOnly_refs: user._id } }
|
||||
} else if (privileges === 'pendingEditor') {
|
||||
updateOp = {
|
||||
$addToSet: { readOnly_refs: user._id, pendingEditor_refs: user._id },
|
||||
}
|
||||
}
|
||||
db.projects.updateOne({ _id: new ObjectId(projectId) }, updateOp, callback)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ describe('CollaboratorsGetter', function () {
|
|||
this.ownerRef = new ObjectId()
|
||||
this.readOnlyRef1 = new ObjectId()
|
||||
this.readOnlyRef2 = new ObjectId()
|
||||
this.pendingEditorRef = new ObjectId()
|
||||
this.readWriteRef1 = new ObjectId()
|
||||
this.readWriteRef2 = new ObjectId()
|
||||
this.readOnlyTokenRef = new ObjectId()
|
||||
|
@ -25,7 +26,12 @@ describe('CollaboratorsGetter', function () {
|
|||
this.project = {
|
||||
_id: new ObjectId(),
|
||||
owner_ref: [this.ownerRef],
|
||||
readOnly_refs: [this.readOnlyRef1, this.readOnlyRef2],
|
||||
readOnly_refs: [
|
||||
this.readOnlyRef1,
|
||||
this.readOnlyRef2,
|
||||
this.pendingEditorRef,
|
||||
],
|
||||
pendingEditor_refs: [this.pendingEditorRef],
|
||||
collaberator_refs: [this.readWriteRef1, this.readWriteRef2],
|
||||
tokenAccessReadAndWrite_refs: [this.readWriteTokenRef],
|
||||
tokenAccessReadOnly_refs: [this.readOnlyTokenRef],
|
||||
|
@ -99,6 +105,12 @@ describe('CollaboratorsGetter', function () {
|
|||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
},
|
||||
{
|
||||
id: this.pendingEditorRef.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
source: 'invite',
|
||||
pendingEditor: true,
|
||||
},
|
||||
{
|
||||
id: this.readOnlyTokenRef.toString(),
|
||||
privilegeLevel: 'readOnly',
|
||||
|
@ -139,6 +151,7 @@ describe('CollaboratorsGetter', function () {
|
|||
this.readOnlyRef2.toString(),
|
||||
this.readWriteRef1.toString(),
|
||||
this.readWriteRef2.toString(),
|
||||
this.pendingEditorRef.toString(),
|
||||
this.readWriteTokenRef.toString(),
|
||||
this.readOnlyTokenRef.toString(),
|
||||
])
|
||||
|
@ -157,6 +170,7 @@ describe('CollaboratorsGetter', function () {
|
|||
this.readOnlyRef2.toString(),
|
||||
this.readWriteRef1.toString(),
|
||||
this.readWriteRef2.toString(),
|
||||
this.pendingEditorRef.toString(),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@ -484,4 +498,14 @@ describe('CollaboratorsGetter', function () {
|
|||
expect(count).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInvitedPendingEditorCount', function () {
|
||||
it('should return the count of pending editors', async function () {
|
||||
const count =
|
||||
await this.CollaboratorsGetter.promises.getInvitedPendingEditorCount(
|
||||
this.project._id
|
||||
)
|
||||
expect(count).to.equal(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -106,6 +106,7 @@ describe('CollaboratorsHandler', function () {
|
|||
$pull: {
|
||||
collaberator_refs: this.userId,
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
tokenAccessReadOnly_refs: this.userId,
|
||||
tokenAccessReadAndWrite_refs: this.userId,
|
||||
archived: this.userId,
|
||||
|
@ -148,6 +149,7 @@ describe('CollaboratorsHandler', function () {
|
|||
$pull: {
|
||||
collaberator_refs: this.userId,
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
tokenAccessReadOnly_refs: this.userId,
|
||||
tokenAccessReadAndWrite_refs: this.userId,
|
||||
trashed: this.userId,
|
||||
|
@ -182,6 +184,7 @@ describe('CollaboratorsHandler', function () {
|
|||
$pull: {
|
||||
collaberator_refs: this.userId,
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
tokenAccessReadOnly_refs: this.userId,
|
||||
tokenAccessReadAndWrite_refs: this.userId,
|
||||
archived: this.userId,
|
||||
|
@ -377,6 +380,7 @@ describe('CollaboratorsHandler', function () {
|
|||
$pull: {
|
||||
collaberator_refs: this.userId,
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
tokenAccessReadOnly_refs: this.userId,
|
||||
tokenAccessReadAndWrite_refs: this.userId,
|
||||
archived: this.userId,
|
||||
|
@ -457,6 +461,24 @@ describe('CollaboratorsHandler', function () {
|
|||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
this.ProjectMock.expects('updateMany')
|
||||
.withArgs(
|
||||
{ pendingEditor_refs: this.fromUserId },
|
||||
{
|
||||
$addToSet: { pendingEditor_refs: this.toUserId },
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
this.ProjectMock.expects('updateMany')
|
||||
.withArgs(
|
||||
{ pendingEditor_refs: this.fromUserId },
|
||||
{
|
||||
$pull: { pendingEditor_refs: this.fromUserId },
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves()
|
||||
})
|
||||
|
||||
describe('successfully', function () {
|
||||
|
@ -501,7 +523,10 @@ describe('CollaboratorsHandler', function () {
|
|||
],
|
||||
},
|
||||
{
|
||||
$pull: { collaberator_refs: this.userId },
|
||||
$pull: {
|
||||
collaberator_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
},
|
||||
$addToSet: { readOnly_refs: this.userId },
|
||||
}
|
||||
)
|
||||
|
@ -526,7 +551,10 @@ describe('CollaboratorsHandler', function () {
|
|||
},
|
||||
{
|
||||
$addToSet: { collaberator_refs: this.userId },
|
||||
$pull: { readOnly_refs: this.userId },
|
||||
$pull: {
|
||||
readOnly_refs: this.userId,
|
||||
pendingEditor_refs: this.userId,
|
||||
},
|
||||
}
|
||||
)
|
||||
.chain('exec')
|
||||
|
|
|
@ -131,6 +131,11 @@ describe('ProjectController', function () {
|
|||
isUserInvitedReadWriteMemberOfProject: sinon.stub().resolves(true),
|
||||
},
|
||||
}
|
||||
this.CollaboratorsHandler = {
|
||||
promises: {
|
||||
setCollaboratorPrivilegeLevel: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.ProjectEntityHandler = {}
|
||||
this.UserGetter = {
|
||||
getUserFullEmails: sinon.stub().yields(null, []),
|
||||
|
@ -200,12 +205,16 @@ describe('ProjectController', function () {
|
|||
this.OnboardingDataCollectionManager = {
|
||||
getOnboardingDataValue: sinon.stub().resolves(null),
|
||||
}
|
||||
this.Modules = {
|
||||
promises: { hooks: { fire: sinon.stub().resolves() } },
|
||||
}
|
||||
|
||||
this.ProjectController = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'mongodb-legacy': { ObjectId },
|
||||
'@overleaf/settings': this.settings,
|
||||
'@overleaf/metrics': this.Metrics,
|
||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../SplitTests/SplitTestHandler': this.SplitTestHandler,
|
||||
'../SplitTests/SplitTestSessionHandler': this.SplitTestSessionHandler,
|
||||
'./ProjectDeleter': this.ProjectDeleter,
|
||||
|
@ -255,6 +264,7 @@ describe('ProjectController', function () {
|
|||
updateUser: sinon.stub().resolves(),
|
||||
},
|
||||
},
|
||||
'../../infrastructure/Modules': this.Modules,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -1015,9 +1025,13 @@ describe('ProjectController', function () {
|
|||
|
||||
describe('link sharing changes active', function () {
|
||||
beforeEach(function () {
|
||||
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
|
||||
variant: 'active',
|
||||
})
|
||||
this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
|
||||
async (userId, test) => {
|
||||
if (test === 'link-sharing-warning') {
|
||||
return { variant: 'active' }
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('when user is a read write token member (and not already a named editor)', function () {
|
||||
|
@ -1059,6 +1073,57 @@ describe('ProjectController', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('link sharing enforcement', function () {
|
||||
describe('when not active (default)', function () {
|
||||
beforeEach(function () {
|
||||
this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
|
||||
async (userId, test) => {
|
||||
if (test === 'link-sharing-warning') {
|
||||
return { variant: 'active' }
|
||||
} else if (test === 'link-sharing-enforcement') {
|
||||
return { variant: 'default' }
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should not call the collaborator limit enforcement check', function (done) {
|
||||
this.res.render = (pageName, opts) => {
|
||||
this.Modules.promises.hooks.fire.should.not.have.been.calledWith(
|
||||
'enforceCollaboratorLimit'
|
||||
)
|
||||
done()
|
||||
}
|
||||
this.ProjectController.loadEditor(this.req, this.res)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when active', function () {
|
||||
beforeEach(function () {
|
||||
this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
|
||||
async (userId, test) => {
|
||||
if (test === 'link-sharing-warning') {
|
||||
return { variant: 'active' }
|
||||
} else if (test === 'link-sharing-enforcement') {
|
||||
return { variant: 'active' }
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the collaborator limit enforcement check', function (done) {
|
||||
this.res.render = (pageName, opts) => {
|
||||
this.Modules.promises.hooks.fire.should.have.been.calledWith(
|
||||
'enforceCollaboratorLimit',
|
||||
this.project_id
|
||||
)
|
||||
done()
|
||||
}
|
||||
this.ProjectController.loadEditor(this.req, this.res)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('userProjectsJson', function () {
|
||||
|
|
Loading…
Reference in a new issue