Răsfoiți Sursa

Merge pull request #21 from sharelatex/hof-history-sync

resync project history endpoint
Hayden Faulds 8 ani în urmă
părinte
comite
4a5731440a

+ 4 - 1
services/document-updater/app.coffee

@@ -3,6 +3,8 @@ http = require("http")
 Settings = require('settings-sharelatex')
 logger = require('logger-sharelatex')
 logger.initialize("documentupdater")
+logger.logger.serializers.docs = require("./app/js/LoggerSerializers").docs
+logger.logger.serializers.files = require("./app/js/LoggerSerializers").files
 if Settings.sentry?.dsn?
 	logger.initializeErrorReporting(Settings.sentry.dsn)
 
@@ -47,7 +49,8 @@ app.post   '/project/:project_id/doc/:doc_id',                          HttpCont
 app.post   '/project/:project_id/doc/:doc_id/flush',                    HttpController.flushDocIfLoaded
 app.delete '/project/:project_id/doc/:doc_id',                          HttpController.flushAndDeleteDoc
 app.delete '/project/:project_id',                                      HttpController.deleteProject
-app.post   '/project/:project_id',                   										HttpController.updateProject
+app.post   '/project/:project_id',                                      HttpController.updateProject
+app.post   '/project/:project_id/history/resync',                       HttpController.resyncProjectHistory
 app.post   '/project/:project_id/flush',                                HttpController.flushProject
 app.post   '/project/:project_id/doc/:doc_id/change/:change_id/accept', HttpController.acceptChanges
 app.post   '/project/:project_id/doc/:doc_id/change/accept',            HttpController.acceptChanges

+ 16 - 0
services/document-updater/app/coffee/DocumentManager.coffee

@@ -1,4 +1,5 @@
 RedisManager = require "./RedisManager"
+ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
 PersistenceManager = require "./PersistenceManager"
 DiffCodec = require "./DiffCodec"
 logger = require "logger-sharelatex"
@@ -179,6 +180,17 @@ module.exports = DocumentManager =
 			else
 				callback(null, lines, version)
 
+	resyncDocContents: (project_id, doc_id, callback) ->
+		RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
+			return callback(error) if error?
+
+			if !lines? or !version?
+				PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
+					return callback(error) if error?
+					ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback
+			else
+				ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback
+
 	getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
 		UpdateManager = require "./UpdateManager"
 		UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback
@@ -214,3 +226,7 @@ module.exports = DocumentManager =
 	renameDocWithLock: (project_id, doc_id, user_id, update, callback = (error) ->) ->
 		UpdateManager = require "./UpdateManager"
 		UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, callback
+
+	resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) ->
+		UpdateManager = require "./UpdateManager"
+		UpdateManager.lockUpdatesAndDo DocumentManager.resyncDocContents, project_id, doc_id, callback

+ 13 - 2
services/document-updater/app/coffee/HistoryManager.coffee

@@ -1,7 +1,10 @@
-Settings = require "settings-sharelatex"
-request  = require "request"
+async = require "async"
 logger = require "logger-sharelatex"
+request  = require "request"
+Settings = require "settings-sharelatex"
 HistoryRedisManager = require "./HistoryRedisManager"
+ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
+RedisManager = require "./RedisManager"
 
 module.exports = HistoryManager =
 	flushDocChangesAsync: (project_id, doc_id) ->
@@ -61,3 +64,11 @@ module.exports = HistoryManager =
 		prevBlock = Math.floor(previousLength / threshold)
 		newBlock  = Math.floor(length / threshold)
 		return newBlock != prevBlock
+
+	resyncProjectHistory: (project_id, docs, files, callback) ->
+		ProjectHistoryRedisManager.queueResyncProjectStructure project_id, docs, files, (error) ->
+			return callback(error) if error?
+			DocumentManager = require "./DocumentManager"
+			resyncDoc = (doc, cb) ->
+				DocumentManager.resyncDocContentsWithLock project_id, doc.doc, cb
+			async.each docs, resyncDoc, callback

+ 10 - 0
services/document-updater/app/coffee/HttpController.coffee

@@ -169,3 +169,13 @@ module.exports = HttpController =
 			return next(error) if error?
 			logger.log project_id: project_id, "updated project via http"
 			res.send 204 # No Content
