2014-02-12 05:40:42 -05:00
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 ( )
2014-02-28 13:29:05 -05:00
" ./TrackChangesManager " : @TrackChangesManager = { }
2014-02-12 05:40:42 -05:00
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
2014-02-24 11:52:12 -05:00
describe " pushDocOp " , ->
beforeEach ->
@op = " mock-op "
2014-02-26 09:49:52 -05:00
@RedisManager.pushDocOp = sinon . stub ( ) . callsArgWith ( 2 , null , @version = 42 )
2014-03-19 11:56:44 -04:00
@TrackChangesManager.pushUncompressedHistoryOp = sinon . stub ( ) . callsArg ( 3 )
2014-02-24 11:52:12 -05:00
@ 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 " , ->
2014-02-28 13:29:05 -05:00
@ TrackChangesManager . pushUncompressedHistoryOp
2014-03-19 11:56:44 -04:00
. calledWith ( @ project_id , @ doc_id , @ op )
2014-02-24 11:52:12 -05:00
. should . equal true
2014-02-26 09:49:52 -05:00
it " should call the callback with the version " , ->
@ callback . calledWith ( null , @ version ) . should . equal true
2014-02-24 11:52:12 -05:00
2014-02-12 05:40:42 -05:00