Merge pull request #15001 from overleaf/em-invite-audit-logs

Project audit logs for invite operations

GitOrigin-RevId: c2db4bc719f508c5bf33be2c59eddfb63fcdae25
This commit is contained in:
Eric Mc Sween
2023-10-10 08:10:39 -04:00
committed by Copybot
parent 03aaabd5cb
commit 33765cd650
10 changed files with 437 additions and 389 deletions

View File

@@ -12,6 +12,8 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
const SessionManager = require('../Authentication/SessionManager')
const { RateLimiter } = require('../../infrastructure/RateLimiter')
const { expressify } = require('../../util/promises')
const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
const Errors = require('../Errors/Errors')
// This rate limiter allows a different number of requests depending on the
// number of callaborators a user is allowed. This is implemented by providing
@@ -138,7 +140,20 @@ const CollaboratorsInviteController = {
email,
privileges
)
ProjectAuditLogHandler.addEntryInBackground(
projectId,
'send-invite',
sendingUserId,
req.ip,
{
inviteId: invite._id,
privileges,
}
)
logger.debug({ projectId, email, sendingUserId }, 'invite created')
EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
@@ -150,21 +165,40 @@ const CollaboratorsInviteController = {
async revokeInvite(req, res) {
const projectId = req.params.Project_id
const inviteId = req.params.invite_id
const user = SessionManager.getSessionUser(req.session)
logger.debug({ projectId, inviteId }, 'revoking invite')
await CollaboratorsInviteHandler.promises.revokeInvite(projectId, inviteId)
EditorRealTimeController.emitToRoom(
const invite = await CollaboratorsInviteHandler.promises.revokeInvite(
projectId,
'project:membership:changed',
{ invites: true }
inviteId
)
res.sendStatus(201)
if (invite != null) {
ProjectAuditLogHandler.addEntryInBackground(
projectId,
'revoke-invite',
user._id,
req.ip,
{
inviteId: invite._id,
privileges: invite.privileges,
}
)
EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
{ invites: true }
)
}
res.sendStatus(204)
},
async resendInvite(req, res) {
const projectId = req.params.Project_id
const inviteId = req.params.invite_id
const user = SessionManager.getSessionUser(req.session)
logger.debug({ projectId, inviteId }, 'resending invite')
const sendingUser = SessionManager.getSessionUser(req.session)
@@ -175,12 +209,25 @@ const CollaboratorsInviteController = {
return res.sendStatus(429)
}
await CollaboratorsInviteHandler.promises.resendInvite(
const invite = await CollaboratorsInviteHandler.promises.resendInvite(
projectId,
sendingUser,
inviteId
)
if (invite != null) {
ProjectAuditLogHandler.addEntryInBackground(
projectId,
'resend-invite',
user._id,
req.ip,
{
inviteId: invite._id,
privileges: invite.privileges,
}
)
}
res.sendStatus(201)
},
@@ -248,21 +295,40 @@ const CollaboratorsInviteController = {
},
async acceptInvite(req, res) {
const projectId = req.params.Project_id
const { token } = req.params
const { Project_id: projectId, token } = req.params
const currentUser = SessionManager.getSessionUser(req.session)
logger.debug(
{ projectId, userId: currentUser._id },
'got request to accept invite'
)
await CollaboratorsInviteHandler.promises.acceptInvite(
const invite = await CollaboratorsInviteHandler.promises.getInviteByToken(
projectId,
token
)
if (invite == null) {
throw new Errors.NotFoundError('no matching invite found')
}
await ProjectAuditLogHandler.promises.addEntry(
projectId,
'accept-invite',
currentUser._id,
req.ip,
{
inviteId: invite._id,
privileges: invite.privileges,
}
)
await CollaboratorsInviteHandler.promises.acceptInvite(
invite,
projectId,
token,
currentUser
)
EditorRealTimeController.emitToRoom(
await EditorRealTimeController.emitToRoom(
projectId,
'project:membership:changed',
{ invites: true, members: true }

View File

@@ -5,7 +5,6 @@ const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
const CollaboratorsHandler = require('./CollaboratorsHandler')
const UserGetter = require('../User/UserGetter')
const ProjectGetter = require('../Project/ProjectGetter')
const Errors = require('../Errors/Errors')
const Crypto = require('crypto')
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
@@ -107,7 +106,10 @@ const CollaboratorsInviteHandler = {
async revokeInvite(projectId, inviteId) {
logger.debug({ projectId, inviteId }, 'removing invite')
await ProjectInvite.deleteOne({ projectId, _id: inviteId }).exec()
const invite = await ProjectInvite.findOneAndDelete({
projectId,
_id: inviteId,
}).exec()
CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch(
err => {
logger.err(
@@ -116,6 +118,7 @@ const CollaboratorsInviteHandler = {
)
}
)
return invite
},
async resendInvite(projectId, sendingUser, inviteId) {
@@ -127,7 +130,7 @@ const CollaboratorsInviteHandler = {
if (invite == null) {
logger.warn({ projectId, inviteId }, 'no invite found, nothing to resend')
return
return null
}
await CollaboratorsInviteHandler._sendMessages(
@@ -135,6 +138,8 @@ const CollaboratorsInviteHandler = {
sendingUser,
invite
)
return invite
},
async getInviteByToken(projectId, tokenString) {
@@ -152,17 +157,7 @@ const CollaboratorsInviteHandler = {
return invite
},
async acceptInvite(projectId, tokenString, user) {
logger.debug({ projectId, userId: user._id }, 'accepting invite')
const invite = await CollaboratorsInviteHandler.getInviteByToken(
projectId,
tokenString
)
if (!invite) {
throw new Errors.NotFoundError('no matching invite found')
}
const inviteId = invite._id
async acceptInvite(invite, projectId, user) {
CollaboratorsHandler.promises.addUserIdToProject(
projectId,
invite.sendingUserId,
@@ -171,6 +166,7 @@ const CollaboratorsInviteHandler = {
)
// Remove invite
const inviteId = invite._id
logger.debug({ projectId, inviteId }, 'removing invite')
await ProjectInvite.deleteOne({ _id: inviteId }).exec()
CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch(

View File

@@ -49,6 +49,7 @@ async function transferOwnership(projectId, newOwnerId, options = {}) {
projectId,
'transfer-ownership',
sessionUserId,
'', // IP address
{ previousOwnerId, newOwnerId }
)
await _transferOwnership(projectId, previousOwnerId, newOwnerId)

View File

@@ -1,3 +1,4 @@
const logger = require('@overleaf/logger')
const { ProjectAuditLogEntry } = require('../../models/ProjectAuditLogEntry')
const { callbackify } = require('../../util/promises')
@@ -5,7 +6,8 @@ module.exports = {
promises: {
addEntry,
},
addEntry: callbackify(addEntry), // callback version of adEntry
addEntry: callbackify(addEntry), // callback version of addEntry
addEntryInBackground,
}
/**
@@ -17,12 +19,39 @@ module.exports = {
* - userId: the user on behalf of whom the operation was performed
* - message: a string detailing what happened
*/
async function addEntry(projectId, operation, initiatorId, info = {}) {
async function addEntry(
projectId,
operation,
initiatorId,
ipAddress,
info = {}
) {
const entry = {
projectId,
operation,
initiatorId,
ipAddress,
info,
}
await ProjectAuditLogEntry.create(entry)
}
/**
* Add an audit log entry in the background
*
* This function doesn't return a promise. Instead, it catches any error and logs it.
*/
function addEntryInBackground(
projectId,
operation,
initiatorId,
ipAddress,
info = {}
) {
addEntry(projectId, operation, initiatorId, ipAddress, info).catch(err => {
logger.error(
{ err, projectId, operation, initiatorId, ipAddress, info },
'Failed to write audit log'
)
})
}

View File

@@ -133,6 +133,7 @@ const ProjectController = {
projectId,
'toggle-access-level',
user._id,
req.ip,
{ publicAccessLevel: req.body.publicAccessLevel, status: 'OK' },
callback
)

View File

@@ -6,6 +6,7 @@ const ProjectAuditLogEntrySchema = new Schema(
projectId: { type: Schema.Types.ObjectId, index: true },
operation: { type: String },
initiatorId: { type: Schema.Types.ObjectId },
ipAddress: { type: String },
timestamp: { type: Date, default: Date.now },
info: { type: Object },
},

View File

@@ -4,21 +4,36 @@ const SandboxedModule = require('sandboxed-module')
const MockRequest = require('../helpers/MockRequest')
const MockResponse = require('../helpers/MockResponse')
const { ObjectId } = require('mongodb')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const MODULE_PATH =
'../../../../app/src/Features/Collaborators/CollaboratorsInviteController.js'
describe('CollaboratorsInviteController', function () {
beforeEach(function () {
this.user = { _id: 'id' }
this.AnalyticsManger = { recordEventForUser: sinon.stub() }
this.sendingUser = null
this.AuthenticationController = {
getSessionUser: req => {
this.sendingUser = req.session.user
return this.sendingUser
},
this.projectId = 'project-id-123'
this.token = 'some-opaque-token'
this.targetEmail = 'user@example.com'
this.privileges = 'readAndWrite'
this.currentUser = {
_id: 'current-user-id',
email: 'current-user@example.com',
}
this.invite = {
_id: ObjectId(),
token: this.token,
sendingUserId: this.currentUser._id,
projectId: this.projectId,
email: this.targetEmail,
privileges: this.privileges,
createdAt: new Date(),
}
this.SessionManager = {
getSessionUser: sinon.stub().returns(this.currentUser),
}
this.AnalyticsManger = { recordEventForUser: sinon.stub() }
this.rateLimiter = {
consume: sinon.stub().resolves(),
@@ -27,7 +42,13 @@ describe('CollaboratorsInviteController', function () {
RateLimiter: sinon.stub().returns(this.rateLimiter),
}
this.LimitationsManager = { promises: {} }
this.LimitationsManager = {
promises: {
allowedNumberOfCollaboratorsForUser: sinon.stub(),
canAddXCollaborators: sinon.stub().resolves(true),
},
}
this.UserGetter = {
promises: {
getUserByAnyEmail: sinon.stub(),
@@ -35,34 +56,61 @@ describe('CollaboratorsInviteController', function () {
},
}
this.ProjectGetter = { promises: {} }
this.CollaboratorsGetter = { promises: {} }
this.CollaboratorsInviteHandler = { promises: {} }
this.ProjectGetter = {
promises: {
getProject: sinon.stub(),
},
}
this.CollaboratorsGetter = {
promises: {
isUserInvitedMemberOfProject: sinon.stub(),
},
}
this.CollaboratorsInviteHandler = {
promises: {
getAllInvites: sinon.stub(),
inviteToProject: sinon.stub().resolves(this.invite),
getInviteByToken: sinon.stub().resolves(this.invite),
resendInvite: sinon.stub().resolves(this.invite),
revokeInvite: sinon.stub().resolves(this.invite),
acceptInvite: sinon.stub(),
},
}
this.EditorRealTimeController = {
emitToRoom: sinon.stub(),
}
this.settings = {}
this.ProjectAuditLogHandler = {
promises: {
addEntry: sinon.stub().resolves(),
},
addEntryInBackground: sinon.stub(),
}
this.CollaboratorsInviteController = SandboxedModule.require(MODULE_PATH, {
requires: {
'../Project/ProjectGetter': this.ProjectGetter,
'../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
'../Subscription/LimitationsManager': this.LimitationsManager,
'../User/UserGetter': this.UserGetter,
'./CollaboratorsGetter': this.CollaboratorsGetter,
'./CollaboratorsInviteHandler': this.CollaboratorsInviteHandler,
'../Editor/EditorRealTimeController': this.EditorRealTimeController,
'../Analytics/AnalyticsManager': this.AnalyticsManger,
'../Authentication/AuthenticationController':
this.AuthenticationController,
'../Authentication/SessionManager': this.SessionManager,
'@overleaf/settings': this.settings,
'../../infrastructure/RateLimiter': this.RateLimiter,
},
})
this.res = new MockResponse()
this.req = new MockRequest()
this.project_id = 'project-id-123'
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('getAllInvites', function () {
@@ -71,17 +119,15 @@ describe('CollaboratorsInviteController', function () {
{ _id: ObjectId(), one: 1 },
{ _id: ObjectId(), two: 2 },
]
this.req.params = { Project_id: this.project_id }
this.res.json = sinon.stub()
this.next = sinon.stub()
this.req.params = { Project_id: this.projectId }
})
describe('when all goes well', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.getAllInvites = sinon
.stub()
.resolves(this.fakeInvites)
this.res.json.callsFake(() => done())
this.CollaboratorsInviteHandler.promises.getAllInvites.resolves(
this.fakeInvites
)
this.res.callback = () => done()
this.CollaboratorsInviteController.getAllInvites(
this.req,
this.res,
@@ -105,16 +151,16 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsInviteHandler.promises.getAllInvites
.calledWith(this.project_id)
.calledWith(this.projectId)
.should.equal(true)
})
})
describe('when CollaboratorsInviteHandler.getAllInvites produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.getAllInvites = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsInviteHandler.promises.getAllInvites.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.getAllInvites(
this.req,
@@ -132,32 +178,11 @@ describe('CollaboratorsInviteController', function () {
describe('inviteToProject', function () {
beforeEach(function () {
this.targetEmail = 'user@example.com'
this.req.params = { Project_id: this.project_id }
this.current_user = { _id: (this.current_user_id = 'current-user-id') }
this.req.session = { user: this.current_user }
this.req.params = { Project_id: this.projectId }
this.req.body = {
email: this.targetEmail,
privileges: (this.privileges = 'readAndWrite'),
privileges: this.privileges,
}
this.res.json = sinon.stub()
this.res.sendStatus = sinon.stub()
this.invite = {
_id: ObjectId(),
token: 'htnseuthaouse',
sendingUserId: this.current_user_id,
projectId: this.targetEmail,
targetEmail: 'user@example.com',
createdAt: new Date(),
}
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteHandler.promises.inviteToProject = sinon
.stub()
.resolves(this.invite)
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('when all goes well', function (done) {
@@ -167,10 +192,7 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.res.json.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
@@ -190,7 +212,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.LimitationsManager.promises.canAddXCollaborators
.calledWith(this.project_id)
.calledWith(this.projectId)
.should.equal(true)
})
@@ -209,8 +231,8 @@ describe('CollaboratorsInviteController', function () {
)
this.CollaboratorsInviteHandler.promises.inviteToProject
.calledWith(
this.project_id,
this.current_user,
this.projectId,
this.currentUser,
this.targetEmail,
this.privileges
)
@@ -220,9 +242,22 @@ describe('CollaboratorsInviteController', function () {
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:membership:changed')
.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 collaborators', function () {
@@ -232,10 +267,8 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(false)
this.res.json.callsFake(() => done())
this.LimitationsManager.promises.canAddXCollaborators.resolves(false)
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
@@ -253,7 +286,7 @@ describe('CollaboratorsInviteController', function () {
0
)
this.CollaboratorsInviteController.promises._checkShouldInviteEmail
.calledWith(this.sendingUser, this.targetEmail)
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
@@ -271,9 +304,9 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.rejects(new Error('woops'))
this.LimitationsManager.promises.canAddXCollaborators.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.inviteToProject(
this.req,
@@ -292,7 +325,7 @@ describe('CollaboratorsInviteController', function () {
0
)
this.CollaboratorsInviteController.promises._checkShouldInviteEmail
.calledWith(this.sendingUser, this.targetEmail)
.calledWith(this.currentUser, this.targetEmail)
.should.equal(false)
})
@@ -310,9 +343,9 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteHandler.promises.inviteToProject = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsInviteHandler.promises.inviteToProject.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.inviteToProject(
this.req,
@@ -331,7 +364,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.LimitationsManager.promises.canAddXCollaborators
.calledWith(this.project_id)
.calledWith(this.projectId)
.should.equal(true)
})
@@ -350,8 +383,8 @@ describe('CollaboratorsInviteController', function () {
)
this.CollaboratorsInviteHandler.promises.inviteToProject
.calledWith(
this.project_id,
this.current_user,
this.projectId,
this.currentUser,
this.targetEmail,
this.privileges
)
@@ -366,10 +399,7 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.res.json.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
@@ -408,9 +438,6 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.inviteToProject(
this.req,
@@ -442,16 +469,12 @@ describe('CollaboratorsInviteController', function () {
describe('when the user invites themselves to the project', function () {
beforeEach(function () {
this.req.session.user = { _id: 'abc', email: 'me@example.com' }
this.req.body.email = 'me@example.com'
this.req.body.email = this.currentUser.email
this.CollaboratorsInviteController.promises._checkShouldInviteEmail =
sinon.stub().resolves(true)
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
@@ -497,10 +520,7 @@ describe('CollaboratorsInviteController', function () {
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(false)
this.LimitationsManager.promises.canAddXCollaborators = sinon
.stub()
.resolves(true)
this.res.sendStatus.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.inviteToProject(
this.req,
this.res,
@@ -526,27 +546,12 @@ describe('CollaboratorsInviteController', function () {
describe('viewInvite', function () {
beforeEach(function () {
this.token = 'some-opaque-token'
this.req.params = {
Project_id: this.project_id,
Project_id: this.projectId,
token: this.token,
}
this.req.session = {
user: { _id: (this.current_user_id = 'current-user-id') },
}
this.res.render = sinon.stub()
this.res.redirect = sinon.stub()
this.res.sendStatus = sinon.stub()
this.invite = {
_id: ObjectId(),
token: this.token,
sendingUserId: ObjectId(),
projectId: this.project_id,
targetEmail: 'user@example.com',
createdAt: new Date(),
}
this.fakeProject = {
_id: this.project_id,
_id: this.projectId,
name: 'some project',
owner_ref: this.invite.sendingUserId,
collaberator_refs: [],
@@ -559,24 +564,19 @@ describe('CollaboratorsInviteController', function () {
email: 'john@example.com',
}
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject = sinon
.stub()
.resolves(false)
this.CollaboratorsInviteHandler.promises.getInviteByToken = sinon
.stub()
.resolves(this.invite)
this.ProjectGetter.promises.getProject = sinon
.stub()
.resolves(this.fakeProject)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
false
)
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(
this.invite
)
this.ProjectGetter.promises.getProject.resolves(this.fakeProject)
this.UserGetter.promises.getUser.resolves(this.owner)
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('when the token is valid', function () {
beforeEach(function (done) {
this.res.render.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@@ -598,7 +598,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -621,17 +621,17 @@ describe('CollaboratorsInviteController', function () {
it('should call ProjectGetter.getProject', function () {
this.ProjectGetter.promises.getProject.callCount.should.equal(1)
this.ProjectGetter.promises.getProject
.calledWith(this.project_id)
.calledWith(this.projectId)
.should.equal(true)
})
})
describe('when user is already a member of the project', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject = sinon
.stub()
.resolves(true)
this.res.redirect.callsFake(() => done())
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.resolves(
true
)
this.res.callback = () => done()
this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@@ -642,7 +642,7 @@ describe('CollaboratorsInviteController', function () {
it('should redirect to the project page', function () {
this.res.redirect.callCount.should.equal(1)
this.res.redirect
.calledWith(`/project/${this.project_id}`)
.calledWith(`/project/${this.projectId}`)
.should.equal(true)
})
@@ -655,7 +655,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -676,9 +676,9 @@ describe('CollaboratorsInviteController', function () {
describe('when isUserInvitedMemberOfProject produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.viewInvite(
this.req,
@@ -697,7 +697,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -739,7 +739,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -748,7 +748,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -764,7 +764,7 @@ describe('CollaboratorsInviteController', function () {
describe('when the getInviteByToken does not produce an invite', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(null)
this.res.render.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@@ -788,7 +788,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -797,7 +797,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -831,7 +831,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -856,7 +856,7 @@ describe('CollaboratorsInviteController', function () {
describe('when User.getUser does not find a user', function () {
beforeEach(function (done) {
this.UserGetter.promises.getUser.resolves(null)
this.res.render.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@@ -880,7 +880,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -923,7 +923,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -948,7 +948,7 @@ describe('CollaboratorsInviteController', function () {
describe('when Project.getUser does not find a user', function () {
beforeEach(function (done) {
this.ProjectGetter.promises.getProject.resolves(null)
this.res.render.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.viewInvite(
this.req,
this.res,
@@ -972,7 +972,7 @@ describe('CollaboratorsInviteController', function () {
1
)
this.CollaboratorsGetter.promises.isUserInvitedMemberOfProject
.calledWith(this.current_user_id, this.project_id)
.calledWith(this.currentUser._id, this.projectId)
.should.equal(true)
})
@@ -998,27 +998,17 @@ describe('CollaboratorsInviteController', function () {
describe('resendInvite', function () {
beforeEach(function () {
this.req.params = {
Project_id: this.project_id,
invite_id: (this.invite_id = 'thuseoautoh'),
Project_id: this.projectId,
invite_id: this.invite._id.toString(),
}
this.req.session = {
user: { _id: (this.current_user_id = 'current-user-id') },
}
this.res.render = sinon.stub()
this.res.sendStatus = sinon.stub()
this.CollaboratorsInviteHandler.promises.resendInvite = sinon
.stub()
.resolves(null)
this.CollaboratorsInviteController.promises._checkRateLimit = sinon
.stub()
.resolves(true)
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('when resendInvite does not produce an error', function () {
beforeEach(function (done) {
this.res.sendStatus.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.resendInvite(
this.req,
this.res,
@@ -1042,13 +1032,26 @@ describe('CollaboratorsInviteController', function () {
1
)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'resend-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
describe('when resendInvite produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.resendInvite = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsInviteHandler.promises.resendInvite.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.resendInvite(
this.req,
@@ -1077,23 +1080,14 @@ describe('CollaboratorsInviteController', function () {
describe('revokeInvite', function () {
beforeEach(function () {
this.req.params = {
Project_id: this.project_id,
invite_id: (this.invite_id = 'thuseoautoh'),
Project_id: this.projectId,
invite_id: this.invite._id.toString(),
}
this.current_user = { _id: (this.current_user_id = 'current-user-id') }
this.req.session = { user: this.current_user }
this.res.render = sinon.stub()
this.res.sendStatus = sinon.stub()
this.CollaboratorsInviteHandler.promises.revokeInvite = sinon
.stub()
.resolves(null)
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('when revokeInvite does not produce an error', function () {
beforeEach(function (done) {
this.res.sendStatus.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.revokeInvite(
this.req,
this.res,
@@ -1101,9 +1095,9 @@ describe('CollaboratorsInviteController', function () {
)
})
it('should produce a 201 response', function () {
it('should produce a 204 response', function () {
this.res.sendStatus.callCount.should.equal(1)
this.res.sendStatus.calledWith(201).should.equal(true)
this.res.sendStatus.should.have.been.calledWith(204)
})
it('should have called revokeInvite', function () {
@@ -1115,16 +1109,29 @@ describe('CollaboratorsInviteController', function () {
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:membership:changed')
.calledWith(this.projectId, 'project:membership:changed')
.should.equal(true)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.addEntryInBackground.should.have.been.calledWith(
this.projectId,
'revoke-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
describe('when revokeInvite produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.revokeInvite = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsInviteHandler.promises.revokeInvite.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.revokeInvite(
this.req,
@@ -1153,24 +1160,14 @@ describe('CollaboratorsInviteController', function () {
describe('acceptInvite', function () {
beforeEach(function () {
this.req.params = {
Project_id: this.project_id,
token: (this.token = 'mock-token'),
Project_id: this.projectId,
token: this.token,
}
this.req.session = {
user: { _id: (this.current_user_id = 'current-user-id') },
}
this.res.render = sinon.stub()
this.res.redirect = sinon.stub()
this.CollaboratorsInviteHandler.promises.acceptInvite = sinon
.stub()
.resolves(null)
this.callback = sinon.stub()
this.next = sinon.stub()
})
describe('when acceptInvite does not produce an error', function () {
beforeEach(function (done) {
this.res.redirect.callsFake(() => done())
this.res.callback = () => done()
this.CollaboratorsInviteController.acceptInvite(
this.req,
this.res,
@@ -1179,31 +1176,65 @@ describe('CollaboratorsInviteController', function () {
})
it('should redirect to project page', function () {
this.res.redirect.callCount.should.equal(1)
this.res.redirect
.calledWith(`/project/${this.project_id}`)
.should.equal(true)
this.res.redirect.should.have.been.calledOnce
this.res.redirect.should.have.been.calledWith(
`/project/${this.projectId}`
)
})
it('should have called acceptInvite', function () {
this.CollaboratorsInviteHandler.promises.acceptInvite
.calledWith(this.project_id, this.token)
.should.equal(true)
this.CollaboratorsInviteHandler.promises.acceptInvite.should.have.been.calledWith(
this.invite,
this.projectId,
this.currentUser
)
})
it('should have called emitToRoom', function () {
this.EditorRealTimeController.emitToRoom.callCount.should.equal(1)
this.EditorRealTimeController.emitToRoom
.calledWith(this.project_id, 'project:membership:changed')
.should.equal(true)
this.EditorRealTimeController.emitToRoom.should.have.been.calledOnce
this.EditorRealTimeController.emitToRoom.should.have.been.calledWith(
this.projectId,
'project:membership:changed'
)
})
it('should add a project audit log entry', function () {
this.ProjectAuditLogHandler.promises.addEntry.should.have.been.calledWith(
this.projectId,
'accept-invite',
this.currentUser._id,
this.req.ip,
{
inviteId: this.invite._id,
privileges: this.privileges,
}
)
})
})
describe('when revokeInvite produces an error', function () {
describe('when the invite is not found', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.acceptInvite = sinon
.stub()
.rejects(new Error('woops'))
this.CollaboratorsInviteHandler.promises.getInviteByToken.resolves(null)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.acceptInvite(
this.req,
this.res,
this.next
)
})
it('throws a NotFoundError', function () {
expect(this.next).to.have.been.calledWith(
sinon.match.instanceOf(Errors.NotFoundError)
)
})
})
describe('when acceptInvite produces an error', function () {
beforeEach(function (done) {
this.CollaboratorsInviteHandler.promises.acceptInvite.rejects(
new Error('woops')
)
this.next.callsFake(() => done())
this.CollaboratorsInviteController.acceptInvite(
this.req,
@@ -1227,6 +1258,23 @@ describe('CollaboratorsInviteController', function () {
)
})
})
describe('when the project audit log entry fails', function () {
beforeEach(function (done) {
this.ProjectAuditLogHandler.promises.addEntry.rejects(new Error('oops'))
this.next.callsFake(() => done())
this.CollaboratorsInviteController.acceptInvite(
this.req,
this.res,
this.next
)
})
it('should not accept the invite', function () {
this.CollaboratorsInviteHandler.promises.acceptInvite.should.not.have
.been.called
})
})
})
describe('_checkShouldInviteEmail', function () {
@@ -1248,9 +1296,7 @@ describe('CollaboratorsInviteController', function () {
describe('when user account is present', function () {
beforeEach(function () {
this.user = { _id: ObjectId().toString() }
this.UserGetter.promises.getUserByAnyEmail = sinon
.stub()
.resolves(this.user)
this.UserGetter.promises.getUserByAnyEmail.resolves(this.user)
})
it('should callback with `true`', function (done) {
@@ -1265,9 +1311,7 @@ describe('CollaboratorsInviteController', function () {
describe('when user account is absent', function () {
beforeEach(function () {
this.user = null
this.UserGetter.promises.getUserByAnyEmail = sinon
.stub()
.resolves(this.user)
this.UserGetter.promises.getUserByAnyEmail.resolves(this.user)
})
it('should callback with `false`', function (done) {
@@ -1295,9 +1339,7 @@ describe('CollaboratorsInviteController', function () {
describe('when getUser produces an error', function () {
beforeEach(function () {
this.user = null
this.UserGetter.promises.getUserByAnyEmail = sinon
.stub()
.rejects(new Error('woops'))
this.UserGetter.promises.getUserByAnyEmail.rejects(new Error('woops'))
})
it('should callback with an error', function (done) {
@@ -1315,23 +1357,21 @@ describe('CollaboratorsInviteController', function () {
describe('_checkRateLimit', function () {
beforeEach(function () {
this.settings.restrictInvitesToExistingAccounts = false
this.sendingUserId = '32312313'
this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser =
sinon.stub()
this.currentUserId = '32312313'
this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser
.withArgs(this.sendingUserId)
.withArgs(this.currentUserId)
.resolves(17)
})
it('should callback with `true` when rate limit under', function (done) {
this.CollaboratorsInviteController._checkRateLimit(
this.sendingUserId,
this.currentUserId,
(err, result) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(
this.sendingUserId
this.currentUserId
)
result.should.equal(true)
done()
@@ -1342,13 +1382,13 @@ describe('CollaboratorsInviteController', function () {
it('should callback with `false` when rate limit hit', function (done) {
this.rateLimiter.consume.rejects({ remainingPoints: 0 })
this.CollaboratorsInviteController._checkRateLimit(
this.sendingUserId,
this.currentUserId,
(err, result) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(
this.sendingUserId
this.currentUserId
)
result.should.equal(false)
done()
@@ -1358,13 +1398,13 @@ describe('CollaboratorsInviteController', function () {
it('should allow 10x the collaborators', function (done) {
this.CollaboratorsInviteController._checkRateLimit(
this.sendingUserId,
this.currentUserId,
(err, result) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(
this.sendingUserId,
this.currentUserId,
Math.floor(40000 / 170)
)
done()
@@ -1374,16 +1414,16 @@ describe('CollaboratorsInviteController', function () {
it('should allow 200 requests when collaborators is -1', function (done) {
this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser
.withArgs(this.sendingUserId)
.withArgs(this.currentUserId)
.resolves(-1)
this.CollaboratorsInviteController._checkRateLimit(
this.sendingUserId,
this.currentUserId,
(err, result) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(
this.sendingUserId,
this.currentUserId,
Math.floor(40000 / 200)
)
done()
@@ -1393,16 +1433,16 @@ describe('CollaboratorsInviteController', function () {
it('should allow 10 requests when user has no collaborators set', function (done) {
this.LimitationsManager.promises.allowedNumberOfCollaboratorsForUser
.withArgs(this.sendingUserId)
.withArgs(this.currentUserId)
.resolves(null)
this.CollaboratorsInviteController._checkRateLimit(
this.sendingUserId,
this.currentUserId,
(err, result) => {
if (err) {
return done(err)
}
expect(this.rateLimiter.consume).to.have.been.calledWith(
this.sendingUserId,
this.currentUserId,
Math.floor(40000 / 10)
)
done()

View File

@@ -3,7 +3,6 @@ const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const { ObjectId } = require('mongodb')
const Crypto = require('crypto')
const Errors = require('../../../../app/src/Features/Errors/Errors')
const MODULE_PATH =
'../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.js'
@@ -26,6 +25,7 @@ describe('CollaboratorsInviteHandler', function () {
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 = {
@@ -177,8 +177,6 @@ describe('CollaboratorsInviteHandler', function () {
})
describe('when all goes well', function () {
beforeEach(function () {})
it('should produce the invite object', async function () {
const invite = await this.call()
expect(invite).to.not.equal(null)
@@ -296,13 +294,13 @@ describe('CollaboratorsInviteHandler', function () {
describe('revokeInvite', function () {
beforeEach(function () {
this.ProjectInvite.deleteOne.returns({
exec: sinon.stub().resolves(),
this.ProjectInvite.findOneAndDelete.returns({
exec: sinon.stub().resolves(this.fakeInvite),
})
this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification =
sinon.stub().resolves()
this.call = async () => {
await this.CollaboratorsInviteHandler.promises.revokeInvite(
return await this.CollaboratorsInviteHandler.promises.revokeInvite(
this.projectId,
this.inviteId
)
@@ -310,14 +308,13 @@ describe('CollaboratorsInviteHandler', function () {
})
describe('when all goes well', function () {
beforeEach(function () {})
it('should call ProjectInvite.deleteOne', async function () {
it('should call ProjectInvite.findOneAndDelete', async function () {
await this.call()
this.ProjectInvite.deleteOne.callCount.should.equal(1)
this.ProjectInvite.deleteOne
.calledWith({ projectId: this.projectId, _id: this.inviteId })
.should.equal(true)
this.ProjectInvite.findOneAndDelete.should.have.been.calledOnce
this.ProjectInvite.findOneAndDelete.should.have.been.calledWith({
projectId: this.projectId,
_id: this.inviteId,
})
})
it('should call _tryCancelInviteNotification', async function () {
@@ -329,11 +326,16 @@ describe('CollaboratorsInviteHandler', function () {
.calledWith(this.inviteId)
.should.equal(true)
})
it('should return the deleted invite', async function () {
const invite = await this.call()
expect(invite).to.deep.equal(this.fakeInvite)
})
})
describe('when remove produces an error', function () {
beforeEach(function () {
this.ProjectInvite.deleteOne.returns({
this.ProjectInvite.findOneAndDelete.returns({
exec: sinon.stub().rejects(new Error('woops')),
})
})
@@ -353,7 +355,7 @@ describe('CollaboratorsInviteHandler', function () {
.stub()
.resolves()
this.call = async () => {
await this.CollaboratorsInviteHandler.promises.resendInvite(
return await this.CollaboratorsInviteHandler.promises.resendInvite(
this.projectId,
this.sendingUser,
this.inviteId
@@ -362,8 +364,6 @@ describe('CollaboratorsInviteHandler', function () {
})
describe('when all goes well', function () {
beforeEach(function () {})
it('should call ProjectInvite.findOne', async function () {
await this.call()
this.ProjectInvite.findOne.callCount.should.equal(1)
@@ -381,6 +381,11 @@ describe('CollaboratorsInviteHandler', function () {
.calledWith(this.projectId, this.sendingUser, this.fakeInvite)
.should.equal(true)
})
it('should return the invite', async function () {
const invite = await this.call()
expect(invite).to.deep.equal(this.fakeInvite)
})
})
describe('when findOne produces an error', function () {
@@ -480,49 +485,30 @@ describe('CollaboratorsInviteHandler', function () {
readOnly_refs: [],
}
this.CollaboratorsHandler.promises.addUserIdToProject.resolves()
this._getInviteByToken = sinon.stub(
this.CollaboratorsInviteHandler.promises,
'getInviteByToken'
)
this._getInviteByToken.resolves(this.fakeInvite)
this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification =
sinon.stub().resolves()
this.ProjectInvite.deleteOne.returns({ exec: sinon.stub().resolves() })
this.call = async () => {
await this.CollaboratorsInviteHandler.promises.acceptInvite(
this.fakeInvite,
this.projectId,
this.token,
this.user
)
}
})
afterEach(function () {
this._getInviteByToken.restore()
})
describe('when all goes well', function () {
it('should have called getInviteByToken', async function () {
await this.call()
this._getInviteByToken.callCount.should.equal(1)
this._getInviteByToken
.calledWith(this.projectId, this.token)
.should.equal(true)
})
it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
await this.call()
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
1
)
this.CollaboratorsHandler.promises.addUserIdToProject
.calledWith(
this.projectId,
this.sendingUserId,
this.userId,
this.fakeInvite.privileges
)
.should.equal(true)
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
this.projectId,
this.sendingUserId,
this.userId,
this.fakeInvite.privileges
)
})
it('should have called ProjectInvite.deleteOne', async function () {
@@ -537,7 +523,6 @@ describe('CollaboratorsInviteHandler', function () {
describe('when the invite is for readOnly access', function () {
beforeEach(function () {
this.fakeInvite.privileges = 'readOnly'
this._getInviteByToken.resolves(this.fakeInvite)
})
it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
@@ -556,66 +541,6 @@ describe('CollaboratorsInviteHandler', function () {
})
})
describe('when getInviteByToken does not find an invite', function () {
beforeEach(function () {
this._getInviteByToken.resolves(null)
})
it('should produce an error', async function () {
await expect(this.call()).to.be.rejectedWith(Errors.NotFoundError)
})
it('should have called getInviteByToken', async function () {
await expect(this.call()).to.be.rejected
this._getInviteByToken.callCount.should.equal(1)
this._getInviteByToken
.calledWith(this.projectId, this.token)
.should.equal(true)
})
it('should not have called CollaboratorsHandler.addUserIdToProject', async function () {
await expect(this.call()).to.be.rejected
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
0
)
})
it('should not have called ProjectInvite.deleteOne', async function () {
await expect(this.call()).to.be.rejected
this.ProjectInvite.deleteOne.callCount.should.equal(0)
})
})
describe('when getInviteByToken produces an error', function () {
beforeEach(function () {
this._getInviteByToken.rejects(new Error('woops'))
})
it('should produce an error', async function () {
await expect(this.call()).to.be.rejectedWith(Error)
})
it('should have called getInviteByToken', async function () {
await expect(this.call()).to.be.rejected
this._getInviteByToken.callCount.should.equal(1)
this._getInviteByToken
.calledWith(this.projectId, this.token)
.should.equal(true)
})
it('should not have called CollaboratorsHandler.addUserIdToProject', async function () {
await expect(this.call()).to.be.rejected
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
0
)
})
it('should not have called ProjectInvite.deleteOne', async function () {
await expect(this.call()).to.be.rejected
this.ProjectInvite.deleteOne.callCount.should.equal(0)
})
})
describe('when addUserIdToProject produces an error', function () {
beforeEach(function () {
this.CollaboratorsHandler.promises.addUserIdToProject.callsArgWith(
@@ -628,14 +553,6 @@ describe('CollaboratorsInviteHandler', function () {
await expect(this.call()).to.be.rejectedWith(Error)
})
it('should have called getInviteByToken', async function () {
await expect(this.call()).to.be.rejected
this._getInviteByToken.callCount.should.equal(1)
this._getInviteByToken
.calledWith(this.projectId, this.token)
.should.equal(true)
})
it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
await expect(this.call()).to.be.rejected
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
@@ -668,27 +585,17 @@ describe('CollaboratorsInviteHandler', function () {
await expect(this.call()).to.be.rejectedWith(Error)
})
it('should have called getInviteByToken', async function () {
await expect(this.call()).to.be.rejected
this._getInviteByToken.callCount.should.equal(1)
this._getInviteByToken
.calledWith(this.projectId, this.token)
.should.equal(true)
})
it('should have called CollaboratorsHandler.addUserIdToProject', async function () {
await expect(this.call()).to.be.rejected
this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal(
1
)
this.CollaboratorsHandler.promises.addUserIdToProject
.calledWith(
this.projectId,
this.sendingUserId,
this.userId,
this.fakeInvite.privileges
)
.should.equal(true)
this.CollaboratorsHandler.promises.addUserIdToProject.should.have.been.calledWith(
this.projectId,
this.sendingUserId,
this.userId,
this.fakeInvite.privileges
)
})
it('should have called ProjectInvite.deleteOne', async function () {

View File

@@ -234,6 +234,7 @@ describe('OwnershipTransferHandler', function () {
this.project._id,
'transfer-ownership',
sessionUserId,
'', // IP address
{
previousOwnerId: this.user._id,
newOwnerId: this.collaborator._id,

View File

@@ -314,10 +314,16 @@ describe('ProjectController', function () {
}
this.res.sendStatus = code => {
this.ProjectAuditLogHandler.addEntry
.calledWith(this.project_id, 'toggle-access-level', this.user._id, {
publicAccessLevel: 'readOnly',
status: 'OK',
})
.calledWith(
this.project_id,
'toggle-access-level',
this.user._id,
this.req.ip,
{
publicAccessLevel: 'readOnly',
status: 'OK',
}
)
.should.equal(true)
done()
}