mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Fix restore of docs from old deleted docs
This commit is contained in:
parent
b4fa47d664
commit
1e8439a2c6
16 changed files with 242 additions and 243 deletions
|
@ -180,7 +180,7 @@ clean_css:
|
||||||
rm -f public/stylesheets/*.css*
|
rm -f public/stylesheets/*.css*
|
||||||
|
|
||||||
clean_ci:
|
clean_ci:
|
||||||
docker-compose down
|
docker-compose down -v
|
||||||
|
|
||||||
test: test_unit test_frontend test_acceptance
|
test: test_unit test_frontend test_acceptance
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ test_acceptance_module: $(MODULE_MAKEFILES)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
test_clean:
|
test_clean:
|
||||||
docker-compose ${DOCKER_COMPOSE_FLAGS} down
|
docker-compose ${DOCKER_COMPOSE_FLAGS} down -v
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
MOCHA_ARGS="--reporter tap" \
|
MOCHA_ARGS="--reporter tap" \
|
||||||
|
|
|
@ -57,26 +57,9 @@ module.exports = EditorHttpController =
|
||||||
privilegeLevel
|
privilegeLevel
|
||||||
)
|
)
|
||||||
|
|
||||||
restoreDoc: (req, res, next) ->
|
|
||||||
project_id = req.params.Project_id
|
|
||||||
doc_id = req.params.doc_id
|
|
||||||
name = req.body.name
|
|
||||||
|
|
||||||
if !name?
|
|
||||||
return res.sendStatus 400 # Malformed request
|
|
||||||
|
|
||||||
logger.log project_id: project_id, doc_id: doc_id, "restoring doc"
|
|
||||||
ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, name, (err, doc, folder_id) =>
|
|
||||||
return next(error) if error?
|
|
||||||
EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc)
|
|
||||||
res.json {
|
|
||||||
doc_id: doc._id
|
|
||||||
}
|
|
||||||
|
|
||||||
_nameIsAcceptableLength: (name)->
|
_nameIsAcceptableLength: (name)->
|
||||||
return name? and name.length < 150 and name.length != 0
|
return name? and name.length < 150 and name.length != 0
|
||||||
|
|
||||||
|
|
||||||
addDoc: (req, res, next) ->
|
addDoc: (req, res, next) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
name = req.body.name
|
name = req.body.name
|
||||||
|
|
|
@ -14,8 +14,6 @@ module.exports =
|
||||||
webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc
|
webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc
|
||||||
webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder
|
webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder
|
||||||
|
|
||||||
webRouter.post '/project/:Project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.restoreDoc
|
|
||||||
|
|
||||||
# Called by the real-time API to load up the current project state.
|
# Called by the real-time API to load up the current project state.
|
||||||
# This is a post request because it's more than just a getting of data. We take actions
|
# This is a post request because it's more than just a getting of data. We take actions
|
||||||
# whenever a user joins a project, like updating the deleted status.
|
# whenever a user joins a project, like updating the deleted status.
|
||||||
|
|
|
@ -73,13 +73,28 @@ module.exports = HistoryController =
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.sendStatus 204
|
res.sendStatus 204
|
||||||
|
|
||||||
restoreFile: (req, res, next) ->
|
restoreFileFromV2: (req, res, next) ->
|
||||||
{project_id} = req.params
|
{project_id} = req.params
|
||||||
{version, pathname} = req.body
|
{version, pathname} = req.body
|
||||||
user_id = AuthenticationController.getLoggedInUserId req
|
user_id = AuthenticationController.getLoggedInUserId req
|
||||||
RestoreManager.restoreFile user_id, project_id, version, pathname, (error, entity) ->
|
logger.log {project_id, version, pathname}, "restoring file from v2"
|
||||||
|
RestoreManager.restoreFileFromV2 user_id, project_id, version, pathname, (error, entity) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.json {
|
res.json {
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
id: entity._id
|
id: entity._id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreDocFromDeletedDoc: (req, res, next) ->
|
||||||
|
{project_id, doc_id} = req.params
|
||||||
|
{name} = req.body
|
||||||
|
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
if !name?
|
||||||
|
return res.sendStatus 400 # Malformed request
|
||||||
|
logger.log {project_id, doc_id, user_id}, "restoring doc from v1 deleted doc"
|
||||||
|
RestoreManager.restoreDocFromDeletedDoc user_id, project_id, doc_id, name, (err, doc) =>
|
||||||
|
return next(error) if error?
|
||||||
|
res.json {
|
||||||
|
doc_id: doc._id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,23 @@ Settings = require 'settings-sharelatex'
|
||||||
Path = require 'path'
|
Path = require 'path'
|
||||||
FileWriter = require '../../infrastructure/FileWriter'
|
FileWriter = require '../../infrastructure/FileWriter'
|
||||||
FileSystemImportManager = require '../Uploads/FileSystemImportManager'
|
FileSystemImportManager = require '../Uploads/FileSystemImportManager'
|
||||||
|
ProjectEntityHandler = require '../Project/ProjectEntityHandler'
|
||||||
ProjectLocator = require '../Project/ProjectLocator'
|
ProjectLocator = require '../Project/ProjectLocator'
|
||||||
|
EditorController = require '../Editor/EditorController'
|
||||||
Errors = require '../Errors/Errors'
|
Errors = require '../Errors/Errors'
|
||||||
moment = require 'moment'
|
moment = require 'moment'
|
||||||
|
|
||||||
module.exports = RestoreManager =
|
module.exports = RestoreManager =
|
||||||
restoreFile: (user_id, project_id, version, pathname, callback = (error, entity) ->) ->
|
restoreDocFromDeletedDoc: (user_id, project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
|
||||||
|
# getDoc will return the deleted doc's lines, but we don't actually remove
|
||||||
|
# the deleted doc, just create a new one from its lines.
|
||||||
|
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
addDocWithName = (name, callback) ->
|
||||||
|
EditorController.addDoc project_id, null, name, lines, 'restore', user_id, callback
|
||||||
|
RestoreManager._addEntityWithUniqueName addDocWithName, name, callback
|
||||||
|
|
||||||
|
restoreFileFromV2: (user_id, project_id, version, pathname, callback = (error, entity) ->) ->
|
||||||
RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) ->
|
RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
basename = Path.basename(pathname)
|
basename = Path.basename(pathname)
|
||||||
|
@ -16,7 +27,9 @@ module.exports = RestoreManager =
|
||||||
dirname = ''
|
dirname = ''
|
||||||
RestoreManager._findFolderOrRootFolderId project_id, dirname, (error, parent_folder_id) ->
|
RestoreManager._findFolderOrRootFolderId project_id, dirname, (error, parent_folder_id) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RestoreManager._addEntityWithUniqueName user_id, project_id, parent_folder_id, basename, fsPath, callback
|
addEntityWithName = (name, callback) ->
|
||||||
|
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, name, fsPath, false, callback
|
||||||
|
RestoreManager._addEntityWithUniqueName addEntityWithName, basename, callback
|
||||||
|
|
||||||
_findFolderOrRootFolderId: (project_id, dirname, callback = (error, folder_id) ->) ->
|
_findFolderOrRootFolderId: (project_id, dirname, callback = (error, folder_id) ->) ->
|
||||||
# We're going to try to recover the file into the folder it was in previously,
|
# We're going to try to recover the file into the folder it was in previously,
|
||||||
|
@ -30,8 +43,8 @@ module.exports = RestoreManager =
|
||||||
else
|
else
|
||||||
return callback(null, null)
|
return callback(null, null)
|
||||||
|
|
||||||
_addEntityWithUniqueName: (user_id, project_id, parent_folder_id, basename, fsPath, callback = (error) ->) ->
|
_addEntityWithUniqueName: (addEntityWithName, basename, callback = (error) ->) ->
|
||||||
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, (error, entity) ->
|
addEntityWithName basename, (error, entity) ->
|
||||||
if error?
|
if error?
|
||||||
if error instanceof Errors.InvalidNameError
|
if error instanceof Errors.InvalidNameError
|
||||||
# likely a duplicate name, so try with a prefix
|
# likely a duplicate name, so try with a prefix
|
||||||
|
@ -42,7 +55,7 @@ module.exports = RestoreManager =
|
||||||
basename = "#{basename} (Restored on #{date})"
|
basename = "#{basename} (Restored on #{date})"
|
||||||
if extension != ''
|
if extension != ''
|
||||||
basename = "#{basename}#{extension}"
|
basename = "#{basename}#{extension}"
|
||||||
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, callback
|
addEntityWithName basename, callback
|
||||||
else
|
else
|
||||||
callback(error)
|
callback(error)
|
||||||
else
|
else
|
||||||
|
|
|
@ -108,15 +108,6 @@ module.exports = ProjectEntityUpdateHandler = self =
|
||||||
logger.log project_id: project_id, "removing root doc"
|
logger.log project_id: project_id, "removing root doc"
|
||||||
Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback
|
Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback
|
||||||
|
|
||||||
restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
|
|
||||||
if not SafePath.isCleanFilename name
|
|
||||||
return callback new Errors.InvalidNameError("invalid element name")
|
|
||||||
# getDoc will return the deleted doc's lines, but we don't actually remove
|
|
||||||
# the deleted doc, just create a new one from its lines.
|
|
||||||
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
|
|
||||||
return callback(error) if error?
|
|
||||||
self.addDoc project_id, null, name, lines, callback
|
|
||||||
|
|
||||||
addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
|
addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
|
||||||
self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
|
self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
|
|
@ -10,18 +10,25 @@ module.exports = FileWriter =
|
||||||
callback = _.once(callback)
|
callback = _.once(callback)
|
||||||
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
||||||
|
|
||||||
writeStream = fs.createWriteStream(fsPath)
|
stream.pause()
|
||||||
stream.pipe(writeStream)
|
fs.mkdir Settings.path.dumpFolder, (error) ->
|
||||||
|
stream.resume()
|
||||||
|
if error? and error.code != 'EEXIST'
|
||||||
|
# Ignore error about already existing
|
||||||
|
return callback(error)
|
||||||
|
|
||||||
stream.on 'error', (err)->
|
writeStream = fs.createWriteStream(fsPath)
|
||||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream"
|
stream.pipe(writeStream)
|
||||||
callback(err)
|
|
||||||
writeStream.on 'error', (err)->
|
stream.on 'error', (err)->
|
||||||
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk"
|
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream"
|
||||||
callback(err)
|
callback(err)
|
||||||
writeStream.on "finish", ->
|
writeStream.on 'error', (err)->
|
||||||
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
|
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk"
|
||||||
callback null, fsPath
|
callback(err)
|
||||||
|
writeStream.on "finish", ->
|
||||||
|
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
|
||||||
|
callback null, fsPath
|
||||||
|
|
||||||
writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) ->
|
writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) ->
|
||||||
callback = _.once(callback)
|
callback = _.once(callback)
|
||||||
|
|
|
@ -201,9 +201,11 @@ module.exports = class Router
|
||||||
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||||
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
||||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||||
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFile
|
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
||||||
|
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
|
||||||
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
|
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
|
||||||
|
|
||||||
|
|
||||||
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
||||||
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.diff-panel.full-size(ng-if="history.isV2", ng-controller="HistoryDiffController")
|
.diff-panel.full-size(ng-if="!history.isV2", ng-controller="HistoryDiffController")
|
||||||
.diff(
|
.diff(
|
||||||
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary"
|
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary"
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,137 +18,175 @@ describe "RestoringFiles", ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe "restoring a text file", ->
|
describe "restoring a deleted doc", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
MockProjectHistoryApi.addOldFile(@project_id, 42, "foo.tex", "hello world, this is foo.tex!")
|
|
||||||
@owner.request {
|
|
||||||
method: "POST",
|
|
||||||
url: "/project/#{@project_id}/restore_file",
|
|
||||||
json:
|
|
||||||
pathname: "foo.tex"
|
|
||||||
version: 42
|
|
||||||
}, (error, response, body) ->
|
|
||||||
throw error if error?
|
|
||||||
expect(response.statusCode).to.equal 204
|
|
||||||
done()
|
|
||||||
|
|
||||||
it "should have created a doc", (done) ->
|
|
||||||
@owner.getProject @project_id, (error, project) =>
|
@owner.getProject @project_id, (error, project) =>
|
||||||
throw error if error?
|
throw error if error?
|
||||||
doc = _.find project.rootFolder[0].docs, (doc) ->
|
@doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||||
doc.name == 'foo.tex'
|
doc.name == 'main.tex'
|
||||||
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
@owner.request {
|
||||||
expect(doc.lines).to.deep.equal [
|
method: "DELETE",
|
||||||
"hello world, this is foo.tex!"
|
url: "/project/#{@project_id}/doc/#{@doc._id}",
|
||||||
]
|
}, (error, response, body) =>
|
||||||
done()
|
throw error if error?
|
||||||
|
expect(response.statusCode).to.equal 204
|
||||||
|
@owner.request {
|
||||||
|
method: "POST",
|
||||||
|
url: "/project/#{@project_id}/doc/#{@doc._id}/restore"
|
||||||
|
json:
|
||||||
|
name: "main.tex"
|
||||||
|
}, (error, response, body) =>
|
||||||
|
throw error if error?
|
||||||
|
expect(response.statusCode).to.equal 200
|
||||||
|
expect(body.doc_id).to.exist
|
||||||
|
@restored_doc_id = body.doc_id
|
||||||
|
done()
|
||||||
|
|
||||||
describe "restoring a binary file", ->
|
it 'should have restored the doc', (done) ->
|
||||||
beforeEach (done) ->
|
|
||||||
MockProjectHistoryApi.addOldFile(@project_id, 42, "image.png", "Mock image.png content")
|
|
||||||
@owner.request {
|
|
||||||
method: "POST",
|
|
||||||
url: "/project/#{@project_id}/restore_file",
|
|
||||||
json:
|
|
||||||
pathname: "image.png"
|
|
||||||
version: 42
|
|
||||||
}, (error, response, body) ->
|
|
||||||
throw error if error?
|
|
||||||
expect(response.statusCode).to.equal 204
|
|
||||||
done()
|
|
||||||
|
|
||||||
it "should have created a file", (done) ->
|
|
||||||
@owner.getProject @project_id, (error, project) =>
|
@owner.getProject @project_id, (error, project) =>
|
||||||
throw error if error?
|
throw error if error?
|
||||||
file = _.find project.rootFolder[0].fileRefs, (file) ->
|
restored_doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||||
file.name == 'image.png'
|
doc.name == 'main.tex'
|
||||||
file = MockFileStoreApi.files[@project_id][file._id]
|
expect(restored_doc._id.toString()).to.equal @restored_doc_id
|
||||||
expect(file.content).to.equal "Mock image.png content"
|
expect(@doc._id).to.not.equal @restored_doc_id
|
||||||
|
# console.log @doc_id, @restored_doc_id, MockDocstoreApi.docs[@project_id]
|
||||||
|
expect(MockDocstoreApi.docs[@project_id][@restored_doc_id].lines).to.deep.equal(
|
||||||
|
MockDocstoreApi.docs[@project_id][@doc._id].lines
|
||||||
|
)
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe "restoring to a directory that exists", ->
|
describe "restoring from v2 history", ->
|
||||||
beforeEach (done) ->
|
describe "restoring a text file", ->
|
||||||
MockProjectHistoryApi.addOldFile(@project_id, 42, "foldername/foo2.tex", "hello world, this is foo-2.tex!")
|
beforeEach (done) ->
|
||||||
@owner.request.post {
|
MockProjectHistoryApi.addOldFile(@project_id, 42, "foo.tex", "hello world, this is foo.tex!")
|
||||||
uri: "project/#{@project_id}/folder",
|
|
||||||
json:
|
|
||||||
name: 'foldername'
|
|
||||||
}, (error, response, body) =>
|
|
||||||
throw error if error?
|
|
||||||
expect(response.statusCode).to.equal 200
|
|
||||||
@owner.request {
|
@owner.request {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/project/#{@project_id}/restore_file",
|
url: "/project/#{@project_id}/restore_file",
|
||||||
json:
|
json:
|
||||||
pathname: "foldername/foo2.tex"
|
pathname: "foo.tex"
|
||||||
version: 42
|
version: 42
|
||||||
}, (error, response, body) ->
|
}, (error, response, body) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
expect(response.statusCode).to.equal 204
|
expect(response.statusCode).to.equal 200
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should have created the doc in the named folder", (done) ->
|
it "should have created a doc", (done) ->
|
||||||
@owner.getProject @project_id, (error, project) =>
|
@owner.getProject @project_id, (error, project) =>
|
||||||
throw error if error?
|
throw error if error?
|
||||||
folder = _.find project.rootFolder[0].folders, (folder) ->
|
doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||||
folder.name == 'foldername'
|
doc.name == 'foo.tex'
|
||||||
doc = _.find folder.docs, (doc) ->
|
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
||||||
doc.name == 'foo2.tex'
|
expect(doc.lines).to.deep.equal [
|
||||||
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
"hello world, this is foo.tex!"
|
||||||
expect(doc.lines).to.deep.equal [
|
]
|
||||||
"hello world, this is foo-2.tex!"
|
done()
|
||||||
]
|
|
||||||
done()
|
|
||||||
|
|
||||||
describe "restoring to a directory that no longer exists", ->
|
describe "restoring a binary file", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
MockProjectHistoryApi.addOldFile(@project_id, 42, "nothere/foo3.tex", "hello world, this is foo-3.tex!")
|
MockProjectHistoryApi.addOldFile(@project_id, 42, "image.png", "Mock image.png content")
|
||||||
@owner.request {
|
@owner.request {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/project/#{@project_id}/restore_file",
|
url: "/project/#{@project_id}/restore_file",
|
||||||
json:
|
json:
|
||||||
pathname: "nothere/foo3.tex"
|
pathname: "image.png"
|
||||||
version: 42
|
version: 42
|
||||||
}, (error, response, body) ->
|
}, (error, response, body) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
expect(response.statusCode).to.equal 204
|
expect(response.statusCode).to.equal 200
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should have created the doc in the root folder", (done) ->
|
it "should have created a file", (done) ->
|
||||||
@owner.getProject @project_id, (error, project) =>
|
@owner.getProject @project_id, (error, project) =>
|
||||||
throw error if error?
|
throw error if error?
|
||||||
doc = _.find project.rootFolder[0].docs, (doc) ->
|
file = _.find project.rootFolder[0].fileRefs, (file) ->
|
||||||
doc.name == 'foo3.tex'
|
file.name == 'image.png'
|
||||||
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
file = MockFileStoreApi.files[@project_id][file._id]
|
||||||
expect(doc.lines).to.deep.equal [
|
expect(file.content).to.equal "Mock image.png content"
|
||||||
"hello world, this is foo-3.tex!"
|
done()
|
||||||
]
|
|
||||||
done()
|
|
||||||
|
|
||||||
describe "restoring to a filename that already exists", ->
|
describe "restoring to a directory that exists", ->
|
||||||
it "should have created the file with a timestamp appended", ->
|
beforeEach (done) ->
|
||||||
beforeEach (done) ->
|
MockProjectHistoryApi.addOldFile(@project_id, 42, "foldername/foo2.tex", "hello world, this is foo-2.tex!")
|
||||||
MockProjectHistoryApi.addOldFile(@project_id, 42, "main.tex", "hello world, this is main.tex!")
|
@owner.request.post {
|
||||||
@owner.request {
|
uri: "project/#{@project_id}/folder",
|
||||||
method: "POST",
|
json:
|
||||||
url: "/project/#{@project_id}/restore_file",
|
name: 'foldername'
|
||||||
json:
|
}, (error, response, body) =>
|
||||||
pathname: "main.tex"
|
throw error if error?
|
||||||
version: 42
|
expect(response.statusCode).to.equal 200
|
||||||
}, (error, response, body) ->
|
@owner.request {
|
||||||
throw error if error?
|
method: "POST",
|
||||||
expect(response.statusCode).to.equal 204
|
url: "/project/#{@project_id}/restore_file",
|
||||||
done()
|
json:
|
||||||
|
pathname: "foldername/foo2.tex"
|
||||||
|
version: 42
|
||||||
|
}, (error, response, body) ->
|
||||||
|
throw error if error?
|
||||||
|
expect(response.statusCode).to.equal 200
|
||||||
|
done()
|
||||||
|
|
||||||
it "should have created the doc in the root folder", (done) ->
|
it "should have created the doc in the named folder", (done) ->
|
||||||
@owner.getProject @project_id, (error, project) =>
|
@owner.getProject @project_id, (error, project) =>
|
||||||
throw error if error?
|
throw error if error?
|
||||||
doc = _.find project.rootFolder[0].docs, (doc) ->
|
folder = _.find project.rootFolder[0].folders, (folder) ->
|
||||||
doc.name.match(/main \(Restored on/)
|
folder.name == 'foldername'
|
||||||
expect(doc).to.exist
|
doc = _.find folder.docs, (doc) ->
|
||||||
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
doc.name == 'foo2.tex'
|
||||||
expect(doc.lines).to.deep.equal [
|
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
||||||
"hello world, this is main.tex!"
|
expect(doc.lines).to.deep.equal [
|
||||||
]
|
"hello world, this is foo-2.tex!"
|
||||||
done()
|
]
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "restoring to a directory that no longer exists", ->
|
||||||
|
beforeEach (done) ->
|
||||||
|
MockProjectHistoryApi.addOldFile(@project_id, 42, "nothere/foo3.tex", "hello world, this is foo-3.tex!")
|
||||||
|
@owner.request {
|
||||||
|
method: "POST",
|
||||||
|
url: "/project/#{@project_id}/restore_file",
|
||||||
|
json:
|
||||||
|
pathname: "nothere/foo3.tex"
|
||||||
|
version: 42
|
||||||
|
}, (error, response, body) ->
|
||||||
|
throw error if error?
|
||||||
|
expect(response.statusCode).to.equal 200
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "should have created the doc in the root folder", (done) ->
|
||||||
|
@owner.getProject @project_id, (error, project) =>
|
||||||
|
throw error if error?
|
||||||
|
doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||||
|
doc.name == 'foo3.tex'
|
||||||
|
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
||||||
|
expect(doc.lines).to.deep.equal [
|
||||||
|
"hello world, this is foo-3.tex!"
|
||||||
|
]
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "restoring to a filename that already exists", ->
|
||||||
|
it "should have created the file with a timestamp appended", ->
|
||||||
|
beforeEach (done) ->
|
||||||
|
MockProjectHistoryApi.addOldFile(@project_id, 42, "main.tex", "hello world, this is main.tex!")
|
||||||
|
@owner.request {
|
||||||
|
method: "POST",
|
||||||
|
url: "/project/#{@project_id}/restore_file",
|
||||||
|
json:
|
||||||
|
pathname: "main.tex"
|
||||||
|
version: 42
|
||||||
|
}, (error, response, body) ->
|
||||||
|
throw error if error?
|
||||||
|
expect(response.statusCode).to.equal 200
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "should have created the doc in the root folder", (done) ->
|
||||||
|
@owner.getProject @project_id, (error, project) =>
|
||||||
|
throw error if error?
|
||||||
|
doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||||
|
doc.name.match(/main \(Restored on/)
|
||||||
|
expect(doc).to.exist
|
||||||
|
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
||||||
|
expect(doc.lines).to.deep.equal [
|
||||||
|
"hello world, this is main.tex!"
|
||||||
|
]
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ module.exports = MockDocUpdaterApi =
|
||||||
res.sendStatus 200
|
res.sendStatus 200
|
||||||
|
|
||||||
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
||||||
res.send 204
|
res.sendStatus 204
|
||||||
|
|
||||||
app.listen 3003, (error) ->
|
app.listen 3003, (error) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
|
|
|
@ -23,15 +23,23 @@ module.exports = MockDocStoreApi =
|
||||||
docs = (doc for doc_id, doc of @docs[req.params.project_id])
|
docs = (doc for doc_id, doc of @docs[req.params.project_id])
|
||||||
res.send JSON.stringify docs
|
res.send JSON.stringify docs
|
||||||
|
|
||||||
|
app.get "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
||||||
|
{project_id, doc_id} = req.params
|
||||||
|
doc = @docs[project_id][doc_id]
|
||||||
|
if doc.deleted and !req.query.include_deleted
|
||||||
|
res.sendStatus 404
|
||||||
|
else
|
||||||
|
res.send JSON.stringify doc
|
||||||
|
|
||||||
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
||||||
{project_id, doc_id} = req.params
|
{project_id, doc_id} = req.params
|
||||||
if !@docs[project_id]?
|
if !@docs[project_id]?
|
||||||
res.send 404
|
res.sendStatus 404
|
||||||
else if !@docs[project_id][doc_id]?
|
else if !@docs[project_id][doc_id]?
|
||||||
res.send 404
|
res.sendStatus 404
|
||||||
else
|
else
|
||||||
@docs[project_id][doc_id] = undefined
|
@docs[project_id][doc_id].deleted = true
|
||||||
res.send 204
|
res.sendStatus 204
|
||||||
|
|
||||||
app.listen 3016, (error) ->
|
app.listen 3016, (error) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
|
|
|
@ -164,35 +164,6 @@ describe "EditorHttpController", ->
|
||||||
it "should return false in the callback", ->
|
it "should return false in the callback", ->
|
||||||
@callback.calledWith(null, null, false).should.equal true
|
@callback.calledWith(null, null, false).should.equal true
|
||||||
|
|
||||||
describe "restoreDoc", ->
|
|
||||||
beforeEach ->
|
|
||||||
@req.params =
|
|
||||||
Project_id: @project_id
|
|
||||||
doc_id: @doc_id
|
|
||||||
@req.body =
|
|
||||||
name: @name = "doc-name"
|
|
||||||
@ProjectEntityUpdateHandler.restoreDoc = sinon.stub().callsArgWith(3, null,
|
|
||||||
@doc = { "mock": "doc", _id: @new_doc_id = "new-doc-id" }
|
|
||||||
@folder_id = "mock-folder-id"
|
|
||||||
)
|
|
||||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
|
||||||
@EditorHttpController.restoreDoc @req, @res
|
|
||||||
|
|
||||||
it "should restore the doc", ->
|
|
||||||
@ProjectEntityUpdateHandler.restoreDoc
|
|
||||||
.calledWith(@project_id, @doc_id, @name)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it "should the real-time clients about the new doc", ->
|
|
||||||
@EditorRealTimeController.emitToRoom
|
|
||||||
.calledWith(@project_id, 'reciveNewDoc', @folder_id, @doc)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it "should return the new doc id", ->
|
|
||||||
@res.json
|
|
||||||
.calledWith(doc_id: @new_doc_id)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
describe "addDoc", ->
|
describe "addDoc", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@doc = { "mock": "doc" }
|
@doc = { "mock": "doc" }
|
||||||
|
|
|
@ -22,6 +22,7 @@ describe "HistoryController", ->
|
||||||
"./HistoryManager": @HistoryManager = {}
|
"./HistoryManager": @HistoryManager = {}
|
||||||
"../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {}
|
"../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {}
|
||||||
"../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {}
|
"../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {}
|
||||||
|
"./RestoreManager": @RestoreManager = {}
|
||||||
@settings.apis =
|
@settings.apis =
|
||||||
trackchanges:
|
trackchanges:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
@ -15,6 +15,8 @@ describe 'RestoreManager', ->
|
||||||
'../Uploads/FileSystemImportManager': @FileSystemImportManager = {}
|
'../Uploads/FileSystemImportManager': @FileSystemImportManager = {}
|
||||||
'../Project/ProjectLocator': @ProjectLocator = {}
|
'../Project/ProjectLocator': @ProjectLocator = {}
|
||||||
'../Errors/Errors': Errors
|
'../Errors/Errors': Errors
|
||||||
|
'../Project/ProjectEntityHandler': @ProjectEntityHandler = {}
|
||||||
|
'../Editor/EditorController': @EditorController = {}
|
||||||
'logger-sharelatex': @logger = {log: sinon.stub(), err: sinon.stub()}
|
'logger-sharelatex': @logger = {log: sinon.stub(), err: sinon.stub()}
|
||||||
@user_id = 'mock-user-id'
|
@user_id = 'mock-user-id'
|
||||||
@project_id = 'mock-project-id'
|
@project_id = 'mock-project-id'
|
||||||
|
@ -25,16 +27,16 @@ describe 'RestoreManager', ->
|
||||||
afterEach ->
|
afterEach ->
|
||||||
tk.reset()
|
tk.reset()
|
||||||
|
|
||||||
describe 'restoreFile', ->
|
describe 'restoreFileFromV2', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RestoreManager._writeFileVersionToDisk = sinon.stub().yields(null, @fsPath = "/tmp/path/on/disk")
|
@RestoreManager._writeFileVersionToDisk = sinon.stub().yields(null, @fsPath = "/tmp/path/on/disk")
|
||||||
@RestoreManager._findFolderOrRootFolderId = sinon.stub().yields(null, @folder_id = 'mock-folder-id')
|
@RestoreManager._findFolderOrRootFolderId = sinon.stub().yields(null, @folder_id = 'mock-folder-id')
|
||||||
@RestoreManager._addEntityWithUniqueName = sinon.stub().yields(null, @entity = 'mock-entity')
|
@FileSystemImportManager.addEntity = sinon.stub().yields(null, @entity = 'mock-entity')
|
||||||
|
|
||||||
describe "with a file not in a folder", ->
|
describe "with a file not in a folder", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@pathname = 'foo.tex'
|
@pathname = 'foo.tex'
|
||||||
@RestoreManager.restoreFile @user_id, @project_id, @version, @pathname, @callback
|
@RestoreManager.restoreFileFromV2 @user_id, @project_id, @version, @pathname, @callback
|
||||||
|
|
||||||
it 'should write the file version to disk', ->
|
it 'should write the file version to disk', ->
|
||||||
@RestoreManager._writeFileVersionToDisk
|
@RestoreManager._writeFileVersionToDisk
|
||||||
|
@ -47,18 +49,17 @@ describe 'RestoreManager', ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should add the entity', ->
|
it 'should add the entity', ->
|
||||||
@RestoreManager._addEntityWithUniqueName
|
@FileSystemImportManager.addEntity
|
||||||
.calledWith(@user_id, @project_id, @folder_id, 'foo.tex', @fsPath)
|
.calledWith(@user_id, @project_id, @folder_id, 'foo.tex', @fsPath, false)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should call the callback with the entity', ->
|
it 'should call the callback with the entity', ->
|
||||||
@callback.calledWith(null, @entity).should.equal true
|
@callback.calledWith(null, @entity).should.equal true
|
||||||
|
|
||||||
|
|
||||||
describe "with a file in a folder", ->
|
describe "with a file in a folder", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@pathname = 'foo/bar.tex'
|
@pathname = 'foo/bar.tex'
|
||||||
@RestoreManager.restoreFile @user_id, @project_id, @version, @pathname, @callback
|
@RestoreManager.restoreFileFromV2 @user_id, @project_id, @version, @pathname, @callback
|
||||||
|
|
||||||
it 'should find the folder', ->
|
it 'should find the folder', ->
|
||||||
@RestoreManager._findFolderOrRootFolderId
|
@RestoreManager._findFolderOrRootFolderId
|
||||||
|
@ -66,8 +67,8 @@ describe 'RestoreManager', ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should add the entity by its basename', ->
|
it 'should add the entity by its basename', ->
|
||||||
@RestoreManager._addEntityWithUniqueName
|
@FileSystemImportManager.addEntity
|
||||||
.calledWith(@user_id, @project_id, @folder_id, 'bar.tex', @fsPath)
|
.calledWith(@user_id, @project_id, @folder_id, 'bar.tex', @fsPath, false)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe '_findFolderOrRootFolderId', ->
|
describe '_findFolderOrRootFolderId', ->
|
||||||
|
@ -94,40 +95,32 @@ describe 'RestoreManager', ->
|
||||||
|
|
||||||
describe '_addEntityWithUniqueName', ->
|
describe '_addEntityWithUniqueName', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@parent_folder_id = 'mock-folder-id'
|
@addEntityWithName = sinon.stub()
|
||||||
@fsPath = '/tmp/file/on/disk'
|
|
||||||
@name = 'foo.tex'
|
@name = 'foo.tex'
|
||||||
|
|
||||||
describe 'with a valid name', ->
|
describe 'with a valid name', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@FileSystemImportManager.addEntity = sinon.stub().yields(null, @entity = 'mock-entity')
|
@addEntityWithName.yields(null, @entity = 'mock-entity')
|
||||||
@RestoreManager._addEntityWithUniqueName @user_id, @project_id, @parent_folder_id, @name, @fsPath, @callback
|
@RestoreManager._addEntityWithUniqueName @addEntityWithName, @name, @callback
|
||||||
|
|
||||||
it 'should add the entity', ->
|
it 'should add the entity', ->
|
||||||
@FileSystemImportManager.addEntity
|
@addEntityWithName.calledWith(@name).should.equal true
|
||||||
.calledWith(@user_id, @project_id, @parent_folder_id, @name, @fsPath, false)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it 'should return the entity', ->
|
it 'should return the entity', ->
|
||||||
@callback.calledWith(null, @entity).should.equal true
|
@callback.calledWith(null, @entity).should.equal true
|
||||||
|
|
||||||
describe "with an invalid name", ->
|
describe "with an invalid name", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@FileSystemImportManager.addEntity = sinon.stub()
|
@addEntityWithName.onFirstCall().yields(new Errors.InvalidNameError())
|
||||||
@FileSystemImportManager.addEntity.onFirstCall().yields(new Errors.InvalidNameError())
|
@addEntityWithName.onSecondCall().yields(null, @entity = 'mock-entity')
|
||||||
@FileSystemImportManager.addEntity.onSecondCall().yields(null, @entity = 'mock-entity')
|
@RestoreManager._addEntityWithUniqueName @addEntityWithName, @name, @callback
|
||||||
@RestoreManager._addEntityWithUniqueName @user_id, @project_id, @parent_folder_id, @name, @fsPath, @callback
|
|
||||||
|
|
||||||
it 'should try to add the entity with its original name', ->
|
it 'should try to add the entity with its original name', ->
|
||||||
@FileSystemImportManager.addEntity
|
@addEntityWithName.calledWith('foo.tex').should.equal true
|
||||||
.calledWith(@user_id, @project_id, @parent_folder_id, 'foo.tex', @fsPath, false)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it 'should try to add the entity with a unique name', ->
|
it 'should try to add the entity with a unique name', ->
|
||||||
date = moment(new Date()).format('Do MMM YY H:mm:ss')
|
date = moment(new Date()).format('Do MMM YY H:mm:ss')
|
||||||
@FileSystemImportManager.addEntity
|
@addEntityWithName.calledWith("foo (Restored on #{date}).tex").should.equal true
|
||||||
.calledWith(@user_id, @project_id, @parent_folder_id, "foo (Restored on #{date}).tex", @fsPath, false)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it 'should return the entity', ->
|
it 'should return the entity', ->
|
||||||
@callback.calledWith(null, @entity).should.equal true
|
@callback.calledWith(null, @entity).should.equal true
|
||||||
|
|
|
@ -246,27 +246,6 @@ describe 'ProjectEntityUpdateHandler', ->
|
||||||
.calledWith({_id : project_id}, {$unset : {rootDoc_id: true}})
|
.calledWith({_id : project_id}, {$unset : {rootDoc_id: true}})
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "restoreDoc", ->
|
|
||||||
beforeEach ->
|
|
||||||
@doc = { "mock": "doc" }
|
|
||||||
@ProjectEntityHandler.getDoc = sinon.stub().yields(null, @docLines)
|
|
||||||
@ProjectEntityUpdateHandler.addDoc = sinon.stub().yields(null, @doc, folder_id)
|
|
||||||
|
|
||||||
@ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, @docName, @callback
|
|
||||||
|
|
||||||
it 'should get the doc lines', ->
|
|
||||||
@ProjectEntityHandler.getDoc
|
|
||||||
.calledWith(project_id, doc_id, include_deleted: true)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it "should add a new doc with these doc lines", ->
|
|
||||||
@ProjectEntityUpdateHandler.addDoc
|
|
||||||
.calledWith(project_id, null, @docName, @docLines)
|
|
||||||
.should.equal true
|
|
||||||
|
|
||||||
it "should call the callback with the new folder and doc", ->
|
|
||||||
@callback.calledWith(null, @doc, folder_id).should.equal true
|
|
||||||
|
|
||||||
describe 'addDoc', ->
|
describe 'addDoc', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@path = "/path/to/doc"
|
@path = "/path/to/doc"
|
||||||
|
|
Loading…
Reference in a new issue