| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- 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()
- 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)
- @RedisManager.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", ->
- @RedisManager.pushUncompressedHistoryOp
- .calledWith(@doc_id, @op)
- .should.equal true
- it "should call the callback with the version", ->
- @callback.calledWith(null, @version).should.equal true
-
|