+
+	resyncProjectHistory: (req, res, next = (error) ->) ->
+		project_id = req.params.project_id
+		{docs, files} = req.body
+
+		logger.log {project_id, docs, files}, "queuing project history resync via http"
+		HistoryManager.resyncProjectHistory project_id, docs, files, (error) ->
+			return next(error) if error?
+			logger.log {project_id}, "queued project history resync via http"
+			res.send 204

+ 14 - 0
services/document-updater/app/coffee/LoggerSerializers.coffee

@@ -0,0 +1,14 @@
+module.exports =
+	docs: (docs) ->
+		docs.map (doc) ->
+			{
+				path: doc.path
+				id: doc.doc
+			}
+
+	files: (files) ->
+		files.map (file) ->
+			{
+				path: file.path
+				id: file.file
+			}

+ 59 - 0
services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee

@@ -0,0 +1,59 @@
+Settings = require('settings-sharelatex')
+projectHistoryKeys = Settings.redis?.project_history?.key_schema
+rclient = require("redis-sharelatex").createClient(Settings.redis.documentupdater)
+logger = require('logger-sharelatex')
+
+module.exports = ProjectHistoryRedisManager =
+	queueOps: (project_id, ops..., callback) ->
+		rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback
+
+	queueRenameEntity: (project_id, entity_type, entity_id, user_id, update, callback) ->
+		update =
+			pathname: update.pathname
+			new_pathname: update.newPathname
+			meta:
+				user_id: user_id
+				ts: new Date()
+		update[entity_type] = entity_id
+
+		logger.log {project_id, update}, "queue rename operation to project-history"
+		jsonUpdate = JSON.stringify(update)
+
+		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
+
+	queueAddEntity: (project_id, entity_type, entitiy_id, user_id, update, callback = (error) ->) ->
+		update =
+			pathname: update.pathname
+			docLines: update.docLines
+			url: update.url
+			meta:
+				user_id: user_id
+				ts: new Date()
+		update[entity_type] = entitiy_id
+
+		logger.log {project_id, update}, "queue add operation to project-history"
+		jsonUpdate = JSON.stringify(update)
+
+		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
+
+	queueResyncProjectStructure: (project_id, docs, files, callback) ->
+		logger.log {project_id, docs, files}, "queue project structure resync"
+		update =
+			resyncProjectStructure: { docs, files }
+			meta:
+				ts: new Date()
+		jsonUpdate = JSON.stringify update
+		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
+
+	queueResyncDocContent: (project_id, doc_id, lines, version, pathname, callback) ->
+		logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync"
+		update =
+			resyncDocContent:
+				content: lines.join("\n"),
+				version: version
+			path: pathname
+			doc: doc_id
+			meta:
+				ts: new Date()
+		jsonUpdate = JSON.stringify update
+		ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback

+ 5 - 4
services/document-updater/app/coffee/ProjectManager.coffee

@@ -1,4 +1,5 @@
 RedisManager = require "./RedisManager"
+ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
 DocumentManager = require "./DocumentManager"
 HistoryManager = require "./HistoryManager"
 async = require "async"
@@ -115,22 +116,22 @@ module.exports = ProjectManager =
 		handleDocUpdate = (update, cb) ->
 			doc_id = update.id
 			if update.docLines?
-				RedisManager.addEntity project_id, 'doc', doc_id, user_id, update, (error, count) =>
+				ProjectHistoryRedisManager.queueAddEntity project_id, 'doc', doc_id, user_id, update, (error, count) ->
 					project_ops_length = count
 					cb(error)
 			else
-				DocumentManager.renameDocWithLock project_id, doc_id, user_id, update, (error, count) =>
+				DocumentManager.renameDocWithLock project_id, doc_id, user_id, update, (error, count) ->
 					project_ops_length = count
 					cb(error)
 
 		handleFileUpdate = (update, cb) ->
 			file_id = update.id
 			if update.url?
-				RedisManager.addEntity project_id, 'file', file_id, user_id, update, (error, count) =>
+				ProjectHistoryRedisManager.queueAddEntity project_id, 'file', file_id, user_id, update, (error, count) ->
 					project_ops_length = count
 					cb(error)
 			else
