James Allen 12 лет назад
Родитель
Сommit
83b2aa3082

+ 2 - 0
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"
 

+ 17 - 0
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"

+ 8 - 0
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

+ 12 - 0
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()

+ 67 - 0
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

+ 12 - 0
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?
 

+ 8 - 1
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)
+			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

+ 39 - 0
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"))

+ 26 - 4
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

+ 38 - 0
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
+