Merge pull request #29 from sharelatex/revert-28-revert-26-hof-deleted-project-history

Add projectHistoryId to updates
This commit is contained in:
Hayden Faulds 2018-04-26 09:15:04 +01:00 committed by GitHub
commit 2ce19b13e3
17 changed files with 163 additions and 115 deletions

View file

@ -13,39 +13,39 @@ async = require "async"
MAX_UNFLUSHED_AGE = 300 * 1000 # 5 mins, document should be flushed to mongo this time after a change MAX_UNFLUSHED_AGE = 300 * 1000 # 5 mins, document should be flushed to mongo this time after a change
module.exports = DocumentManager = module.exports = DocumentManager =
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, unflushedTime, alreadyLoaded) ->) -> getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->) ->
timer = new Metrics.Timer("docManager.getDoc") timer = new Metrics.Timer("docManager.getDoc")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, unflushedTime) -> RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime) ->
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API" logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API"
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) -> PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
return callback(error) if error? return callback(error) if error?
logger.log {project_id, doc_id, lines, version, pathname}, "got doc from persistence API" logger.log {project_id, doc_id, lines, version, pathname, projectHistoryId}, "got doc from persistence API"
RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, (error) -> RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, projectHistoryId, (error) ->
return callback(error) if error? return callback(error) if error?
callback null, lines, version, ranges, pathname, null, false callback null, lines, version, ranges, pathname, projectHistoryId, null, false
else else
callback null, lines, version, ranges, pathname, unflushedTime, true callback null, lines, version, ranges, pathname, projectHistoryId, unflushedTime, true
getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, ops, ranges, pathname) ->) -> getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
timer = new Metrics.Timer("docManager.getDocAndRecentOps") timer = new Metrics.Timer("docManager.getDocAndRecentOps")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) -> DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
return callback(error) if error? return callback(error) if error?
if fromVersion == -1 if fromVersion == -1
callback null, lines, version, [], ranges, pathname callback null, lines, version, [], ranges, pathname, projectHistoryId
else else
RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) -> RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) ->
return callback(error) if error? return callback(error) if error?
callback null, lines, version, ops, ranges, pathname callback null, lines, version, ops, ranges, pathname, projectHistoryId
setDoc: (project_id, doc_id, newLines, source, user_id, undoing, _callback = (error) ->) -> setDoc: (project_id, doc_id, newLines, source, user_id, undoing, _callback = (error) ->) ->
timer = new Metrics.Timer("docManager.setDoc") timer = new Metrics.Timer("docManager.setDoc")
@ -57,7 +57,7 @@ module.exports = DocumentManager =
return callback(new Error("No lines were provided to setDoc")) return callback(new Error("No lines were provided to setDoc"))
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, pathname, unflushedTime, alreadyLoaded) -> DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
return callback(error) if error? return callback(error) if error?
if oldLines? and oldLines.length > 0 and oldLines[0].text? if oldLines? and oldLines.length > 0 and oldLines[0].text?
@ -161,16 +161,16 @@ module.exports = DocumentManager =
return callback(error) if error? return callback(error) if error?
callback() callback()
renameDoc: (project_id, doc_id, user_id, update, _callback = (error) ->) -> renameDoc: (project_id, doc_id, user_id, update, projectHistoryId, _callback = (error) ->) ->
timer = new Metrics.Timer("docManager.updateProject") timer = new Metrics.Timer("docManager.updateProject")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
RedisManager.renameDoc project_id, doc_id, user_id, update, callback RedisManager.renameDoc project_id, doc_id, user_id, update, projectHistoryId, callback
getDocAndFlushIfOld: (project_id, doc_id, callback = (error, doc) ->) -> getDocAndFlushIfOld: (project_id, doc_id, callback = (error, doc) ->) ->
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, unflushedTime, alreadyLoaded) -> DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
return callback(error) if error? return callback(error) if error?
# if doc was already loaded see if it needs to be flushed # if doc was already loaded see if it needs to be flushed
if alreadyLoaded and unflushedTime? and (Date.now() - unflushedTime) > MAX_UNFLUSHED_AGE if alreadyLoaded and unflushedTime? and (Date.now() - unflushedTime) > MAX_UNFLUSHED_AGE
@ -181,21 +181,21 @@ module.exports = DocumentManager =
callback(null, lines, version) callback(null, lines, version)
resyncDocContents: (project_id, doc_id, callback) -> resyncDocContents: (project_id, doc_id, callback) ->
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) -> RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) -> PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
return callback(error) if error? return callback(error) if error?
ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
else else
ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) -> getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback
getDocAndRecentOpsWithLock: (project_id, doc_id, fromVersion, callback = (error, lines, version, ops, ranges, pathname) ->) -> getDocAndRecentOpsWithLock: (project_id, doc_id, fromVersion, callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndRecentOps, project_id, doc_id, fromVersion, callback UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndRecentOps, project_id, doc_id, fromVersion, callback
@ -223,9 +223,9 @@ module.exports = DocumentManager =
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
UpdateManager.lockUpdatesAndDo DocumentManager.deleteComment, project_id, doc_id, thread_id, callback UpdateManager.lockUpdatesAndDo DocumentManager.deleteComment, project_id, doc_id, thread_id, callback
renameDocWithLock: (project_id, doc_id, user_id, update, callback = (error) ->) -> renameDocWithLock: (project_id, doc_id, user_id, update, projectHistoryId, callback = (error) ->) ->
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, callback UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, projectHistoryId, callback
resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) -> resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) ->
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"

View file

@ -65,8 +65,8 @@ module.exports = HistoryManager =
newBlock = Math.floor(length / threshold) newBlock = Math.floor(length / threshold)
return newBlock != prevBlock return newBlock != prevBlock
resyncProjectHistory: (project_id, docs, files, callback) -> resyncProjectHistory: (project_id, projectHistoryId, docs, files, callback) ->
ProjectHistoryRedisManager.queueResyncProjectStructure project_id, docs, files, (error) -> ProjectHistoryRedisManager.queueResyncProjectStructure project_id, projectHistoryId, docs, files, (error) ->
return callback(error) if error? return callback(error) if error?
DocumentManager = require "./DocumentManager" DocumentManager = require "./DocumentManager"
resyncDoc = (doc, cb) -> resyncDoc = (doc, cb) ->

View file

