Merge pull request #23131 from overleaf/kh-teardown-link-sharing-split-tests

[web] tear down link sharing split tests

GitOrigin-RevId: 449e9f368405aea1500035269428e7ae0c37d8fb
This commit is contained in:
Kristina
2025-02-04 09:04:25 +00:00
committed by Copybot
parent 8831fb9a18
commit e3fdcdd601
24 changed files with 806 additions and 2264 deletions
@@ -304,89 +304,77 @@ describe('CollaboratorsController', function () {
)
})
describe('when link-sharing-warning test active', function () {
describe('when setting privilege level to readAndWrite', function () {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
variant: 'active',
this.req.body = { privilegeLevel: 'readAndWrite' }
})
describe('when owner can add new edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
true
)
})
it('should set privilege level after checking collaborators can be added', function (done) {
this.res.sendStatus = status => {
expect(status).to.equal(204)
expect(
this.LimitationsManager.promises.canAddXEditCollaborators
).to.have.been.calledWith(this.projectId, 1)
done()
}
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
describe('when setting privilege level to readAndWrite', function () {
describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
this.req.body = { privilegeLevel: 'readAndWrite' }
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
describe('when owner can add new edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
true
)
})
it('should set privilege level after checking collaborators can be added', function (done) {
this.res.sendStatus = status => {
expect(status).to.equal(204)
expect(
this.LimitationsManager.promises.canAddXEditCollaborators
).to.have.been.calledWith(this.projectId, 1)
done()
}
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
it('should return a 403 if trying to set a new edit collaborator', function (done) {
this.HttpErrorHandler.forbidden = sinon.spy((req, res) => {
expect(req).to.equal(this.req)
expect(res).to.equal(this.res)
expect(
this.LimitationsManager.promises.canAddXEditCollaborators
).to.have.been.calledWith(this.projectId, 1)
expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
).to.not.have.been.called
done()
})
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
it('should return a 403 if trying to set a new edit collaborator', function (done) {
this.HttpErrorHandler.forbidden = sinon.spy((req, res) => {
expect(req).to.equal(this.req)
expect(res).to.equal(this.res)
expect(
this.LimitationsManager.promises.canAddXEditCollaborators
).to.have.been.calledWith(this.projectId, 1)
expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
).to.not.have.been.called
done()
})
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
})
describe('when setting privilege level to readOnly', function () {
describe('when setting privilege level to readOnly', function () {
beforeEach(function () {
this.req.body = { privilegeLevel: 'readOnly' }
})
describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
this.req.body = { privilegeLevel: 'readOnly' }
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
describe('when owner cannot add edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
it('should always allow setting a collaborator to viewer even if user cant add edit collaborators', function (done) {
this.res.sendStatus = status => {
expect(status).to.equal(204)
expect(this.LimitationsManager.promises.canAddXEditCollaborators)
.to.not.have.been.called
expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
).to.have.been.calledWith(
this.projectId,
this.user._id,
'readOnly'
)
done()
}
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
it('should always allow setting a collaborator to viewer even if user cant add edit collaborators', function (done) {
this.res.sendStatus = status => {
expect(status).to.equal(204)
expect(this.LimitationsManager.promises.canAddXEditCollaborators).to
.not.have.been.called
expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
).to.have.been.calledWith(this.projectId, this.user._id, 'readOnly')
done()
}
this.CollaboratorsController.setCollaboratorInfo(this.req, this.res)
})
})
})
@@ -230,228 +230,18 @@ describe('CollaboratorsInviteController', function () {
})
})
describe('when in link-sharing-warning test', function (done) {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
variant: 'active',
})
})
describe('when all goes well', function (done) {
beforeEach(async function () {
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
await this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res
)
})
it('should produce json response', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
invite: this.inviteReducedData,
})
})
it('should have called canAddXEditCollaborators', function () {
this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
1
)
this.LimitationsManager.promises.canAddXEditCollaborators
.calledWith(this.projectId)
.should.equal(true)
})
it('should have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
1
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.targetEmail)
.should.equal(true)
})
it('should have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
1
)
this.CollaboratorsInviteHandler.promises.inviteToProject
.calledWith(
this.projectId,
this.currentUser,
this.targetEmail,
this.privileges
)
.should.equal(true)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('adds a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'send-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
describe('when the user is not allowed to add more edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
describe('readAndWrite collaborator', function () {
beforeEach(function (done) {
this.privileges = 'readAndWrite'
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
)
})
it('should produce json response without an invite', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
invite: null,
})
})
it('should not have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
0
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
it('should not have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
0
)
})
})
describe('readOnly collaborator (always allowed)', function () {
beforeEach(function (done) {
this.req.body = {
email: this.targetEmail,
privileges: (this.privileges = 'readOnly'),
}
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
)
})
it('should produce json response', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
invite: this.inviteReducedData,
})
})
it('should not have called canAddXEditCollaborators', function () {
this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
0
)
})
it('should have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
1
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.targetEmail)
.should.equal(true)
})
it('should have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
1
)
this.CollaboratorsInviteHandler.promises.inviteToProject
.calledWith(
this.projectId,
this.currentUser,
this.targetEmail,
this.privileges
)
.should.equal(true)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('adds a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'send-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
})
})
describe('when all goes well', function (done) {
beforeEach(function (done) {
beforeEach(async function () {
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
await this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
this.res
)
})
@@ -462,11 +252,11 @@ describe('CollaboratorsInviteController', function () {
})
})
it('should have called canAddXCollaborators', function () {
this.LimitationsManager.promises.canAddXCollaborators.callCount.should.equal(
it('should have called canAddXEditCollaborators', function () {
this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
1
)
this.LimitationsManager.promises.canAddXCollaborators
this.LimitationsManager.promises.canAddXEditCollaborators
.calledWith(this.projectId)
.should.equal(true)
})
@@ -475,6 +265,7 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
1
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.targetEmail)
.should.equal(true)
@@ -515,81 +306,128 @@ describe('CollaboratorsInviteController', function () {
})
})
describe('when the user is not allowed to add more collaborators', function () {
beforeEach(function (done) {
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators.resolves(false)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
describe('when the user is not allowed to add more edit collaborators', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAddXEditCollaborators.resolves(
false
)
})
it('should produce json response without an invite', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({ invite: null })
describe('readAndWrite collaborator', function () {
beforeEach(function (done) {
this.privileges = 'readAndWrite'
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
)
})
it('should produce json response without an invite', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
invite: null,
})
})
it('should not have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
0
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
it('should not have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
0
)
})
})
it('should not have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
0
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
describe('readOnly collaborator (always allowed)', function () {
beforeEach(function (done) {
this.req.body = {
email: this.targetEmail,
privileges: (this.privileges = 'readOnly'),
}
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
)
})
it('should not have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
0
)
})
})
it('should produce json response', function () {
this.res.json.callCount.should.equal(1)
expect(this.res.json.firstCall.args[0]).to.deep.equal({
invite: this.inviteReducedData,
})
})
describe('when canAddXCollaborators produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsInviteController._checkShouldInviteEmail = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
this.next
)
})
it('should not have called canAddXEditCollaborators', function () {
this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
0
)
})
it('should call next with an error', function () {
this.next.callCount.should.equal(1)
this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
})
it('should have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
1
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.targetEmail)
.should.equal(true)
})
it('should not have called _checkShouldInviteEmail', function () {
this.CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal(
0
)
this.CollaboratorsInviteController._checkShouldInviteEmail
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
it('should have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
1
)
this.CollaboratorsInviteHandler.promises.inviteToProject
.calledWith(
this.projectId,
this.currentUser,
this.targetEmail,
this.privileges
)
.should.equal(true)
})
it('should not have called inviteToProject', function () {
this.CollaboratorsInviteHandler.promises.inviteToProject.callCount.should.equal(
0
)
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('adds a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'send-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
})
@@ -617,11 +455,11 @@ describe('CollaboratorsInviteController', function () {
expect(this.next).to.have.been.calledWith(sinon.match.instanceOf(Error))
})
it('should have called canAddXCollaborators', function () {
this.LimitationsManager.promises.canAddXCollaborators.callCount.should.equal(
it('should have called canAddXEditCollaborators', function () {
this.LimitationsManager.promises.canAddXEditCollaborators.callCount.should.equal(
1
)
this.LimitationsManager.promises.canAddXCollaborators
this.LimitationsManager.promises.canAddXEditCollaborators
.calledWith(this.projectId)
.should.equal(true)
})
@@ -492,6 +492,9 @@ describe('CollaboratorsInviteHandler', function () {
this.CollaboratorsHandler.promises.addUserIdToProject.resolves()
this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification =
sinon.stub().resolves()
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
this.ProjectInvite.deleteOne.returns({ exec: sinon.stub().resolves() })
this.call = async () => {
await this.CollaboratorsInviteHandler.promises.acceptInvite(
@@ -503,11 +506,8 @@ describe('CollaboratorsInviteHandler', function () {
})
describe('when all goes well', function () {
it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
it('should add readAndWrite invitees to the project as normal', async function () {
await this.call()
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
1
)
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
this.projectId,
this.sendingUserId,
@@ -546,55 +546,29 @@ describe('CollaboratorsInviteHandler', function () {
})
})
describe('when link-sharing-enforcement is active', function () {
describe('when the project has no more edit collaborator slots', function () {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
variant: 'active',
})
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
false
)
})
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
)
})
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 }
)
})
})
@@ -1031,108 +1031,56 @@ describe('ProjectController', function () {
})
})
describe('link sharing changes active', function () {
describe('when user is a read write token member (and not already a named editor)', function () {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
async (userId, test) => {
if (test === 'link-sharing-warning') {
return { variant: 'active' }
}
}
this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
true
)
this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
false
)
})
describe('when user is a read write token member (and not already a named editor)', function () {
beforeEach(function () {
this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
true
)
this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
false
)
})
it('should redirect to the sharing-updates page', function (done) {
this.res.redirect = url => {
expect(url).to.equal(`/project/${this.project_id}/sharing-updates`)
done()
}
this.ProjectController.loadEditor(this.req, this.res)
})
})
describe('when user is a read write token member but also a named editor', function () {
beforeEach(function () {
this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
true
)
this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
true
)
})
it('should not redirect to the sharing-updates page, and should load the editor', function (done) {
this.res.render = (pageName, opts) => {
done()
}
this.ProjectController.loadEditor(this.req, this.res)
})
it('should redirect to the sharing-updates page', function (done) {
this.res.redirect = url => {
expect(url).to.equal(`/project/${this.project_id}/sharing-updates`)
done()
}
this.ProjectController.loadEditor(this.req, this.res)
})
})
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 user is a read write token member but also a named editor', function () {
beforeEach(function () {
this.CollaboratorsGetter.promises.userIsTokenMember.resolves(true)
this.CollaboratorsGetter.promises.userIsReadWriteTokenMember.resolves(
true
)
this.CollaboratorsGetter.promises.isUserInvitedReadWriteMemberOfProject.resolves(
true
)
})
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)
})
it('should not redirect to the sharing-updates page, and should load the editor', function (done) {
this.res.render = (pageName, opts) => {
done()
}
this.ProjectController.loadEditor(this.req, this.res)
})
})
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('chatEnabled flag', function () {
it('should be set to false when the feature is disabled', function (done) {
this.Features.hasFeature = sinon.stub().withArgs('chat').returns(false)
@@ -192,10 +192,22 @@ describe('TokenAccessController', function () {
})
describe('grantTokenAccessReadAndWrite', function () {
describe('normal case', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
})
describe('normal case (edit slot available)', function () {
beforeEach(function (done) {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
this.req.params = { token: this.token }
this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
this.req.body = {
confirmedByUser: true,
tokenHashPrefix: '#prefix',
}
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
@@ -204,10 +216,15 @@ describe('TokenAccessController', function () {
)
})
it('grants read and write access', function () {
it('adds the user as a read and write invited member', function () {
expect(
this.TokenAccessHandler.promises.addReadAndWriteUserToProject
).to.have.been.calledWith(this.user._id, this.project._id)
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
})
it('writes a project audit log', function () {
@@ -215,13 +232,32 @@ describe('TokenAccessController', function () {
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'join-via-token',
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readAndWrite' }
)
})
it('records a project-joined event for the user', function () {
expect(
this.AnalyticsManager.recordEventForUserInBackground
).to.have.been.calledWith(this.user._id, 'project-joined', {
mode: 'read-write',
projectId: this.project._id.toString(),
})
})
it('emits a project membership changed event', function () {
expect(
this.EditorRealTimeController.emitToRoom
).to.have.been.calledWith(
this.project._id,
'project:membership:changed',
{ members: true, invites: true }
)
})
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
@@ -235,262 +271,78 @@ describe('TokenAccessController', function () {
})
})
describe('when project owner in link-sharing-warning split test', function () {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.callsFake(
async (userId, test) => {
if (test === 'link-sharing-warning') {
return { variant: 'active' }
}
}
describe('when there are no edit collaborator slots available', function () {
beforeEach(function (done) {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
false
)
})
it('tells the ui to show the link-sharing-warning variant', async function () {
this.req.params = { token: this.token }
this.req.body = { tokenHashPrefix: '#prefix' }
await this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req.body = {
confirmedByUser: true,
tokenHashPrefix: '#prefix',
}
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
{
json: content => {
expect(content).to.deep.equal({
requireAccept: {
linkSharingChanges: true,
projectName: this.project.name,
},
})
},
}
this.res,
done
)
})
describe('normal case', function () {
beforeEach(function (done) {
this.req.params = { token: this.token }
this.req.body = { confirmedByUser: true, tokenHashPrefix: '#prefix' }
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
this.res,
done
)
})
it('adds the user as a read only invited member instead (pendingEditor)', function () {
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_ONLY,
{ pendingEditor: true }
)
})
it('adds the user as a read and write invited member', function () {
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
})
it('writes a project audit log', function () {
expect(
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readOnly', pendingEditor: true }
)
})
it('writes a project audit log', function () {
expect(
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readAndWrite' }
)
})
it('records a project-joined event for the user', function () {
expect(
this.AnalyticsManager.recordEventForUserInBackground
).to.have.been.calledWith(this.user._id, 'project-joined', {
mode: 'read-write',
projectId: this.project._id.toString(),
})
})
it('emits a project membership changed event', function () {
expect(
this.EditorRealTimeController.emitToRoom
).to.have.been.calledWith(
this.project._id,
'project:membership:changed',
{ members: true, invites: true }
)
})
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
).to.have.been.calledWith(
this.token,
'#prefix',
'readAndWrite',
this.user._id,
{ projectId: this.project._id, action: 'continue' }
)
it('records a project-joined event for the user', function () {
expect(
this.AnalyticsManager.recordEventForUserInBackground
).to.have.been.calledWith(this.user._id, 'project-joined', {
mode: 'read-only',
projectId: this.project._id.toString(),
pendingEditor: true,
})
})
describe('when the project owner is in the link-sharing-enforcement split test', 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('emits a project membership changed event', function () {
expect(
this.EditorRealTimeController.emitToRoom
).to.have.been.calledWith(
this.project._id,
'project:membership:changed',
{ members: true, invites: true }
)
})
describe('normal case (edit slot available)', function () {
beforeEach(function (done) {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
this.req.params = { token: this.token }
this.req.body = {
confirmedByUser: true,
tokenHashPrefix: '#prefix',
}
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
this.res,
done
)
})
it('adds the user as a read and write invited member', function () {
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
})
it('writes a project audit log', function () {
expect(
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readAndWrite' }
)
})
it('records a project-joined event for the user', function () {
expect(
this.AnalyticsManager.recordEventForUserInBackground
).to.have.been.calledWith(this.user._id, 'project-joined', {
mode: 'read-write',
projectId: this.project._id.toString(),
})
})
it('emits a project membership changed event', function () {
expect(
this.EditorRealTimeController.emitToRoom
).to.have.been.calledWith(
this.project._id,
'project:membership:changed',
{ members: true, invites: true }
)
})
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
).to.have.been.calledWith(
this.token,
'#prefix',
'readAndWrite',
this.user._id,
{ projectId: this.project._id, action: 'continue' }
)
})
})
describe('when there are no edit collaborator slots available', function () {
beforeEach(function (done) {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
false
)
this.req.params = { token: this.token }
this.req.body = {
confirmedByUser: true,
tokenHashPrefix: '#prefix',
}
this.res.callback = done
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
this.res,
done
)
})
it('adds the user as a read only invited member instead (pendingEditor)', function () {
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_ONLY,
{ pendingEditor: true }
)
})
it('writes a project audit log', function () {
expect(
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readOnly', pendingEditor: true }
)
})
it('records a project-joined event for the user', function () {
expect(
this.AnalyticsManager.recordEventForUserInBackground
).to.have.been.calledWith(this.user._id, 'project-joined', {
mode: 'read-only',
projectId: this.project._id.toString(),
pendingEditor: true,
})
})
it('emits a project membership changed event', function () {
expect(
this.EditorRealTimeController.emitToRoom
).to.have.been.calledWith(
this.project._id,
'project:membership:changed',
{ members: true, invites: true }
)
})
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
).to.have.been.calledWith(
this.token,
'#prefix',
'readAndWrite',
this.user._id,
{ projectId: this.project._id, action: 'continue' }
)
})
})
it('checks token hash', function () {
expect(
this.TokenAccessHandler.checkTokenHashPrefix
).to.have.been.calledWith(
this.token,
'#prefix',
'readAndWrite',
this.user._id,
{ projectId: this.project._id, action: 'continue' }
)
})
})
@@ -507,9 +359,16 @@ describe('TokenAccessController', function () {
)
})
it("doesn't write a project audit log", function () {
expect(this.ProjectAuditLogHandler.promises.addEntry).to.not.have.been
.called
it('writes a project audit log', function () {
expect(
this.ProjectAuditLogHandler.promises.addEntry
).to.have.been.calledWith(
this.project._id,
'accept-via-link-sharing',
this.user._id,
this.req.ip,
{ privileges: 'readAndWrite' }
)
})
it('checks token hash', function () {
@@ -537,10 +396,15 @@ describe('TokenAccessController', function () {
)
})
it('grants read and write access', function () {
it('adds the user as a read and write invited member', function () {
expect(
this.TokenAccessHandler.promises.addReadAndWriteUserToProject
).to.have.been.calledWith(this.user._id, this.project._id)
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
})
it('checks the hash prefix', function () {
@@ -820,8 +684,13 @@ describe('TokenAccessController', function () {
.resolves(projectFromInternalStaff)
this.res.callback = () => {
expect(
this.TokenAccessHandler.promises.addReadAndWriteUserToProject
).to.have.been.calledWith(admin._id, projectFromInternalStaff._id)
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
projectFromInternalStaff._id,
undefined,
admin._id,
PrivilegeLevels.READ_AND_WRITE
)
}
this.TokenAccessController.grantTokenAccessReadAndWrite(
this.req,
@@ -1151,138 +1020,77 @@ describe('TokenAccessController', function () {
this.req.params = { Project_id: this.project._id }
})
describe('read only invited viewer gaining edit access via link sharing', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
describe('when there are collaborator slots available', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
it('sets the privilege level to read and write for the invited viewer', function () {
expect(
this.CollaboratorsHandler.promises.setCollaboratorPrivilegeLevel
).to.have.been.calledWith(
this.project._id,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
describe('previously joined token access user moving to named collaborator', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
it('sets the privilege level to read and write for the invited viewer', function () {
expect(
this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
).to.have.been.calledWith(this.user._id, this.project._id)
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
describe('when link-sharing-enforcement test is active', function () {
beforeEach(function () {
this.SplitTestHandler.promises.getAssignmentForUser.resolves({
variant: 'active',
})
})
describe('when there are collaborator slots available', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
true
)
})
describe('previously joined token access user moving to named collaborator', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
it('sets the privilege level to read and write for the invited viewer', function () {
expect(
this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
).to.have.been.calledWith(this.user._id, this.project._id)
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
})
describe('when there are no edit collaborator slots available', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
describe('previously joined token access user moving to named collaborator', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
describe('previously joined token access user moving to named collaborator', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
it('sets the privilege level to read and write for the invited viewer', function () {
expect(
this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
).to.have.been.calledWith(this.user._id, this.project._id)
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_AND_WRITE
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
})
it('sets the privilege level to read only for the invited viewer (pendingEditor)', function () {
expect(
this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
).to.have.been.calledWith(this.user._id, this.project._id)
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_ONLY,
{ pendingEditor: true }
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
describe('when there are no edit collaborator slots available', function () {
beforeEach(function () {
this.LimitationsManager.promises.canAcceptEditCollaboratorInvite.resolves(
false
)
})
describe('previously joined token access user moving to named collaborator', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.res.callback = done
this.TokenAccessController.moveReadWriteToCollaborators(
this.req,
this.res,
done
)
})
it('sets the privilege level to read only for the invited viewer (pendingEditor)', function () {
expect(
this.TokenAccessHandler.promises.removeReadAndWriteUserFromProject
).to.have.been.calledWith(this.user._id, this.project._id)
expect(
this.CollaboratorsHandler.promises.addUserIdToProject
).to.have.been.calledWith(
this.project._id,
undefined,
this.user._id,
PrivilegeLevels.READ_ONLY,
{ pendingEditor: true }
)
expect(this.res.sendStatus).to.have.been.calledWith(204)
})
})
})