mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-31 21:21:03 -04:00
330 lines
11 KiB
CoffeeScript
330 lines
11 KiB
CoffeeScript
sinon = require('sinon')
|
|
chai = require('chai')
|
|
should = chai.should()
|
|
modulePath = "../../../../app/js/DocOpsManager.js"
|
|
SandboxedModule = require('sandboxed-module')
|
|
ObjectId = require("../../../../app/js/mongojs").ObjectId
|
|
|
|
describe "DocOpsManager", ->
|
|
beforeEach ->
|
|
@doc_id = ObjectId().toString()
|
|
@project_id = "project-id"
|
|
@callback = sinon.stub()
|
|
@DocOpsManager = SandboxedModule.require modulePath, requires:
|
|
"./RedisManager": @RedisManager = {}
|
|
"./mongojs":
|
|
db: @db = { docOps: {} }
|
|
ObjectId: ObjectId
|
|
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
|
"./Metrics": @Metrics =
|
|
Timer: class Timer
|
|
done: sinon.stub()
|
|
"./TrackChangesManager": @TrackChangesManager = {}
|
|
|
|
describe "flushDocOpsToMongo", ->
|
|
describe "when versions are consistent", ->
|
|
beforeEach ->
|
|
@mongo_version = 40
|
|
@redis_version = 42
|
|
@ops = [ "mock-op-1", "mock-op-2" ]
|
|
@DocOpsManager.getDocVersionInMongo = sinon.stub().callsArgWith(1, null, @mongo_version)
|
|
@RedisManager.getDocVersion = sinon.stub().callsArgWith(1, null, @redis_version)
|
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
|
@DocOpsManager._appendDocOpsInMongo = sinon.stub().callsArg(3)
|
|
@DocOpsManager.flushDocOpsToMongo @project_id, @doc_id, @callback
|
|
|
|
it "should get the version from Mongo", ->
|
|
@DocOpsManager.getDocVersionInMongo
|
|
.calledWith(@doc_id)
|
|
.should.equal true
|
|
|
|
it "should get the version from REdis", ->
|
|
@RedisManager.getDocVersion
|
|
.calledWith(@doc_id)
|
|
.should.equal true
|
|
|
|
it "should get all doc ops since the version in Mongo", ->
|
|
@RedisManager.getPreviousDocOps
|
|
.calledWith(@doc_id, @mongo_version, -1)
|
|
.should.equal true
|
|
|
|
it "should update Mongo with the new ops", ->
|
|
@DocOpsManager._appendDocOpsInMongo
|
|
.calledWith(@doc_id, @ops, @redis_version)
|
|
.should.equal true
|
|
|
|
it "should call the callback", ->
|
|
@callback.called.should.equal true
|
|
|
|
it "should time the execution", ->
|
|
@Metrics.Timer::done.called.should.equal true
|
|
|
|
describe "when the number of ops does not match the difference in versions", ->
|
|
beforeEach ->
|
|
@mongo_version = 40
|
|
@redis_version = 45
|
|
@ops = [ "mock-op-1", "mock-op-2" ]
|
|
@DocOpsManager.getDocVersionInMongo = sinon.stub().callsArgWith(1, null, @mongo_version)
|
|
@RedisManager.getDocVersion = sinon.stub().callsArgWith(1, null, @redis_version)
|
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
|
@DocOpsManager._appendDocOpsInMongo = sinon.stub().callsArg(3)
|
|
@DocOpsManager.flushDocOpsToMongo @project_id, @doc_id, @callback
|
|
|
|
it "should call the callback with an error", ->
|
|
@callback.calledWith(new Error("inconsistet versions")).should.equal true
|
|
|
|
it "should log an error", ->
|
|
@logger.error
|
|
.calledWith(doc_id: @doc_id, mongoVersion: @mongo_version, redisVersion: @redis_version, opsLength: @ops.length, "version difference does not match ops length")
|
|
.should.equal true
|
|
|
|
it "should not modify mongo", ->
|
|
@DocOpsManager._appendDocOpsInMongo.called.should.equal false
|
|
|
|
it "should time the execution", ->
|
|
@Metrics.Timer::done.called.should.equal true
|
|
|
|
describe "when redis version is behind mongo version", ->
|
|
beforeEach ->
|
|
@mongo_version = 40
|
|
@redis_version = 30
|
|
@DocOpsManager.getDocVersionInMongo = sinon.stub().callsArgWith(1, null, @mongo_version)
|
|
@RedisManager.getDocVersion = sinon.stub().callsArgWith(1, null, @redis_version)
|
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
|
@DocOpsManager._appendDocOpsInMongo = sinon.stub().callsArg(3)
|
|
@DocOpsManager.flushDocOpsToMongo @project_id, @doc_id, @callback
|
|
|
|
it "should call the callback with an error", ->
|
|
@callback.calledWith(new Error("inconsistet versions")).should.equal true
|
|
|
|
it "should log an error", ->
|
|
@logger.error
|
|
.calledWith(doc_id: @doc_id, mongoVersion: @mongo_version, redisVersion: @redis_version, "mongo version is ahead of redis")
|
|
.should.equal true
|
|
|
|
it "should not modify mongo", ->
|
|
@DocOpsManager._appendDocOpsInMongo.called.should.equal false
|
|
|
|
it "should time the execution", ->
|
|
@Metrics.Timer::done.called.should.equal true
|
|
|
|
describe "getPreviousDocOps", ->
|
|
beforeEach ->
|
|
@ops = [ "mock-op-1", "mock-op-2" ]
|
|
@start = 30
|
|
@end = 32
|
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
|
@DocOpsManager._ensureOpsAreLoaded = sinon.stub().callsArg(3)
|
|
@DocOpsManager.getPreviousDocOps @project_id, @doc_id, @start, @end, @callback
|
|
|
|
it "should ensure the ops are loaded back far enough", ->
|
|
@DocOpsManager._ensureOpsAreLoaded
|
|
.calledWith(@project_id, @doc_id, @start)
|
|
.should.equal true
|
|
|
|
it "should get the previous doc ops", ->
|
|
@RedisManager.getPreviousDocOps
|
|
.calledWith(@doc_id, @start, @end)
|
|
.should.equal true
|
|
|
|
it "should call the callback with the ops", ->
|
|
@callback.calledWith(null, @ops).should.equal true
|
|
|
|
it "should time the execution", ->
|
|
@Metrics.Timer::done.called.should.equal true
|
|
|
|
describe "_ensureOpsAreLoaded", ->
|
|
describe "when the ops are not loaded", ->
|
|
beforeEach ->
|
|
@redisVersion = 42
|
|
@redisOpsLength = 10
|
|
@backToVersion = 30
|
|
@ops = [ "mock-op-1", "mock-op-2" ]
|
|
@RedisManager.getDocVersion = sinon.stub().callsArgWith(1, null, @redisVersion)
|
|
@RedisManager.getDocOpsLength = sinon.stub().callsArgWith(1, null, @redisOpsLength)
|
|
@DocOpsManager._getDocOpsFromMongo = sinon.stub().callsArgWith(3, null, @ops)
|
|
@RedisManager.prependDocOps = sinon.stub().callsArgWith(2, null)
|
|
@DocOpsManager._ensureOpsAreLoaded @project_id, @doc_id, @backToVersion, @callback
|
|
|
|
it "should get the doc version from redis", ->
|
|
@RedisManager.getDocVersion
|
|
.calledWith(@doc_id)
|
|
.should.equal true
|
|
|
|
it "should get the doc ops length in redis", ->
|
|
@RedisManager.getDocOpsLength
|
|
.calledWith(@doc_id)
|
|
.should.equal true
|
|
|
|
it "should get the doc ops that need loading from Mongo", ->
|
|
@DocOpsManager._getDocOpsFromMongo
|
|
.calledWith(@doc_id, @backToVersion, @redisVersion - @redisOpsLength)
|
|
.should.equal true
|
|
|
|
it "should prepend the retrieved ops to redis", ->
|
|
@RedisManager.prependDocOps
|
|
.calledWith(@doc_id, @ops)
|
|
.should.equal true
|
|
|
|
it "should call the callback", ->
|
|
@callback.called.should.equal true
|
|
|
|
describe "when the ops are loaded", ->
|
|
beforeEach ->
|
|
@redisVersion = 42
|
|
@redisOpsLength = 10
|
|
@backToVersion = 35
|
|
@RedisManager.getDocVersion = sinon.stub().callsArgWith(1, null, @redisVersion)
|
|
@RedisManager.getDocOpsLength = sinon.stub().callsArgWith(1, null, @redisOpsLength)
|
|
@DocOpsManager._getDocOpsFromMongo = sinon.stub().callsArgWith(3, null, @ops)
|
|
@RedisManager.prependDocOps = sinon.stub().callsArgWith(2, null)
|
|
@DocOpsManager._ensureOpsAreLoaded @project_id, @doc_id, @backToVersion, @callback
|
|
|
|
it "should not need to get the docs from Mongo or put any into redis", ->
|
|
@DocOpsManager._getDocOpsFromMongo.called.should.equal false
|
|
@RedisManager.prependDocOps.called.should.equal false
|
|
|
|
it "should call the callback", ->
|
|
@callback.called.should.equal true
|
|
|
|
describe "getDocVersionInMongo", ->
|
|
describe "when the doc exists", ->
|
|
beforeEach ->
|
|
@doc =
|
|
version: @version = 42
|
|
@db.docOps.find = sinon.stub().callsArgWith(2, null, [@doc])
|
|
@DocOpsManager.getDocVersionInMongo @doc_id, @callback
|
|
|
|
it "should look for the doc in the database", ->
|
|
@db.docOps.find
|
|
.calledWith({ doc_id: ObjectId(@doc_id) }, {version: 1})
|
|
.should.equal true
|
|
|
|
it "should call the callback with the version", ->
|
|
@callback.calledWith(null, @version).should.equal true
|
|
|
|
describe "when the doc doesn't exist", ->
|
|
beforeEach ->
|
|
@db.docOps.find = sinon.stub().callsArgWith(2, null, [])
|
|
@DocOpsManager.getDocVersionInMongo @doc_id, @callback
|
|
|
|
it "should call the callback with 0", ->
|
|
@callback.calledWith(null, 0).should.equal true
|
|
|
|
describe "_appendDocOpsInMongo", ->
|
|
describe "with a small set of updates", ->
|
|
beforeEach (done) ->
|
|
@ops = [ "mock-op-1", "mock-op-2" ]
|
|
@version = 42
|
|
@db.docOps.update = sinon.stub().callsArg(3)
|
|
@DocOpsManager._appendDocOpsInMongo @doc_id, @ops, @version, (error) =>
|
|
@callback(error)
|
|
done()
|
|
|
|
it "should update the database", ->
|
|
@db.docOps.update
|
|
.calledWith({
|
|
doc_id: ObjectId(@doc_id)
|
|
}, {
|
|
$push: docOps: { $each: @ops, $slice: -100 }
|
|
$set: version: @version
|
|
}, {
|
|
upsert: true
|
|
})
|
|
.should.equal true
|
|
|
|
it "should call the callbak", ->
|
|
@callback.called.should.equal true
|
|
|
|
describe "with a large set of updates", ->
|
|
beforeEach (done) ->
|
|
@ops = [ "mock-op-1", "mock-op-2", "mock-op-3", "mock-op-4", "mock-op-5" ]
|
|
@version = 42
|
|
@DocOpsManager.APPEND_OPS_BATCH_SIZE = 2
|
|
@db.docOps.update = sinon.stub().callsArg(3)
|
|
@DocOpsManager._appendDocOpsInMongo @doc_id, @ops, @version, (error) =>
|
|
@callback(error)
|
|
done()
|
|
|
|
it "should update the database in batches", ->
|
|
@db.docOps.update
|
|
.calledWith({ doc_id: ObjectId(@doc_id) }, {
|
|
$push: docOps: { $each: @ops.slice(0,2), $slice: -100 }
|
|
$set: version: @version - 3
|
|
}, { upsert: true })
|
|
.should.equal true
|
|
@db.docOps.update
|
|
.calledWith({ doc_id: ObjectId(@doc_id) }, {
|
|
$push: docOps: { $each: @ops.slice(2,4), $slice: -100 }
|
|
$set: version: @version - 1
|
|
}, { upsert: true })
|
|
.should.equal true
|
|
@db.docOps.update
|
|
.calledWith({ doc_id: ObjectId(@doc_id) }, {
|
|
$push: docOps: { $each: @ops.slice(4,5), $slice: -100 }
|
|
$set: version: @version
|
|
}, { upsert: true })
|
|
.should.equal true
|
|
|
|
it "should call the callbak", ->
|
|
@callback.called.should.equal true
|
|
|
|
describe "with no updates", ->
|
|
beforeEach (done) ->
|
|
@ops = []
|
|
@version = 42
|
|
@db.docOps.update = sinon.stub().callsArg(3)
|
|
@DocOpsManager._appendDocOpsInMongo @doc_id, @ops, @version, (error) =>
|
|
@callback(error)
|
|
done()
|
|
|
|
it "should not try to update the database", ->
|
|
@db.docOps.update.called.should.equal false
|
|
|
|
describe "_getDocsOpsFromMongo", ->
|
|
beforeEach ->
|
|
@version = 42
|
|
@start = 32
|
|
@limit = 5
|
|
@doc =
|
|
docOps: ["mock-ops"]
|
|
@DocOpsManager.getDocVersionInMongo = sinon.stub().callsArgWith(1, null, @version)
|
|
@db.docOps.find = sinon.stub().callsArgWith(2, null, [@doc])
|
|
@DocOpsManager._getDocOpsFromMongo @doc_id, @start, @start + @limit, @callback
|
|
|
|
it "should get the current version", ->
|
|
@DocOpsManager.getDocVersionInMongo
|
|
.calledWith(@doc_id)
|
|
.should.equal true
|
|
|
|
it "should get the doc ops", ->
|
|
@db.docOps.find
|
|
.calledWith({ doc_id: ObjectId(@doc_id) }, {
|
|
docOps: $slice: [-(@version - @start), @limit]
|
|
})
|
|
.should.equal true
|
|
|
|
it "should return the ops", ->
|
|
@callback.calledWith(null, @doc.docOps).should.equal true
|
|
|
|
describe "pushDocOp", ->
|
|
beforeEach ->
|
|
@op = "mock-op"
|
|
@RedisManager.pushDocOp = sinon.stub().callsArgWith(2, null, @version = 42)
|
|
@TrackChangesManager.pushUncompressedHistoryOp = sinon.stub().callsArg(2)
|
|
@DocOpsManager.pushDocOp @project_id, @doc_id, @op, @callback
|
|
|
|
it "should push the op in to the docOps list", ->
|
|
@RedisManager.pushDocOp
|
|
.calledWith(@doc_id, @op)
|
|
.should.equal true
|
|
|
|
it "should push the op into the pushUncompressedHistoryOp", ->
|
|
@TrackChangesManager.pushUncompressedHistoryOp
|
|
.calledWith(@doc_id, @op)
|
|
.should.equal true
|
|
|
|
it "should call the callback with the version", ->
|
|
@callback.calledWith(null, @version).should.equal true
|
|
|
|
|