@ -161,10 +161,10 @@ module.exports = HttpController =
updateProject: (req, res, next = (error) ->) -> updateProject: (req, res, next = (error) ->) ->
timer = new Metrics.Timer("http.updateProject") timer = new Metrics.Timer("http.updateProject")
project_id = req.params.project_id project_id = req.params.project_id
{userId, docUpdates, fileUpdates, version} = req.body {projectHistoryId, userId, docUpdates, fileUpdates, version} = req.body
logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http" logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http"
ProjectManager.updateProjectWithLocks project_id, userId, docUpdates, fileUpdates, version, (error) -> ProjectManager.updateProjectWithLocks project_id, projectHistoryId, userId, docUpdates, fileUpdates, version, (error) ->
timer.done() timer.done()
return next(error) if error? return next(error) if error?
logger.log project_id: project_id, "updated project via http" logger.log project_id: project_id, "updated project via http"
@ -172,10 +172,10 @@ module.exports = HttpController =
resyncProjectHistory: (req, res, next = (error) ->) -> resyncProjectHistory: (req, res, next = (error) ->) ->
project_id = req.params.project_id project_id = req.params.project_id
{docs, files} = req.body {projectHistoryId, docs, files} = req.body
logger.log {project_id, docs, files}, "queuing project history resync via http" logger.log {project_id, docs, files}, "queuing project history resync via http"
HistoryManager.resyncProjectHistory project_id, docs, files, (error) -> HistoryManager.resyncProjectHistory project_id, projectHistoryId, docs, files, (error) ->
return next(error) if error? return next(error) if error?
logger.log {project_id}, "queued project history resync via http" logger.log {project_id}, "queued project history resync via http"
res.send 204 res.send 204

View file

@ -13,7 +13,7 @@ request = (require("requestretry")).defaults({
MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds
module.exports = PersistenceManager = module.exports = PersistenceManager =
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname) ->) -> getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId) ->) ->
timer = new Metrics.Timer("persistenceManager.getDoc") timer = new Metrics.Timer("persistenceManager.getDoc")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
@ -44,7 +44,7 @@ module.exports = PersistenceManager =
return callback(new Error("web API response had no valid doc version")) return callback(new Error("web API response had no valid doc version"))
if !body.pathname? if !body.pathname?
return callback(new Error("web API response had no valid doc pathname")) return callback(new Error("web API response had no valid doc pathname"))
return callback null, body.lines, body.version, body.ranges, body.pathname return callback null, body.lines, body.version, body.ranges, body.pathname, body.projectHistoryId
else if res.statusCode == 404 else if res.statusCode == 404
return callback(new Errors.NotFoundError("doc not not found: #{url}")) return callback(new Errors.NotFoundError("doc not not found: #{url}"))
else else

View file

@ -7,7 +7,7 @@ module.exports = ProjectHistoryRedisManager =
queueOps: (project_id, ops..., callback) -> queueOps: (project_id, ops..., callback) ->
rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback
queueRenameEntity: (project_id, entity_type, entity_id, user_id, projectUpdate, callback) -> queueRenameEntity: (project_id, projectHistoryId, entity_type, entity_id, user_id, projectUpdate, callback) ->
projectUpdate = projectUpdate =
pathname: projectUpdate.pathname pathname: projectUpdate.pathname
new_pathname: projectUpdate.newPathname new_pathname: projectUpdate.newPathname
@ -15,6 +15,7 @@ module.exports = ProjectHistoryRedisManager =
user_id: user_id user_id: user_id
ts: new Date() ts: new Date()
version: projectUpdate.version version: projectUpdate.version
projectHistoryId: projectHistoryId
projectUpdate[entity_type] = entity_id projectUpdate[entity_type] = entity_id
logger.log {project_id, projectUpdate}, "queue rename operation to project-history" logger.log {project_id, projectUpdate}, "queue rename operation to project-history"
@ -22,7 +23,7 @@ module.exports = ProjectHistoryRedisManager =
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
queueAddEntity: (project_id, entity_type, entitiy_id, user_id, projectUpdate, callback = (error) ->) -> queueAddEntity: (project_id, projectHistoryId, entity_type, entitiy_id, user_id, projectUpdate, callback = (error) ->) ->
projectUpdate = projectUpdate =
pathname: projectUpdate.pathname pathname: projectUpdate.pathname
docLines: projectUpdate.docLines docLines: projectUpdate.docLines
@ -31,6 +32,7 @@ module.exports = ProjectHistoryRedisManager =
user_id: user_id user_id: user_id
ts: new Date() ts: new Date()
version: projectUpdate.version version: projectUpdate.version
projectHistoryId: projectHistoryId
projectUpdate[entity_type] = entitiy_id projectUpdate[entity_type] = entitiy_id
logger.log {project_id, projectUpdate}, "queue add operation to project-history" logger.log {project_id, projectUpdate}, "queue add operation to project-history"
@ -38,21 +40,23 @@ module.exports = ProjectHistoryRedisManager =
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
queueResyncProjectStructure: (project_id, docs, files, callback) -> queueResyncProjectStructure: (project_id, projectHistoryId, docs, files, callback) ->
logger.log {project_id, docs, files}, "queue project structure resync" logger.log {project_id, docs, files}, "queue project structure resync"
projectUpdate = projectUpdate =
resyncProjectStructure: { docs, files } resyncProjectStructure: { docs, files }
projectHistoryId: projectHistoryId
meta: meta:
ts: new Date() ts: new Date()
jsonUpdate = JSON.stringify projectUpdate jsonUpdate = JSON.stringify projectUpdate
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
queueResyncDocContent: (project_id, doc_id, lines, version, pathname, callback) -> queueResyncDocContent: (project_id, projectHistoryId, doc_id, lines, version, pathname, callback) ->
logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync" logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync"
projectUpdate = projectUpdate =
resyncDocContent: resyncDocContent:
content: lines.join("\n"), content: lines.join("\n"),
version: version version: version
projectHistoryId: projectHistoryId
path: pathname path: pathname
doc: doc_id doc: doc_id
meta: meta:

View file

@ -105,7 +105,7 @@ module.exports = ProjectManager =
clearProjectState: (project_id, callback = (error) ->) -> clearProjectState: (project_id, callback = (error) ->) ->
RedisManager.clearProjectState project_id, callback RedisManager.clearProjectState project_id, callback
updateProjectWithLocks: (project_id, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) -> updateProjectWithLocks: (project_id, projectHistoryId, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
timer = new Metrics.Timer("projectManager.updateProject") timer = new Metrics.Timer("projectManager.updateProject")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
@ -120,11 +120,11 @@ module.exports = ProjectManager =
doc_id = projectUpdate.id doc_id = projectUpdate.id
projectUpdate.version = "#{project_version}.#{project_subversion++}" projectUpdate.version = "#{project_version}.#{project_subversion++}"
if projectUpdate.docLines? if projectUpdate.docLines?
ProjectHistoryRedisManager.queueAddEntity project_id, 'doc', doc_id, user_id, projectUpdate, (error, count) -> ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
project_ops_length = count project_ops_length = count
cb(error) cb(error)
else else
DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, (error, count) -> DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, projectHistoryId, (error, count) ->
project_ops_length = count project_ops_length = count
cb(error) cb(error)
@ -132,11 +132,11 @@ module.exports = ProjectManager =
file_id = projectUpdate.id file_id = projectUpdate.id
projectUpdate.version = "#{project_version}.#{project_subversion++}" projectUpdate.version = "#{project_version}.#{project_subversion++}"
if projectUpdate.url? if projectUpdate.url?
ProjectHistoryRedisManager.queueAddEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) -> ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
project_ops_length = count project_ops_length = count
cb(error) cb(error)
else else
ProjectHistoryRedisManager.queueRenameEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) -> ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
project_ops_length = count project_ops_length = count
cb(error) cb(error)

