clean up broken project on error in ProjectDuplicator

This commit is contained in:
Brian Gough 2018-10-02 12:10:37 +01:00
parent 6d5908f2f4
commit 4621234220
2 changed files with 130 additions and 76 deletions

View file

@ -2,6 +2,7 @@ projectCreationHandler = require('./ProjectCreationHandler')
ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler') ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler')
projectLocator = require('./ProjectLocator') projectLocator = require('./ProjectLocator')
projectOptionsHandler = require('./ProjectOptionsHandler') projectOptionsHandler = require('./ProjectOptionsHandler')
projectDeleter = require('./ProjectDeleter')
DocumentUpdaterHandler = require("../DocumentUpdater/DocumentUpdaterHandler") DocumentUpdaterHandler = require("../DocumentUpdater/DocumentUpdaterHandler")
DocstoreManager = require "../Docstore/DocstoreManager" DocstoreManager = require "../Docstore/DocstoreManager"
ProjectGetter = require("./ProjectGetter") ProjectGetter = require("./ProjectGetter")
@ -69,18 +70,17 @@ module.exports = ProjectDuplicator =
DocumentUpdaterHandler.flushProjectToMongo originalProject_id, cb DocumentUpdaterHandler.flushProjectToMongo originalProject_id, cb
originalProject: (cb)-> originalProject: (cb)->
ProjectGetter.getProject originalProject_id, {compiler:true, rootFolder:true, rootDoc_id:true}, cb ProjectGetter.getProject originalProject_id, {compiler:true, rootFolder:true, rootDoc_id:true}, cb
newProject: (cb)->
projectCreationHandler.createBlankProject owner._id, newProjectName, cb
originalRootDoc: (cb)-> originalRootDoc: (cb)->
projectLocator.findRootDoc {project_id:originalProject_id}, cb projectLocator.findRootDoc {project_id:originalProject_id}, cb
docContentsArray: (cb)-> docContentsArray: (cb)->
DocstoreManager.getAllDocs originalProject_id, cb DocstoreManager.getAllDocs originalProject_id, cb
# Get the contents of the original project first
async.series jobs, (err, results)-> async.series jobs, (err, results)->
if err? if err?
logger.err err:err, originalProject_id:originalProject_id, "error duplicating project" logger.err err:err, originalProject_id:originalProject_id, "error duplicating project reading original project"
return callback(err) return callback(err)
{originalProject, newProject, originalRootDoc, docContentsArray} = results {originalProject, originalRootDoc, docContentsArray} = results
originalRootDoc = originalRootDoc?[0] originalRootDoc = originalRootDoc?[0]
@ -88,9 +88,27 @@ module.exports = ProjectDuplicator =
for docContent in docContentsArray for docContent in docContentsArray
docContents[docContent._id] = docContent docContents[docContent._id] = docContent
projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, -> # Now create the new project, cleaning it up on failure if necessary
projectCreationHandler.createBlankProject owner._id, newProjectName, (err, newProject) ->
ProjectDuplicator._copyFolderRecursivly owner._id, newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, ->
if err? if err?
logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, "error cloning project" logger.err err:err, originalProject_id:originalProject_id, "error duplicating project when creating new project"
callback(err, newProject) return callback(err)
copyJobs =
setCompiler: (cb) ->
projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, cb
copyFiles: (cb) ->
ProjectDuplicator._copyFolderRecursivly owner._id, newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, cb
# Copy the contents of the original project into the new project
async.series copyJobs, (err) ->
if err?
logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, newProject_id: newProject._id, "error cloning project, will delete broken clone"
# Clean up broken clone on error.
# Make sure we delete the new failed project, not the original one!
projectDeleter.deleteProject newProject._id, (delete_err) ->
if delete_err?
logger.error newProject_id: newProject._id, delete_err:delete_err, "error deleting broken clone of project"
callback(err)
else
callback(null, newProject)

View file

@ -17,7 +17,7 @@ describe 'ProjectDuplicator', ->
_id:"level1folderId" _id:"level1folderId"
docs:[@doc1 = {_id: "doc1_id", name:"level1folderDocName"}] docs:[@doc1 = {_id: "doc1_id", name:"level1folderDocName"}]
folders:[@level2folder] folders:[@level2folder]
fileRefs:[{name:"file1", _id:"file1"}, null] fileRefs:[{name:"file1", _id:"file1"}, null] # the null is intentional to test null docs/files
@rootFolder = @rootFolder =
name:"rootFolder" name:"rootFolder"
_id:"rootFolderId" _id:"rootFolderId"
@ -62,13 +62,17 @@ describe 'ProjectDuplicator', ->
findRootDoc : sinon.stub().callsArgWith(1, null, @foundRootDoc, {}) findRootDoc : sinon.stub().callsArgWith(1, null, @foundRootDoc, {})
@projectOptionsHandler = @projectOptionsHandler =
setCompiler : sinon.stub() setCompiler : sinon.stub().callsArg(2)
@ProjectEntityUpdateHandler = @ProjectEntityUpdateHandler =
addDoc: sinon.stub().callsArgWith(5, null, {name:"somDoc"}) addDoc: sinon.stub().callsArgWith(5, null, {name:"somDoc"})
copyFileFromExistingProjectWithProject: sinon.stub().callsArgWith(5) copyFileFromExistingProjectWithProject: sinon.stub()
setRootDoc: sinon.stub() setRootDoc: sinon.stub()
addFolder: sinon.stub().callsArgWith(3, null, @newFolder) addFolder: sinon.stub().callsArgWith(3, null, @newFolder)
@ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject.withArgs(sinon.match.any, sinon.match.any, sinon.match.any, "BROKEN-FILE", sinon.match.any, sinon.match.any).callsArgWith(5, new Error("failed"))
@ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject.withArgs(sinon.match.any, sinon.match.any, sinon.match.any, sinon.match.object, sinon.match.any).callsArg(5)
@ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject.withArgs(sinon.match.any, sinon.match.any, sinon.match.any, null, sinon.match.any).callsArg(5)
@DocumentUpdaterHandler = @DocumentUpdaterHandler =
flushProjectToMongo: sinon.stub().callsArg(1) flushProjectToMongo: sinon.stub().callsArg(1)
@ -81,16 +85,25 @@ describe 'ProjectDuplicator', ->
@ProjectGetter.getProject.withArgs(@old_project_id, sinon.match.any).callsArgWith(2, null, @project) @ProjectGetter.getProject.withArgs(@old_project_id, sinon.match.any).callsArgWith(2, null, @project)
@ProjectGetter.getProject.withArgs(@new_project_id, sinon.match.any).callsArgWith(2, null, @stubbedNewProject) @ProjectGetter.getProject.withArgs(@new_project_id, sinon.match.any).callsArgWith(2, null, @stubbedNewProject)
@ProjectDeleter =
deleteProject: sinon.stub().callsArgWith(1,null)
@duplicator = SandboxedModule.require modulePath, requires: @duplicator = SandboxedModule.require modulePath, requires:
'../../models/Project':{Project:@Project} '../../models/Project':{Project:@Project}
"../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler "../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler
'./ProjectCreationHandler': @creationHandler './ProjectCreationHandler': @creationHandler
'./ProjectEntityUpdateHandler': @ProjectEntityUpdateHandler './ProjectEntityUpdateHandler': @ProjectEntityUpdateHandler
'./ProjectLocator': @locator './ProjectLocator': @locator
'./ProjectDeleter': @ProjectDeleter
'./ProjectOptionsHandler': @projectOptionsHandler './ProjectOptionsHandler': @projectOptionsHandler
"../Docstore/DocstoreManager": @DocstoreManager "../Docstore/DocstoreManager": @DocstoreManager
"./ProjectGetter":@ProjectGetter "./ProjectGetter":@ProjectGetter
'logger-sharelatex':{log:->} 'logger-sharelatex':{
log:->
err:->
}
describe "when the copy succeeds", ->
it "should look up the original project", (done) -> it "should look up the original project", (done) ->
newProjectName = "someProj" newProjectName = "someProj"
@ -162,3 +175,26 @@ describe 'ProjectDuplicator', ->
.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0], @owner._id) .calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0], @owner._id)
.should.equal true .should.equal true
done() done()
describe 'when there is an error', ->
beforeEach ->
@rootFolder.fileRefs = [{name:"file0", _id:"file0"}, "BROKEN-FILE"]
it 'should delete the broken cloned project', (done) ->
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@ProjectDeleter.deleteProject
.calledWith(@stubbedNewProject._id)
.should.equal true
done()
it 'should not delete the original project', (done) ->
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@ProjectDeleter.deleteProject
.calledWith(@old_project_id)
.should.equal false
done()
it 'should return an error', (done) ->
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
err.should.not.equal null
done()