mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Persist Last Author id on Document Flush (#50)
Persist Last Author id on Document Flush
This commit is contained in:
commit
70c505830b
11 changed files with 115 additions and 36 deletions
|
@ -102,14 +102,14 @@ module.exports = DocumentManager =
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
_callback(args...)
|
_callback(args...)
|
||||||
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
|
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if !lines? or !version?
|
if !lines? or !version?
|
||||||
logger.log project_id: project_id, doc_id: doc_id, "doc is not loaded so not flushing"
|
logger.log project_id: project_id, doc_id: doc_id, "doc is not loaded so not flushing"
|
||||||
callback null # TODO: return a flag to bail out, as we go on to remove doc from memory?
|
callback null # TODO: return a flag to bail out, as we go on to remove doc from memory?
|
||||||
else
|
else
|
||||||
logger.log project_id: project_id, doc_id: doc_id, version: version, "flushing doc"
|
logger.log project_id: project_id, doc_id: doc_id, version: version, "flushing doc"
|
||||||
PersistenceManager.setDoc project_id, doc_id, lines, version, ranges, (error) ->
|
PersistenceManager.setDoc project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RedisManager.clearUnflushedTime doc_id, callback
|
RedisManager.clearUnflushedTime doc_id, callback
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ module.exports = DocumentManager =
|
||||||
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
|
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
|
||||||
RangesManager.acceptChanges change_ids, ranges, (error, new_ranges) ->
|
RangesManager.acceptChanges change_ids, ranges, (error, new_ranges) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, (error) ->
|
RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, {}, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ module.exports = DocumentManager =
|
||||||
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
|
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
|
||||||
RangesManager.deleteComment comment_id, ranges, (error, new_ranges) ->
|
RangesManager.deleteComment comment_id, ranges, (error, new_ranges) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, (error) ->
|
RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, {}, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ module.exports = PersistenceManager =
|
||||||
else
|
else
|
||||||
return callback(new Error("error accessing web API: #{url} #{res.statusCode}"))
|
return callback(new Error("error accessing web API: #{url} #{res.statusCode}"))
|
||||||
|
|
||||||
setDoc: (project_id, doc_id, lines, version, ranges, _callback = (error) ->) ->
|
setDoc: (project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy,_callback = (error) ->) ->
|
||||||
timer = new Metrics.Timer("persistenceManager.setDoc")
|
timer = new Metrics.Timer("persistenceManager.setDoc")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
|
@ -64,6 +64,8 @@ module.exports = PersistenceManager =
|
||||||
lines: lines
|
lines: lines
|
||||||
ranges: ranges
|
ranges: ranges
|
||||||
version: version
|
version: version
|
||||||
|
lastUpdatedBy: lastUpdatedBy
|
||||||
|
lastUpdatedAt: lastUpdatedAt
|
||||||
auth:
|
auth:
|
||||||
user: Settings.apis.web.user
|
user: Settings.apis.web.user
|
||||||
pass: Settings.apis.web.pass
|
pass: Settings.apis.web.pass
|
||||||
|
|
|
@ -90,6 +90,8 @@ module.exports = RedisManager =
|
||||||
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.projectHistoryId(doc_id:doc_id)
|
||||||
multi.del keys.unflushedTime(doc_id:doc_id)
|
multi.del keys.unflushedTime(doc_id:doc_id)
|
||||||
|
multi.del keys.lastUpdatedAt(doc_id: doc_id)
|
||||||
|
multi.del keys.lastUpdatedBy(doc_id: doc_id)
|
||||||
multi.exec (error) ->
|
multi.exec (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
multi = rclient.multi()
|
multi = rclient.multi()
|
||||||
|
@ -120,7 +122,9 @@ module.exports = RedisManager =
|
||||||
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.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, projectHistoryId, unflushedTime])->
|
multi.get keys.lastUpdatedAt(doc_id: doc_id)
|
||||||
|
multi.get keys.lastUpdatedBy(doc_id: doc_id)
|
||||||
|
multi.exec (error, [docLines, version, storedHash, doc_project_id, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy])->
|
||||||
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
|
||||||
|
@ -152,14 +156,14 @@ module.exports = RedisManager =
|
||||||
|
|
||||||
# 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, projectHistoryId, unflushedTime
|
return callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy
|
||||||
|
|
||||||
# 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, projectHistoryId, unflushedTime
|
callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy
|
||||||
|
|
||||||
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) ->
|
||||||
|
@ -209,7 +213,7 @@ module.exports = RedisManager =
|
||||||
|
|
||||||
DOC_OPS_TTL: 60 * minutes
|
DOC_OPS_TTL: 60 * minutes
|
||||||
DOC_OPS_MAX_LENGTH: 100
|
DOC_OPS_MAX_LENGTH: 100
|
||||||
updateDocument : (project_id, doc_id, docLines, newVersion, appliedOps = [], ranges, callback = (error) ->)->
|
updateDocument : (project_id, doc_id, docLines, newVersion, appliedOps = [], ranges, updateMeta, callback = (error) ->)->
|
||||||
RedisManager.getDocVersion doc_id, (error, currentVersion) ->
|
RedisManager.getDocVersion doc_id, (error, currentVersion) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if currentVersion + appliedOps.length != newVersion
|
if currentVersion + appliedOps.length != newVersion
|
||||||
|
@ -261,6 +265,11 @@ module.exports = RedisManager =
|
||||||
# hasn't been modified before (the content in mongo has been
|
# hasn't been modified before (the content in mongo has been
|
||||||
# valid up to this point). Otherwise leave it alone ("NX" flag).
|
# valid up to this point). Otherwise leave it alone ("NX" flag).
|
||||||
multi.set keys.unflushedTime(doc_id: doc_id), Date.now(), "NX"
|
multi.set keys.unflushedTime(doc_id: doc_id), Date.now(), "NX"
|
||||||
|
multi.set keys.lastUpdatedAt(doc_id: doc_id), Date.now() # index 8
|
||||||
|
if updateMeta?.user_id
|
||||||
|
multi.set keys.lastUpdatedBy(doc_id: doc_id), updateMeta.user_id # index 9
|
||||||
|
else
|
||||||
|
multi.del keys.lastUpdatedBy(doc_id: doc_id) # index 9
|
||||||
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
|
||||||
|
|
|
@ -85,7 +85,7 @@ module.exports = UpdateManager =
|
||||||
UpdateManager._addProjectHistoryMetadataToOps(appliedOps, pathname, projectHistoryId, 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, update.meta, (error, doc_ops_length, project_ops_length) ->
|
||||||
profile.log("RedisManager.updateDocument")
|
profile.log("RedisManager.updateDocument")
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
HistoryManager.recordAndFlushHistoryOps project_id, doc_id, appliedOps, doc_ops_length, project_ops_length, (error) ->
|
HistoryManager.recordAndFlushHistoryOps project_id, doc_id, appliedOps, doc_ops_length, project_ops_length, (error) ->
|
||||||
|
|
|
@ -79,6 +79,8 @@ module.exports =
|
||||||
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:{#{doc_id}}"
|
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:{#{doc_id}}"
|
||||||
projectState: ({project_id}) -> "ProjectState:{#{project_id}}"
|
projectState: ({project_id}) -> "ProjectState:{#{project_id}}"
|
||||||
pendingUpdates: ({doc_id}) -> "PendingUpdates:{#{doc_id}}"
|
pendingUpdates: ({doc_id}) -> "PendingUpdates:{#{doc_id}}"
|
||||||
|
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:{#{doc_id}}"
|
||||||
|
lastUpdatedAt: ({doc_id}) -> "lastUpdatedAt:{#{doc_id}}"
|
||||||
redisOptions:
|
redisOptions:
|
||||||
keepAlive: 100
|
keepAlive: 100
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ describe "Flushing a doc to Mongo", ->
|
||||||
@version = 42
|
@version = 42
|
||||||
@update =
|
@update =
|
||||||
doc: @doc_id
|
doc: @doc_id
|
||||||
|
meta: { user_id: 'last-author-fake-id' }
|
||||||
op: [{
|
op: [{
|
||||||
i: "one and a half\n"
|
i: "one and a half\n"
|
||||||
p: 4
|
p: 4
|
||||||
|
@ -42,6 +43,13 @@ describe "Flushing a doc to Mongo", ->
|
||||||
.calledWith(@project_id, @doc_id, @result, @version + 1)
|
.calledWith(@project_id, @doc_id, @result, @version + 1)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should flush the last update author and time to the web api", ->
|
||||||
|
lastUpdatedAt = MockWebApi.setDocument.lastCall.args[5]
|
||||||
|
parseInt(lastUpdatedAt).should.be.closeTo((new Date()).getTime(), 30000)
|
||||||
|
|
||||||
|
lastUpdatedBy = MockWebApi.setDocument.lastCall.args[6]
|
||||||
|
lastUpdatedBy.should.equal 'last-author-fake-id'
|
||||||
|
|
||||||
describe "when the doc does not exist in the doc updater", ->
|
describe "when the doc does not exist in the doc updater", ->
|
||||||
before (done) ->
|
before (done) ->
|
||||||
[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
|
[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
|
||||||
|
@ -65,7 +73,7 @@ describe "Flushing a doc to Mongo", ->
|
||||||
version: @version
|
version: @version
|
||||||
}
|
}
|
||||||
t = 30000
|
t = 30000
|
||||||
sinon.stub MockWebApi, "setDocument", (project_id, doc_id, lines, version, ranges, callback = (error) ->) ->
|
sinon.stub MockWebApi, "setDocument", (project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, callback = (error) ->) ->
|
||||||
setTimeout callback, t
|
setTimeout callback, t
|
||||||
t = 0
|
t = 0
|
||||||
DocUpdaterClient.preloadDoc @project_id, @doc_id, done
|
DocUpdaterClient.preloadDoc @project_id, @doc_id, done
|
||||||
|
|
|
@ -12,12 +12,14 @@ module.exports = MockWebApi =
|
||||||
doc.pathname = '/a/b/c.tex'
|
doc.pathname = '/a/b/c.tex'
|
||||||
@docs["#{project_id}:#{doc_id}"] = doc
|
@docs["#{project_id}:#{doc_id}"] = doc
|
||||||
|
|
||||||
setDocument: (project_id, doc_id, lines, version, ranges, callback = (error) ->) ->
|
setDocument: (project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, callback = (error) ->) ->
|
||||||
doc = @docs["#{project_id}:#{doc_id}"] ||= {}
|
doc = @docs["#{project_id}:#{doc_id}"] ||= {}
|
||||||
doc.lines = lines
|
doc.lines = lines
|
||||||
doc.version = version
|
doc.version = version
|
||||||
doc.ranges = ranges
|
doc.ranges = ranges
|
||||||
doc.pathname = '/a/b/c.tex'
|
doc.pathname = '/a/b/c.tex'
|
||||||
|
doc.lastUpdatedAt = lastUpdatedAt
|
||||||
|
doc.lastUpdatedBy = lastUpdatedBy
|
||||||
callback null
|
callback null
|
||||||
|
|
||||||
getDocument: (project_id, doc_id, callback = (error, doc) ->) ->
|
getDocument: (project_id, doc_id, callback = (error, doc) ->) ->
|
||||||
|
@ -34,7 +36,7 @@ module.exports = MockWebApi =
|
||||||
res.send 404
|
res.send 404
|
||||||
|
|
||||||
app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) =>
|
app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) =>
|
||||||
MockWebApi.setDocument req.params.project_id, req.params.doc_id, req.body.lines, req.body.version, req.body.ranges, (error) ->
|
MockWebApi.setDocument req.params.project_id, req.params.doc_id, req.body.lines, req.body.version, req.body.ranges, req.body.lastUpdatedAt, req.body.lastUpdatedBy, (error) ->
|
||||||
if error?
|
if error?
|
||||||
res.send 500
|
res.send 500
|
||||||
else
|
else
|
||||||
|
|
|
@ -35,6 +35,8 @@ describe "DocumentManager", ->
|
||||||
@ranges = { comments: "mock", entries: "mock" }
|
@ranges = { comments: "mock", entries: "mock" }
|
||||||
@pathname = '/a/b/c.tex'
|
@pathname = '/a/b/c.tex'
|
||||||
@unflushedTime = Date.now()
|
@unflushedTime = Date.now()
|
||||||
|
@lastUpdatedAt = Date.now()
|
||||||
|
@lastUpdatedBy = 'last-author-id'
|
||||||
|
|
||||||
afterEach ->
|
afterEach ->
|
||||||
tk.reset()
|
tk.reset()
|
||||||
|
@ -70,7 +72,7 @@ describe "DocumentManager", ->
|
||||||
describe "flushDocIfLoaded", ->
|
describe "flushDocIfLoaded", ->
|
||||||
describe "when the doc is in Redis", ->
|
describe "when the doc is in Redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime, @lastUpdatedAt, @lastUpdatedBy)
|
||||||
@RedisManager.clearUnflushedTime = sinon.stub().callsArgWith(1, null)
|
@RedisManager.clearUnflushedTime = sinon.stub().callsArgWith(1, null)
|
||||||
@PersistenceManager.setDoc = sinon.stub().yields()
|
@PersistenceManager.setDoc = sinon.stub().yields()
|
||||||
@DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback
|
@DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback
|
||||||
|
@ -82,7 +84,7 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "should write the doc lines to the persistence layer", ->
|
it "should write the doc lines to the persistence layer", ->
|
||||||
@PersistenceManager.setDoc
|
@PersistenceManager.setDoc
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, @ranges)
|
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback without error", ->
|
it "should call the callback without error", ->
|
||||||
|
@ -324,7 +326,7 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "should save the updated ranges", ->
|
it "should save the updated ranges", ->
|
||||||
@RedisManager.updateDocument
|
@RedisManager.updateDocument
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, [], @updated_ranges)
|
.calledWith(@project_id, @doc_id, @lines, @version, [], @updated_ranges, {})
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback", ->
|
it "should call the callback", ->
|
||||||
|
@ -378,7 +380,7 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "should save the updated ranges", ->
|
it "should save the updated ranges", ->
|
||||||
@RedisManager.updateDocument
|
@RedisManager.updateDocument
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, [], @updated_ranges)
|
.calledWith(@project_id, @doc_id, @lines, @version, [], @updated_ranges, {})
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback", ->
|
it "should call the callback", ->
|
||||||
|
|
|
@ -24,6 +24,8 @@ describe "PersistenceManager", ->
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
@ranges = { comments: "mock", entries: "mock" }
|
@ranges = { comments: "mock", entries: "mock" }
|
||||||
@pathname = '/a/b/c.tex'
|
@pathname = '/a/b/c.tex'
|
||||||
|
@lastUpdatedAt = Date.now()
|
||||||
|
@lastUpdatedBy = 'last-author-id'
|
||||||
@Settings.apis =
|
@Settings.apis =
|
||||||
web:
|
web:
|
||||||
url: @url = "www.example.com"
|
url: @url = "www.example.com"
|
||||||
|
@ -133,7 +135,7 @@ describe "PersistenceManager", ->
|
||||||
describe "with a successful response from the web api", ->
|
describe "with a successful response from the web api", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.callsArgWith(1, null, {statusCode: 200})
|
@request.callsArgWith(1, null, {statusCode: 200})
|
||||||
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
|
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback)
|
||||||
|
|
||||||
it "should call the web api", ->
|
it "should call the web api", ->
|
||||||
@request
|
@request
|
||||||
|
@ -143,6 +145,8 @@ describe "PersistenceManager", ->
|
||||||
lines: @lines
|
lines: @lines
|
||||||
version: @version
|
version: @version
|
||||||
ranges: @ranges
|
ranges: @ranges
|
||||||
|
lastUpdatedAt: @lastUpdatedAt
|
||||||
|
lastUpdatedBy: @lastUpdatedBy
|
||||||
method: "POST"
|
method: "POST"
|
||||||
auth:
|
auth:
|
||||||
user: @user
|
user: @user
|
||||||
|
@ -162,7 +166,7 @@ describe "PersistenceManager", ->
|
||||||
describe "when request returns an error", ->
|
describe "when request returns an error", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.callsArgWith(1, @error = new Error("oops"), null, null)
|
@request.callsArgWith(1, @error = new Error("oops"), null, null)
|
||||||
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
|
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback)
|
||||||
|
|
||||||
it "should return the error", ->
|
it "should return the error", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
@ -173,7 +177,7 @@ describe "PersistenceManager", ->
|
||||||
describe "when the request returns 404", ->
|
describe "when the request returns 404", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.callsArgWith(1, null, {statusCode: 404}, "")
|
@request.callsArgWith(1, null, {statusCode: 404}, "")
|
||||||
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
|
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback)
|
||||||
|
|
||||||
it "should return a NotFoundError", ->
|
it "should return a NotFoundError", ->
|
||||||
@callback.calledWith(new Errors.NotFoundError("not found")).should.equal true
|
@callback.calledWith(new Errors.NotFoundError("not found")).should.equal true
|
||||||
|
@ -184,7 +188,7 @@ describe "PersistenceManager", ->
|
||||||
describe "when the request returns an error status code", ->
|
describe "when the request returns an error status code", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.callsArgWith(1, null, {statusCode: 500}, "")
|
@request.callsArgWith(1, null, {statusCode: 500}, "")
|
||||||
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
|
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback)
|
||||||
|
|
||||||
it "should return an error", ->
|
it "should return an error", ->
|
||||||
@callback.calledWith(new Error("web api error")).should.equal true
|
@callback.calledWith(new Error("web api error")).should.equal true
|
||||||
|
|
|
@ -36,6 +36,8 @@ describe "RedisManager", ->
|
||||||
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{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}"
|
||||||
|
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:#{doc_id}"
|
||||||
|
lastUpdatedAt: ({doc_id}) -> "lastUpdatedAt:#{doc_id}"
|
||||||
history:
|
history:
|
||||||
key_schema:
|
key_schema:
|
||||||
uncompressedHistoryOps: ({doc_id}) -> "UncompressedHistoryOps:#{doc_id}"
|
uncompressedHistoryOps: ({doc_id}) -> "UncompressedHistoryOps:#{doc_id}"
|
||||||
|
@ -116,6 +118,16 @@ describe "RedisManager", ->
|
||||||
.calledWith("ProjectHistoryId:#{@doc_id}")
|
.calledWith("ProjectHistoryId:#{@doc_id}")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should get lastUpdatedAt", ->
|
||||||
|
@multi.get
|
||||||
|
.calledWith("lastUpdatedAt:#{@doc_id}")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should get lastUpdatedBy", ->
|
||||||
|
@multi.get
|
||||||
|
.calledWith("lastUpdatedBy:#{@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}")
|
||||||
|
@ -123,7 +135,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return the document', ->
|
it 'should return the document', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
|
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time, @lastUpdatedAt, @lastUpdatedBy)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should not log any errors', ->
|
it 'should not log any errors', ->
|
||||||
|
@ -132,7 +144,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, null])
|
@multi.exec = sinon.stub().callsArgWith(0, null, [null, 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
|
||||||
|
|
||||||
|
@ -143,7 +155,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return an empty result', ->
|
it 'should return an empty result', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, null, 0, {}, null, null, null)
|
.calledWithExactly(null, null, 0, {}, null, null, null, null, null)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should not log any errors', ->
|
it 'should not log any errors', ->
|
||||||
|
@ -161,7 +173,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return the document', ->
|
it 'should return the document', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
|
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time, @lastUpdatedAt, @lastUpdatedBy)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "with a corrupted document", ->
|
describe "with a corrupted document", ->
|
||||||
|
@ -329,6 +341,7 @@ describe "RedisManager", ->
|
||||||
@version = 42
|
@version = 42
|
||||||
@hash = crypto.createHash('sha1').update(JSON.stringify(@lines),'utf8').digest('hex')
|
@hash = crypto.createHash('sha1').update(JSON.stringify(@lines),'utf8').digest('hex')
|
||||||
@ranges = { comments: "mock", entries: "mock" }
|
@ranges = { comments: "mock", entries: "mock" }
|
||||||
|
@updateMeta = { user_id: 'last-author-fake-id' }
|
||||||
@doc_update_list_length = sinon.stub()
|
@doc_update_list_length = sinon.stub()
|
||||||
@project_update_list_length = sinon.stub()
|
@project_update_list_length = sinon.stub()
|
||||||
|
|
||||||
|
@ -340,7 +353,7 @@ describe "RedisManager", ->
|
||||||
@multi.del = sinon.stub()
|
@multi.del = sinon.stub()
|
||||||
@multi.eval = sinon.stub()
|
@multi.eval = sinon.stub()
|
||||||
@multi.exec = sinon.stub().callsArgWith(0, null,
|
@multi.exec = sinon.stub().callsArgWith(0, null,
|
||||||
[@hash, null, null, null, null, null, null, @doc_update_list_length]
|
[@hash, null, null, null, null, null, null, @doc_update_list_length, null, null]
|
||||||
)
|
)
|
||||||
@ProjectHistoryRedisManager.queueOps = sinon.stub().callsArgWith(
|
@ProjectHistoryRedisManager.queueOps = sinon.stub().callsArgWith(
|
||||||
@ops.length + 1, null, @project_update_list_length
|
@ops.length + 1, null, @project_update_list_length
|
||||||
|
@ -353,7 +366,7 @@ describe "RedisManager", ->
|
||||||
describe "with project history enabled", ->
|
describe "with project history enabled", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@settings.apis.project_history.enabled = true
|
@settings.apis.project_history.enabled = true
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it "should get the current doc version to check for consistency", ->
|
it "should get the current doc version to check for consistency", ->
|
||||||
@RedisManager.getDocVersion
|
@RedisManager.getDocVersion
|
||||||
|
@ -385,6 +398,16 @@ describe "RedisManager", ->
|
||||||
.calledWith("UnflushedTime:#{@doc_id}", Date.now(), "NX")
|
.calledWith("UnflushedTime:#{@doc_id}", Date.now(), "NX")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should set the last updated time", ->
|
||||||
|
@multi.set
|
||||||
|
.calledWith("lastUpdatedAt:#{@doc_id}", Date.now())
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should set the last updater", ->
|
||||||
|
@multi.set
|
||||||
|
.calledWith("lastUpdatedBy:#{@doc_id}", 'last-author-fake-id')
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should push the doc op into the doc ops list", ->
|
it "should push the doc op into the doc ops list", ->
|
||||||
@multi.rpush
|
@multi.rpush
|
||||||
.calledWith("DocOps:#{@doc_id}", JSON.stringify(@ops[0]), JSON.stringify(@ops[1]))
|
.calledWith("DocOps:#{@doc_id}", JSON.stringify(@ops[0]), JSON.stringify(@ops[1]))
|
||||||
|
@ -423,7 +446,7 @@ describe "RedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@rclient.rpush = sinon.stub()
|
@rclient.rpush = sinon.stub()
|
||||||
@settings.apis.project_history.enabled = false
|
@settings.apis.project_history.enabled = false
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it "should not push the updates into the project history ops list", ->
|
it "should not push the updates into the project history ops list", ->
|
||||||
@rclient.rpush.called.should.equal false
|
@rclient.rpush.called.should.equal false
|
||||||
|
@ -436,7 +459,7 @@ describe "RedisManager", ->
|
||||||
describe "with an inconsistent version", ->
|
describe "with an inconsistent version", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length - 1)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length - 1)
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it "should not call multi.exec", ->
|
it "should not call multi.exec", ->
|
||||||
@multi.exec.called.should.equal false
|
@multi.exec.called.should.equal false
|
||||||
|
@ -450,7 +473,7 @@ describe "RedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@rclient.rpush = sinon.stub().callsArgWith(1, null, @project_update_list_length)
|
@rclient.rpush = sinon.stub().callsArgWith(1, null, @project_update_list_length)
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version)
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, [], @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, [], @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it "should not try to enqueue doc updates", ->
|
it "should not try to enqueue doc updates", ->
|
||||||
@multi.rpush
|
@multi.rpush
|
||||||
|
@ -470,7 +493,7 @@ describe "RedisManager", ->
|
||||||
describe "with empty ranges", ->
|
describe "with empty ranges", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, {}, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, {}, @updateMeta, @callback
|
||||||
|
|
||||||
it "should not set the ranges", ->
|
it "should not set the ranges", ->
|
||||||
@multi.set
|
@multi.set
|
||||||
|
@ -487,7 +510,7 @@ describe "RedisManager", ->
|
||||||
@badHash = "INVALID-HASH-VALUE"
|
@badHash = "INVALID-HASH-VALUE"
|
||||||
@multi.exec = sinon.stub().callsArgWith(0, null, [@badHash])
|
@multi.exec = sinon.stub().callsArgWith(0, null, [@badHash])
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it 'should log a hash error', ->
|
it 'should log a hash error', ->
|
||||||
@logger.error.calledWith()
|
@logger.error.calledWith()
|
||||||
|
@ -501,7 +524,7 @@ describe "RedisManager", ->
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
||||||
@_stringify = JSON.stringify
|
@_stringify = JSON.stringify
|
||||||
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
|
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
afterEach ->
|
afterEach ->
|
||||||
@JSON.stringify = @_stringify
|
@JSON.stringify = @_stringify
|
||||||
|
@ -516,7 +539,7 @@ describe "RedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
||||||
@RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large"))
|
@RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large"))
|
||||||
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @callback
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback
|
||||||
|
|
||||||
it 'should log an error', ->
|
it 'should log an error', ->
|
||||||
@logger.error.called.should.equal true
|
@logger.error.called.should.equal true
|
||||||
|
@ -524,6 +547,21 @@ describe "RedisManager", ->
|
||||||
it "should call the callback with the error", ->
|
it "should call the callback with the error", ->
|
||||||
@callback.calledWith(new Error("ranges are too large")).should.equal true
|
@callback.calledWith(new Error("ranges are too large")).should.equal true
|
||||||
|
|
||||||
|
describe "without user id from meta", ->
|
||||||
|
beforeEach ->
|
||||||
|
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
|
||||||
|
@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, {}, @callback
|
||||||
|
|
||||||
|
it "should set the last updater to null", ->
|
||||||
|
@multi.del
|
||||||
|
.calledWith("lastUpdatedBy:#{@doc_id}")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should still set the last updated time", ->
|
||||||
|
@multi.set
|
||||||
|
.calledWith("lastUpdatedAt:#{@doc_id}", Date.now())
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
describe "putDocInMemory", ->
|
describe "putDocInMemory", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@multi.set = sinon.stub()
|
@multi.set = sinon.stub()
|
||||||
|
@ -681,6 +719,17 @@ describe "RedisManager", ->
|
||||||
.calledWith("ProjectHistoryId:#{@doc_id}")
|
.calledWith("ProjectHistoryId:#{@doc_id}")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should delete lastUpdatedAt", ->
|
||||||
|
@multi.del
|
||||||
|
.calledWith("lastUpdatedAt:#{@doc_id}")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should delete lastUpdatedBy", ->
|
||||||
|
@multi.del
|
||||||
|
.calledWith("lastUpdatedBy:#{@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)
|
||||||
|
|
|
@ -159,7 +159,8 @@ describe "UpdateManager", ->
|
||||||
|
|
||||||
describe "applyUpdate", ->
|
describe "applyUpdate", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@update = {op: [{p: 42, i: "foo"}]}
|
@updateMeta = { user_id: 'last-author-fake-id' }
|
||||||
|
@update = {op: [{p: 42, i: "foo"}], meta: @updateMeta}
|
||||||
@updatedDocLines = ["updated", "lines"]
|
@updatedDocLines = ["updated", "lines"]
|
||||||
@version = 34
|
@version = 34
|
||||||
@lines = ["original", "lines"]
|
@lines = ["original", "lines"]
|
||||||
|
@ -193,7 +194,7 @@ describe "UpdateManager", ->
|
||||||
|
|
||||||
it "should save the document", ->
|
it "should save the document", ->
|
||||||
@RedisManager.updateDocument
|
@RedisManager.updateDocument
|
||||||
.calledWith(@project_id, @doc_id, @updatedDocLines, @version, @appliedOps, @updated_ranges)
|
.calledWith(@project_id, @doc_id, @updatedDocLines, @version, @appliedOps, @updated_ranges, @updateMeta)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should add metadata to the ops" , ->
|
it "should add metadata to the ops" , ->
|
||||||
|
|
Loading…
Reference in a new issue