View file

@ -36,7 +36,7 @@ historyKeys = Settings.redis.history.key_schema
module.exports = RedisManager = module.exports = RedisManager =
rclient: rclient rclient: rclient
putDocInMemory : (project_id, doc_id, docLines, version, ranges, pathname, _callback)-> putDocInMemory : (project_id, doc_id, docLines, version, ranges, pathname, projectHistoryId, _callback)->
timer = new metrics.Timer("redis.put-doc") timer = new metrics.Timer("redis.put-doc")
callback = (error) -> callback = (error) ->
timer.done() timer.done()
@ -47,7 +47,7 @@ module.exports = RedisManager =
logger.error {err: error, doc_id: doc_id, docLines: docLines}, error.message logger.error {err: error, doc_id: doc_id, docLines: docLines}, error.message
return callback(error) return callback(error)
docHash = RedisManager._computeHash(docLines) docHash = RedisManager._computeHash(docLines)
logger.log {project_id, doc_id, version, docHash, pathname}, "putting doc in redis" logger.log {project_id, doc_id, version, docHash, pathname, projectHistoryId}, "putting doc in redis"
RedisManager._serializeRanges ranges, (error, ranges) -> RedisManager._serializeRanges ranges, (error, ranges) ->
if error? if error?
logger.error {err: error, doc_id, project_id}, error.message logger.error {err: error, doc_id, project_id}, error.message
@ -62,6 +62,7 @@ module.exports = RedisManager =
else else
multi.del keys.ranges(doc_id:doc_id) multi.del keys.ranges(doc_id:doc_id)
multi.set keys.pathname(doc_id:doc_id), pathname multi.set keys.pathname(doc_id:doc_id), pathname
multi.set keys.projectHistoryId(doc_id:doc_id), projectHistoryId
multi.exec (error, result) -> multi.exec (error, result) ->
return callback(error) if error? return callback(error) if error?
# check the hash computed on the redis server # check the hash computed on the redis server
@ -88,6 +89,7 @@ module.exports = RedisManager =
multi.del keys.docHash(doc_id:doc_id) multi.del keys.docHash(doc_id:doc_id)
multi.del keys.ranges(doc_id:doc_id) multi.del keys.ranges(doc_id:doc_id)
multi.del keys.pathname(doc_id:doc_id) multi.del keys.pathname(doc_id:doc_id)
multi.del keys.projectHistoryId(doc_id:doc_id)
multi.del keys.unflushedTime(doc_id:doc_id) multi.del keys.unflushedTime(doc_id:doc_id)
multi.exec (error) -> multi.exec (error) ->
return callback(error) if error? return callback(error) if error?
@ -108,7 +110,7 @@ module.exports = RedisManager =
clearProjectState: (project_id, callback = (error) ->) -> clearProjectState: (project_id, callback = (error) ->) ->
rclient.del keys.projectState(project_id:project_id), callback rclient.del keys.projectState(project_id:project_id), callback
getDoc : (project_id, doc_id, callback = (error, lines, version, ranges, pathname, unflushedTime) ->)-> getDoc : (project_id, doc_id, callback = (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime) ->)->
timer = new metrics.Timer("redis.get-doc") timer = new metrics.Timer("redis.get-doc")
multi = rclient.multi() multi = rclient.multi()
multi.get keys.docLines(doc_id:doc_id) multi.get keys.docLines(doc_id:doc_id)
@ -117,8 +119,9 @@ module.exports = RedisManager =
multi.get keys.projectKey(doc_id:doc_id) multi.get keys.projectKey(doc_id:doc_id)
multi.get keys.ranges(doc_id:doc_id) multi.get keys.ranges(doc_id:doc_id)
multi.get keys.pathname(doc_id:doc_id) multi.get keys.pathname(doc_id:doc_id)
multi.get keys.projectHistoryId(doc_id:doc_id)
multi.get keys.unflushedTime(doc_id:doc_id) multi.get keys.unflushedTime(doc_id:doc_id)
multi.exec (error, [docLines, version, storedHash, doc_project_id, ranges, pathname, unflushedTime])-> multi.exec (error, [docLines, version, storedHash, doc_project_id, ranges, pathname, projectHistoryId, unflushedTime])->
timeSpan = timer.done() timeSpan = timer.done()
return callback(error) if error? return callback(error) if error?
# check if request took too long and bail out. only do this for # check if request took too long and bail out. only do this for
@ -145,16 +148,19 @@ module.exports = RedisManager =
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project" logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
return callback(new Errors.NotFoundError("document not found")) return callback(new Errors.NotFoundError("document not found"))
if projectHistoryId?
projectHistoryId = parseInt(projectHistoryId)
# doc is not in redis, bail out # doc is not in redis, bail out
if !docLines? if !docLines?
return callback null, docLines, version, ranges, pathname, unflushedTime return callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime
# doc should be in project set, check if missing (workaround for missing docs from putDoc) # doc should be in project set, check if missing (workaround for missing docs from putDoc)
rclient.sadd keys.docsInProject(project_id:project_id), doc_id, (error, result) -> rclient.sadd keys.docsInProject(project_id:project_id), doc_id, (error, result) ->
return callback(error) if error? return callback(error) if error?
if result isnt 0 # doc should already be in set if result isnt 0 # doc should already be in set
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc missing from docsInProject set" logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc missing from docsInProject set"
callback null, docLines, version, ranges, pathname, unflushedTime callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime
getDocVersion: (doc_id, callback = (error, version) ->) -> getDocVersion: (doc_id, callback = (error, version) ->) ->
rclient.get keys.docVersion(doc_id: doc_id), (error, version) -> rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
@ -272,16 +278,16 @@ module.exports = RedisManager =
else else
callback null, docUpdateCount callback null, docUpdateCount
renameDoc: (project_id, doc_id, user_id, update, callback = (error) ->) -> renameDoc: (project_id, doc_id, user_id, update, projectHistoryId, callback = (error) ->) ->
RedisManager.getDoc project_id, doc_id, (error, lines, version) -> RedisManager.getDoc project_id, doc_id, (error, lines, version) ->
return callback(error) if error? return callback(error) if error?
if lines? and version? if lines? and version?
rclient.set keys.pathname(doc_id:doc_id), update.newPathname, (error) -> rclient.set keys.pathname(doc_id:doc_id), update.newPathname, (error) ->
return callback(error) if error? return callback(error) if error?
ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'doc', doc_id, user_id, update, callback
else else
ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'doc', doc_id, user_id, update, callback
clearUnflushedTime: (doc_id, callback = (error) ->) -> clearUnflushedTime: (doc_id, callback = (error) ->) ->
rclient.del keys.unflushedTime(doc_id:doc_id), callback rclient.del keys.unflushedTime(doc_id:doc_id), callback

