| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894 |
- const modulePath = '../../../../app/src/Features/Project/ProjectDeleter'
- const SandboxedModule = require('sandboxed-module')
- const sinon = require('sinon')
- const { expect } = require('chai')
- const tk = require('timekeeper')
- const moment = require('moment')
- const { Project } = require('../helpers/models/Project')
- const { DeletedProject } = require('../helpers/models/DeletedProject')
- const { ObjectId } = require('mongodb')
- const Errors = require('../../../../app/src/Features/Errors/Errors')
- describe('ProjectDeleter', function () {
- beforeEach(function () {
- tk.freeze(Date.now())
- this.ip = '192.170.18.1'
- this.project = dummyProject()
- this.user = {
- _id: '588f3ddae8ebc1bac07c9fa4',
- first_name: 'bjkdsjfk',
- features: {},
- }
- this.doc = {
- _id: '5bd975f54f62e803cb8a8fec',
- lines: ['a bunch of lines', 'for a sunny day', 'in London town'],
- ranges: {},
- project_id: '5cf9270b4eff6e186cf8b05e',
- }
- this.deletedProjects = [
- {
- _id: '5cf7f145c1401f0ca0eb1aaa',
- deleterData: {
- _id: '5cf7f145c1401f0ca0eb1aac',
- deletedAt: moment().subtract(95, 'days').toDate(),
- deleterId: '588f3ddae8ebc1bac07c9fa4',
- deleterIpAddress: '172.19.0.1',
- deletedProjectId: '5cf9270b4eff6e186cf8b05e',
- },
- project: {
- _id: '5cf9270b4eff6e186cf8b05e',
- overleaf: {
- history: {
- id: new ObjectId(),
- },
- },
- },
- },
- {
- _id: '5cf8eb11c1401f0ca0eb1ad7',
- deleterData: {
- _id: '5b74360c0fbe57011ae9938f',
- deletedAt: moment().subtract(95, 'days').toDate(),
- deleterId: '588f3ddae8ebc1bac07c9fa4',
- deleterIpAddress: '172.20.0.1',
- deletedProjectId: '5cf8f95a0c87371362c23919',
- },
- project: {
- _id: '5cf8f95a0c87371362c23919',
- },
- },
- ]
- this.DocumentUpdaterHandler = {
- promises: {
- flushProjectToMongoAndDelete: sinon.stub().resolves(),
- },
- }
- this.EditorRealTimeController = {
- emitToRoom: sinon.stub(),
- }
- this.TagsHandler = {
- promises: {
- removeProjectFromAllTags: sinon.stub().resolves(),
- },
- }
- this.CollaboratorsHandler = {
- promises: {
- removeUserFromAllProjects: sinon.stub().resolves(),
- },
- }
- this.CollaboratorsGetter = {
- promises: {
- getMemberIds: sinon
- .stub()
- .withArgs(this.project._id)
- .resolves(['member-id-1', 'member-id-2']),
- },
- }
- this.ProjectDetailsHandler = {
- promises: {
- generateUniqueName: sinon.stub().resolves(this.project.name),
- },
- }
- this.ProjectHelper = {
- calculateArchivedArray: sinon.stub(),
- }
- this.db = {
- deletedFiles: {
- indexExists: sinon.stub().resolves(false),
- deleteMany: sinon.stub(),
- },
- projects: {
- insertOne: sinon.stub().resolves(),
- },
- }
- this.DocstoreManager = {
- promises: {
- archiveProject: sinon.stub().resolves(),
- destroyProject: sinon.stub().resolves(),
- },
- }
- this.HistoryManager = {
- promises: {
- deleteProject: sinon.stub().resolves(),
- },
- }
- this.ProjectMock = sinon.mock(Project)
- this.DeletedProjectMock = sinon.mock(DeletedProject)
- this.FileStoreHandler = {
- promises: {
- deleteProject: sinon.stub().resolves(),
- },
- }
- this.TpdsUpdateSender = {
- promises: {
- deleteProject: sinon.stub().resolves(),
- },
- }
- this.Features = {
- hasFeature: sinon.stub().returns(true),
- }
- this.ChatApiHandler = {
- promises: {
- destroyProject: sinon.stub().resolves(),
- },
- }
- this.ProjectDeleter = SandboxedModule.require(modulePath, {
- requires: {
- '../../infrastructure/Features': this.Features,
- '../Editor/EditorRealTimeController': this.EditorRealTimeController,
- '../../models/Project': { Project },
- './ProjectHelper': this.ProjectHelper,
- '../../models/DeletedProject': { DeletedProject },
- '../DocumentUpdater/DocumentUpdaterHandler':
- this.DocumentUpdaterHandler,
- '../Tags/TagsHandler': this.TagsHandler,
- '../FileStore/FileStoreHandler': this.FileStoreHandler,
- '../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
- '../Chat/ChatApiHandler': this.ChatApiHandler,
- '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
- '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
- '../Docstore/DocstoreManager': this.DocstoreManager,
- './ProjectDetailsHandler': this.ProjectDetailsHandler,
- '../../infrastructure/mongodb': { db: this.db, ObjectId },
- '../History/HistoryManager': this.HistoryManager,
- },
- })
- })
- afterEach(function () {
- tk.reset()
- this.DeletedProjectMock.restore()
- this.ProjectMock.restore()
- })
- describe('mark as deleted by external source', function () {
- beforeEach(function () {
- this.ProjectMock.expects('updateOne')
- .withArgs(
- { _id: this.project._id },
- { deletedByExternalDataSource: true }
- )
- .chain('exec')
- .resolves()
- })
- it('should update the project with the flag set to true', async function () {
- await this.ProjectDeleter.promises.markAsDeletedByExternalSource(
- this.project._id
- )
- this.ProjectMock.verify()
- })
- it('should tell the editor controler so users are notified', async function () {
- await this.ProjectDeleter.promises.markAsDeletedByExternalSource(
- this.project._id
- )
- expect(this.EditorRealTimeController.emitToRoom).to.have.been.calledWith(
- this.project._id,
- 'projectRenamedOrDeletedByExternalSource'
- )
- })
- })
- describe('unmarkAsDeletedByExternalSource', function () {
- beforeEach(async function () {
- this.ProjectMock.expects('updateOne')
- .withArgs(
- { _id: this.project._id },
- { deletedByExternalDataSource: false }
- )
- .chain('exec')
- .resolves()
- await this.ProjectDeleter.promises.unmarkAsDeletedByExternalSource(
- this.project._id
- )
- })
- it('should remove the flag from the project', function () {
- this.ProjectMock.verify()
- })
- })
- describe('deleteUsersProjects', function () {
- beforeEach(function () {
- this.projects = [dummyProject(), dummyProject()]
- this.ProjectMock.expects('find')
- .withArgs({ owner_ref: this.user._id })
- .chain('exec')
- .resolves(this.projects)
- for (const project of this.projects) {
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: project._id })
- .chain('exec')
- .resolves(project)
- this.ProjectMock.expects('deleteOne')
- .withArgs({ _id: project._id })
- .chain('exec')
- .resolves()
- this.DeletedProjectMock.expects('updateOne')
- .withArgs(
- { 'deleterData.deletedProjectId': project._id },
- {
- project,
- deleterData: sinon.match.object,
- },
- { upsert: true }
- )
- .resolves()
- }
- })
- it('should delete all projects owned by the user', async function () {
- await this.ProjectDeleter.promises.deleteUsersProjects(this.user._id)
- this.ProjectMock.verify()
- this.DeletedProjectMock.verify()
- })
- it('should remove any collaboration from this user', async function () {
- await this.ProjectDeleter.promises.deleteUsersProjects(this.user._id)
- sinon.assert.calledWith(
- this.CollaboratorsHandler.promises.removeUserFromAllProjects,
- this.user._id
- )
- sinon.assert.calledOnce(
- this.CollaboratorsHandler.promises.removeUserFromAllProjects
- )
- })
- })
- describe('deleteProject', function () {
- beforeEach(function () {
- this.deleterData = {
- deletedAt: new Date(),
- deletedProjectId: this.project._id,
- deletedProjectOwnerId: this.project.owner_ref,
- deletedProjectCollaboratorIds: this.project.collaberator_refs,
- deletedProjectReadOnlyIds: this.project.readOnly_refs,
- deletedProjectReadWriteTokenAccessIds:
- this.project.tokenAccessReadAndWrite_refs,
- deletedProjectReadOnlyTokenAccessIds:
- this.project.tokenAccessReadOnly_refs,
- deletedProjectReadWriteToken: this.project.tokens.readAndWrite,
- deletedProjectReadOnlyToken: this.project.tokens.readOnly,
- deletedProjectOverleafId: this.project.overleaf.id,
- deletedProjectOverleafHistoryId: this.project.overleaf.history.id,
- deletedProjectLastUpdatedAt: this.project.lastUpdated,
- }
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves(this.project)
- })
- it('should save a DeletedProject with additional deleterData', async function () {
- this.deleterData.deleterIpAddress = this.ip
- this.deleterData.deleterId = this.user._id
- this.ProjectMock.expects('deleteOne').chain('exec').resolves()
- this.DeletedProjectMock.expects('updateOne')
- .withArgs(
- { 'deleterData.deletedProjectId': this.project._id },
- {
- project: this.project,
- deleterData: this.deleterData,
- },
- { upsert: true }
- )
- .resolves()
- await this.ProjectDeleter.promises.deleteProject(this.project._id, {
- deleterUser: this.user,
- ipAddress: this.ip,
- })
- this.DeletedProjectMock.verify()
- })
- it('should flushProjectToMongoAndDelete in doc updater', async function () {
- this.ProjectMock.expects('deleteOne').chain('exec').resolves()
- this.DeletedProjectMock.expects('updateOne').resolves()
- await this.ProjectDeleter.promises.deleteProject(this.project._id, {
- deleterUser: this.user,
- ipAddress: this.ip,
- })
- this.DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete
- .calledWith(this.project._id)
- .should.equal(true)
- })
- it('should flush docs out of mongo', async function () {
- this.ProjectMock.expects('deleteOne').chain('exec').resolves()
- this.DeletedProjectMock.expects('updateOne').resolves()
- await this.ProjectDeleter.promises.deleteProject(this.project._id, {
- deleterUser: this.user,
- ipAddress: this.ip,
- })
- expect(
- this.DocstoreManager.promises.archiveProject
- ).to.have.been.calledWith(this.project._id)
- })
- it('should flush docs out of mongo and ignore errors', async function () {
- this.ProjectMock.expects('deleteOne').chain('exec').resolves()
- this.DeletedProjectMock.expects('updateOne').resolves()
- this.DocstoreManager.promises.archiveProject.rejects(new Error('foo'))
- await this.ProjectDeleter.promises.deleteProject(this.project._id, {
- deleterUser: this.user,
- ipAddress: this.ip,
- })
- })
- it('should removeProjectFromAllTags', async function () {
- this.ProjectMock.expects('deleteOne').chain('exec').resolves()
- this.DeletedProjectMock.expects('updateOne').resolves()
- await this.ProjectDeleter.promises.deleteProject(this.project._id)
- sinon.assert.calledWith(
- this.TagsHandler.promises.removeProjectFromAllTags,
- 'member-id-1',
- this.project._id
- )
- sinon.assert.calledWith(
- this.TagsHandler.promises.removeProjectFromAllTags,
- 'member-id-2',
- this.project._id
- )
- })
- it('should remove the project from Mongo', async function () {
- this.ProjectMock.expects('deleteOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves()
- this.DeletedProjectMock.expects('updateOne').resolves()
- await this.ProjectDeleter.promises.deleteProject(this.project._id)
- this.ProjectMock.verify()
- })
- })
- describe('expireDeletedProjectsAfterDuration', function () {
- beforeEach(async function () {
- for (const deletedProject of this.deletedProjects) {
- this.ProjectMock.expects('findById')
- .withArgs(deletedProject.deleterData.deletedProjectId)
- .chain('exec')
- .resolves(null)
- }
- this.DeletedProjectMock.expects('find')
- .withArgs({
- 'deleterData.deletedAt': {
- $lt: new Date(moment().subtract(90, 'days')),
- },
- project: {
- $ne: null,
- },
- })
- .chain('exec')
- .resolves(this.deletedProjects)
- for (const deletedProject of this.deletedProjects) {
- this.DeletedProjectMock.expects('findOne')
- .withArgs({
- 'deleterData.deletedProjectId': deletedProject.project._id,
- })
- .chain('exec')
- .resolves(deletedProject)
- this.DeletedProjectMock.expects('updateOne')
- .withArgs(
- {
- _id: deletedProject._id,
- },
- {
- $set: {
- 'deleterData.deleterIpAddress': null,
- project: null,
- },
- }
- )
- .chain('exec')
- .resolves()
- }
- await this.ProjectDeleter.promises.expireDeletedProjectsAfterDuration()
- })
- it('should expire projects older than 90 days', function () {
- this.DeletedProjectMock.verify()
- })
- })
- describe('expireDeletedProject', function () {
- describe('on an inactive project', function () {
- beforeEach(async function () {
- this.ProjectMock.expects('findById')
- .withArgs(this.deletedProjects[0].deleterData.deletedProjectId)
- .chain('exec')
- .resolves(null)
- this.DeletedProjectMock.expects('updateOne')
- .withArgs(
- {
- _id: this.deletedProjects[0]._id,
- },
- {
- $set: {
- 'deleterData.deleterIpAddress': null,
- project: null,
- },
- }
- )
- .chain('exec')
- .resolves()
- this.DeletedProjectMock.expects('findOne')
- .withArgs({
- 'deleterData.deletedProjectId': this.deletedProjects[0].project._id,
- })
- .chain('exec')
- .resolves(this.deletedProjects[0])
- await this.ProjectDeleter.promises.expireDeletedProject(
- this.deletedProjects[0].project._id
- )
- })
- it('should find the specified deletedProject and remove its project and ip address', function () {
- this.DeletedProjectMock.verify()
- })
- it('should destroy the docs in docstore', function () {
- expect(
- this.DocstoreManager.promises.destroyProject
- ).to.have.been.calledWith(this.deletedProjects[0].project._id)
- })
- it('should delete the project in history', function () {
- expect(
- this.HistoryManager.promises.deleteProject
- ).to.have.been.calledWith(
- this.deletedProjects[0].project._id,
- this.deletedProjects[0].project.overleaf.history.id
- )
- })
- it('should destroy the files in filestore', function () {
- expect(
- this.FileStoreHandler.promises.deleteProject
- ).to.have.been.calledWith(this.deletedProjects[0].project._id)
- })
- it('should destroy the files in project-archiver', function () {
- expect(
- this.TpdsUpdateSender.promises.deleteProject
- ).to.have.been.calledWith({
- project_id: this.deletedProjects[0].project._id,
- })
- })
- it('should destroy the chat threads and messages', function () {
- expect(
- this.ChatApiHandler.promises.destroyProject
- ).to.have.been.calledWith(this.deletedProjects[0].project._id)
- })
- })
- describe('when history-v1 is not available', function () {
- beforeEach(async function () {
- this.Features.hasFeature.returns(false)
- this.ProjectMock.expects('findById')
- .withArgs(this.deletedProjects[0].deleterData.deletedProjectId)
- .chain('exec')
- .resolves(null)
- this.DeletedProjectMock.expects('updateOne')
- .withArgs(
- {
- _id: this.deletedProjects[0]._id,
- },
- {
- $set: {
- 'deleterData.deleterIpAddress': null,
- project: null,
- },
- }
- )
- .chain('exec')
- .resolves()
- this.DeletedProjectMock.expects('findOne')
- .withArgs({
- 'deleterData.deletedProjectId': this.deletedProjects[0].project._id,
- })
- .chain('exec')
- .resolves(this.deletedProjects[0])
- await this.ProjectDeleter.promises.expireDeletedProject(
- this.deletedProjects[0].project._id
- )
- })
- it('should destroy the docs in docstore', function () {
- expect(
- this.DocstoreManager.promises.destroyProject
- ).to.have.been.calledWith(this.deletedProjects[0].project._id)
- })
- it('should not call project history', function () {
- expect(this.HistoryManager.promises.deleteProject).to.not.have.been
- .called
- })
- })
- describe('on an active project (from an incomplete delete)', function () {
- beforeEach(async function () {
- this.ProjectMock.expects('findById')
- .withArgs(this.deletedProjects[0].deleterData.deletedProjectId)
- .chain('exec')
- .resolves(this.deletedProjects[0].project)
- this.DeletedProjectMock.expects('deleteOne')
- .withArgs({
- 'deleterData.deletedProjectId': this.deletedProjects[0].project._id,
- })
- .chain('exec')
- .resolves()
- await this.ProjectDeleter.promises.expireDeletedProject(
- this.deletedProjects[0].project._id
- )
- })
- it('should delete the spurious deleted project record', function () {
- this.DeletedProjectMock.verify()
- })
- it('should not destroy the docs in docstore', function () {
- expect(this.DocstoreManager.promises.destroyProject).to.not.have.been
- .called
- })
- it('should not delete the project in history', function () {
- expect(this.HistoryManager.promises.deleteProject).to.not.have.been
- .called
- })
- it('should not destroy the files in filestore', function () {
- expect(this.FileStoreHandler.promises.deleteProject).to.not.have.been
- .called
- })
- it('should not destroy the files in project-archiver', function () {
- expect(this.TpdsUpdateSender.promises.deleteProject).to.not.have.been
- .called
- })
- it('should not destroy the chat threads and messages', function () {
- expect(this.ChatApiHandler.promises.destroyProject).to.not.have.been
- .called
- })
- })
- })
- describe('archiveProject', function () {
- beforeEach(function () {
- const archived = [ObjectId(this.user._id)]
- this.ProjectHelper.calculateArchivedArray.returns(archived)
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('updateOne')
- .withArgs(
- { _id: this.project._id },
- {
- $set: { archived },
- $pull: { trashed: ObjectId(this.user._id) },
- }
- )
- .resolves()
- })
- it('should update the project', async function () {
- await this.ProjectDeleter.promises.archiveProject(
- this.project._id,
- this.user._id
- )
- this.ProjectMock.verify()
- })
- it('calculates the archived array', async function () {
- await this.ProjectDeleter.promises.archiveProject(
- this.project._id,
- this.user._id
- )
- expect(this.ProjectHelper.calculateArchivedArray).to.have.been.calledWith(
- this.project,
- this.user._id,
- 'ARCHIVE'
- )
- })
- })
- describe('unarchiveProject', function () {
- beforeEach(function () {
- const archived = [ObjectId(this.user._id)]
- this.ProjectHelper.calculateArchivedArray.returns(archived)
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('updateOne')
- .withArgs({ _id: this.project._id }, { $set: { archived } })
- .resolves()
- })
- it('should update the project', async function () {
- await this.ProjectDeleter.promises.unarchiveProject(
- this.project._id,
- this.user._id
- )
- this.ProjectMock.verify()
- })
- it('calculates the archived array', async function () {
- await this.ProjectDeleter.promises.unarchiveProject(
- this.project._id,
- this.user._id
- )
- expect(this.ProjectHelper.calculateArchivedArray).to.have.been.calledWith(
- this.project,
- this.user._id,
- 'UNARCHIVE'
- )
- })
- })
- describe('trashProject', function () {
- beforeEach(function () {
- const archived = [ObjectId(this.user._id)]
- this.ProjectHelper.calculateArchivedArray.returns(archived)
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('updateOne')
- .withArgs(
- { _id: this.project._id },
- {
- $addToSet: { trashed: ObjectId(this.user._id) },
- $set: { archived },
- }
- )
- .resolves()
- })
- it('should update the project', async function () {
- await this.ProjectDeleter.promises.trashProject(
- this.project._id,
- this.user._id
- )
- this.ProjectMock.verify()
- })
- it('unarchives the project', async function () {
- await this.ProjectDeleter.promises.trashProject(
- this.project._id,
- this.user._id
- )
- expect(this.ProjectHelper.calculateArchivedArray).to.have.been.calledWith(
- this.project,
- this.user._id,
- 'UNARCHIVE'
- )
- })
- })
- describe('untrashProject', function () {
- beforeEach(function () {
- this.ProjectMock.expects('findOne')
- .withArgs({ _id: this.project._id })
- .chain('exec')
- .resolves(this.project)
- this.ProjectMock.expects('updateOne')
- .withArgs(
- { _id: this.project._id },
- { $pull: { trashed: ObjectId(this.user._id) } }
- )
- .resolves()
- })
- it('should update the project', async function () {
- await this.ProjectDeleter.promises.untrashProject(
- this.project._id,
- this.user._id
- )
- this.ProjectMock.verify()
- })
- })
- describe('restoreProject', function () {
- beforeEach(function () {
- this.ProjectMock.expects('updateOne')
- .withArgs(
- {
- _id: this.project._id,
- },
- {
- $unset: { archived: true },
- }
- )
- .chain('exec')
- .resolves()
- })
- it('should unset the archive attribute', async function () {
- await this.ProjectDeleter.promises.restoreProject(this.project._id)
- })
- })
- describe('undeleteProject', function () {
- beforeEach(function () {
- this.unknownProjectId = ObjectId()
- this.purgedProjectId = ObjectId()
- this.deletedProject = {
- _id: 'deleted',
- project: this.project,
- deleterData: {
- deletedProjectId: this.project._id,
- deletedProjectOwnerId: this.project.owner_ref,
- },
- }
- this.purgedProject = {
- _id: 'purged',
- deleterData: {
- deletedProjectId: this.purgedProjectId,
- deletedProjectOwnerId: 'potato',
- },
- }
- this.DeletedProjectMock.expects('findOne')
- .withArgs({ 'deleterData.deletedProjectId': this.project._id })
- .chain('exec')
- .resolves(this.deletedProject)
- this.DeletedProjectMock.expects('findOne')
- .withArgs({ 'deleterData.deletedProjectId': this.purgedProjectId })
- .chain('exec')
- .resolves(this.purgedProject)
- this.DeletedProjectMock.expects('findOne')
- .withArgs({ 'deleterData.deletedProjectId': this.unknownProjectId })
- .chain('exec')
- .resolves(null)
- this.DeletedProjectMock.expects('deleteOne').chain('exec').resolves()
- })
- it('should return not found if the project does not exist', async function () {
- await expect(
- this.ProjectDeleter.promises.undeleteProject(
- this.unknownProjectId.toString()
- )
- ).to.be.rejectedWith(Errors.NotFoundError, 'project_not_found')
- })
- it('should return not found if the project has been expired', async function () {
- await expect(
- this.ProjectDeleter.promises.undeleteProject(
- this.purgedProjectId.toString()
- )
- ).to.be.rejectedWith(Errors.NotFoundError, 'project_too_old_to_restore')
- })
- it('should insert the project into the collection', async function () {
- await this.ProjectDeleter.promises.undeleteProject(this.project._id)
- sinon.assert.calledWith(
- this.db.projects.insertOne,
- sinon.match({
- _id: this.project._id,
- name: this.project.name,
- })
- )
- })
- it('should clear the archive bit', async function () {
- this.project.archived = true
- await this.ProjectDeleter.promises.undeleteProject(this.project._id)
- sinon.assert.calledWith(
- this.db.projects.insertOne,
- sinon.match({ archived: undefined })
- )
- })
- it('should generate a unique name for the project', async function () {
- await this.ProjectDeleter.promises.undeleteProject(this.project._id)
- sinon.assert.calledWith(
- this.ProjectDetailsHandler.promises.generateUniqueName,
- this.project.owner_ref
- )
- })
- it('should add a suffix to the project name', async function () {
- await this.ProjectDeleter.promises.undeleteProject(this.project._id)
- sinon.assert.calledWith(
- this.ProjectDetailsHandler.promises.generateUniqueName,
- this.project.owner_ref,
- this.project.name + ' (Restored)'
- )
- })
- it('should remove the DeletedProject', async function () {
- // need to change the mock just to include the methods we want
- this.DeletedProjectMock.restore()
- this.DeletedProjectMock = sinon.mock(DeletedProject)
- this.DeletedProjectMock.expects('findOne')
- .withArgs({ 'deleterData.deletedProjectId': this.project._id })
- .chain('exec')
- .resolves(this.deletedProject)
- this.DeletedProjectMock.expects('deleteOne')
- .withArgs({ _id: 'deleted' })
- .chain('exec')
- .resolves()
- await this.ProjectDeleter.promises.undeleteProject(this.project._id)
- this.DeletedProjectMock.verify()
- })
- })
- })
- function dummyProject() {
- return {
- _id: new ObjectId(),
- lastUpdated: new Date(),
- rootFolder: [],
- collaberator_refs: [new ObjectId(), new ObjectId()],
- readOnly_refs: [new ObjectId(), new ObjectId()],
- tokenAccessReadAndWrite_refs: [new ObjectId(), new ObjectId()],
- tokenAccessReadOnly_refs: [new ObjectId(), new ObjectId()],
- owner_ref: new ObjectId(),
- tokens: {
- readOnly: 'wombat',
- readAndWrite: 'potato',
- },
- overleaf: {
- id: 1234,
- history: {
- id: 5678,
- },
- },
- name: 'a very scientific analysis of spooky ghosts',
- }
- }
|