From 83b2aa3082866cb0769ec94f5c4d55a358f377e2 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 10 Mar 2014 16:58:26 +0000 Subject: [PATCH] add in restore end point --- services/track-changes/app.coffee | 2 + .../app/coffee/DocumentUpdaterManager.coffee | 17 +++++ .../app/coffee/HttpController.coffee | 8 +++ .../app/coffee/RestoreManager.coffee | 12 ++++ .../coffee/RestoringVersions.coffee | 67 +++++++++++++++++++ .../coffee/helpers/MockDocUpdaterApi.coffee | 12 ++++ .../coffee/helpers/TrackChangesClient.coffee | 9 ++- .../DocumentUpdaterManagerTests.coffee | 39 +++++++++++ .../HttpController/HttpControllerTests.coffee | 30 +++++++-- .../RestoreManager/RestoreManagerTests.coffee | 38 +++++++++++ 10 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 services/track-changes/app/coffee/RestoreManager.coffee create mode 100644 services/track-changes/test/acceptance/coffee/RestoringVersions.coffee create mode 100644 services/track-changes/test/unit/coffee/RestoreManager/RestoreManagerTests.coffee diff --git a/services/track-changes/app.coffee b/services/track-changes/app.coffee index ac6215e20d..7c47607aeb 100644 --- a/services/track-changes/app.coffee +++ b/services/track-changes/app.coffee @@ -16,6 +16,8 @@ app.get "/project/:project_id/doc/:doc_id/diff", HttpController.getDiff app.get "/project/:project_id/doc/:doc_id/updates", HttpController.getUpdates +app.post "/project/:project_id/doc/:doc_id/version/:version/restore", HttpController.restore + app.get "/status", (req, res, next) -> res.send "track-changes is alive" diff --git a/services/track-changes/app/coffee/DocumentUpdaterManager.coffee b/services/track-changes/app/coffee/DocumentUpdaterManager.coffee index e88f0f3520..6dea2b3f14 100644 --- a/services/track-changes/app/coffee/DocumentUpdaterManager.coffee +++ b/services/track-changes/app/coffee/DocumentUpdaterManager.coffee @@ -15,6 +15,23 @@ module.exports = DocumentUpdaterManager = catch error return callback(error) callback null, body.lines.join("\n"), body.version + else + error = new Error("doc updater returned a non-success status code: #{res.statusCode}") + logger.error err: error, project_id:project_id, doc_id:doc_id, url: url, "error accessing doc updater" + callback error + + setDocument: (project_id, doc_id, content, callback = (error) ->) -> + url = "#{Settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}" + logger.log project_id:project_id, doc_id: doc_id, "setting doc in document updater" + request.post { + url: url + json: + lines: content.split("\n") + }, (error, res, body)-> + if error? + return callback(error) + if res.statusCode >= 200 and res.statusCode < 300 + callback null else error = new Error("doc updater returned a non-success status code: #{res.statusCode}") logger.error err: error, project_id:project_id, doc_id:doc_id, url: url, "error accessing doc updater" diff --git a/services/track-changes/app/coffee/HttpController.coffee b/services/track-changes/app/coffee/HttpController.coffee index 5d7bcf6c72..f3878ab7cd 100644 --- a/services/track-changes/app/coffee/HttpController.coffee +++ b/services/track-changes/app/coffee/HttpController.coffee @@ -1,5 +1,6 @@ UpdatesManager = require "./UpdatesManager" DiffManager = require "./DiffManager" +RestoreManager = require "./RestoreManager" logger = require "logger-sharelatex" module.exports = HttpController = @@ -45,3 +46,10 @@ module.exports = HttpController = v: update.v } res.send JSON.stringify updates: formattedUpdates + + restore: (req, res, next = (error) ->) -> + {doc_id, project_id, version} = req.params + version = parseInt(version, 10) + RestoreManager.restoreToBeforeVersion project_id, doc_id, version, (error) -> + return next(error) if error? + res.send 204 diff --git a/services/track-changes/app/coffee/RestoreManager.coffee b/services/track-changes/app/coffee/RestoreManager.coffee new file mode 100644 index 0000000000..6c5ccef6fb --- /dev/null +++ b/services/track-changes/app/coffee/RestoreManager.coffee @@ -0,0 +1,12 @@ +DocumentUpdaterManager = require "./DocumentUpdaterManager" +DiffManager = require "./DiffManager" +logger = require "logger-sharelatex" + +module.exports = RestoreManager = + restoreToBeforeVersion: (project_id, doc_id, version, callback = (error) ->) -> + logger.log project_id: project_id, doc_id: doc_id, version: version, "restoring document" + DiffManager.getDocumentBeforeVersion project_id, doc_id, version, (error, content) -> + return callback(error) if error? + DocumentUpdaterManager.setDocument project_id, doc_id, content, (error) -> + return callback(error) if error? + callback() diff --git a/services/track-changes/test/acceptance/coffee/RestoringVersions.coffee b/services/track-changes/test/acceptance/coffee/RestoringVersions.coffee new file mode 100644 index 0000000000..21666bab3d --- /dev/null +++ b/services/track-changes/test/acceptance/coffee/RestoringVersions.coffee @@ -0,0 +1,67 @@ +sinon = require "sinon" +chai = require("chai") +chai.should() +expect = chai.expect +mongojs = require "../../../app/js/mongojs" +db = mongojs.db +ObjectId = mongojs.ObjectId +Settings = require "settings-sharelatex" + +TrackChangesClient = require "./helpers/TrackChangesClient" +MockDocUpdaterApi = require "./helpers/MockDocUpdaterApi" +MockWebApi = require "./helpers/MockWebApi" + +describe "Restoring a version", -> + before (done) -> + sinon.spy MockDocUpdaterApi, "setDoc" + + @now = Date.now() + @user_id = ObjectId().toString() + @doc_id = ObjectId().toString() + @project_id = ObjectId().toString() + + minutes = 60 * 1000 + + @updates = [{ + op: [{ i: "one ", p: 0 }] + meta: { ts: @now - 6 * minutes, user_id: @user_id } + v: 3 + }, { + op: [{ i: "two ", p: 4 }] + meta: { ts: @now - 4 * minutes, user_id: @user_id } + v: 4 + }, { + op: [{ i: "three ", p: 8 }] + meta: { ts: @now - 2 * minutes, user_id: @user_id } + v: 5 + }, { + op: [{ i: "four", p: 14 }] + meta: { ts: @now, user_id: @user_id } + v: 6 + }] + @lines = ["one two three four"] + @restored_lines = ["one two "] + @beforeVersion = 5 + + MockWebApi.users[@user_id] = @user = + email: "user@sharelatex.com" + first_name: "Leo" + last_name: "Lion" + id: @user_id + MockDocUpdaterApi.docs[@doc_id] = + lines: @lines + version: 7 + + TrackChangesClient.pushRawUpdates @doc_id, @updates, (error) => + throw error if error? + TrackChangesClient.restoreDoc @project_id, @doc_id, @beforeVersion, (error) => + throw error if error? + done() + + after () -> + MockDocUpdaterApi.setDoc.restore() + + it "should set the doc in the doc updater", -> + MockDocUpdaterApi.setDoc + .calledWith(@project_id, @doc_id, @restored_lines) + .should.equal true diff --git a/services/track-changes/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee b/services/track-changes/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee index a64098503c..fcddace406 100644 --- a/services/track-changes/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee +++ b/services/track-changes/test/acceptance/coffee/helpers/MockDocUpdaterApi.coffee @@ -7,6 +7,11 @@ module.exports = MockDocUpdaterApi = getDoc: (project_id, doc_id, callback = (error) ->) -> callback null, @docs[doc_id] + setDoc: (project_id, doc_id, lines, callback = (error) ->) -> + @docs[doc_id] ||= {} + @docs[doc_id].lines = lines + callback() + run: () -> app.get "/project/:project_id/doc/:doc_id", (req, res, next) => @getDoc req.params.project_id, req.params.doc_id, (error, doc) -> @@ -17,6 +22,13 @@ module.exports = MockDocUpdaterApi = else res.send JSON.stringify doc + app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) => + @setDoc req.params.project_id, req.params.doc_id, req.body.lines, (errr, doc) -> + if error? + res.send 500 + else + res.send 204 + app.listen 3003, (error) -> throw error if error? diff --git a/services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee b/services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee index d2b31f623d..ab85b67171 100644 --- a/services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee +++ b/services/track-changes/test/acceptance/coffee/helpers/TrackChangesClient.coffee @@ -28,4 +28,11 @@ module.exports = TrackChangesClient = url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/updates?to=#{options.to}&limit=#{options.limit}" }, (error, response, body) => response.statusCode.should.equal 200 - callback null, JSON.parse(body) \ No newline at end of file + callback null, JSON.parse(body) + + restoreDoc: (project_id, doc_id, version, callback = (error) ->) -> + request.post { + url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/version/#{version}/restore" + }, (error, response, body) => + response.statusCode.should.equal 204 + callback null \ No newline at end of file diff --git a/services/track-changes/test/unit/coffee/DocumentUpdaterManager/DocumentUpdaterManagerTests.coffee b/services/track-changes/test/unit/coffee/DocumentUpdaterManager/DocumentUpdaterManagerTests.coffee index 642909a787..25c61f6279 100644 --- a/services/track-changes/test/unit/coffee/DocumentUpdaterManager/DocumentUpdaterManagerTests.coffee +++ b/services/track-changes/test/unit/coffee/DocumentUpdaterManager/DocumentUpdaterManagerTests.coffee @@ -46,6 +46,45 @@ describe "DocumentUpdaterManager", -> @request.get = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "") @DocumentUpdaterManager.getDocument @project_id, @doc_id, @callback + it "should return the callback with an error", -> + @callback + .calledWith(new Error("doc updater returned failure status code: 500")) + .should.equal true + + describe "setDocument", -> + beforeEach -> + @content = "mock content" + + describe "successfully", -> + beforeEach -> + @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}) + @DocumentUpdaterManager.setDocument @project_id, @doc_id, @content, @callback + + it 'should set the document in the document updater', -> + url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}/doc/#{@doc_id}" + @request.post + .calledWith({ + url: url + json: + lines: @content.split("\n") + }).should.equal true + + it "should call the callback", -> + @callback.calledWith(null).should.equal true + + describe "when the document updater API returns an error", -> + beforeEach -> + @request.post = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null) + @DocumentUpdaterManager.setDocument @project_id, @doc_id, @content, @callback + + it "should return an error to the callback", -> + @callback.calledWith(@error).should.equal true + + describe "when the document updater returns a failure error code", -> + beforeEach -> + @request.post = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "") + @DocumentUpdaterManager.setDocument @project_id, @doc_id, @content, @callback + it "should return the callback with an error", -> @callback .calledWith(new Error("doc updater returned failure status code: 500")) diff --git a/services/track-changes/test/unit/coffee/HttpController/HttpControllerTests.coffee b/services/track-changes/test/unit/coffee/HttpController/HttpControllerTests.coffee index 8e57ea2bd3..791bbd1d04 100644 --- a/services/track-changes/test/unit/coffee/HttpController/HttpControllerTests.coffee +++ b/services/track-changes/test/unit/coffee/HttpController/HttpControllerTests.coffee @@ -11,9 +11,9 @@ describe "HttpController", -> "logger-sharelatex": { log: sinon.stub() } "./UpdatesManager": @UpdatesManager = {} "./DiffManager": @DiffManager = {} + "./RestoreManager": @RestoreManager = {} @doc_id = "doc-id-123" @project_id = "project-id-123" - @version = 42 @next = sinon.stub() describe "flushUpdatesWithLock", -> @@ -36,8 +36,8 @@ describe "HttpController", -> describe "getDiff", -> beforeEach -> - @from = Date.now() - 10000 - @to = Date.now() + @from = 42 + @to = 45 @req = params: doc_id: @doc_id @@ -61,7 +61,7 @@ describe "HttpController", -> describe "getUpdates", -> beforeEach -> - @to = Date.now() + @to = 42 @limit = 10 @req = params: @@ -93,3 +93,25 @@ describe "HttpController", -> v: @v } @res.send.calledWith(JSON.stringify(updates: updates)).should.equal true + + describe "RestoreManager", -> + beforeEach -> + @version = "42" + @req = + params: + doc_id: @doc_id + project_id: @project_id + version: @version + @res = + send: sinon.stub() + + @RestoreManager.restoreToBeforeVersion = sinon.stub().callsArg(3) + @HttpController.restore @req, @res, @next + + it "should restore the document", -> + @RestoreManager.restoreToBeforeVersion + .calledWith(@project_id, @doc_id, parseInt(@version, 10)) + .should.equal true + + it "should return a success code", -> + @res.send.calledWith(204).should.equal true diff --git a/services/track-changes/test/unit/coffee/RestoreManager/RestoreManagerTests.coffee b/services/track-changes/test/unit/coffee/RestoreManager/RestoreManagerTests.coffee new file mode 100644 index 0000000000..36088d4405 --- /dev/null +++ b/services/track-changes/test/unit/coffee/RestoreManager/RestoreManagerTests.coffee @@ -0,0 +1,38 @@ +sinon = require('sinon') +chai = require('chai') +should = chai.should() +expect = chai.expect +modulePath = "../../../../app/js/RestoreManager.js" +SandboxedModule = require('sandboxed-module') + +describe "RestoreManager", -> + beforeEach -> + @RestoreManager = SandboxedModule.require modulePath, requires: + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "./DocumentUpdaterManager": @DocumentUpdaterManager = {} + "./DiffManager": @DiffManager = {} + @callback = sinon.stub() + @project_id = "mock-project-id" + @doc_id = "mock-doc-id" + @version = 42 + + describe "restoreToBeforeVersion", -> + beforeEach -> + @content = "mock content" + @DocumentUpdaterManager.setDocument = sinon.stub().callsArg(3) + @DiffManager.getDocumentBeforeVersion = sinon.stub().callsArgWith(3, null, @content) + @RestoreManager.restoreToBeforeVersion @project_id, @doc_id, @version, @callback + + it "should get the content before the requested version", -> + @DiffManager.getDocumentBeforeVersion + .calledWith(@project_id, @doc_id, @version) + .should.equal true + + it "should set the document in the document updater", -> + @DocumentUpdaterManager.setDocument + .calledWith(@project_id, @doc_id, @content) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + \ No newline at end of file