View file

@ -71,7 +71,7 @@ module.exports = UpdateManager =
profile = new Profiler("applyUpdate", {project_id, doc_id}) profile = new Profiler("applyUpdate", {project_id, doc_id})
UpdateManager._sanitizeUpdate update UpdateManager._sanitizeUpdate update
profile.log("sanitizeUpdate") profile.log("sanitizeUpdate")
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) -> DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
profile.log("getDoc") profile.log("getDoc")
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
@ -80,7 +80,7 @@ module.exports = UpdateManager =
profile.log("sharejs.applyUpdate") profile.log("sharejs.applyUpdate")
return callback(error) if error? return callback(error) if error?
RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, updatedDocLines, (error, new_ranges) -> RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, updatedDocLines, (error, new_ranges) ->
UpdateManager._addProjectHistoryMetadataToOps(appliedOps, pathname, lines) UpdateManager._addProjectHistoryMetadataToOps(appliedOps, pathname, projectHistoryId, lines)
profile.log("RangesManager.applyUpdate") profile.log("RangesManager.applyUpdate")
return callback(error) if error? return callback(error) if error?
RedisManager.updateDocument project_id, doc_id, updatedDocLines, version, appliedOps, new_ranges, (error, doc_ops_length, project_ops_length) -> RedisManager.updateDocument project_id, doc_id, updatedDocLines, version, appliedOps, new_ranges, (error, doc_ops_length, project_ops_length) ->
@ -130,12 +130,13 @@ module.exports = UpdateManager =
op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD") op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
return update return update
_addProjectHistoryMetadataToOps: (updates, pathname, lines) -> _addProjectHistoryMetadataToOps: (updates, pathname, projectHistoryId, lines) ->
doc_length = _.reduce lines, doc_length = _.reduce lines,
(chars, line) -> chars + line.length, (chars, line) -> chars + line.length,
0 0
doc_length += lines.length - 1 # count newline characters doc_length += lines.length - 1 # count newline characters
updates.forEach (update) -> updates.forEach (update) ->
update.projectHistoryId = projectHistoryId
update.meta ||= {} update.meta ||= {}
update.meta.pathname = pathname update.meta.pathname = pathname
update.meta.doc_length = doc_length update.meta.doc_length = doc_length

View file

@ -46,6 +46,7 @@ module.exports =
docsInProject: ({project_id}) -> "DocsIn:#{project_id}" docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
ranges: ({doc_id}) -> "Ranges:#{doc_id}" ranges: ({doc_id}) -> "Ranges:#{doc_id}"
pathname: ({doc_id}) -> "Pathname:#{doc_id}" pathname: ({doc_id}) -> "Pathname:#{doc_id}"
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{doc_id}"
projectState: ({project_id}) -> "ProjectState:#{project_id}" projectState: ({project_id}) -> "ProjectState:#{project_id}"
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}" unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
# cluster: [{ # cluster: [{

View file

@ -25,6 +25,7 @@ describe "DocumentManager", ->
"./UpdateManager": @UpdateManager = {} "./UpdateManager": @UpdateManager = {}
"./RangesManager": @RangesManager = {} "./RangesManager": @RangesManager = {}
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = "history-id-123"
@doc_id = "doc-id-123" @doc_id = "doc-id-123"
@user_id = 1234 @user_id = 1234
@callback = sinon.stub() @callback = sinon.stub()
@ -111,7 +112,7 @@ describe "DocumentManager", ->
describe "getDocAndRecentOps", -> describe "getDocAndRecentOps", ->
describe "with a previous version specified", -> describe "with a previous version specified", ->
beforeEach -> beforeEach ->
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops) @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback @DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback
@ -126,14 +127,14 @@ describe "DocumentManager", ->
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @ops, @ranges, @pathname).should.equal true @callback.calledWith(null, @lines, @version, @ops, @ranges, @pathname, @projectHistoryId).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
describe "with no previous version specified", -> describe "with no previous version specified", ->
beforeEach -> beforeEach ->
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops) @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback @DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback
@ -146,7 +147,7 @@ describe "DocumentManager", ->
@RedisManager.getPreviousDocOps.called.should.equal false @RedisManager.getPreviousDocOps.called.should.equal false
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, [], @ranges, @pathname).should.equal true @callback.calledWith(null, @lines, @version, [], @ranges, @pathname, @projectHistoryId).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -154,7 +155,7 @@ describe "DocumentManager", ->
describe "getDoc", -> describe "getDoc", ->
describe "when the doc exists in Redis", -> describe "when the doc exists in Redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @unflushedTime) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime)
@DocumentManager.getDoc @project_id, @doc_id, @callback @DocumentManager.getDoc @project_id, @doc_id, @callback
it "should get the doc from Redis", -> it "should get the doc from Redis", ->
@ -163,7 +164,7 @@ describe "DocumentManager", ->
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @ranges, @pathname, @unflushedTime, true).should.equal true @callback.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime, true).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -171,7 +172,7 @@ describe "DocumentManager", ->
describe "when the doc does not exist in Redis", -> describe "when the doc does not exist in Redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null, null) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null, null)
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname) @PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@RedisManager.putDocInMemory = sinon.stub().yields() @RedisManager.putDocInMemory = sinon.stub().yields()
@DocumentManager.getDoc @project_id, @doc_id, @callback @DocumentManager.getDoc @project_id, @doc_id, @callback
@ -187,11 +188,11 @@ describe "DocumentManager", ->
it "should set the doc in Redis", -> it "should set the doc in Redis", ->
@RedisManager.putDocInMemory @RedisManager.putDocInMemory
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname) .calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId)
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @ranges, @pathname, null, false).should.equal true @callback.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId, null, false).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -202,7 +203,7 @@ describe "DocumentManager", ->
@beforeLines = ["before", "lines"] @beforeLines = ["before", "lines"]
@afterLines = ["after", "lines"] @afterLines = ["after", "lines"]
@ops = [{ i: "foo", p: 4 }, { d: "bar", p: 42 }] @ops = [{ i: "foo", p: 4 }, { d: "bar", p: 42 }]
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, @pathname, @unflushedTime, true) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime, true)
@DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops) @DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops)
@UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null) @UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null)
@DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2) @DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2)
@ -402,7 +403,7 @@ describe "DocumentManager", ->
describe "when the doc is in Redis", -> describe "when the doc is in Redis", ->
describe "and has changes to be flushed", -> describe "and has changes to be flushed", ->
beforeEach -> beforeEach ->
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, Date.now() - 1e9, true) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @projectHistoryId, @pathname, Date.now() - 1e9, true)
@DocumentManager.getDocAndFlushIfOld @project_id, @doc_id, @callback @DocumentManager.getDocAndFlushIfOld @project_id, @doc_id, @callback
it "should get the doc", -> it "should get the doc", ->
@ -459,11 +460,11 @@ describe "DocumentManager", ->
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@DocumentManager.renameDoc @project_id, @doc_id, @user_id, @update, @callback @DocumentManager.renameDoc @project_id, @doc_id, @user_id, @update, @projectHistoryId, @callback
it "should rename the document", -> it "should rename the document", ->
@RedisManager.renameDoc @RedisManager.renameDoc
.calledWith(@project_id, @doc_id, @user_id, @update) .calledWith(@project_id, @doc_id, @user_id, @update, @projectHistoryId)
.should.equal true .should.equal true
it "should call the callback", -> it "should call the callback", ->
@ -472,7 +473,7 @@ describe "DocumentManager", ->
describe "resyncDocContents", -> describe "resyncDocContents", ->
describe "when doc is loaded in redis", -> describe "when doc is loaded in redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub() @ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback @DocumentManager.resyncDocContents @project_id, @doc_id, @callback
@ -483,13 +484,13 @@ describe "DocumentManager", ->
it "queues a resync doc content update", -> it "queues a resync doc content update", ->
@ProjectHistoryRedisManager.queueResyncDocContent @ProjectHistoryRedisManager.queueResyncDocContent
.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback) .calledWith(@project_id, @projectHistoryId, @doc_id, @lines, @version, @pathname, @callback)
.should.equal true .should.equal true
describe "when doc is not loaded in redis", -> describe "when doc is not loaded in redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null)
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname) @PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub() @ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback @DocumentManager.resyncDocContents @project_id, @doc_id, @callback
@ -505,5 +506,5 @@ describe "DocumentManager", ->
it "queues a resync doc content update", -> it "queues a resync doc content update", ->
@ProjectHistoryRedisManager.queueResyncDocContent @ProjectHistoryRedisManager.queueResyncDocContent
.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback) .calledWith(@project_id, @projectHistoryId, @doc_id, @lines, @version, @pathname, @callback)
.should.equal true .should.equal true