-				RedisManager.renameFile project_id, file_id, user_id, update, (error, count) =>
+				ProjectHistoryRedisManager.queueRenameEntity project_id, 'file', file_id, user_id, update, (error, count) ->
 					project_ops_length = count
 					cb(error)
 

+ 4 - 36
services/document-updater/app/coffee/RedisManager.coffee

@@ -5,6 +5,7 @@ logger = require('logger-sharelatex')
 metrics = require('./Metrics')
 Errors = require "./Errors"
 crypto = require "crypto"
+ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
 
 # Sometimes Redis calls take an unexpectedly long time.  We have to be
 # quick with Redis calls because we're holding a lock that expires
@@ -31,7 +32,6 @@ MAX_RANGES_SIZE = 3 * MEGABYTES
 
 keys = Settings.redis.documentupdater.key_schema
 historyKeys = Settings.redis.history.key_schema
-projectHistoryKeys = Settings.redis?.project_history?.key_schema
 
 module.exports = RedisManager =
 	rclient: rclient
@@ -267,7 +267,7 @@ module.exports = RedisManager =
 						docUpdateCount = result[7]
 
 						if jsonOps.length > 0 && Settings.apis?.project_history?.enabled
-							rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), jsonOps..., (error, projectUpdateCount) ->
+							ProjectHistoryRedisManager.queueOps project_id, jsonOps..., (error, projectUpdateCount) ->
 								callback null, docUpdateCount, projectUpdateCount
 						else
 							callback null, docUpdateCount
@@ -279,41 +279,9 @@ module.exports = RedisManager =
 			if lines? and version?
 				rclient.set keys.pathname(doc_id:doc_id), update.newPathname, (error) ->
 					return callback(error) if error?
-					RedisManager._renameEntity project_id, 'doc', doc_id, user_id, update, callback
+					ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback
 			else
-				RedisManager._renameEntity project_id, 'doc', doc_id, user_id, update, callback
-
-	renameFile: (project_id, file_id, user_id, update, callback = (error) ->) ->
-		RedisManager._renameEntity project_id, 'file', file_id, user_id, update, callback
-
-	_renameEntity: (project_id, entity_type, entity_id, user_id, update, callback = (error) ->) ->
-		update =
-			pathname: update.pathname
-			new_pathname: update.newPathname
-			meta:
-				user_id: user_id
-				ts: new Date()
-		update[entity_type] = entity_id
-
-		logger.log {project_id, update}, "queue rename operation to project-history"
-		jsonUpdate = JSON.stringify(update)
-
-		rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), jsonUpdate, callback
-
-	addEntity: (project_id, entity_type, entitiy_id, user_id, update, callback = (error) ->) ->
-		update =
-			pathname: update.pathname
-			docLines: update.docLines
-			url: update.url
-			meta:
-				user_id: user_id
-				ts: new Date()
-		update[entity_type] = entitiy_id
-
-		logger.log {project_id, update}, "queue add operation to project-history"
-		jsonUpdate = JSON.stringify(update)
-
-		rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), jsonUpdate, callback
+				ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback
 
 	clearUnflushedTime: (doc_id, callback = (error) ->) ->
 		rclient.del keys.unflushedTime(doc_id:doc_id), callback

+ 40 - 0
services/document-updater/test/unit/coffee/DocumentManager/DocumentManagerTests.coffee

@@ -10,6 +10,7 @@ describe "DocumentManager", ->
 	beforeEach ->
 		@DocumentManager = SandboxedModule.require modulePath, requires:
 			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 			"./PersistenceManager": @PersistenceManager = {}
 			"./HistoryManager": @HistoryManager =
 				flushDocChangesAsync: sinon.stub()
@@ -467,3 +468,42 @@ describe "DocumentManager", ->
 
 			it "should call the callback", ->
 				@callback.called.should.equal true
