Merge pull request #2255 from overleaf/em-audit-log
Project audit logs GitOrigin-RevId: 439add2959be140c4f56ce9b41b9f59d432c494d
This commit is contained in:
committed by
sharelatex
parent
f6e4be616c
commit
06de9233b8
@@ -0,0 +1,80 @@
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const { Project } = require('../helpers/models/Project')
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/Project/ProjectAuditLogHandler'
|
||||
|
||||
describe('ProjectAuditLogHandler', function() {
|
||||
beforeEach(function() {
|
||||
this.projectId = ObjectId()
|
||||
this.userId = ObjectId()
|
||||
this.ProjectMock = sinon.mock(Project)
|
||||
this.ProjectAuditLogHandler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../../models/Project': { Project }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
this.ProjectMock.restore()
|
||||
})
|
||||
|
||||
describe('addEntry', function() {
|
||||
describe('success', function() {
|
||||
beforeEach(async function() {
|
||||
this.dbUpdate = this.ProjectMock.expects('updateOne').withArgs(
|
||||
{ _id: this.projectId },
|
||||
{
|
||||
$push: {
|
||||
auditLog: {
|
||||
$each: [
|
||||
{
|
||||
operation: 'translate',
|
||||
initiatorId: this.userId,
|
||||
info: { destinationLanguage: 'tagalog' },
|
||||
timestamp: sinon.match.typeOf('date')
|
||||
}
|
||||
],
|
||||
$slice: -200
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
this.dbUpdate.chain('exec').resolves({ nModified: 1 })
|
||||
this.operationId = await this.ProjectAuditLogHandler.promises.addEntry(
|
||||
this.projectId,
|
||||
'translate',
|
||||
this.userId,
|
||||
{ destinationLanguage: 'tagalog' }
|
||||
)
|
||||
})
|
||||
|
||||
it('writes a log', async function() {
|
||||
this.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project does not exist', function() {
|
||||
beforeEach(function() {
|
||||
this.ProjectMock.expects('updateOne')
|
||||
.chain('exec')
|
||||
.resolves({ nModified: 0 })
|
||||
})
|
||||
|
||||
it('throws an error', async function() {
|
||||
await expect(
|
||||
this.ProjectAuditLogHandler.promises.addEntry(
|
||||
this.projectId,
|
||||
'translate',
|
||||
this.userId,
|
||||
{ destinationLanguage: 'tagalog' }
|
||||
)
|
||||
).to.be.rejected
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -73,9 +73,6 @@ describe('ProjectController', function() {
|
||||
findAllUsersProjects: sinon.stub(),
|
||||
getProject: sinon.stub()
|
||||
}
|
||||
this.ProjectDetailsHandler = {
|
||||
transferOwnership: sinon.stub().yields()
|
||||
}
|
||||
this.ProjectHelper = {
|
||||
isArchived: sinon.stub(),
|
||||
isTrashed: sinon.stub(),
|
||||
@@ -1268,46 +1265,4 @@ describe('ProjectController', function() {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('transferOwnership', function() {
|
||||
beforeEach(function() {
|
||||
this.req.body = { user_id: this.user._id.toString() }
|
||||
})
|
||||
|
||||
it('validates the request body', function(done) {
|
||||
this.req.body = {}
|
||||
this.ProjectController.transferOwnership(this.req, this.res, err => {
|
||||
expect(err).to.be.instanceof(HttpErrors.BadRequestError)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('returns 204 on success', function(done) {
|
||||
this.res.sendStatus = status => {
|
||||
expect(status).to.equal(204)
|
||||
done()
|
||||
}
|
||||
this.ProjectController.transferOwnership(this.req, this.res)
|
||||
})
|
||||
|
||||
it('returns 404 if the project does not exist', function(done) {
|
||||
this.ProjectDetailsHandler.transferOwnership.yields(
|
||||
new Errors.ProjectNotFoundError()
|
||||
)
|
||||
this.ProjectController.transferOwnership(this.req, this.res, err => {
|
||||
expect(err).to.be.instanceof(HttpErrors.NotFoundError)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('returns 404 if the user does not exist', function(done) {
|
||||
this.ProjectDetailsHandler.transferOwnership.yields(
|
||||
new Errors.UserNotFoundError()
|
||||
)
|
||||
this.ProjectController.transferOwnership(this.req, this.res, err => {
|
||||
expect(err).to.be.instanceof(HttpErrors.NotFoundError)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,23 +3,22 @@ const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/Features/Project/ProjectDetailsHandler'
|
||||
|
||||
describe('ProjectDetailsHandler', function() {
|
||||
beforeEach(function() {
|
||||
this.user = {
|
||||
_id: ObjectId('abcdefabcdefabcdefabcdef'),
|
||||
_id: ObjectId(),
|
||||
email: 'user@example.com',
|
||||
features: 'mock-features'
|
||||
}
|
||||
this.collaborator = {
|
||||
_id: ObjectId('123456123456123456123456'),
|
||||
_id: ObjectId(),
|
||||
email: 'collaborator@example.com'
|
||||
}
|
||||
this.project = {
|
||||
_id: ObjectId('5d5dabdbb351de090cdff0b2'),
|
||||
_id: ObjectId(),
|
||||
name: 'project',
|
||||
description: 'this is a great project',
|
||||
something: 'should not exist',
|
||||
@@ -55,22 +54,6 @@ describe('ProjectDetailsHandler', function() {
|
||||
moveEntity: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.ProjectEntityHandler = {
|
||||
promises: {
|
||||
flushProjectToThirdPartyDataStore: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.CollaboratorsHandler = {
|
||||
promises: {
|
||||
removeUserFromProject: sinon.stub().resolves(),
|
||||
addUserIdToProject: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.EmailHandler = {
|
||||
promises: {
|
||||
sendEmail: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.ProjectTokenGenerator = {
|
||||
readAndWriteToken: sinon.stub(),
|
||||
promises: {
|
||||
@@ -91,9 +74,6 @@ describe('ProjectDetailsHandler', function() {
|
||||
},
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
|
||||
'./ProjectEntityHandler': this.ProjectEntityHandler,
|
||||
'../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
|
||||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'logger-sharelatex': {
|
||||
log() {},
|
||||
warn() {},
|
||||
@@ -143,135 +123,6 @@ describe('ProjectDetailsHandler', function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe('transferOwnership', function() {
|
||||
beforeEach(function() {
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(this.user._id)
|
||||
.resolves(this.user)
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(this.collaborator._id)
|
||||
.resolves(this.collaborator)
|
||||
})
|
||||
|
||||
it("should return a not found error if the project can't be found", async function() {
|
||||
this.ProjectGetter.promises.getProject.resolves(null)
|
||||
await expect(
|
||||
this.handler.promises.transferOwnership('abc', this.collaborator._id)
|
||||
).to.be.rejectedWith(Errors.ProjectNotFoundError)
|
||||
})
|
||||
|
||||
it("should return a not found error if the user can't be found", async function() {
|
||||
this.UserGetter.promises.getUser
|
||||
.withArgs(this.collaborator._id)
|
||||
.resolves(null)
|
||||
await expect(
|
||||
this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
).to.be.rejectedWith(Errors.UserNotFoundError)
|
||||
})
|
||||
|
||||
it('should return an error if user cannot be removed as collaborator ', async function() {
|
||||
this.CollaboratorsHandler.promises.removeUserFromProject.rejects(
|
||||
new Error('user-cannot-be-removed')
|
||||
)
|
||||
await expect(
|
||||
this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
).to.be.rejected
|
||||
})
|
||||
|
||||
it('should transfer ownership of the project', async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
expect(this.ProjectModel.update).to.have.been.calledWith(
|
||||
{ _id: this.project._id },
|
||||
sinon.match({ $set: { owner_ref: this.collaborator._id } })
|
||||
)
|
||||
})
|
||||
|
||||
it('should do nothing if transferring back to the owner', async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.user._id
|
||||
)
|
||||
expect(this.ProjectModel.update).not.to.have.been.called
|
||||
})
|
||||
|
||||
it("should remove the user from the project's collaborators", async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
expect(
|
||||
this.CollaboratorsHandler.promises.removeUserFromProject
|
||||
).to.have.been.calledWith(this.project._id, this.collaborator._id)
|
||||
})
|
||||
|
||||
it('should add the former project owner as a read/write collaborator', async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
expect(
|
||||
this.CollaboratorsHandler.promises.addUserIdToProject
|
||||
).to.have.been.calledWith(
|
||||
this.project._id,
|
||||
this.collaborator._id,
|
||||
this.user._id,
|
||||
PrivilegeLevels.READ_AND_WRITE
|
||||
)
|
||||
})
|
||||
|
||||
it('should flush the project to tpds', async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
expect(
|
||||
this.ProjectEntityHandler.promises.flushProjectToThirdPartyDataStore
|
||||
).to.have.been.calledWith(this.project._id)
|
||||
})
|
||||
|
||||
it('should send an email notification', async function() {
|
||||
await this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
expect(this.EmailHandler.promises.sendEmail).to.have.been.calledWith(
|
||||
'ownershipTransferConfirmationPreviousOwner',
|
||||
{
|
||||
to: this.user.email,
|
||||
project: this.project,
|
||||
newOwner: this.collaborator
|
||||
}
|
||||
)
|
||||
expect(this.EmailHandler.promises.sendEmail).to.have.been.calledWith(
|
||||
'ownershipTransferConfirmationNewOwner',
|
||||
{
|
||||
to: this.collaborator.email,
|
||||
project: this.project,
|
||||
previousOwner: this.user
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should decline to transfer ownership to a non-collaborator', async function() {
|
||||
this.project.collaberator_refs = []
|
||||
await expect(
|
||||
this.handler.promises.transferOwnership(
|
||||
this.project._id,
|
||||
this.collaborator._id
|
||||
)
|
||||
).to.be.rejectedWith(Errors.UserNotCollaboratorError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getProjectDescription', function() {
|
||||
it('should make a call to mongo just for the description', async function() {
|
||||
this.ProjectGetter.promises.getProject.resolves()
|
||||
|
||||
Reference in New Issue
Block a user