View file

@ -164,6 +164,7 @@ describe "HistoryManager", ->
describe "resyncProjectHistory", -> describe "resyncProjectHistory", ->
beforeEach -> beforeEach ->
@projectHistoryId = 'history-id-1234'
@docs = [ @docs = [
doc: @doc_id doc: @doc_id
path: 'main.tex' path: 'main.tex'
@ -175,11 +176,11 @@ describe "HistoryManager", ->
] ]
@ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields() @ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields()
@DocumentManager.resyncDocContentsWithLock = sinon.stub().yields() @DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
@HistoryManager.resyncProjectHistory @project_id, @docs, @files, @callback @HistoryManager.resyncProjectHistory @project_id, @projectHistoryId, @docs, @files, @callback
it "should queue a project structure reync", -> it "should queue a project structure reync", ->
@ProjectHistoryRedisManager.queueResyncProjectStructure @ProjectHistoryRedisManager.queueResyncProjectStructure
.calledWith(@project_id, @docs, @files) .calledWith(@project_id, @projectHistoryId, @docs, @files)
.should.equal true .should.equal true
it "should queue doc content reyncs", -> it "should queue doc content reyncs", ->

View file

@ -509,23 +509,24 @@ describe "HttpController", ->
describe "updateProject", -> describe "updateProject", ->
beforeEach -> beforeEach ->
@projectHistoryId = "history-id-123"
@userId = "user-id-123" @userId = "user-id-123"
@docUpdates = sinon.stub() @docUpdates = sinon.stub()
@fileUpdates = sinon.stub() @fileUpdates = sinon.stub()
@version = 1234567 @version = 1234567
@req = @req =
body: {@userId, @docUpdates, @fileUpdates, @version} body: {@projectHistoryId, @userId, @docUpdates, @fileUpdates, @version}
params: params:
project_id: @project_id project_id: @project_id
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5) @ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6)
@HttpController.updateProject(@req, @res, @next) @HttpController.updateProject(@req, @res, @next)
it "should accept the change", -> it "should accept the change", ->
@ProjectManager.updateProjectWithLocks @ProjectManager.updateProjectWithLocks
.calledWith(@project_id, @userId, @docUpdates, @fileUpdates, @version) .calledWith(@project_id, @projectHistoryId, @userId, @docUpdates, @fileUpdates, @version)
.should.equal true .should.equal true
it "should return a successful No Content response", -> it "should return a successful No Content response", ->
@ -538,7 +539,7 @@ describe "HttpController", ->
describe "when an errors occurs", -> describe "when an errors occurs", ->
beforeEach -> beforeEach ->
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5, new Error("oops")) @ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6, new Error("oops"))
@HttpController.updateProject(@req, @res, @next) @HttpController.updateProject(@req, @res, @next)
it "should call next with the error", -> it "should call next with the error", ->
@ -548,23 +549,24 @@ describe "HttpController", ->
describe "resyncProjectHistory", -> describe "resyncProjectHistory", ->
beforeEach -> beforeEach ->
@projectHistoryId = "history-id-123"
@docs = sinon.stub() @docs = sinon.stub()
@files = sinon.stub() @files = sinon.stub()
@fileUpdates = sinon.stub() @fileUpdates = sinon.stub()
@req = @req =
body: body:
{@docs, @files} {@projectHistoryId, @docs, @files}
params: params:
project_id: @project_id project_id: @project_id
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@HistoryManager.resyncProjectHistory = sinon.stub().callsArg(3) @HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4)
@HttpController.resyncProjectHistory(@req, @res, @next) @HttpController.resyncProjectHistory(@req, @res, @next)
it "should accept the change", -> it "should accept the change", ->
@HistoryManager.resyncProjectHistory @HistoryManager.resyncProjectHistory
.calledWith(@project_id, @docs, @files) .calledWith(@project_id, @projectHistoryId, @docs, @files)
.should.equal true .should.equal true
it "should return a successful No Content response", -> it "should return a successful No Content response", ->
@ -574,7 +576,7 @@ describe "HttpController", ->
describe "when an errors occurs", -> describe "when an errors occurs", ->
beforeEach -> beforeEach ->
@HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(3, new Error("oops")) @HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4, new Error("oops"))
@HttpController.resyncProjectHistory(@req, @res, @next) @HttpController.resyncProjectHistory(@req, @res, @next)
it "should call next with the error", -> it "should call next with the error", ->

View file