+
+	describe "resyncDocContents", ->
+		describe "when doc is loaded in redis", ->
+			beforeEach ->
+				@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
+				@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
+				@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
+
+			it "gets the doc contents from redis", ->
+				@RedisManager.getDoc
+					.calledWith(@project_id, @doc_id)
+					.should.equal true
+
+			it "queues a resync doc content update", ->
+				@ProjectHistoryRedisManager.queueResyncDocContent
+					.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback)
+					.should.equal true
+
+		describe "when doc is not loaded in redis", ->
+			beforeEach ->
+				@RedisManager.getDoc = sinon.stub().callsArgWith(2, null)
+				@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
+				@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
+				@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
+
+			it "tries to get the doc contents from redis", ->
+				@RedisManager.getDoc
+					.calledWith(@project_id, @doc_id)
+					.should.equal true
+
+			it "gets the doc contents from web", ->
+				@PersistenceManager.getDoc
+					.calledWith(@project_id, @doc_id)
+					.should.equal true
+
+			it "queues a resync doc content update", ->
+				@ProjectHistoryRedisManager.queueResyncDocContent
+					.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback)
+					.should.equal true

+ 32 - 0
services/document-updater/test/unit/coffee/HistoryManager/HistoryManagerTests.coffee

@@ -16,7 +16,10 @@ describe "HistoryManager", ->
 						url: "http://trackchanges.example.com"
 			}
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
+			"./DocumentManager": @DocumentManager = {}
 			"./HistoryRedisManager": @HistoryRedisManager = {}
+			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 		@project_id = "mock-project-id"
 		@doc_id = "mock-doc-id"
 		@callback = sinon.stub()
@@ -158,3 +161,32 @@ describe "HistoryManager", ->
 				# Previously we were on 16 ops
 				# We didn't pass over a multiple of 5
 				@HistoryManager.shouldFlushHistoryOps(17, ['a', 'b', 'c'].length, 5).should.equal true
+
+	describe "resyncProjectHistory", ->
+		beforeEach ->
+			@docs = [
+				doc: @doc_id
+				path: 'main.tex'
+			]
+			@files = [
+				file: 'mock-file-id'
+				path: 'universe.png'
+				url: "www.filestore.test/#{@project_id}/mock-file-id"
+			]
+			@ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields()
+			@DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
+			@HistoryManager.resyncProjectHistory @project_id, @docs, @files, @callback
+
+		it "should queue a project structure reync", ->
+			@ProjectHistoryRedisManager.queueResyncProjectStructure
+				.calledWith(@project_id, @docs, @files)
+				.should.equal true
+
+		it "should queue doc content reyncs", ->
+			@DocumentManager
+				.resyncDocContentsWithLock
+				.calledWith(@project_id, @doc_id)
+				.should.equal true
+
+		it "should call the callback", ->
+			@callback.called.should.equal true

+ 36 - 0
services/document-updater/test/unit/coffee/HttpController/HttpControllerTests.coffee

@@ -544,3 +544,39 @@ describe "HttpController", ->
 				@next
 					.calledWith(new Error("oops"))
 					.should.equal true
+
+	describe "resyncProjectHistory", ->
+		beforeEach ->
+			@docs = sinon.stub()
+			@files = sinon.stub()
+			@fileUpdates = sinon.stub()
+			@req =
+				body:
+					{@docs, @files}
+				params:
+					project_id: @project_id
+
+		describe "successfully", ->
+			beforeEach ->
+				@HistoryManager.resyncProjectHistory = sinon.stub().callsArg(3)
+				@HttpController.resyncProjectHistory(@req, @res, @next)
+
+			it "should accept the change", ->
+				@HistoryManager.resyncProjectHistory
+					.calledWith(@project_id, @docs, @files)
+					.should.equal true
+
+			it "should return a successful No Content response", ->
+				@res.send
+					.calledWith(204)
+					.should.equal true
+
+		describe "when an errors occurs", ->
+			beforeEach ->
+				@HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(3, new Error("oops"))
+				@HttpController.resyncProjectHistory(@req, @res, @next)
+
+			it "should call next with the error", ->
+				@next
+					.calledWith(new Error("oops"))
+					.should.equal true

+ 101 - 0
services/document-updater/test/unit/coffee/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.coffee

@@ -0,0 +1,101 @@
+sinon = require('sinon')
+chai = require('chai')
+should = chai.should()
+modulePath = "../../../../app/js/ProjectHistoryRedisManager.js"
+SandboxedModule = require('sandboxed-module')
+tk = require "timekeeper"
+
+describe "ProjectHistoryRedisManager", ->
+	beforeEach ->
+		@project_id = "project-id-123"
+		@user_id = "user-id-123"
+		@callback = sinon.stub()
+		@rclient = {}
+		tk.freeze(new Date())
+		@ProjectHistoryRedisManager = SandboxedModule.require modulePath,
+			requires:
+				"settings-sharelatex": @settings = {
+					redis:
+						project_history:
+							key_schema:
+								projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:#{project_id}"
+				}
+				"redis-sharelatex":
+					createClient: () => @rclient
+			globals:
+				JSON: @JSON = JSON
+
+		afterEach ->
+			tk.reset()
+
+	describe "queueOps", ->
+		beforeEach ->
+			@ops = ["mock-op-1", "mock-op-2"]
+			@rclient.rpush = sinon.stub()
+			@ProjectHistoryRedisManager.queueOps @project_id, @ops..., @callback
+
+		it "should queue an update", ->
+			@rclient.rpush
+				.calledWithExactly(
+					"ProjectHistory:Ops:#{@project_id}"
+					@ops[0]
+					@ops[1]
+					@callback
+				).should.equal true
+
+	describe "queueRenameEntity", ->
+		beforeEach () ->
+			@file_id = 1234
+
+			@rawUpdate =
+				pathname: @pathname = '/old'
+				newPathname: @newPathname = '/new'
+
+			@ProjectHistoryRedisManager.queueOps = sinon.stub()
+			@ProjectHistoryRedisManager.queueRenameEntity @project_id, 'file', @file_id, @user_id, @rawUpdate, @callback
+
+		it "should queue an update", ->
+			update =
+				pathname: @pathname
+				new_pathname: @newPathname
+				meta:
+					user_id: @user_id
+					ts: new Date()
+				file: @file_id
+
+			@ProjectHistoryRedisManager.queueOps
+				.calledWithExactly(@project_id, @JSON.stringify(update), @callback)
+				.should.equal true
+
+	describe "queueAddEntity", ->
+		beforeEach () ->
+			@rclient.rpush = sinon.stub().yields()
+			@doc_id = 1234
+
+			@rawUpdate =
+				pathname: @pathname = '/old'
+				docLines: @docLines = 'a\nb'
+				url: @url = 'filestore.example.com'
+
+			@ProjectHistoryRedisManager.queueOps = sinon.stub()
+			@ProjectHistoryRedisManager.queueAddEntity @project_id, 'doc', @doc_id, @user_id, @rawUpdate, @callback
+
+		it "should queue an update", ->
+			update =
+				pathname: @pathname
+				docLines: @docLines
+				url: @url
+				meta:
+					user_id: @user_id
+					ts: new Date()
+				doc: @doc_id
+
+			@ProjectHistoryRedisManager.queueOps
+				.calledWithExactly(@project_id, @JSON.stringify(update), @callback)
+				.should.equal true
+
+		describe "queueResyncProjectStructure", ->
+			it "should queue an update", ->
+
+		describe "queueResyncDocContent", ->
+			it "should queue an update", ->

+ 1 - 0
services/document-updater/test/unit/coffee/ProjectManager/flushAndDeleteProjectTests.coffee

@@ -8,6 +8,7 @@ describe "ProjectManager - flushAndDeleteProject", ->
 	beforeEach ->
 		@ProjectManager = SandboxedModule.require modulePath, requires:
 			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 			"./DocumentManager": @DocumentManager = {}
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
 			"./HistoryManager": @HistoryManager =

+ 1 - 2
services/document-updater/test/unit/coffee/ProjectManager/flushProjectTests.coffee

@@ -8,6 +8,7 @@ describe "ProjectManager - flushProject", ->
 	beforeEach ->
 		@ProjectManager = SandboxedModule.require modulePath, requires:
 			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 			"./DocumentManager": @DocumentManager = {}
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
 			"./HistoryManager": @HistoryManager = {}
@@ -72,5 +73,3 @@ describe "ProjectManager - flushProject", ->
 
 		it "should time the execution", ->
 			@Metrics.Timer::done.called.should.equal true
-
-

+ 1 - 0
services/document-updater/test/unit/coffee/ProjectManager/getProjectDocsTests.coffee

@@ -9,6 +9,7 @@ describe "ProjectManager - getProjectDocsAndFlushIfOld", ->
 	beforeEach ->
 		@ProjectManager = SandboxedModule.require modulePath, requires:
 			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 			"./DocumentManager": @DocumentManager = {}
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
 			"./HistoryManager": @HistoryManager = {}

+ 11 - 11
services/document-updater/test/unit/coffee/ProjectManager/updateProjectTests.coffee

@@ -8,6 +8,7 @@ describe "ProjectManager", ->
 	beforeEach ->
 		@ProjectManager = SandboxedModule.require modulePath, requires:
 			"./RedisManager": @RedisManager = {}
+			"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 			"./DocumentManager": @DocumentManager = {}
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
 			"./HistoryManager": @HistoryManager = {}
@@ -40,7 +41,7 @@ describe "ProjectManager", ->
 					newPathname: 'bar2'
 				@fileUpdates = [ @firstFileUpdate ]
 				@DocumentManager.renameDocWithLock = sinon.stub().yields()
-				@RedisManager.renameFile = sinon.stub().yields()
+				@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
 
 			describe "successfully", ->
 				beforeEach ->
@@ -55,8 +56,8 @@ describe "ProjectManager", ->
 						.should.equal true
 
 				it "should rename the files in the updates", ->
-					@RedisManager.renameFile
-						.calledWith(@project_id, @firstFileUpdate.id, @user_id, @firstFileUpdate)
+					@ProjectHistoryRedisManager.queueRenameEntity
+						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, @firstFileUpdate)
 						.should.equal true
 
 				it "should not flush the history", ->
@@ -79,7 +80,7 @@ describe "ProjectManager", ->
 			describe "when renaming a file fails", ->
 				beforeEach ->
 					@error = new Error('error')
-					@RedisManager.renameFile = sinon.stub().yields(@error)
+					@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error)
 					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
 
 				it "should call the callback with the error", ->
@@ -108,22 +109,22 @@ describe "ProjectManager", ->
 					id: 2
 					url: 'filestore.example.com/2'
 				@fileUpdates = [ @firstFileUpdate ]
-				@RedisManager.addEntity = sinon.stub().yields()
+				@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields()
 
 			describe "successfully", ->
 				beforeEach ->
 					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
 
 				it "should add the docs in the updates", ->
-					@RedisManager.addEntity
+					@ProjectHistoryRedisManager.queueAddEntity
 						.calledWith(@project_id, 'doc', @firstDocUpdate.id, @user_id, @firstDocUpdate)
 						.should.equal true
-					@RedisManager.addEntity
+					@ProjectHistoryRedisManager.queueAddEntity
 						.calledWith(@project_id, 'doc', @secondDocUpdate.id, @user_id, @secondDocUpdate)
 						.should.equal true
 
 				it "should add the files in the updates", ->
-					@RedisManager.addEntity
+					@ProjectHistoryRedisManager.queueAddEntity
 						.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, @firstFileUpdate)
 						.should.equal true
 
@@ -138,7 +139,7 @@ describe "ProjectManager", ->
 			describe "when adding a doc fails", ->
 				beforeEach ->
 					@error = new Error('error')
-					@RedisManager.addEntity = sinon.stub().yields(@error)
+					@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
 					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
 
 				it "should call the callback with the error", ->
@@ -147,7 +148,7 @@ describe "ProjectManager", ->
 			describe "when adding a file fails", ->
 				beforeEach ->
 					@error = new Error('error')
-					@RedisManager.addEntity = sinon.stub().yields(@error)
+					@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
 					@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @callback
 
 				it "should call the callback with the error", ->
@@ -162,4 +163,3 @@ describe "ProjectManager", ->
 					@HistoryManager.flushProjectChangesAsync
 						.calledWith(@project_id)
 						.should.equal true
-

+ 14 - 68
services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee

@@ -15,6 +15,7 @@ describe "RedisManager", ->
 		@RedisManager = SandboxedModule.require modulePath,
 			requires:
 				"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }
+				"./ProjectHistoryRedisManager": @ProjectHistoryRedisManager = {}
 				"settings-sharelatex": @settings = {
 					documentupdater: {logHashErrors: {write:true, read:true}}
 					apis:
@@ -38,9 +39,6 @@ describe "RedisManager", ->
 							key_schema:
 								uncompressedHistoryOps: ({doc_id}) -> "UncompressedHistoryOps:#{doc_id}"
 								docsWithHistoryOps: ({project_id}) -> "DocsWithHistoryOps:#{project_id}"
-						project_history:
-							key_schema:
-								projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:#{project_id}"
 				}
 				"redis-sharelatex":
 					createClient: () => @rclient
@@ -337,7 +335,9 @@ describe "RedisManager", ->
 			@multi.exec = sinon.stub().callsArgWith(0, null,
 				[@hash, null, null, null, null, null, null, @doc_update_list_length]
 			)
-			@rclient.rpush = sinon.stub().callsArgWith(@ops.length + 1, null, @project_update_list_length)
+			@ProjectHistoryRedisManager.queueOps = sinon.stub().callsArgWith(
+				@ops.length + 1, null, @project_update_list_length
+			)
 
 		describe "with a consistent version", ->
 			beforeEach ->
@@ -399,8 +399,8 @@ describe "RedisManager", ->
 						.should.equal true
 
 				it "should push the updates into the project history ops list", ->
-					@rclient.rpush
-						.calledWith("ProjectHistory:Ops:#{@project_id}", JSON.stringify(@ops[0]), JSON.stringify(@ops[1]))
+					@ProjectHistoryRedisManager.queueOps
+						.calledWith(@project_id, JSON.stringify(@ops[0]))
 						.should.equal true
 
 				it "should call the callback", ->
@@ -686,6 +686,7 @@ describe "RedisManager", ->
 		describe "the document is cached in redis", ->
 			beforeEach ->
 				@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, 'lines', 'version')
+				@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
 				@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback
 
 			it "update the cached pathname", ->
@@ -694,75 +695,20 @@ describe "RedisManager", ->
 					.should.equal true
 
 			it "should queue an update", ->
-				update =
-					pathname: @pathname
-					new_pathname: @newPathname
-					meta:
-						user_id: @userId
-						ts: new Date()
-					doc: @doc_id
-				@rclient.rpush
-					.calledWith("ProjectHistory:Ops:#{@project_id}", JSON.stringify(update))
+				@ProjectHistoryRedisManager.queueRenameEntity
+					.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback)
 					.should.equal true
 
-			it "should call the callback", ->
-				@callback.calledWith().should.equal true
-
 		describe "the document is not cached in redis", ->
 			beforeEach ->
 				@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null)
+				@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
 				@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback
 
 			it "does not update the cached pathname", ->
 				@rclient.set.called.should.equal false
 
-	describe "renameFile", ->
-		beforeEach () ->
-			@rclient.rpush = sinon.stub().yields()
-			@file_id = 1234
-
-			@update =
-				pathname: @pathname = '/old'
-				newPathname: @newPathname = '/new'
-
-			@RedisManager.renameFile @project_id, @file_id, @userId, @update
-
-		it "should queue an update", ->
-			update =
-				pathname: @pathname
-				new_pathname: @newPathname
-				meta:
-					user_id: @userId
-					ts: new Date()
-				file: @file_id
-
-			@rclient.rpush
-				.calledWith("ProjectHistory:Ops:#{@project_id}", JSON.stringify(update))
-				.should.equal true
-
-	describe "addEntity", ->
-		beforeEach (done) ->
-			@rclient.rpush = sinon.stub().yields()
-			@entity_id = 1234
-			@entity_type = 'type'
-
-			@update =
-				pathname: @pathname = '/old'
-				docLines: @docLines = 'a\nb'
-				url: @url = 'filestore.example.com'
-
-			@RedisManager.addEntity @project_id, @entity_type, @entity_id, @userId, @update, done
-
-		it "should queue an update", ->
-			update =
-				pathname: @pathname
-				docLines: @docLines
-				url: @url
-				meta:
-					user_id: @user_id
-					ts: new Date()
-			update[@entity_type] = @entity_id
-
-			@rclient.rpush
-				.calledWith("ProjectHistory:Ops:#{@project_id}", JSON.stringify(update))
-				.should.equal true
+			it "should queue an update", ->
+				@ProjectHistoryRedisManager.queueRenameEntity
+					.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback)
+					.should.equal true