2019-10-23 08:24:09 -04:00
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
const sinon = require('sinon')
|
|
|
|
const { expect } = require('chai')
|
|
|
|
const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
|
|
|
|
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
|
|
|
const { ObjectId } = require('mongodb')
|
|
|
|
|
|
|
|
const MODULE_PATH =
|
|
|
|
'../../../../app/src/Features/Collaborators/OwnershipTransferHandler'
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('OwnershipTransferHandler', function () {
|
|
|
|
beforeEach(function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
this.user = { _id: ObjectId(), email: 'owner@example.com' }
|
|
|
|
this.collaborator = { _id: ObjectId(), email: 'collaborator@example.com' }
|
|
|
|
this.project = {
|
|
|
|
_id: ObjectId(),
|
|
|
|
name: 'project',
|
|
|
|
owner_ref: this.user._id,
|
|
|
|
collaberator_refs: [this.collaborator._id]
|
|
|
|
}
|
|
|
|
this.ProjectGetter = {
|
|
|
|
promises: {
|
|
|
|
getProject: sinon.stub().resolves(this.project)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.ProjectModel = {
|
2020-11-03 04:19:05 -05:00
|
|
|
updateOne: sinon.stub().returns({
|
2019-10-23 08:24:09 -04:00
|
|
|
exec: sinon.stub().resolves()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
this.UserGetter = {
|
|
|
|
promises: {
|
|
|
|
getUser: sinon.stub().resolves(this.user)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.TpdsUpdateSender = {
|
|
|
|
promises: {
|
|
|
|
moveEntity: sinon.stub().resolves()
|
|
|
|
}
|
|
|
|
}
|
2019-11-26 08:11:19 -05:00
|
|
|
this.TpdsProjectFlusher = {
|
2019-10-23 08:24:09 -04:00
|
|
|
promises: {
|
2019-11-26 08:11:19 -05:00
|
|
|
flushProjectToTpds: sinon.stub().resolves()
|
2019-10-23 08:24:09 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.CollaboratorsHandler = {
|
|
|
|
promises: {
|
|
|
|
removeUserFromProject: sinon.stub().resolves(),
|
|
|
|
addUserIdToProject: sinon.stub().resolves()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.EmailHandler = {
|
|
|
|
promises: {
|
|
|
|
sendEmail: sinon.stub().resolves()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.ProjectAuditLogHandler = {
|
|
|
|
promises: {
|
|
|
|
addEntry: sinon.stub().resolves()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.handler = SandboxedModule.require(MODULE_PATH, {
|
|
|
|
requires: {
|
|
|
|
'../Project/ProjectGetter': this.ProjectGetter,
|
|
|
|
'../../models/Project': {
|
|
|
|
Project: this.ProjectModel
|
|
|
|
},
|
|
|
|
'../User/UserGetter': this.UserGetter,
|
2019-11-26 08:11:19 -05:00
|
|
|
'../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher,
|
2019-10-23 08:24:09 -04:00
|
|
|
'../Project/ProjectAuditLogHandler': this.ProjectAuditLogHandler,
|
|
|
|
'../Email/EmailHandler': this.EmailHandler,
|
2021-03-31 08:20:55 -04:00
|
|
|
'./CollaboratorsHandler': this.CollaboratorsHandler
|
2019-10-23 08:24:09 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
describe('transferOwnership', function () {
|
|
|
|
beforeEach(function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
this.UserGetter.promises.getUser
|
|
|
|
.withArgs(this.user._id)
|
|
|
|
.resolves(this.user)
|
|
|
|
this.UserGetter.promises.getUser
|
|
|
|
.withArgs(this.collaborator._id)
|
|
|
|
.resolves(this.collaborator)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it("should return a not found error if the project can't be found", async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
this.ProjectGetter.promises.getProject.resolves(null)
|
|
|
|
await expect(
|
|
|
|
this.handler.promises.transferOwnership('abc', this.collaborator._id)
|
|
|
|
).to.be.rejectedWith(Errors.ProjectNotFoundError)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it("should return a not found error if the user can't be found", async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should return an error if user cannot be removed as collaborator ', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
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
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should transfer ownership of the project', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
await this.handler.promises.transferOwnership(
|
|
|
|
this.project._id,
|
|
|
|
this.collaborator._id
|
|
|
|
)
|
2020-11-03 04:19:05 -05:00
|
|
|
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
|
2019-10-23 08:24:09 -04:00
|
|
|
{ _id: this.project._id },
|
|
|
|
sinon.match({ $set: { owner_ref: this.collaborator._id } })
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should do nothing if transferring back to the owner', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
await this.handler.promises.transferOwnership(
|
|
|
|
this.project._id,
|
|
|
|
this.user._id
|
|
|
|
)
|
2020-11-03 04:19:05 -05:00
|
|
|
expect(this.ProjectModel.updateOne).not.to.have.been.called
|
2019-10-23 08:24:09 -04:00
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it("should remove the user from the project's collaborators", async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should add the former project owner as a read/write collaborator', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
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
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should flush the project to tpds', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
await this.handler.promises.transferOwnership(
|
|
|
|
this.project._id,
|
|
|
|
this.collaborator._id
|
|
|
|
)
|
|
|
|
expect(
|
2019-11-26 08:11:19 -05:00
|
|
|
this.TpdsProjectFlusher.promises.flushProjectToTpds
|
2019-10-23 08:24:09 -04:00
|
|
|
).to.have.been.calledWith(this.project._id)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should send an email notification', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
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
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should write an entry in the audit log', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
const sessionUserId = ObjectId()
|
|
|
|
await this.handler.promises.transferOwnership(
|
|
|
|
this.project._id,
|
|
|
|
this.collaborator._id,
|
|
|
|
{ sessionUserId }
|
|
|
|
)
|
|
|
|
expect(
|
|
|
|
this.ProjectAuditLogHandler.promises.addEntry
|
|
|
|
).to.have.been.calledWith(
|
|
|
|
this.project._id,
|
|
|
|
'transfer-ownership',
|
|
|
|
sessionUserId,
|
|
|
|
{
|
|
|
|
previousOwnerId: this.user._id,
|
|
|
|
newOwnerId: this.collaborator._id
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
2021-04-14 09:17:21 -04:00
|
|
|
it('should decline to transfer ownership to a non-collaborator', async function () {
|
2019-10-23 08:24:09 -04:00
|
|
|
this.project.collaberator_refs = []
|
|
|
|
await expect(
|
|
|
|
this.handler.promises.transferOwnership(
|
|
|
|
this.project._id,
|
|
|
|
this.collaborator._id
|
|
|
|
)
|
|
|
|
).to.be.rejectedWith(Errors.UserNotCollaboratorError)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|