@ -17,6 +17,7 @@ describe "PersistenceManager", ->
done: sinon.stub() done: sinon.stub()
"logger-sharelatex": @logger = {log: sinon.stub(), err: sinon.stub()} "logger-sharelatex": @logger = {log: sinon.stub(), err: sinon.stub()}
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = "history-id-123"
@doc_id = "doc-id-123" @doc_id = "doc-id-123"
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@version = 42 @version = 42
@ -36,6 +37,7 @@ describe "PersistenceManager", ->
version: @version, version: @version,
ranges: @ranges ranges: @ranges
pathname: @pathname, pathname: @pathname,
projectHistoryId: @projectHistoryId
} }
describe "with a successful response from the web api", -> describe "with a successful response from the web api", ->
@ -60,7 +62,9 @@ describe "PersistenceManager", ->
.should.equal true .should.equal true
it "should call the callback with the doc lines, version and ranges", -> it "should call the callback with the doc lines, version and ranges", ->
@callback.calledWith(null, @lines, @version, @ranges, @pathname).should.equal true @callback
.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId)
.should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true

View file

@ -8,6 +8,7 @@ tk = require "timekeeper"
describe "ProjectHistoryRedisManager", -> describe "ProjectHistoryRedisManager", ->
beforeEach -> beforeEach ->
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = "history-id-123"
@user_id = "user-id-123" @user_id = "user-id-123"
@callback = sinon.stub() @callback = sinon.stub()
@rclient = {} @rclient = {}
@ -50,9 +51,10 @@ describe "ProjectHistoryRedisManager", ->
@rawUpdate = @rawUpdate =
pathname: @pathname = '/old' pathname: @pathname = '/old'
newPathname: @newPathname = '/new' newPathname: @newPathname = '/new'
version: @version = 2
@ProjectHistoryRedisManager.queueOps = sinon.stub() @ProjectHistoryRedisManager.queueOps = sinon.stub()
@ProjectHistoryRedisManager.queueRenameEntity @project_id, 'file', @file_id, @user_id, @rawUpdate, @callback @ProjectHistoryRedisManager.queueRenameEntity @project_id, @projectHistoryId, 'file', @file_id, @user_id, @rawUpdate, @callback
it "should queue an update", -> it "should queue an update", ->
update = update =
@ -61,6 +63,8 @@ describe "ProjectHistoryRedisManager", ->
meta: meta:
user_id: @user_id user_id: @user_id
ts: new Date() ts: new Date()
version: @version
projectHistoryId: @projectHistoryId
file: @file_id file: @file_id
@ProjectHistoryRedisManager.queueOps @ProjectHistoryRedisManager.queueOps
@ -75,10 +79,11 @@ describe "ProjectHistoryRedisManager", ->
@rawUpdate = @rawUpdate =
pathname: @pathname = '/old' pathname: @pathname = '/old'
docLines: @docLines = 'a\nb' docLines: @docLines = 'a\nb'
version: @version = 2
url: @url = 'filestore.example.com' url: @url = 'filestore.example.com'
@ProjectHistoryRedisManager.queueOps = sinon.stub() @ProjectHistoryRedisManager.queueOps = sinon.stub()
@ProjectHistoryRedisManager.queueAddEntity @project_id, 'doc', @doc_id, @user_id, @rawUpdate, @callback @ProjectHistoryRedisManager.queueAddEntity @project_id, @projectHistoryId, 'doc', @doc_id, @user_id, @rawUpdate, @callback
it "should queue an update", -> it "should queue an update", ->
update = update =
@ -88,6 +93,8 @@ describe "ProjectHistoryRedisManager", ->
meta: meta:
user_id: @user_id user_id: @user_id
ts: new Date() ts: new Date()
version: @version
projectHistoryId: @projectHistoryId
doc: @doc_id doc: @doc_id
@ProjectHistoryRedisManager.queueOps @ProjectHistoryRedisManager.queueOps

View file

@ -18,6 +18,7 @@ describe "ProjectManager", ->
done: sinon.stub() done: sinon.stub()
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = 'history-id-123'
@user_id = "user-id-123" @user_id = "user-id-123"
@version = 1234567 @version = 1234567
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false) @HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false)
@ -27,7 +28,6 @@ describe "ProjectManager", ->
describe "updateProjectWithLocks", -> describe "updateProjectWithLocks", ->
describe "rename operations", -> describe "rename operations", ->
beforeEach -> beforeEach ->
@firstDocUpdate = @firstDocUpdate =
id: 1 id: 1
pathname: 'foo' pathname: 'foo'
@ -47,22 +47,22 @@ describe "ProjectManager", ->
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should rename the docs in the updates", -> it "should rename the docs in the updates", ->
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"}) firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"}) secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
@DocumentManager.renameDocWithLock @DocumentManager.renameDocWithLock
.calledWith(@project_id, @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion) .calledWith(@project_id, @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion, @projectHistoryId)
.should.equal true .should.equal true
@DocumentManager.renameDocWithLock @DocumentManager.renameDocWithLock
.calledWith(@project_id, @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion) .calledWith(@project_id, @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion, @projectHistoryId)
.should.equal true .should.equal true
it "should rename the files in the updates", -> it "should rename the files in the updates", ->
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"}) firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
@ProjectHistoryRedisManager.queueRenameEntity @ProjectHistoryRedisManager.queueRenameEntity
.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion) .calledWith(@project_id, @projectHistoryId, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
.should.equal true .should.equal true
it "should not flush the history", -> it "should not flush the history", ->
@ -77,7 +77,7 @@ describe "ProjectManager", ->
beforeEach -> beforeEach ->
@error = new Error('error') @error = new Error('error')
@DocumentManager.renameDocWithLock = sinon.stub().yields(@error) @DocumentManager.renameDocWithLock = sinon.stub().yields(@error)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should call the callback with the error", -> it "should call the callback with the error", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
@ -86,7 +86,7 @@ describe "ProjectManager", ->
beforeEach -> beforeEach ->
@error = new Error('error') @error = new Error('error')
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error) @ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should call the callback with the error", -> it "should call the callback with the error", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
@ -94,7 +94,7 @@ describe "ProjectManager", ->
describe "with enough ops to flush", -> describe "with enough ops to flush", ->
beforeEach -> beforeEach ->
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true) @HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should flush the history", -> it "should flush the history", ->
@HistoryManager.flushProjectChangesAsync @HistoryManager.flushProjectChangesAsync
@ -121,26 +121,26 @@ describe "ProjectManager", ->
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should add the docs in the updates", -> it "should add the docs in the updates", ->
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"}) firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"}) secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
@ProjectHistoryRedisManager.queueAddEntity.getCall(0) @ProjectHistoryRedisManager.queueAddEntity.getCall(0)
.calledWith(@project_id, 'doc', @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion) .calledWith(@project_id, @projectHistoryId, 'doc', @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
.should.equal true .should.equal true
@ProjectHistoryRedisManager.queueAddEntity.getCall(1) @ProjectHistoryRedisManager.queueAddEntity.getCall(1)
.calledWith(@project_id, 'doc', @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion) .calledWith(@project_id, @projectHistoryId, 'doc', @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
.should.equal true .should.equal true
it "should add the files in the updates", -> it "should add the files in the updates", ->
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"}) firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
secondFileUpdateWithVersion = _.extend({}, @secondFileUpdate, {version: "#{@version}.3"}) secondFileUpdateWithVersion = _.extend({}, @secondFileUpdate, {version: "#{@version}.3"})
@ProjectHistoryRedisManager.queueAddEntity.getCall(2) @ProjectHistoryRedisManager.queueAddEntity.getCall(2)
.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion) .calledWith(@project_id, @projectHistoryId, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
.should.equal true .should.equal true
@ProjectHistoryRedisManager.queueAddEntity.getCall(3) @ProjectHistoryRedisManager.queueAddEntity.getCall(3)
.calledWith(@project_id, 'file', @secondFileUpdate.id, @user_id, secondFileUpdateWithVersion) .calledWith(@project_id, @projectHistoryId, 'file', @secondFileUpdate.id, @user_id, secondFileUpdateWithVersion)
.should.equal true .should.equal true
it "should not flush the history", -> it "should not flush the history", ->
@ -155,7 +155,7 @@ describe "ProjectManager", ->
beforeEach -> beforeEach ->
@error = new Error('error') @error = new Error('error')
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error) @ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should call the callback with the error", -> it "should call the callback with the error", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
@ -164,7 +164,7 @@ describe "ProjectManager", ->
beforeEach -> beforeEach ->
@error = new Error('error') @error = new Error('error')
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error) @ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should call the callback with the error", -> it "should call the callback with the error", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
@ -172,7 +172,7 @@ describe "ProjectManager", ->
describe "with enough ops to flush", -> describe "with enough ops to flush", ->
beforeEach -> beforeEach ->
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true) @HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback @ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
it "should flush the history", -> it "should flush the history", ->
@HistoryManager.flushProjectChangesAsync @HistoryManager.flushProjectChangesAsync

