diff --git a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee index d8613680a8..f7dd9f3e17 100644 --- a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee +++ b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee @@ -204,11 +204,11 @@ module.exports = DocumentUpdaterHandler = logger.error {project_id, doc_id, thread_id}, "doc updater returned a non-success status code: #{res.statusCode}" callback new Error("doc updater returned a non-success status code: #{res.statusCode}") - updateProjectStructure : (project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback = (error) ->)-> + updateProjectStructure : (project_id, userId, changes, callback = (error) ->)-> return callback() if !settings.apis.project_history?.enabled - docUpdates = DocumentUpdaterHandler._getRenameUpdates('doc', oldDocs, newDocs) - fileUpdates = DocumentUpdaterHandler._getRenameUpdates('file', oldFiles, newFiles) + docUpdates = DocumentUpdaterHandler._getRenameUpdates('doc', changes.oldDocs, changes.newDocs) + fileUpdates = DocumentUpdaterHandler._getRenameUpdates('file', changes.oldFiles, changes.newFiles) timer = new metrics.Timer("set-document") url = "#{settings.apis.documentupdater.url}/project/#{project_id}" @@ -231,14 +231,25 @@ module.exports = DocumentUpdaterHandler = callback new Error("doc updater returned a non-success status code: #{res.statusCode}") _getRenameUpdates: (entityType, oldEntities, newEntities) -> + oldEntities ||= [] + newEntities ||= [] updates = [] - for oldEntity in oldEntities - id = oldEntity[entityType]._id - newEntity = _.find newEntities, (newEntity) -> - newEntity[entityType]._id.toString() == id.toString() + oldEntitiesHash = _.indexBy oldEntities, (entity) -> entity[entityType]._id.toString() + newEntitiesHash = _.indexBy newEntities, (entity) -> entity[entityType]._id.toString() - if newEntity.path != oldEntity.path + for id, newEntity of newEntitiesHash + oldEntity = oldEntitiesHash[id] + + if !oldEntity? + # entity added + updates.push + id: id + pathname: newEntity.path + docLines: newEntity.docLines + url: newEntity.url + else if newEntity.path != oldEntity.path + # entity renamed updates.push id: id pathname: oldEntity.path diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee index a43d6f0447..f66094f9e8 100644 --- a/services/web/app/coffee/Features/Editor/EditorController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorController.coffee @@ -19,48 +19,48 @@ module.exports = EditorController = DocumentUpdaterHandler.flushDocToMongo project_id, doc_id, callback - addDoc: (project_id, folder_id, docName, docLines, source, callback = (error, doc)->)-> + addDoc: (project_id, folder_id, docName, docLines, source, user_id, callback = (error, doc)->)-> LockManager.getLock project_id, (err)-> if err? logger.err err:err, project_id:project_id, source:source, "could not get lock to addDoc" return callback(err) - EditorController.addDocWithoutLock project_id, folder_id, docName, docLines, source, (error, doc)-> + EditorController.addDocWithoutLock project_id, folder_id, docName, docLines, source, user_id, (error, doc)-> LockManager.releaseLock project_id, -> callback(error, doc) - addDocWithoutLock: (project_id, folder_id, docName, docLines, source, callback = (error, doc)->)-> + addDocWithoutLock: (project_id, folder_id, docName, docLines, source, user_id, callback = (error, doc)->)-> docName = docName.trim() logger.log {project_id, folder_id, docName, source}, "sending new doc to project" Metrics.inc "editor.add-doc" - ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, (err, doc, folder_id)=> + ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, user_id, (err, doc, folder_id)=> if err? logger.err err:err, project_id:project_id, docName:docName, "error adding doc without lock" return callback(err) EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc, source) callback(err, doc) - addFile: (project_id, folder_id, fileName, path, source, callback = (error, file)->)-> + addFile: (project_id, folder_id, fileName, path, source, user_id, callback = (error, file)->)-> LockManager.getLock project_id, (err)-> if err? logger.err err:err, project_id:project_id, source:source, "could not get lock to addFile" return callback(err) - EditorController.addFileWithoutLock project_id, folder_id, fileName, path, source, (error, file)-> + EditorController.addFileWithoutLock project_id, folder_id, fileName, path, source, user_id, (error, file)-> LockManager.releaseLock project_id, -> callback(error, file) - addFileWithoutLock: (project_id, folder_id, fileName, path, source, callback = (error, file)->)-> + addFileWithoutLock: (project_id, folder_id, fileName, path, source, user_id, callback = (error, file)->)-> fileName = fileName.trim() logger.log {project_id, folder_id, fileName, path}, "sending new file to project" Metrics.inc "editor.add-file" - ProjectEntityHandler.addFile project_id, folder_id, fileName, path, (err, fileRef, folder_id)=> + ProjectEntityHandler.addFile project_id, folder_id, fileName, path, user_id, (err, fileRef, folder_id)=> if err? logger.err err:err, project_id:project_id, folder_id:folder_id, fileName:fileName, "error adding file without lock" return callback(err) EditorRealTimeController.emitToRoom(project_id, 'reciveNewFile', folder_id, fileRef, source) callback(err, fileRef) - replaceFile: (project_id, file_id, fsPath, source, callback = (error) ->)-> - ProjectEntityHandler.replaceFile project_id, file_id, fsPath, callback + replaceFile: (project_id, file_id, fsPath, source, user_id, callback = (error) ->)-> + ProjectEntityHandler.replaceFile project_id, file_id, fsPath, user_id, callback addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)-> LockManager.getLock project_id, (err)-> diff --git a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee index e9bac7300d..cbc296b69f 100644 --- a/services/web/app/coffee/Features/Editor/EditorHttpController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorHttpController.coffee @@ -81,10 +81,11 @@ module.exports = EditorHttpController = project_id = req.params.Project_id name = req.body.name parent_folder_id = req.body.parent_folder_id + user_id = AuthenticationController.getLoggedInUserId(req) logger.log project_id:project_id, name:name, parent_folder_id:parent_folder_id, "getting request to add doc to project" if !EditorHttpController._nameIsAcceptableLength(name) return res.sendStatus 400 - EditorController.addDoc project_id, parent_folder_id, name, [], "editor", (error, doc) -> + EditorController.addDoc project_id, parent_folder_id, name, [], "editor", user_id, (error, doc) -> if error == "project_has_to_many_files" res.status(400).json(req.i18n.translate("project_has_to_many_files")) else if error? @@ -113,9 +114,9 @@ module.exports = EditorHttpController = entity_id = req.params.entity_id entity_type = req.params.entity_type name = req.body.name - user_id = AuthenticationController.getLoggedInUserId(req) if !EditorHttpController._nameIsAcceptableLength(name) return res.sendStatus 400 + user_id = AuthenticationController.getLoggedInUserId(req) EditorController.renameEntity project_id, entity_id, entity_type, name, user_id, (error) -> return next(error) if error? res.sendStatus 204 diff --git a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee index 92efe7bfcb..ef363fbbc7 100644 --- a/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee +++ b/services/web/app/coffee/Features/FileStore/FileStoreHandler.coffee @@ -21,9 +21,9 @@ module.exports = FileStoreHandler = return callback(new Error("can not upload symlink")) _cb = callback - callback = (err) -> + callback = (err, url) -> callback = -> # avoid double callbacks - _cb(err) + _cb(err, url) logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk" readStream = fs.createReadStream(fsPath) @@ -31,9 +31,10 @@ module.exports = FileStoreHandler = logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk" callback err readStream.on "open", () -> + url = FileStoreHandler._buildUrl(project_id, file_id) opts = method: "post" - uri: FileStoreHandler._buildUrl(project_id, file_id) + uri: url timeout:fiveMinsInMs writeStream = request(opts) writeStream.on "error", (err)-> @@ -45,7 +46,7 @@ module.exports = FileStoreHandler = logger.err {err, statusCode: response.statusCode}, "error uploading to filestore" callback(err) else - callback(null) + callback(null, url) readStream.pipe writeStream getFileStream: (project_id, file_id, query, callback)-> @@ -91,7 +92,7 @@ module.exports = FileStoreHandler = request opts, (err)-> if err? logger.err err:err, oldProject_id:oldProject_id, oldFile_id:oldFile_id, newProject_id:newProject_id, newFile_id:newFile_id, "something went wrong telling filestore api to copy file" - callback(err) + callback(err, opts.uri) _buildUrl: (project_id, file_id)-> return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}" diff --git a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee index a38b214a9e..cd78cddc09 100644 --- a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee @@ -52,7 +52,7 @@ module.exports = ProjectCreationHandler = return callback(error) if error? self._buildTemplate "mainbasic.tex", owner_id, projectName, (error, docLines)-> return callback(error) if error? - ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, (error, doc)-> + ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, owner_id, (error, doc)-> if error? logger.err err:error, "error adding doc when creating basic project" return callback(error) @@ -67,17 +67,17 @@ module.exports = ProjectCreationHandler = (callback) -> self._buildTemplate "main.tex", owner_id, projectName, (error, docLines)-> return callback(error) if error? - ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, (error, doc)-> + ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, owner_id, (error, doc)-> return callback(error) if error? ProjectEntityHandler.setRootDoc project._id, doc._id, callback (callback) -> self._buildTemplate "references.bib", owner_id, projectName, (error, docLines)-> return callback(error) if error? - ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "references.bib", docLines, (error, doc)-> + ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "references.bib", docLines, owner_id, (error, doc)-> callback(error) (callback) -> universePath = Path.resolve(__dirname + "/../../../templates/project_files/universe.jpg") - ProjectEntityHandler.addFile project._id, project.rootFolder[0]._id, "universe.jpg", universePath, callback + ProjectEntityHandler.addFile project._id, project.rootFolder[0]._id, "universe.jpg", universePath, owner_id, callback ], (error) -> callback(error, project) diff --git a/services/web/app/coffee/Features/Project/ProjectDuplicator.coffee b/services/web/app/coffee/Features/Project/ProjectDuplicator.coffee index d945326395..815e31eb27 100644 --- a/services/web/app/coffee/Features/Project/ProjectDuplicator.coffee +++ b/services/web/app/coffee/Features/Project/ProjectDuplicator.coffee @@ -12,7 +12,7 @@ logger = require("logger-sharelatex") module.exports = ProjectDuplicator = - _copyDocs: (newProject, originalRootDoc, originalFolder, desFolder, docContents, callback)-> + _copyDocs: (owner_id, newProject, originalRootDoc, originalFolder, desFolder, docContents, callback)-> setRootDoc = _.once (doc_id)-> projectEntityHandler.setRootDoc newProject._id, doc_id docs = originalFolder.docs or [] @@ -21,7 +21,7 @@ module.exports = ProjectDuplicator = if !doc?._id? return callback() content = docContents[doc._id.toString()] - projectEntityHandler.addDocWithProject newProject, desFolder._id, doc.name, content.lines, (err, newDoc)-> + projectEntityHandler.addDocWithProject newProject, desFolder._id, doc.name, content.lines, owner_id, (err, newDoc)-> if err? logger.err err:err, "error copying doc" return callback(err) @@ -31,15 +31,15 @@ module.exports = ProjectDuplicator = async.series jobs, callback - _copyFiles: (newProject, originalProject_id, originalFolder, desFolder, callback)-> + _copyFiles: (owner_id, newProject, originalProject_id, originalFolder, desFolder, callback)-> fileRefs = originalFolder.fileRefs or [] jobs = fileRefs.map (file)-> return (cb)-> - projectEntityHandler.copyFileFromExistingProjectWithProject newProject, desFolder._id, originalProject_id, file, cb + projectEntityHandler.copyFileFromExistingProjectWithProject newProject, desFolder._id, originalProject_id, file, owner_id, cb async.parallelLimit jobs, 5, callback - _copyFolderRecursivly: (newProject_id, originalProject_id, originalRootDoc, originalFolder, desFolder, docContents, callback)-> + _copyFolderRecursivly: (owner_id, newProject_id, originalProject_id, originalRootDoc, originalFolder, desFolder, docContents, callback)-> ProjectGetter.getProject newProject_id, {rootFolder:true, name:true}, (err, newProject)-> if err? logger.err project_id:newProject_id, "could not get project" @@ -53,12 +53,12 @@ module.exports = ProjectDuplicator = return cb() projectEntityHandler.addFolderWithProject newProject, desFolder?._id, childFolder.name, (err, newFolder)-> return cb(err) if err? - ProjectDuplicator._copyFolderRecursivly newProject_id, originalProject_id, originalRootDoc, childFolder, newFolder, docContents, cb + ProjectDuplicator._copyFolderRecursivly owner_id, newProject_id, originalProject_id, originalRootDoc, childFolder, newFolder, docContents, cb jobs.push (cb)-> - ProjectDuplicator._copyFiles newProject, originalProject_id, originalFolder, desFolder, cb + ProjectDuplicator._copyFiles owner_id, newProject, originalProject_id, originalFolder, desFolder, cb jobs.push (cb)-> - ProjectDuplicator._copyDocs newProject, originalRootDoc, originalFolder, desFolder, docContents, cb + ProjectDuplicator._copyDocs owner_id, newProject, originalRootDoc, originalFolder, desFolder, docContents, cb async.series jobs, callback @@ -90,7 +90,7 @@ module.exports = ProjectDuplicator = projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, -> - ProjectDuplicator._copyFolderRecursivly newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, -> + ProjectDuplicator._copyFolderRecursivly owner._id, newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, -> if err? logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, "error cloning project" callback(err, newProject) diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index abd5c7f3d8..446786fa57 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -16,7 +16,7 @@ projectUpdateHandler = require('./ProjectUpdateHandler') DocstoreManager = require "../Docstore/DocstoreManager" ProjectGetter = require "./ProjectGetter" CooldownManager = require '../Cooldown/CooldownManager' - +DocumentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') module.exports = ProjectEntityHandler = getAllFolders: (project_id, callback) -> @@ -106,8 +106,7 @@ module.exports = ProjectEntityHandler = flushProjectToThirdPartyDataStore: (project_id, callback) -> self = @ logger.log project_id:project_id, "flushing project to tpds" - documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') - documentUpdaterHandler.flushProjectToMongo project_id, (error) -> + DocumentUpdaterHandler.flushProjectToMongo project_id, (error) -> return callback(error) if error? ProjectGetter.getProject project_id, {name:true}, (error, project) -> return callback(error) if error? @@ -150,14 +149,14 @@ module.exports = ProjectEntityHandler = else DocstoreManager.getDoc project_id, doc_id, options, callback - addDoc: (project_id, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=> + addDoc: (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=> ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) -> if err? logger.err project_id:project_id, err:err, "error getting project for add doc" return callback(err) - ProjectEntityHandler.addDocWithProject project, folder_id, docName, docLines, callback + ProjectEntityHandler.addDocWithProject project, folder_id, docName, docLines, userId, callback - addDocWithProject: (project, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=> + addDocWithProject: (project, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=> project_id = project._id logger.log project_id: project_id, folder_id: folder_id, doc_name: docName, "adding doc to project with project" confirmFolder project, folder_id, (folder_id)=> @@ -177,7 +176,14 @@ module.exports = ProjectEntityHandler = rev: 0 }, (err) -> return callback(err) if err? - callback(null, doc, folder_id) + newDocs = [ + doc: doc + path: result?.path?.fileSystem + docLines: docLines.join('\n') + ] + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newDocs}, (error) -> + return callback(error) if error? + callback null, doc, folder_id restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) -> # getDoc will return the deleted doc's lines, but we don't actually remove @@ -186,20 +192,20 @@ module.exports = ProjectEntityHandler = return callback(error) if error? ProjectEntityHandler.addDoc project_id, null, name, lines, callback - addFile: (project_id, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)-> + addFile: (project_id, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id) ->)-> ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) -> if err? logger.err project_id:project_id, err:err, "error getting project for add file" return callback(err) - ProjectEntityHandler.addFileWithProject project, folder_id, fileName, path, callback + ProjectEntityHandler.addFileWithProject project, folder_id, fileName, path, userId, callback - addFileWithProject: (project, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)-> + addFileWithProject: (project, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id) ->)-> project_id = project._id logger.log project_id: project._id, folder_id: folder_id, file_name: fileName, path:path, "adding file" return callback(err) if err? confirmFolder project, folder_id, (folder_id)-> fileRef = new File name : fileName - FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (err)-> + FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (err, fileStoreUrl)-> if err? logger.err err:err, project_id: project._id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3" return callback(err) @@ -209,27 +215,31 @@ module.exports = ProjectEntityHandler = return callback(err) tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) -> return callback(err) if err? - callback(null, fileRef, folder_id) + newFiles = [ + file: fileRef + path: result?.path?.fileSystem + url: fileStoreUrl + ] + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) -> + return callback(error) if error? + callback null, fileRef, folder_id - replaceFile: (project_id, file_id, fsPath, callback)-> - ProjectGetter.getProject project_id, {name:true}, (err, project) -> + replaceFile: (project_id, file_id, fsPath, userId, callback)-> + self = ProjectEntityHandler + FileStoreHandler.uploadFileFromDisk project_id, file_id, fsPath, (err, fileStoreUrl)-> return callback(err) if err? - findOpts = - project_id:project._id - element_id:file_id - type:"file" - FileStoreHandler.uploadFileFromDisk project._id, file_id, fsPath, (err)-> + ProjectGetter.getProject project_id, {rootFolder: true, name:true}, (err, project) -> return callback(err) if err? # Note there is a potential race condition here (and elsewhere) # If the file tree changes between findElement and the Project.update # then the path to the file element will be out of date. In practice # this is not a problem so long as we do not do anything longer running # between them (like waiting for the file to upload.) - projectLocator.findElement findOpts, (err, fileRef, path)=> + projectLocator.findElement {project:project, element_id: file_id, type: 'file'}, (err, fileRef, path)=> return callback(err) if err? - tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (error) -> + tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (err) -> return callback(err) if err? - conditons = _id:project._id + conditions = _id:project._id inc = {} inc["#{path.mongo}.rev"] = 1 set = {} @@ -237,39 +247,43 @@ module.exports = ProjectEntityHandler = update = "$inc": inc "$set": set - Project.update conditons, update, {}, (err, second)-> - callback() + Project.findOneAndUpdate conditions, update, { "new": true}, (err) -> + return callback(err) if err? + newFiles = [ + file: fileRef + path: path.fileSystem + url: fileStoreUrl + ] + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, callback - copyFileFromExistingProject: (project_id, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)-> - logger.log project_id:project_id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "copying file in s3" - ProjectGetter.getProject project_id, {name:true}, (err, project) -> - if err? - logger.err project_id:project_id, err:err, "error getting project for copy file from existing project" - return callback(err) - ProjectEntityHandler.copyFileFromExistingProjectWithProject project, folder_id, originalProject_id, origonalFileRef, callback - - - copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)-> + copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)-> project_id = project._id - logger.log project_id:project_id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "copying file in s3 with project" + logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project" return callback(err) if err? confirmFolder project, folder_id, (folder_id)=> if !origonalFileRef? - logger.err project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "file trying to copy is null" + logger.err { project_id, folder_id, originalProject_id, origonalFileRef }, "file trying to copy is null" return callback() fileRef = new File name : origonalFileRef.name - FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err)-> + FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err, fileStoreUrl)-> if err? - logger.err err:err, project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "error coping file in s3" + logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error coping file in s3" return callback(err) ProjectEntityHandler._putElement project, folder_id, fileRef, "file", (err, result)=> if err? - logger.err err:err, project_id:project._id, folder_id:folder_id, "error putting element as part of copy" + logger.err { err, project_id, folder_id }, "error putting element as part of copy" return callback(err) - tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) -> + tpdsUpdateSender.addFile { project_id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) -> if err? - logger.err err:err, project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "error sending file to tpds worker" - callback(null, fileRef, folder_id) + logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error sending file to tpds worker" + newFiles = [ + file: fileRef + path: result?.path?.fileSystem + url: fileStoreUrl + ] + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) -> + return callback(error) if error? + callback null, fileRef, folder_id mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)-> self = @ @@ -381,11 +395,9 @@ module.exports = ProjectEntityHandler = endPath: result.path.fileSystem, rev: entity.rev tpdsUpdateSender.moveEntity opts - self.getAllEntitiesFromProject newProject, (error, newDocs, newFiles - ) => + self.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) => return callback(error) if error? - documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') - documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback _checkValidMove: (project, entityType, entityPath, destFolderId, callback = (error) ->) -> return callback() if !entityType.match(/folder/) @@ -442,8 +454,7 @@ module.exports = ProjectEntityHandler = return callback(error) if error? ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) => return callback(error) if error? - documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') - documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback _cleanUpEntity: (project, entity, entityType, callback = (error) ->) -> if(entityType.indexOf("file") != -1) @@ -466,7 +477,7 @@ module.exports = ProjectEntityHandler = unsetRootDocIfRequired (error) -> return callback(error) if error? - require('../../Features/DocumentUpdater/DocumentUpdaterHandler').deleteDoc project_id, doc_id, (error) -> + DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) -> return callback(error) if error? ProjectEntityHandler._insertDeletedDocReference project._id, doc, (error) -> return callback(error) if error? diff --git a/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee b/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee index bce0befe22..010594d16a 100644 --- a/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee +++ b/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee @@ -28,7 +28,7 @@ module.exports = FileTypeManager.isBinary path, fsPath, (err, isFile)-> return callback(err) if err? if isFile - self.p.processFile project_id, elementId, fsPath, path, source, callback + self.p.processFile project_id, elementId, fsPath, path, source, user_id, callback else self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback @@ -57,9 +57,9 @@ module.exports = if err? logger.err err:err, project_id:project_id, doc_id:doc_id, path:path, "error processing file" return callback(err) - editorController.addDoc project_id, folder._id, fileName, docLines, source, callback + editorController.addDoc project_id, folder._id, fileName, docLines, source, user_id, callback - processFile: (project_id, file_id, fsPath, path, source, callback)-> + processFile: (project_id, file_id, fsPath, path, source, user_id, callback)-> finish = (err)-> logger.log project_id:project_id, file_id:file_id, path:path, "completed processing file update from tpds" callback(err) @@ -69,9 +69,9 @@ module.exports = logger.err err:err, project_id:project_id, file_id:file_id, path:path, "error processing file" return callback(err) else if file_id? - editorController.replaceFile project_id, file_id, fsPath, source, finish + editorController.replaceFile project_id, file_id, fsPath, source, user_id, finish else - editorController.addFile project_id, folder?._id, fileName, fsPath, source, finish + editorController.addFile project_id, folder?._id, fileName, fsPath, source, user_id, finish writeStreamToDisk: (project_id, file_id, stream, callback = (err, fsPath)->)-> if !file_id? diff --git a/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee b/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee index f491807e9d..0cec7cfc9d 100644 --- a/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee +++ b/services/web/app/coffee/Features/Uploads/FileSystemImportManager.coffee @@ -28,9 +28,9 @@ module.exports = FileSystemImportManager = if existingDoc? EditorController.setDoc project_id, existingDoc._id, user_id, lines, "upload", callback else - EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback + EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", user_id, callback else - EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback + EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", user_id, callback addFile: (user_id, project_id, folder_id, name, path, replace, callback = (error, file)-> )-> FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)-> @@ -39,7 +39,7 @@ module.exports = FileSystemImportManager = return callback("path is symlink") if !replace - EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback + EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", user_id, callback else ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) -> return callback(error) if error? @@ -50,9 +50,9 @@ module.exports = FileSystemImportManager = existingFile = fileRef break if existingFile? - EditorController.replaceFile project_id, existingFile._id, path, "upload", callback + EditorController.replaceFile project_id, existingFile._id, path, "upload", user_id, callback else - EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback + EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", user_id, callback addFolder: (user_id, project_id, folder_id, name, path, replace, callback = (error)-> ) -> FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)-> diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 98d1c9e031..65a8bf1e91 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -111,7 +111,7 @@ module.exports = settings = trackchanges: url : "http://localhost:3015" project_history: - enabled: false + enabled: process.env.PROJECT_HISTORY_ENABLED == 'true' or false url : "http://localhost:3054" docstore: url : "http://#{process.env['DOCSTORE_HOST'] or 'localhost'}:3016" diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index aaa8666a16..ed12abf0be 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -24,6 +24,7 @@ services: REDIS_HOST: redis MONGO_URL: "mongodb://mongo/sharelatex" SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true' + PROJECT_HISTORY_ENABLED: 'true' depends_on: - redis - mongo diff --git a/services/web/docker-shared.template.yml b/services/web/docker-shared.template.yml index 48adb7a036..d697e59e96 100644 --- a/services/web/docker-shared.template.yml +++ b/services/web/docker-shared.template.yml @@ -25,6 +25,7 @@ services: - ./config:/app/config - ./test/unit/coffee:/app/test/unit/coffee:ro - ./test/acceptance/coffee:/app/test/acceptance/coffee:ro + - ./test/acceptance/files:/app/test/acceptance/files:ro - ./test/smoke/coffee:/app/test/smoke/coffee:ro MODULE_VOLUMES - working_dir: /app \ No newline at end of file + working_dir: /app diff --git a/services/web/test/acceptance/coffee/ProjectStructureTests.coffee b/services/web/test/acceptance/coffee/ProjectStructureTests.coffee new file mode 100644 index 0000000000..e54d4fac9d --- /dev/null +++ b/services/web/test/acceptance/coffee/ProjectStructureTests.coffee @@ -0,0 +1,307 @@ +async = require "async" +expect = require("chai").expect +mkdirp = require "mkdirp" +ObjectId = require("mongojs").ObjectId +Path = require "path" +fs = require "fs" +Settings = require "settings-sharelatex" +_ = require "underscore" + +ProjectGetter = require "../../../app/js/Features/Project/ProjectGetter.js" + +MockDocUpdaterApi = require './helpers/MockDocUpdaterApi' +MockFileStoreApi = require './helpers/MockFileStoreApi' +MockProjectHistoryApi = require './helpers/MockProjectHistoryApi' +request = require "./helpers/request" +User = require "./helpers/User" + +describe "ProjectStructureChanges", -> + before (done) -> + @owner = new User() + @owner.login done + + describe "creating a project from the example template", -> + before (done) -> + MockDocUpdaterApi.clearProjectStructureUpdates() + @owner.createProject "example-project", {template: "example"}, (error, project_id) => + throw error if error? + @example_project_id = project_id + done() + + it "should version creating a doc", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates + expect(updates.length).to.equal(2) + _.each updates, (update) => + expect(update.userId).to.equal(@owner._id) + expect(update.docLines).to.be.a('string') + expect(_.where(updates, pathname: "/main.tex").length).to.equal 1 + expect(_.where(updates, pathname: "/references.bib").length).to.equal 1 + + it "should version creating a file", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/universe.jpg") + expect(update.url).to.be.a('string'); + + describe "duplicating a project", -> + before (done) -> + MockDocUpdaterApi.clearProjectStructureUpdates() + @owner.request.post { + uri: "/Project/#{@example_project_id}/clone", + json: + projectName: 'new.tex' + }, (error, res, body) => + throw error if error? + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to add doc #{res.statusCode}") + @dup_project_id = body.project_id + done() + + it "should version the dosc created", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@dup_project_id).docUpdates + expect(updates.length).to.equal(2) + _.each updates, (update) => + expect(update.userId).to.equal(@owner._id) + expect(update.docLines).to.be.a('string') + expect(_.where(updates, pathname: "/main.tex").length).to.equal(1) + expect(_.where(updates, pathname: "/references.bib").length).to.equal(1) + + it "should version the files created", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@dup_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/universe.jpg") + expect(update.url).to.be.a('string'); + + describe "adding a doc", -> + before (done) -> + MockDocUpdaterApi.clearProjectStructureUpdates() + + ProjectGetter.getProject @example_project_id, (error, projects) => + throw error if error? + @owner.request.post { + uri: "project/#{@example_project_id}/doc", + json: + name: 'new.tex' + parent_folder_id: projects[0].rootFolder[0]._id + }, (error, res, body) => + throw error if error? + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to add doc #{res.statusCode}") + done() + + it "should version the doc added", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/new.tex") + expect(update.docLines).to.be.a('string'); + + describe "uploading a project", -> + before (done) -> + MockDocUpdaterApi.clearProjectStructureUpdates() + + zip_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test_project.zip')) + + req = @owner.request.post { + uri: "project/new/upload", + formData: + qqfile: zip_file + }, (error, res, body) => + throw error if error? + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload project #{res.statusCode}") + @uploaded_project_id = JSON.parse(body).project_id + done() + + it "should version the dosc created", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@uploaded_project_id).docUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/main.tex") + expect(update.docLines).to.equal("Test") + + it "should version the files created", -> + updates = MockDocUpdaterApi.getProjectStructureUpdates(@uploaded_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") + expect(update.url).to.be.a('string'); + + describe "uploading a file", -> + before (done) -> + ProjectGetter.getProject @example_project_id, (error, projects) => + throw error if error? + @root_folder_id = projects[0].rootFolder[0]._id.toString() + done() + + beforeEach () -> + MockDocUpdaterApi.clearProjectStructureUpdates() + + it "should version a newly uploaded file", (done) -> + image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/1pixel.png')) + + req = @owner.request.post { + uri: "project/#{@example_project_id}/upload", + qs: + folder_id: @root_folder_id + formData: + qqfile: + value: image_file + options: + filename: '1pixel.png', + contentType: 'image/png' + }, (error, res, body) => + throw error if error? + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload file #{res.statusCode}") + + updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") + expect(update.url).to.be.a('string'); + @original_file_url = update.url + + done() + + it "should version a replacement file", (done) -> + image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/2pixel.png')) + + req = @owner.request.post { + uri: "project/#{@example_project_id}/upload", + qs: + folder_id: @root_folder_id + formData: + qqfile: + value: image_file + options: + filename: '1pixel.png', + contentType: 'image/png' + }, (error, res, body) => + throw error if error? + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload file #{res.statusCode}") + + updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") + expect(update.url).to.be.a('string'); + + done() + + describe "tpds", -> + before (done) -> + @tpds_project_name = "tpds-project-#{new ObjectId().toString()}" + @owner.createProject @tpds_project_name, (error, project_id) => + throw error if error? + @tpds_project_id = project_id + mkdirp Settings.path.dumpFolder, done + + beforeEach () -> + MockDocUpdaterApi.clearProjectStructureUpdates() + + it "should version adding a doc", (done) -> + tex_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test.tex')) + + req = @owner.request.post { + uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/test.tex", + auth: + user: _.keys(Settings.httpAuthUsers)[0] + pass: _.values(Settings.httpAuthUsers)[0] + sendImmediately: true + } + + tex_file.on "error", (err) -> + throw err + + req.on "error", (err) -> + throw err + + req.on "response", (res) => + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload file #{res.statusCode}") + + updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).docUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/test.tex") + expect(update.docLines).to.equal("Test") + + done() + + tex_file.pipe(req) + + it "should version adding a new file", (done) -> + image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/1pixel.png')) + + req = @owner.request.post { + uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/1pixel.png", + auth: + user: _.keys(Settings.httpAuthUsers)[0] + pass: _.values(Settings.httpAuthUsers)[0] + sendImmediately: true + } + + image_file.on "error", (err) -> + throw err + + req.on "error", (err) -> + throw err + + req.on "response", (res) => + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload file #{res.statusCode}") + + updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") + expect(update.url).to.be.a('string'); + + done() + + image_file.pipe(req) + + it "should version replacing a file", (done) -> + image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/2pixel.png')) + + req = @owner.request.post { + uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/1pixel.png", + auth: + user: _.keys(Settings.httpAuthUsers)[0] + pass: _.values(Settings.httpAuthUsers)[0] + sendImmediately: true + } + + image_file.on "error", (err) -> + throw err + + req.on "error", (err) -> + throw err + + req.on "response", (res) => + if res.statusCode < 200 || res.statusCode >= 300 + throw new Error("failed to upload file #{res.statusCode}") + + updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).fileUpdates + expect(updates.length).to.equal(1) + update = updates[0] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") + expect(update.url).to.be.a('string'); + + done() + + image_file.pipe(req) diff --git a/services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee b/services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee index aefcd4513a..b00cd6b173 100644 --- a/services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee @@ -1,11 +1,38 @@ express = require("express") app = express() +bodyParser = require "body-parser" +jsonParser = bodyParser.json() module.exports = MockDocUpdaterApi = + updates: {} + + clearProjectStructureUpdates: () -> + @updates = {} + + getProjectStructureUpdates: (project_id) -> + @updates[project_id] || { docUpdates: [], fileUpdates: [] } + + addProjectStructureUpdates: (project_id, userId, docUpdates, fileUpdates) -> + @updates[project_id] ||= { docUpdates: [], fileUpdates: [] } + + for update in docUpdates + update.userId = userId + @updates[project_id].docUpdates.push(update) + + for update in fileUpdates + update.userId = userId + @updates[project_id].fileUpdates.push(update) + run: () -> app.post "/project/:project_id/flush", (req, res, next) => res.sendStatus 200 + app.post "/project/:project_id", jsonParser, (req, res, next) => + project_id = req.params.project_id + {userId, docUpdates, fileUpdates} = req.body + @addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates) + res.sendStatus 200 + app.listen 3003, (error) -> throw error if error? .on "error", (error) -> diff --git a/services/web/test/acceptance/coffee/helpers/MockDocstoreApi.coffee b/services/web/test/acceptance/coffee/helpers/MockDocstoreApi.coffee index 2133d40b9f..c5b003ac75 100644 --- a/services/web/test/acceptance/coffee/helpers/MockDocstoreApi.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockDocstoreApi.coffee @@ -13,6 +13,7 @@ module.exports = MockDocStoreApi = @docs[project_id][doc_id] = {lines, version, ranges} @docs[project_id][doc_id].rev ?= 0 @docs[project_id][doc_id].rev += 1 + @docs[project_id][doc_id]._id = doc_id res.json { modified: true rev: @docs[project_id][doc_id].rev diff --git a/services/web/test/acceptance/coffee/helpers/MockFileStoreApi.coffee b/services/web/test/acceptance/coffee/helpers/MockFileStoreApi.coffee new file mode 100644 index 0000000000..f3022302f4 --- /dev/null +++ b/services/web/test/acceptance/coffee/helpers/MockFileStoreApi.coffee @@ -0,0 +1,20 @@ +express = require("express") +app = express() + +module.exports = MockFileStoreApi = + files: {} + + run: () -> + app.post "/project/:project_id/file/:file_id", (req, res, next) => + req.on 'data', -> + + req.on 'end', -> + res.send 200 + + app.listen 3009, (error) -> + throw error if error? + .on "error", (error) -> + console.error "error starting MockFileStoreApi:", error.message + process.exit(1) + +MockFileStoreApi.run() diff --git a/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee new file mode 100644 index 0000000000..9027e22468 --- /dev/null +++ b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee @@ -0,0 +1,18 @@ +express = require("express") +app = express() + +module.exports = MockProjectHistoryApi = + docs: {} + + run: () -> + app.post "/project", (req, res, next) => + res.json project: id: 1 + + app.listen 3054, (error) -> + throw error if error? + .on "error", (error) -> + console.error "error starting MockProjectHistoryApi:", error.message + process.exit(1) + + +MockProjectHistoryApi.run() diff --git a/services/web/test/acceptance/coffee/helpers/User.coffee b/services/web/test/acceptance/coffee/helpers/User.coffee index 6cd7edd7d4..ad728fb263 100644 --- a/services/web/test/acceptance/coffee/helpers/User.coffee +++ b/services/web/test/acceptance/coffee/helpers/User.coffee @@ -99,11 +99,14 @@ class User getProject: (project_id, callback = (error, project)->) -> db.projects.findOne {_id: ObjectId(project_id.toString())}, callback - createProject: (name, callback = (error, project_id) ->) -> + createProject: (name, options, callback = (error, oroject_id) ->) -> + if typeof options == "function" + callback = options + options = {} + @request.post { url: "/project/new", - json: - projectName: name + json: Object.assign({projectName: name}, options) }, (error, response, body) -> return callback(error) if error? if !body?.project_id? diff --git a/services/web/test/acceptance/files/1pixel.png b/services/web/test/acceptance/files/1pixel.png new file mode 100644 index 0000000000..a97e79570b Binary files /dev/null and b/services/web/test/acceptance/files/1pixel.png differ diff --git a/services/web/test/acceptance/files/2pixel.png b/services/web/test/acceptance/files/2pixel.png new file mode 100644 index 0000000000..dea591ec24 Binary files /dev/null and b/services/web/test/acceptance/files/2pixel.png differ diff --git a/services/web/test/acceptance/files/test.tex b/services/web/test/acceptance/files/test.tex new file mode 100644 index 0000000000..8318c86b35 --- /dev/null +++ b/services/web/test/acceptance/files/test.tex @@ -0,0 +1 @@ +Test \ No newline at end of file diff --git a/services/web/test/acceptance/files/test_project.zip b/services/web/test/acceptance/files/test_project.zip new file mode 100644 index 0000000000..eb489458c3 Binary files /dev/null and b/services/web/test/acceptance/files/test_project.zip differ diff --git a/services/web/test/acceptance/scripts/full-test.sh b/services/web/test/acceptance/scripts/full-test.sh index ca10c32a0a..9351df81e3 100755 --- a/services/web/test/acceptance/scripts/full-test.sh +++ b/services/web/test/acceptance/scripts/full-test.sh @@ -3,7 +3,7 @@ # If you're running on OS X, you probably need to rebuild # some dependencies in the docker container, before it will start. # -# npm rebuild --update-binary +#npm rebuild --update-binary echo ">> Starting server..." diff --git a/services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee b/services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee index ec81ae1c2e..14ccaa3a33 100644 --- a/services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee +++ b/services/web/test/unit/coffee/DocumentUpdater/DocumentUpdaterHandlerTests.coffee @@ -390,26 +390,13 @@ describe 'DocumentUpdaterHandler', -> describe "updateProjectStructure ", -> beforeEach -> @user_id = 1234 - @docIdA = new ObjectId() - @docIdB = new ObjectId() - @oldDocs = [ - { path: '/old_a', doc: _id: @docIdA } - { path: '/old_b', doc: _id: @docIdB } - ] - # create new instances of the same ObjectIds so that == doens't pass - @newDocs = [ - { path: '/old_a', doc: _id: new ObjectId(@docIdA.toString()) } - { path: '/new_b', doc: _id: new ObjectId(@docIdB.toString()) } - ] - @oldFiles = [] - @newFiles = [] describe "with project history disabled", -> beforeEach -> @settings.apis.project_history.enabled = false @request.post = sinon.stub() - @handler.updateProjectStructure @project_id, @user_id, @oldDocs, @newDocs, @oldFiles, @newFiles, @callback + @handler.updateProjectStructure @project_id, @user_id, {}, @callback it 'does not make a web request', -> @request.post.called.should.equal false @@ -420,20 +407,85 @@ describe 'DocumentUpdaterHandler', -> describe "with project history enabled", -> beforeEach -> @settings.apis.project_history.enabled = true + @url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}" @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "") - @handler.updateProjectStructure @project_id, @user_id, @oldDocs, @newDocs, @oldFiles, @newFiles, @callback - it 'should send the structure update to the document updater', -> - docUpdates = [ - id: @docIdB, - pathname: "/old_b" - newPathname: "/new_b" - ] + describe "when an entity has changed name", -> + it 'should send the structure update to the document updater', (done) -> + @docIdA = new ObjectId() + @docIdB = new ObjectId() + @changes = { + oldDocs: [ + { path: '/old_a', doc: _id: @docIdA } + { path: '/old_b', doc: _id: @docIdB } + ] + # create new instances of the same ObjectIds so that == doesn't pass + newDocs: [ + { path: '/old_a', doc: _id: new ObjectId(@docIdA.toString()) } + { path: '/new_b', doc: _id: new ObjectId(@docIdB.toString()) } + ] + } - url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}" - @request.post - .calledWith(url: url, json: {docUpdates, fileUpdates: [], userId: @user_id}) - .should.equal true + docUpdates = [ + id: @docIdB.toString(), + pathname: "/old_b" + newPathname: "/new_b" + ] + + @handler.updateProjectStructure @project_id, @user_id, @changes, () => + @request.post + .calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id}) + .should.equal true + done() + + describe "when a doc has been added", -> + it 'should send the structure update to the document updater', (done) -> + @docId = new ObjectId() + @changes = newDocs: [ + { path: '/foo', docLines: 'a\nb', doc: _id: @docId } + ] + + docUpdates = [ + id: @docId.toString(), + pathname: "/foo" + docLines: 'a\nb' + url: undefined + ] + + @handler.updateProjectStructure @project_id, @user_id, @changes, () => + @request.post + .calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id}) + .should.equal true + done() + + describe "when a file has been added", -> + it 'should send the structure update to the document updater', (done) -> + @fileId = new ObjectId() + @changes = newFiles: [ + { path: '/bar', url: 'filestore.example.com/file', file: _id: @fileId } + ] + + fileUpdates = [ + id: @fileId.toString(), + pathname: "/bar" + url: 'filestore.example.com/file' + docLines: undefined + ] + + @handler.updateProjectStructure @project_id, @user_id, @changes, () => + @request.post + .calledWith(url: @url, json: {docUpdates: [], fileUpdates, userId: @user_id}) + .should.equal true + done() + + describe "when a doc has been deleted", -> + it 'should do nothing', (done) -> + @docId = new ObjectId() + @changes = oldDocs: [ + { path: '/foo', docLines: 'a\nb', doc: _id: @docId } + ] + + @handler.updateProjectStructure @project_id, @user_id, @changes, () => + @request.post.called.should.equal false + done() - it "should call the callback with no error", -> - @callback.calledWith(null).should.equal true diff --git a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee index ae5e8d79e3..4e1af79b46 100644 --- a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee +++ b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee @@ -131,24 +131,24 @@ describe "EditorController", -> @docLines = ["1234","dskl"] it 'should add the doc using the project entity handler', (done)-> - mock = sinon.mock(@ProjectEntityHandler).expects("addDoc").withArgs(@project_id, @folder_id, @docName, @docLines).callsArg(4) + mock = sinon.mock(@ProjectEntityHandler).expects("addDoc").withArgs(@project_id, @folder_id, @docName, @docLines).callsArg(5) - @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, -> + @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, -> mock.verify() done() it 'should send the update out to the users in the project', (done)-> - @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(4, null, @doc, @folder_id) + @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(5, null, @doc, @folder_id) - @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, => + @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, => @EditorRealTimeController.emitToRoom .calledWith(@project_id, "reciveNewDoc", @folder_id, @doc, @source) .should.equal true done() it 'should return the doc to the callback', (done) -> - @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(4, null, @doc, @folder_id) - @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, (error, doc) => + @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(5, null, @doc, @folder_id) + @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, (error, doc) => doc.should.equal @doc done() @@ -157,32 +157,29 @@ describe "EditorController", -> beforeEach -> @LockManager.getLock.callsArgWith(1) @LockManager.releaseLock.callsArgWith(1) - @EditorController.addDocWithoutLock = sinon.stub().callsArgWith(5) + @EditorController.addDocWithoutLock = sinon.stub().callsArgWith(6) it "should call addDocWithoutLock", (done)-> - @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => - @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @docName, @docLines, @source).should.equal true + @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, => + @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @docName, @docLines, @source, @user_id).should.equal true done() it "should take the lock", (done)-> - @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => + @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, => @LockManager.getLock.calledWith(@project_id).should.equal true done() it "should release the lock", (done)-> - @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => + @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, => @LockManager.releaseLock.calledWith(@project_id).should.equal true done() it "should error if it can't cat the lock", (done)-> @LockManager.getLock = sinon.stub().callsArgWith(1, "timed out") - @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, (err)=> + @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, (err)=> expect(err).to.exist err.should.equal "timed out" - done() - - - + done() describe 'addFileWithoutLock:', -> beforeEach -> @@ -196,23 +193,23 @@ describe "EditorController", -> @stream = new ArrayBuffer() it 'should add the folder using the project entity handler', (done)-> - @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4) - @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, => - @ProjectEntityHandler.addFile.calledWith(@project_id, @folder_id).should.equal true + @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5) + @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, => + @ProjectEntityHandler.addFile.calledWith(@project_id, @folder_id, @fileName, @stream, @user_id).should.equal true done() it 'should send the update of a new folder out to the users in the project', (done)-> - @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4, null, @file, @folder_id) + @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5, null, @file, @folder_id) - @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, => + @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, => @EditorRealTimeController.emitToRoom .calledWith(@project_id, "reciveNewFile", @folder_id, @file, @source) .should.equal true done() it "should return the file in the callback", (done) -> - @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4, null, @file, @folder_id) - @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, (error, file) => + @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5, null, @file, @folder_id) + @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) => file.should.equal @file done() @@ -222,28 +219,28 @@ describe "EditorController", -> beforeEach -> @LockManager.getLock.callsArgWith(1) @LockManager.releaseLock.callsArgWith(1) - @EditorController.addFileWithoutLock = sinon.stub().callsArgWith(5) + @EditorController.addFileWithoutLock = sinon.stub().callsArgWith(6) it "should call addFileWithoutLock", (done)-> - @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => - @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @fileName, @stream, @source).should.equal true + @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) => + @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @fileName, @stream, @source, @user_id).should.equal true done() it "should take the lock", (done)-> - @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => + @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) => @LockManager.getLock.calledWith(@project_id).should.equal true done() it "should release the lock", (done)-> - @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => + @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) => @LockManager.releaseLock.calledWith(@project_id).should.equal true done() it "should error if it can't cat the lock", (done)-> @LockManager.getLock = sinon.stub().callsArgWith(1, "timed out") - @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (err, file) => - expect(err).to.exist - err.should.equal "timed out" + @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) => + expect(error).to.exist + error.should.equal "timed out" done() @@ -256,9 +253,9 @@ describe "EditorController", -> @fsPath = "/folder/file.png" it 'should send the replace file message to the editor controller', (done)-> - @ProjectEntityHandler.replaceFile = sinon.stub().callsArgWith(3) - @EditorController.replaceFile @project_id, @file_id, @fsPath, @source, => - @ProjectEntityHandler.replaceFile.calledWith(@project_id, @file_id, @fsPath).should.equal true + @ProjectEntityHandler.replaceFile = sinon.stub().callsArgWith(4) + @EditorController.replaceFile @project_id, @file_id, @fsPath, @source, @user_id, => + @ProjectEntityHandler.replaceFile.calledWith(@project_id, @file_id, @fsPath, @user_id).should.equal true done() describe 'addFolderWithoutLock :', -> diff --git a/services/web/test/unit/coffee/Editor/EditorHttpControllerTests.coffee b/services/web/test/unit/coffee/Editor/EditorHttpControllerTests.coffee index 2df4af72ec..e05846c5d5 100644 --- a/services/web/test/unit/coffee/Editor/EditorHttpControllerTests.coffee +++ b/services/web/test/unit/coffee/Editor/EditorHttpControllerTests.coffee @@ -201,7 +201,7 @@ describe "EditorHttpController", -> @req.body = name: @name = "doc-name" parent_folder_id: @parent_folder_id - @EditorController.addDoc = sinon.stub().callsArgWith(5, null, @doc) + @EditorController.addDoc = sinon.stub().callsArgWith(6, null, @doc) describe "successfully", -> beforeEach -> @@ -209,7 +209,7 @@ describe "EditorHttpController", -> it "should call EditorController.addDoc", -> @EditorController.addDoc - .calledWith(@project_id, @parent_folder_id, @name, [], "editor") + .calledWith(@project_id, @parent_folder_id, @name, [], "editor", @userId) .should.equal true it "should send the doc back as JSON", -> diff --git a/services/web/test/unit/coffee/FileStore/FileStoreHandlerTests.coffee b/services/web/test/unit/coffee/FileStore/FileStoreHandlerTests.coffee index 73f32f3a60..90a3e870d1 100644 --- a/services/web/test/unit/coffee/FileStore/FileStoreHandlerTests.coffee +++ b/services/web/test/unit/coffee/FileStore/FileStoreHandlerTests.coffee @@ -16,7 +16,7 @@ describe "FileStoreHandler", -> }) @writeStream = my:"writeStream" - on: (type, cb)-> + on: (type, cb)-> if type == "response" cb({statusCode: 200}) @readStream = {my:"readStream", on: sinon.stub()} @@ -38,7 +38,7 @@ describe "FileStoreHandler", -> @isSafeOnFileSystem = true it "should create read stream", (done)-> - @fs.createReadStream.returns + @fs.createReadStream.returns pipe:-> on: (type, cb)-> if type == "open" @@ -49,8 +49,8 @@ describe "FileStoreHandler", -> it "should pipe the read stream to request", (done)-> @request.returns(@writeStream) - @fs.createReadStream.returns - on: (type, cb)-> + @fs.createReadStream.returns + on: (type, cb)-> if type == "open" cb() pipe:(o)=> @@ -59,9 +59,9 @@ describe "FileStoreHandler", -> @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => it "should pass the correct options to request", (done)-> - @fs.createReadStream.returns + @fs.createReadStream.returns pipe:-> - on: (type, cb)-> + on: (type, cb)-> if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => @@ -70,23 +70,24 @@ describe "FileStoreHandler", -> done() it "builds the correct url", (done)-> - @fs.createReadStream.returns + @fs.createReadStream.returns pipe:-> - on: (type, cb)-> + on: (type, cb)-> if type == "open" cb() @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, => @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true done() - it 'should callback with null', (done) -> + it 'should callback with the url', (done) -> @fs.createReadStream.returns pipe:-> on: (type, cb)-> if type == "open" cb() - @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) => + @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err, url) => expect(err).to.not.exist + expect(url).to.equal(@handler._buildUrl()) done() describe "symlink", -> @@ -218,6 +219,11 @@ describe "FileStoreHandler", -> @handler._buildUrl.calledWith(@newProject_id, @newFile_id).should.equal true done() + it "returns the url", (done)-> + @request.callsArgWith(1, null) + @handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, (err, url) => + url.should.equal "http://filestore.stubbedBuilder.com" + done() it "should return the err", (done)-> error = "errrror" diff --git a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee index 5c32db63d3..470b538bd9 100644 --- a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee @@ -33,8 +33,8 @@ describe 'ProjectCreationHandler', -> constructor:(options)-> {@name} = options @ProjectEntityHandler = - addDoc: sinon.stub().callsArgWith(4, null, {_id: docId}) - addFile: sinon.stub().callsArg(4) + addDoc: sinon.stub().callsArgWith(5, null, {_id: docId}) + addFile: sinon.stub().callsArg(5) setRootDoc: sinon.stub().callsArg(2) @ProjectDetailsHandler = validateProjectName: sinon.stub().yields() @@ -149,7 +149,7 @@ describe 'ProjectCreationHandler', -> .should.equal true it 'should insert main.tex', -> - @ProjectEntityHandler.addDoc.calledWith(project_id, rootFolderId, "main.tex", ["mainbasic.tex", "lines"]) + @ProjectEntityHandler.addDoc.calledWith(project_id, rootFolderId, "main.tex", ["mainbasic.tex", "lines"], ownerId) .should.equal true it 'should set the main doc id', -> @@ -180,19 +180,20 @@ describe 'ProjectCreationHandler', -> it 'should insert main.tex', -> @ProjectEntityHandler.addDoc - .calledWith(project_id, rootFolderId, "main.tex", ["main.tex", "lines"]) + .calledWith(project_id, rootFolderId, "main.tex", ["main.tex", "lines"], ownerId) .should.equal true it 'should insert references.bib', -> @ProjectEntityHandler.addDoc - .calledWith(project_id, rootFolderId, "references.bib", ["references.bib", "lines"]) + .calledWith(project_id, rootFolderId, "references.bib", ["references.bib", "lines"], ownerId) .should.equal true it 'should insert universe.jpg', -> @ProjectEntityHandler.addFile .calledWith( project_id, rootFolderId, "universe.jpg", - Path.resolve(__dirname + "/../../../../app/templates/project_files/universe.jpg") + Path.resolve(__dirname + "/../../../../app/templates/project_files/universe.jpg"), + ownerId ) .should.equal true diff --git a/services/web/test/unit/coffee/Project/ProjectDuplicatorTests.coffee b/services/web/test/unit/coffee/Project/ProjectDuplicatorTests.coffee index a1101cfbe0..b489014e7e 100644 --- a/services/web/test/unit/coffee/Project/ProjectDuplicatorTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectDuplicatorTests.coffee @@ -64,8 +64,8 @@ describe 'ProjectDuplicator', -> @projectOptionsHandler = setCompiler : sinon.stub() @entityHandler = - addDocWithProject: sinon.stub().callsArgWith(4, null, {name:"somDoc"}) - copyFileFromExistingProjectWithProject: sinon.stub().callsArgWith(4) + addDocWithProject: sinon.stub().callsArgWith(5, null, {name:"somDoc"}) + copyFileFromExistingProjectWithProject: sinon.stub().callsArgWith(5) setRootDoc: sinon.stub() addFolderWithProject: sinon.stub().callsArgWith(3, null, @newFolder) @@ -112,13 +112,13 @@ describe 'ProjectDuplicator', -> done() it 'should use the same compiler', (done)-> - @entityHandler.addDocWithProject.callsArgWith(4, null, @rootFolder.docs[0]) + @entityHandler.addDocWithProject.callsArgWith(5, null, @rootFolder.docs[0], @owner._id) @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @projectOptionsHandler.setCompiler.calledWith(@stubbedNewProject._id, @project.compiler).should.equal true done() it 'should use the same root doc', (done)-> - @entityHandler.addDocWithProject.callsArgWith(4, null, @rootFolder.docs[0]) + @entityHandler.addDocWithProject.callsArgWith(5, null, @rootFolder.docs[0], @owner._id) @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @entityHandler.setRootDoc.calledWith(@stubbedNewProject._id, @rootFolder.docs[0]._id).should.equal true done() @@ -139,14 +139,26 @@ describe 'ProjectDuplicator', -> it 'should copy all the docs', (done)-> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @DocstoreManager.getAllDocs.calledWith(@old_project_id).should.equal true - @entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @doc0.name, @doc0_lines).should.equal true - @entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @newFolder._id, @doc1.name, @doc1_lines).should.equal true - @entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @newFolder._id, @doc2.name, @doc2_lines).should.equal true + @entityHandler.addDocWithProject + .calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @doc0.name, @doc0_lines, @owner._id) + .should.equal true + @entityHandler.addDocWithProject + .calledWith(@stubbedNewProject, @newFolder._id, @doc1.name, @doc1_lines, @owner._id) + .should.equal true + @entityHandler.addDocWithProject + .calledWith(@stubbedNewProject, @newFolder._id, @doc2.name, @doc2_lines, @owner._id) + .should.equal true done() it 'should copy all the files', (done)-> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> - @entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @project._id, @rootFolder.fileRefs[0]).should.equal true - @entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level1folder.fileRefs[0]).should.equal true - @entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0]).should.equal true + @entityHandler.copyFileFromExistingProjectWithProject + .calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @project._id, @rootFolder.fileRefs[0], @owner._id) + .should.equal true + @entityHandler.copyFileFromExistingProjectWithProject + .calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level1folder.fileRefs[0], @owner._id) + .should.equal true + @entityHandler.copyFileFromExistingProjectWithProject + .calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0], @owner._id) + .should.equal true done() diff --git a/services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee index dc723bf8bd..f62690e226 100644 --- a/services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectEntityHandlerTests.coffee @@ -17,9 +17,10 @@ describe 'ProjectEntityHandler', -> userId = 1234 beforeEach -> + @fileUrl = 'filestore.example.com/file' @FileStoreHandler = - uploadFileFromDisk:(project_id, fileRef, localImagePath, callback)->callback() - copyFile: sinon.stub().callsArgWith(4, null) + uploadFileFromDisk: sinon.stub().callsArgWith(3, null, @fileUrl) + copyFile: sinon.stub().callsArgWith(4, null, @fileUrl) @tpdsUpdateSender = addDoc:sinon.stub().callsArg(1) addFile:sinon.stub().callsArg(1) @@ -67,6 +68,9 @@ describe 'ProjectEntityHandler', -> findElement : sinon.stub() @settings = maxEntitiesPerProject:200 + @documentUpdaterHandler = + updateProjectStructure: sinon.stub().yields() + deleteDoc: sinon.stub().callsArg(2) @ProjectEntityHandler = SandboxedModule.require modulePath, requires: '../../models/Project': Project:@ProjectModel '../../models/Doc': Doc:@DocModel @@ -75,7 +79,7 @@ describe 'ProjectEntityHandler', -> '../FileStore/FileStoreHandler':@FileStoreHandler '../ThirdPartyDataStore/TpdsUpdateSender':@tpdsUpdateSender './ProjectLocator': @projectLocator - '../../Features/DocumentUpdater/DocumentUpdaterHandler':@documentUpdaterHandler = {} + '../../Features/DocumentUpdater/DocumentUpdaterHandler':@documentUpdaterHandler '../Docstore/DocstoreManager': @DocstoreManager = {} 'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->} './ProjectUpdateHandler': @projectUpdater @@ -184,7 +188,6 @@ describe 'ProjectEntityHandler', -> describe "_cleanUpEntity", -> beforeEach -> @entity_id = "4eecaffcbffa66588e000009" - @documentUpdaterHandler.deleteDoc = sinon.stub().callsArg(2) @FileStoreHandler.deleteFile = sinon.stub().callsArg(2) @ProjectEntityHandler.unsetRootDoc = sinon.stub().callsArg(1) @@ -240,7 +243,6 @@ describe 'ProjectEntityHandler', -> @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, path: @pathAfterMove) @ProjectGetter.getProject.callsArgWith(2, null, @project) @tpdsUpdateSender.moveEntity = sinon.stub() - @documentUpdaterHandler.updateProjectStructure = sinon.stub().callsArg(6) @ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub() @ProjectEntityHandler.getAllEntitiesFromProject .onFirstCall() @@ -272,7 +274,7 @@ describe 'ProjectEntityHandler', -> it "should should send the update to the doc updater", -> @documentUpdaterHandler.updateProjectStructure - .calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) + .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles}) .should.equal true it 'should remove the element from its current position', -> @@ -324,7 +326,7 @@ describe 'ProjectEntityHandler', -> it "should should send the update to the doc updater", -> @documentUpdaterHandler.updateProjectStructure - .calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) + .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles}) .should.equal true it 'should remove the element from its current position', -> @@ -455,7 +457,7 @@ describe 'ProjectEntityHandler', -> @tpdsUpdateSender.addDoc = sinon.stub().callsArg(1) @DocstoreManager.updateDoc = sinon.stub().yields(null, true, 0) - @ProjectEntityHandler.addDoc project_id, folder_id, @name, @lines, @callback + @ProjectEntityHandler.addDoc project_id, folder_id, @name, @lines, userId, @callback # Created doc @doc = @ProjectEntityHandler._putElement.args[0][2] @@ -484,6 +486,16 @@ describe 'ProjectEntityHandler', -> .calledWith(project_id, @doc._id.toString(), @lines) .should.equal true + it "should should send the change in project structure to the doc updater", () -> + newDocs = [ + doc: @doc + path: @path + docLines: @lines.join('\n') + ] + @documentUpdaterHandler.updateProjectStructure + .calledWith(project_id, userId, {newDocs}) + .should.equal true + describe "restoreDoc", -> beforeEach -> @name = "doc-name" @@ -512,7 +524,10 @@ describe 'ProjectEntityHandler', -> describe 'addFile', -> fileName = "something.jpg" beforeEach -> + @fileSystemPath = "somehintg" + @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem: @fileSystemPath}}) @filePath = "somewhere" + it 'should upload it via the FileStoreHandler', (done)-> @FileStoreHandler.uploadFileFromDisk = (passedProject_id, file_id, filePath, callback)=> file_id.should.equal "file_id" @@ -520,7 +535,7 @@ describe 'ProjectEntityHandler', -> filePath.should.equal @filePath done() - @ProjectEntityHandler.addFile project_id, folder_id, fileName, @filePath, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.addFile project_id, folder_id, fileName, @filePath, userId, (err, fileRef, parentFolder)-> it 'should put file into folder by calling put element', (done)-> @ProjectEntityHandler._putElement = (passedProject, passedFolder_id, passedFileRef, passedType, callback)-> @@ -530,11 +545,10 @@ describe 'ProjectEntityHandler', -> passedType.should.equal 'file' done() - @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)-> it 'should return doc and parent folder', (done)-> - @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}}) - @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)-> parentFolder.should.equal folder_id fileRef.name.should.equal fileName done() @@ -554,33 +568,45 @@ describe 'ProjectEntityHandler', -> options.rev.should.equal 0 done() - @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)-> - describe 'replacing a file', -> + it "should should send the change in project structure to the doc updater", (done) -> + @documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) => + passed_project_id.should.equal project_id + passed_user_id.should.equal userId + { newFiles } = changes + newFiles.length.should.equal 1 + newFile = newFiles[0] + newFile.file.name.should.equal fileName + newFile.path.should.equal @fileSystemPath + newFile.url.should.equal @fileUrl + done() + @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, () -> + + describe 'replaceFile', -> beforeEach -> @projectLocator @file_id = "file_id_here" @fsPath = "fs_path_here.png" - @fileRef = {rev:3, _id:@file_id} - @filePaths = {fileSystem:"/folder1/file.png", mongo:"folder.1.files.somewhere"} + @fileRef = {rev:3, _id: @file_id, name: @fileName = "fileName"} + @filePaths = {fileSystem: @fileSystemPath="/folder1/file.png", mongo:"folder.1.files.somewhere"} @projectLocator.findElement = sinon.stub().callsArgWith(1, null, @fileRef, @filePaths) - @ProjectModel.update = (_, __, ___, cb)-> cb() + @ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3) @ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project) it 'should find the file', (done)-> - - @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => - @projectLocator.findElement.calledWith({element_id:@file_id, type:"file", project_id:project_id}).should.equal true + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => + @projectLocator.findElement + .calledWith({element_id:@file_id, type:"file", project: @project}) + .should.equal true done() it 'should tell the file store handler to upload the file from disk', (done)-> - @FileStoreHandler.uploadFileFromDisk = sinon.stub().callsArgWith(3) - @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => @FileStoreHandler.uploadFileFromDisk.calledWith(project_id, @file_id, @fsPath).should.equal true done() - it 'should send the file to the tpds with an incremented rev', (done)-> @tpdsUpdateSender.addFile = (options)=> options.project_id.should.equal project_id @@ -590,26 +616,39 @@ describe 'ProjectEntityHandler', -> options.rev.should.equal @fileRef.rev + 1 done() - @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => it 'should inc the rev id', (done)-> - @ProjectModel.update = (conditions, update, options, callback)=> + @ProjectModel.findOneAndUpdate = (conditions, update, options, callback)=> conditions._id.should.equal project_id update.$inc["#{@filePaths.mongo}.rev"].should.equal 1 done() - @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => it 'should update the created at date', (done)-> d = new Date() - @ProjectModel.update = (conditions, update, options, callback)=> + @ProjectModel.findOneAndUpdate = (conditions, update, options, callback)=> conditions._id.should.equal project_id differenceInMs = update.$set["#{@filePaths.mongo}.created"].getTime() - d.getTime() differenceInMs.should.be.below(20) done() - @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => + it "should should send the old and new project structure to the doc updater", (done) -> + @documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) => + passed_project_id.should.equal project_id + passed_user_id.should.equal userId + { newFiles } = changes + newFiles.length.should.equal 1 + newFile = newFiles[0] + newFile.file.name.should.equal @fileName + newFile.path.should.equal @fileSystemPath + newFile.url.should.equal @fileUrl + done() + + @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, => describe 'addFolder', -> folderName = "folder1234" @@ -943,19 +982,19 @@ describe 'ProjectEntityHandler', -> @ProjectModel.update.calledWith({_id : @project_id}, {$unset : {rootDoc_id: true}}) .should.equal true - describe 'copyFileFromExistingProject', -> + describe 'copyFileFromExistingProjectWithProject', -> fileName = "something.jpg" filePath = "dumpFolder/somewhere/image.jpeg" oldProject_id = "123kljadas" oldFileRef = {name:fileName, _id:"oldFileRef"} - beforeEach -> - @ProjectGetter.getProject = (project_id, fields, callback)=> callback(null, {name:@project.name, _id:@project._id}) - @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}}) + beforeEach -> + @fileSystemPath = "somehintg" + @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem: @fileSystemPath}}) it 'should copy the file in FileStoreHandler', (done)-> @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}}) - @ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)=> + @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)=> @FileStoreHandler.copyFile.calledWith(oldProject_id, oldFileRef._id, project_id, fileRef._id).should.equal true done() @@ -967,10 +1006,10 @@ describe 'ProjectEntityHandler', -> passedType.should.equal 'file' done() - @ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)-> it 'should return doc and parent folder', (done)-> - @ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)-> parentFolder.should.equal folder_id fileRef.name.should.equal fileName done() @@ -990,8 +1029,21 @@ describe 'ProjectEntityHandler', -> options.rev.should.equal 0 done() - @ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> + @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)-> + it "should should send the change in project structure to the doc updater", (done) -> + @documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) => + passed_project_id.should.equal project_id + passed_user_id.should.equal userId + { newFiles } = changes + newFiles.length.should.equal 1 + newFile = newFiles[0] + newFile.file.name.should.equal fileName + newFile.path.should.equal @fileSystemPath + newFile.url.should.equal @fileUrl + done() + + @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)-> describe "renameEntity", -> beforeEach -> @@ -1012,12 +1064,12 @@ describe 'ProjectEntityHandler', -> @projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: @entity_id, name:"old.tex", rev:4 }, @path) @tpdsUpdateSender.moveEntity = sinon.stub() @ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3, null, @project) - @documentUpdaterHandler.updateProjectStructure = sinon.stub().callsArg(6) + @documentUpdaterHandler.updateProjectStructure = sinon.stub().yields() it "should should send the old and new project structure to the doc updater", (done) -> @ProjectEntityHandler.renameEntity project_id, @entity_id, @entityType, @newName, userId, => @documentUpdaterHandler.updateProjectStructure - .calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) + .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles}) .should.equal true done() diff --git a/services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee b/services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee index 1b00f20bde..e20419765f 100644 --- a/services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee +++ b/services/web/test/unit/coffee/ThirdPartyDataStore/UpdateMergerTests.coffee @@ -63,11 +63,11 @@ describe 'UpdateMerger :', -> file_id = "1231" @projectLocator.findElementByPath = (_, __, cb)->cb(null, {_id:file_id}) @FileTypeManager.isBinary.callsArgWith(2, null, true) - @updateMerger.p.processFile = sinon.stub().callsArgWith(5) + @updateMerger.p.processFile = sinon.stub().callsArgWith(6) filePath = "/folder/file1.png" @updateMerger.mergeUpdate @user_id, @project_id, filePath, @update, @source, => - @updateMerger.p.processFile.calledWith(@project_id, file_id, @fsPath, filePath, @source).should.equal true + @updateMerger.p.processFile.calledWith(@project_id, file_id, @fsPath, filePath, @source, @user_id).should.equal true @FileTypeManager.isBinary.calledWith(filePath, @fsPath).should.equal true @fs.unlink.calledWith(@fsPath).should.equal true done() @@ -97,7 +97,7 @@ describe 'UpdateMerger :', -> path = "folder1/folder2/#{docName}" @editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [folder], folder) @editorController.addDoc = -> - mock = sinon.mock(@editorController).expects("addDoc").withArgs(@project_id, folder._id, docName, @splitDocLines, @source).callsArg(5) + mock = sinon.mock(@editorController).expects("addDoc").withArgs(@project_id, folder._id, docName, @splitDocLines, @source, @user_id).callsArg(6) @update.write(@docLines) @update.end() @@ -114,22 +114,22 @@ describe 'UpdateMerger :', -> @folder = _id: @folder_id @fileName = "file.png" @fsPath = "fs/path.tex" - @editorController.addFile = sinon.stub().callsArg(5) - @editorController.replaceFile = sinon.stub().callsArg(4) + @editorController.addFile = sinon.stub().callsArg(6) + @editorController.replaceFile = sinon.stub().callsArg(5) @editorController.deleteEntity = sinon.stub() @editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [@folder], @folder) @updateMerger.p.writeStreamToDisk = sinon.stub().withArgs(@project_id, @file_id, @update).callsArgWith(3, null, @fsPath) it 'should replace file if the file already exists', (done)-> - @updateMerger.p.processFile @project_id, @file_id, @fsPath, @path, @source, => + @updateMerger.p.processFile @project_id, @file_id, @fsPath, @path, @source, @user_id, => @editorController.addFile.called.should.equal false - @editorController.replaceFile.calledWith(@project_id, @file_id, @fsPath, @source).should.equal true + @editorController.replaceFile.calledWith(@project_id, @file_id, @fsPath, @source, @user_id).should.equal true done() it 'should call add file if the file does not exist', (done)-> - @updateMerger.p.processFile @project_id, undefined, @fsPath, @path, @source, => + @updateMerger.p.processFile @project_id, undefined, @fsPath, @path, @source, @user_id, => @editorController.mkdirp.calledWith(@project_id, "folder/").should.equal true - @editorController.addFile.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source).should.equal true + @editorController.addFile.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source, @user_id).should.equal true @editorController.replaceFile.called.should.equal false done() diff --git a/services/web/test/unit/coffee/Uploads/FileSystemImportManagerTests.coffee b/services/web/test/unit/coffee/Uploads/FileSystemImportManagerTests.coffee index a33775446f..0eb17c0d02 100644 --- a/services/web/test/unit/coffee/Uploads/FileSystemImportManagerTests.coffee +++ b/services/web/test/unit/coffee/Uploads/FileSystemImportManagerTests.coffee @@ -44,14 +44,14 @@ describe "FileSystemImportManager", -> describe "with replace set to false", -> beforeEach -> - @EditorController.addDocWithoutLock = sinon.stub().callsArg(5) + @EditorController.addDocWithoutLock = sinon.stub().callsArg(6) @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback it "should read the file from disk", -> @fs.readFile.calledWith(@path_on_disk, "utf8").should.equal true it "should insert the doc", -> - @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") + @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id) .should.equal true describe "with windows line ending", -> @@ -59,11 +59,11 @@ describe "FileSystemImportManager", -> @docContent = "one\r\ntwo\r\nthree" @docLines = ["one", "two", "three"] @fs.readFile = sinon.stub().callsArgWith(2, null, @docContent) - @EditorController.addDocWithoutLock = sinon.stub().callsArg(5) + @EditorController.addDocWithoutLock = sinon.stub().callsArg(6) @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback it "should strip the \\r characters before adding", -> - @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") + @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id) .should.equal true describe "with replace set to true", -> @@ -76,7 +76,7 @@ describe "FileSystemImportManager", -> }] } @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) - @EditorController.addDocWithoutLock = sinon.stub().callsArg(5) + @EditorController.addDocWithoutLock = sinon.stub().callsArg(6) @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback it "should look up the folder", -> @@ -85,7 +85,7 @@ describe "FileSystemImportManager", -> .should.equal true it "should insert the doc", -> - @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") + @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id) .should.equal true describe "when the doc does exist", -> @@ -114,12 +114,12 @@ describe "FileSystemImportManager", -> describe "addFile with replace set to false", -> beforeEach -> - @EditorController.addFileWithoutLock = sinon.stub().callsArg(5) + @EditorController.addFileWithoutLock = sinon.stub().callsArg(6) @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback it "should add the file", -> - @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload") + @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload", @user_id) .should.equal true describe "addFile with symlink", -> @@ -144,7 +144,7 @@ describe "FileSystemImportManager", -> } @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) - @EditorController.addFileWithoutLock = sinon.stub().callsArg(5) + @EditorController.addFileWithoutLock = sinon.stub().callsArg(6) @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback it "should look up the folder", -> @@ -153,7 +153,7 @@ describe "FileSystemImportManager", -> .should.equal true it "should add the file", -> - @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload") + @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload", @user_id) .should.equal true describe "when the file does exist", -> @@ -169,7 +169,7 @@ describe "FileSystemImportManager", -> } @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) - @EditorController.replaceFile = sinon.stub().callsArg(4) + @EditorController.replaceFile = sinon.stub().callsArg(5) @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback it "should look up the folder", -> @@ -178,7 +178,7 @@ describe "FileSystemImportManager", -> .should.equal true it "should replace the file", -> - @EditorController.replaceFile.calledWith(@project_id, @file_id, @path_on_disk, "upload") + @EditorController.replaceFile.calledWith(@project_id, @file_id, @path_on_disk, "upload", @user_id) .should.equal true describe "addFolder", ->