View file

@ -33,6 +33,7 @@ describe "RedisManager", ->
docsInProject: ({project_id}) -> "DocsIn:#{project_id}" docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
ranges: ({doc_id}) -> "Ranges:#{doc_id}" ranges: ({doc_id}) -> "Ranges:#{doc_id}"
pathname: ({doc_id}) -> "Pathname:#{doc_id}" pathname: ({doc_id}) -> "Pathname:#{doc_id}"
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{doc_id}"
projectState: ({project_id}) -> "ProjectState:#{project_id}" projectState: ({project_id}) -> "ProjectState:#{project_id}"
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}" unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
history: history:
@ -59,6 +60,7 @@ describe "RedisManager", ->
@doc_id = "doc-id-123" @doc_id = "doc-id-123"
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = 123
@callback = sinon.stub() @callback = sinon.stub()
describe "getDoc", -> describe "getDoc", ->
@ -72,7 +74,7 @@ describe "RedisManager", ->
@unflushed_time = 12345 @unflushed_time = 12345
@pathname = '/a/b/c.tex' @pathname = '/a/b/c.tex'
@multi.get = sinon.stub() @multi.get = sinon.stub()
@multi.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @hash, @project_id, @json_ranges, @pathname, @unflushed_time]) @multi.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @hash, @project_id, @json_ranges, @pathname, @projectHistoryId.toString(), @unflushed_time])
@rclient.sadd = sinon.stub().yields(null, 0) @rclient.sadd = sinon.stub().yields(null, 0)
describe "successfully", -> describe "successfully", ->
@ -109,6 +111,11 @@ describe "RedisManager", ->
.calledWith("Pathname:#{@doc_id}") .calledWith("Pathname:#{@doc_id}")
.should.equal true .should.equal true
it "should get the projectHistoryId as an integer", ->
@multi.get
.calledWith("ProjectHistoryId:#{@doc_id}")
.should.equal true
it "should check if the document is in the DocsIn set", -> it "should check if the document is in the DocsIn set", ->
@rclient.sadd @rclient.sadd
.calledWith("DocsIn:#{@project_id}") .calledWith("DocsIn:#{@project_id}")
@ -116,7 +123,7 @@ describe "RedisManager", ->
it 'should return the document', -> it 'should return the document', ->
@callback @callback
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @unflushed_time) .calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
.should.equal true .should.equal true
it 'should not log any errors', -> it 'should not log any errors', ->
@ -125,7 +132,7 @@ describe "RedisManager", ->
describe "when the document is not present", -> describe "when the document is not present", ->
beforeEach -> beforeEach ->
@multi.exec = sinon.stub().callsArgWith(0, null, [null, null, null, null, null, null, null]) @multi.exec = sinon.stub().callsArgWith(0, null, [null, null, null, null, null, null, null, null])
@rclient.sadd = sinon.stub().yields() @rclient.sadd = sinon.stub().yields()
@RedisManager.getDoc @project_id, @doc_id, @callback @RedisManager.getDoc @project_id, @doc_id, @callback
@ -136,7 +143,7 @@ describe "RedisManager", ->
it 'should return an empty result', -> it 'should return an empty result', ->
@callback @callback
.calledWithExactly(null, null, 0, {}, null, null) .calledWithExactly(null, null, 0, {}, null, null, null)
.should.equal true .should.equal true
it 'should not log any errors', -> it 'should not log any errors', ->
@ -154,7 +161,7 @@ describe "RedisManager", ->
it 'should return the document', -> it 'should return the document', ->
@callback @callback
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @unflushed_time) .calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
.should.equal true .should.equal true
describe "with a corrupted document", -> describe "with a corrupted document", ->
@ -532,7 +539,7 @@ describe "RedisManager", ->
describe "with non-empty ranges", -> describe "with non-empty ranges", ->
beforeEach (done) -> beforeEach (done) ->
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, done @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, done
it "should set the lines", -> it "should set the lines", ->
@multi.eval @multi.eval
@ -564,6 +571,11 @@ describe "RedisManager", ->
.calledWith("Pathname:#{@doc_id}", @pathname) .calledWith("Pathname:#{@doc_id}", @pathname)
.should.equal true .should.equal true
it "should set the projectHistoryId for the doc", ->
@multi.set
.calledWith("ProjectHistoryId:#{@doc_id}", @projectHistoryId)
.should.equal true
it "should add the doc_id to the project set", -> it "should add the doc_id to the project set", ->
@rclient.sadd @rclient.sadd
.calledWith("DocsIn:#{@project_id}", @doc_id) .calledWith("DocsIn:#{@project_id}", @doc_id)
@ -575,7 +587,7 @@ describe "RedisManager", ->
describe "with empty ranges", -> describe "with empty ranges", ->
beforeEach (done) -> beforeEach (done) ->
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, {}, @pathname, done @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, {}, @pathname, @projectHistoryId, done
it "should delete the ranges key", -> it "should delete the ranges key", ->
@multi.del @multi.del
@ -590,7 +602,7 @@ describe "RedisManager", ->
describe "with a corrupted write", -> describe "with a corrupted write", ->
beforeEach (done) -> beforeEach (done) ->
@multi.exec = sinon.stub().callsArgWith(0, null, ["INVALID-HASH-VALUE"]) @multi.exec = sinon.stub().callsArgWith(0, null, ["INVALID-HASH-VALUE"])
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, done @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, done
it 'should log a hash error', -> it 'should log a hash error', ->
@logger.error.calledWith() @logger.error.calledWith()
@ -600,7 +612,7 @@ describe "RedisManager", ->
beforeEach -> beforeEach ->
@_stringify = JSON.stringify @_stringify = JSON.stringify
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]' @JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @callback @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, @callback
afterEach -> afterEach ->
@JSON.stringify = @_stringify @JSON.stringify = @_stringify
@ -614,7 +626,7 @@ describe "RedisManager", ->
describe "with ranges that are too big", -> describe "with ranges that are too big", ->
beforeEach -> beforeEach ->
@RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large")) @RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large"))
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @callback @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, @callback
it 'should log an error', -> it 'should log an error', ->
@logger.error.called.should.equal true @logger.error.called.should.equal true
@ -664,6 +676,11 @@ describe "RedisManager", ->
.calledWith("Pathname:#{@doc_id}") .calledWith("Pathname:#{@doc_id}")
.should.equal true .should.equal true
it "should delete the pathname for the doc", ->
@multi.del
.calledWith("ProjectHistoryId:#{@doc_id}")
.should.equal true
describe "clearProjectState", -> describe "clearProjectState", ->
beforeEach (done) -> beforeEach (done) ->
@rclient.del = sinon.stub().callsArg(1) @rclient.del = sinon.stub().callsArg(1)
@ -687,7 +704,7 @@ describe "RedisManager", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, 'lines', 'version') @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, 'lines', 'version')
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields() @ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback @RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @projectHistoryId, @callback
it "update the cached pathname", -> it "update the cached pathname", ->
@rclient.set @rclient.set
@ -696,19 +713,19 @@ describe "RedisManager", ->
it "should queue an update", -> it "should queue an update", ->
@ProjectHistoryRedisManager.queueRenameEntity @ProjectHistoryRedisManager.queueRenameEntity
.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback) .calledWithExactly(@project_id, @projectHistoryId, 'doc', @doc_id, @userId, @update, @callback)
.should.equal true .should.equal true
describe "the document is not cached in redis", -> describe "the document is not cached in redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null)
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields() @ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback @RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @projectHistoryId, @callback
it "does not update the cached pathname", -> it "does not update the cached pathname", ->
@rclient.set.called.should.equal false @rclient.set.called.should.equal false
it "should queue an update", -> it "should queue an update", ->
@ProjectHistoryRedisManager.queueRenameEntity @ProjectHistoryRedisManager.queueRenameEntity
.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback) .calledWithExactly(@project_id, @projectHistoryId, 'doc', @doc_id, @userId, @update, @callback)
.should.equal true .should.equal true

View file

@ -7,6 +7,7 @@ SandboxedModule = require('sandboxed-module')
describe "UpdateManager", -> describe "UpdateManager", ->
beforeEach -> beforeEach ->
@project_id = "project-id-123" @project_id = "project-id-123"
@projectHistoryId = "history-id-123"
@doc_id = "document-id-123" @doc_id = "document-id-123"
@callback = sinon.stub() @callback = sinon.stub()
@UpdateManager = SandboxedModule.require modulePath, requires: @UpdateManager = SandboxedModule.require modulePath, requires:
@ -167,7 +168,7 @@ describe "UpdateManager", ->
@doc_ops_length = sinon.stub() @doc_ops_length = sinon.stub()
@project_ops_length = sinon.stub() @project_ops_length = sinon.stub()
@pathname = '/a/b/c.tex' @pathname = '/a/b/c.tex'
@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges, @pathname) @DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges, @pathname, @projectHistoryId)
@RangesManager.applyUpdate = sinon.stub().yields(null, @updated_ranges) @RangesManager.applyUpdate = sinon.stub().yields(null, @updated_ranges)
@ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps) @ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps)
@RedisManager.updateDocument = sinon.stub().yields(null, @doc_ops_length, @project_ops_length) @RedisManager.updateDocument = sinon.stub().yields(null, @doc_ops_length, @project_ops_length)
@ -196,7 +197,7 @@ describe "UpdateManager", ->
it "should add metadata to the ops" , -> it "should add metadata to the ops" , ->
@UpdateManager._addProjectHistoryMetadataToOps @UpdateManager._addProjectHistoryMetadataToOps
.calledWith(@appliedOps, @pathname, @lines) .calledWith(@appliedOps, @pathname, @projectHistoryId, @lines)
.should.equal true .should.equal true
it "should push the applied ops into the history queue", -> it "should push the applied ops into the history queue", ->
@ -239,7 +240,7 @@ describe "UpdateManager", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
describe "_addProjectHistoryMetadataToOps", -> describe "_addProjectHistoryMetadataToOps", ->
it "should add pathname and doc_length metadata to the ops", -> it "should add projectHistoryId, pathname and doc_length metadata to the ops", ->
lines = [ lines = [
'some' 'some'
'test' 'test'
@ -250,20 +251,23 @@ describe "UpdateManager", ->
{ v: 45, op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] }, { v: 45, op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] },
{ v: 49, op: [{i: "penguin", p: 18}] } { v: 49, op: [{i: "penguin", p: 18}] }
] ]
@UpdateManager._addProjectHistoryMetadataToOps(appliedOps, @pathname, lines) @UpdateManager._addProjectHistoryMetadataToOps(appliedOps, @pathname, @projectHistoryId, lines)
appliedOps.should.deep.equal [{ appliedOps.should.deep.equal [{
projectHistoryId: @projectHistoryId
v: 42 v: 42
op: [{i: "foo", p: 4}, { i: "bar", p: 6 }] op: [{i: "foo", p: 4}, { i: "bar", p: 6 }]
meta: meta:
pathname: @pathname pathname: @pathname
doc_length: 14 doc_length: 14
}, { }, {
projectHistoryId: @projectHistoryId
v: 45 v: 45
op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }]
meta: meta:
pathname: @pathname pathname: @pathname
doc_length: 20 # 14 + 'foo' + 'bar' doc_length: 20 # 14 + 'foo' + 'bar'
}, { }, {
projectHistoryId: @projectHistoryId
v: 49 v: 49
op: [{i: "penguin", p: 18}] op: [{i: "penguin", p: 18}]
meta: meta: