PackManagerTests.coffee 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. assert = require('chai').assert
  4. should = chai.should()
  5. expect = chai.expect
  6. modulePath = "../../../../app/js/PackManager.js"
  7. SandboxedModule = require('sandboxed-module')
  8. {ObjectId} = require("mongojs")
  9. bson = require("bson")
  10. BSON = new bson.BSONPure()
  11. _ = require("underscore")
  12. tk = require "timekeeper"
  13. describe "PackManager", ->
  14. beforeEach ->
  15. tk.freeze(new Date())
  16. @PackManager = SandboxedModule.require modulePath, requires:
  17. "./mongojs" : { db: @db = {}, ObjectId: ObjectId, BSON: BSON }
  18. "./LockManager" : {}
  19. "./MongoAWS": {}
  20. "logger-sharelatex": { log: sinon.stub(), error: sinon.stub() }
  21. 'metrics-sharelatex': {inc: ()->}
  22. "./ProjectIterator": require("../../../../app/js/ProjectIterator.js") # Cache for speed
  23. "settings-sharelatex":
  24. redis: lock: key_schema: {}
  25. @callback = sinon.stub()
  26. @doc_id = ObjectId().toString()
  27. @project_id = ObjectId().toString()
  28. @PackManager.MAX_COUNT = 512
  29. afterEach ->
  30. tk.reset()
  31. describe "insertCompressedUpdates", ->
  32. beforeEach ->
  33. @lastUpdate = {
  34. _id: "12345"
  35. pack: [
  36. { op: "op-1", meta: "meta-1", v: 1},
  37. { op: "op-2", meta: "meta-2", v: 2}
  38. ]
  39. n : 2
  40. sz : 100
  41. }
  42. @newUpdates = [
  43. { op: "op-3", meta: "meta-3", v: 3},
  44. { op: "op-4", meta: "meta-4", v: 4}
  45. ]
  46. @db.docHistory =
  47. save: sinon.stub().callsArg(1)
  48. insert: sinon.stub().callsArg(1)
  49. findAndModify: sinon.stub().callsArg(1)
  50. describe "with no last update", ->
  51. beforeEach ->
  52. @PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  53. @PackManager.insertCompressedUpdates @project_id, @doc_id, null, @newUpdates, true, @callback
  54. describe "for a small update", ->
  55. it "should insert the update into a new pack", ->
  56. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates, true).should.equal true
  57. it "should call the callback", ->
  58. @callback.called.should.equal true
  59. describe "for many small updates", ->
  60. beforeEach ->
  61. @newUpdates = ({ op: "op-#{i}", meta: "meta-#{i}", v: i} for i in [0..2048])
  62. @PackManager.insertCompressedUpdates @project_id, @doc_id, null, @newUpdates, false, @callback
  63. it "should append the initial updates to the existing pack", ->
  64. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[0...512], false).should.equal true
  65. it "should insert the first set remaining updates as a new pack", ->
  66. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[512...1024], false).should.equal true
  67. it "should insert the second set of remaining updates as a new pack", ->
  68. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[1024...1536], false).should.equal true
  69. it "should insert the third set of remaining updates as a new pack", ->
  70. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[1536...2048], false).should.equal true
  71. it "should insert the final set of remaining updates as a new pack", ->
  72. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[2048..2048], false).should.equal true
  73. it "should call the callback", ->
  74. @callback.called.should.equal true
  75. describe "with an existing pack as the last update", ->
  76. beforeEach ->
  77. @PackManager.appendUpdatesToExistingPack = sinon.stub().callsArg(5)
  78. @PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  79. @PackManager.insertCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, false, @callback
  80. describe "for a small update", ->
  81. it "should append the update to the existing pack", ->
  82. @PackManager.appendUpdatesToExistingPack.calledWith(@project_id, @doc_id, @lastUpdate, @newUpdates, false).should.equal true
  83. it "should not insert any new packs", ->
  84. @PackManager.insertUpdatesIntoNewPack.called.should.equal false
  85. it "should call the callback", ->
  86. @callback.called.should.equal true
  87. describe "for many small updates", ->
  88. beforeEach ->
  89. @newUpdates = ({ op: "op-#{i}", meta: "meta-#{i}", v: i} for i in [0..2048])
  90. @PackManager.insertCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, false, @callback
  91. it "should append the initial updates to the existing pack", ->
  92. @PackManager.appendUpdatesToExistingPack.calledWith(@project_id, @doc_id, @lastUpdate, @newUpdates[0...510], false).should.equal true
  93. it "should insert the first set remaining updates as a new pack", ->
  94. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[510...1022], false).should.equal true
  95. it "should insert the second set of remaining updates as a new pack", ->
  96. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[1022...1534], false).should.equal true
  97. it "should insert the third set of remaining updates as a new pack", ->
  98. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[1534...2046], false).should.equal true
  99. it "should insert the final set of remaining updates as a new pack", ->
  100. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[2046..2048], false).should.equal true
  101. it "should call the callback", ->
  102. @callback.called.should.equal true
  103. describe "for many big updates", ->
  104. beforeEach ->
  105. longString = ("a" for [0 .. (0.75*@PackManager.MAX_SIZE)]).join("")
  106. @newUpdates = ({ op: "op-#{i}-#{longString}", meta: "meta-#{i}", v: i} for i in [0..4])
  107. @PackManager.insertCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, false, @callback
  108. it "should append the initial updates to the existing pack", ->
  109. @PackManager.appendUpdatesToExistingPack.calledWith(@project_id, @doc_id, @lastUpdate, @newUpdates[0..0], false).should.equal true
  110. it "should insert the first set remaining updates as a new pack", ->
  111. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[1..1], false).should.equal true
  112. it "should insert the second set of remaining updates as a new pack", ->
  113. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[2..2], false).should.equal true
  114. it "should insert the third set of remaining updates as a new pack", ->
  115. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[3..3], false).should.equal true
  116. it "should insert the final set of remaining updates as a new pack", ->
  117. @PackManager.insertUpdatesIntoNewPack.calledWith(@project_id, @doc_id, @newUpdates[4..4], false).should.equal true
  118. it "should call the callback", ->
  119. @callback.called.should.equal true
  120. describe "flushCompressedUpdates", ->
  121. describe "when there is no previous update", ->
  122. beforeEach ->
  123. @PackManager.flushCompressedUpdates @project_id, @doc_id, null, @newUpdates, true, @callback
  124. describe "for a small update that will expire", ->
  125. it "should insert the update into mongo", ->
  126. @db.docHistory.save.calledWithMatch({
  127. pack: @newUpdates,
  128. project_id: ObjectId(@project_id),
  129. doc_id: ObjectId(@doc_id)
  130. n: @newUpdates.length
  131. v: @newUpdates[0].v
  132. v_end: @newUpdates[@newUpdates.length-1].v
  133. }).should.equal true
  134. it "should set an expiry time in the future", ->
  135. @db.docHistory.save.calledWithMatch({
  136. expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000)
  137. }).should.equal true
  138. it "should call the callback", ->
  139. @callback.called.should.equal true
  140. describe "when there is a recent previous update in mongo that expires", ->
  141. beforeEach ->
  142. @lastUpdate = {
  143. _id: "12345"
  144. pack: [
  145. { op: "op-1", meta: "meta-1", v: 1},
  146. { op: "op-2", meta: "meta-2", v: 2}
  147. ]
  148. n : 2
  149. sz : 100
  150. meta: {start_ts: Date.now() - 6 * 3600 * 1000}
  151. expiresAt: new Date(Date.now())
  152. }
  153. @PackManager.flushCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, true, @callback
  154. describe "for a small update that will expire", ->
  155. it "should append the update in mongo", ->
  156. @db.docHistory.findAndModify.calledWithMatch({
  157. query: {_id: @lastUpdate._id}
  158. update: { $push: {"pack" : {$each: @newUpdates}}, $set: {v_end: @newUpdates[@newUpdates.length-1].v}}
  159. }).should.equal true
  160. it "should set an expiry time in the future", ->
  161. @db.docHistory.findAndModify.calledWithMatch({
  162. update: {$set: {expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000)}}
  163. }).should.equal true
  164. it "should call the callback", ->
  165. @callback.called.should.equal true
  166. describe "when there is a recent previous update in mongo that expires", ->
  167. beforeEach ->
  168. @PackManager.updateIndex = sinon.stub().callsArg(2)
  169. @lastUpdate = {
  170. _id: "12345"
  171. pack: [
  172. { op: "op-1", meta: "meta-1", v: 1},
  173. { op: "op-2", meta: "meta-2", v: 2}
  174. ]
  175. n : 2
  176. sz : 100
  177. meta: {start_ts: Date.now() - 6 * 3600 * 1000}
  178. expiresAt: new Date(Date.now())
  179. }
  180. @PackManager.flushCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, false, @callback
  181. describe "for a small update that will not expire", ->
  182. it "should insert the update into mongo", ->
  183. @db.docHistory.save.calledWithMatch({
  184. pack: @newUpdates,
  185. project_id: ObjectId(@project_id),
  186. doc_id: ObjectId(@doc_id)
  187. n: @newUpdates.length
  188. v: @newUpdates[0].v
  189. v_end: @newUpdates[@newUpdates.length-1].v
  190. }).should.equal true
  191. it "should not set any expiry time", ->
  192. @db.docHistory.save.neverCalledWithMatch(sinon.match.has("expiresAt")).should.equal true
  193. it "should call the callback", ->
  194. @callback.called.should.equal true
  195. describe "when there is an old previous update in mongo", ->
  196. beforeEach ->
  197. @lastUpdate = {
  198. _id: "12345"
  199. pack: [
  200. { op: "op-1", meta: "meta-1", v: 1},
  201. { op: "op-2", meta: "meta-2", v: 2}
  202. ]
  203. n : 2
  204. sz : 100
  205. meta: {start_ts: Date.now() - 30 * 24 * 3600 * 1000}
  206. expiresAt: new Date(Date.now() - 30 * 24 * 3600 * 1000)
  207. }
  208. @PackManager.flushCompressedUpdates @project_id, @doc_id, @lastUpdate, @newUpdates, true, @callback
  209. describe "for a small update that will expire", ->
  210. it "should insert the update into mongo", ->
  211. @db.docHistory.save.calledWithMatch({
  212. pack: @newUpdates,
  213. project_id: ObjectId(@project_id),
  214. doc_id: ObjectId(@doc_id)
  215. n: @newUpdates.length
  216. v: @newUpdates[0].v
  217. v_end: @newUpdates[@newUpdates.length-1].v
  218. }).should.equal true
  219. it "should set an expiry time in the future", ->
  220. @db.docHistory.save.calledWithMatch({
  221. expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000)
  222. }).should.equal true
  223. it "should call the callback", ->
  224. @callback.called.should.equal true
  225. describe "getOpsByVersionRange", ->
  226. describe "loadPacksByVersionRange", ->
  227. describe "fetchPacksIfNeeded", ->
  228. describe "makeProjectIterator", ->
  229. describe "getPackById", ->
  230. describe "increaseTTL", ->
  231. describe "getIndex", ->
  232. describe "getPackFromIndex", ->
  233. # getLastPackFromIndex:
  234. # getIndexWithKeys
  235. # initialiseIndex
  236. # updateIndex
  237. # findCompletedPacks
  238. # findUnindexedPacks
  239. # insertPacksIntoIndexWithLock
  240. # _insertPacksIntoIndex
  241. # archivePack
  242. # checkArchivedPack
  243. # processOldPack
  244. # updateIndexIfNeeded
  245. # findUnarchivedPacks
  246. describe "checkArchiveNotInProgress", ->
  247. describe "when an archive is in progress", ->
  248. beforeEach ->
  249. @db.docHistoryIndex =
  250. findOne: sinon.stub().callsArgWith(2, null, {inS3:false})
  251. @PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
  252. it "should call the callback", ->
  253. @callback.called.should.equal true
  254. it "should return an error", ->
  255. @callback.calledWith(sinon.match.has('message')).should.equal true
  256. describe "when an archive is completed", ->
  257. beforeEach ->
  258. @db.docHistoryIndex =
  259. findOne: sinon.stub().callsArgWith(2, null, {inS3:true})
  260. @PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
  261. it "should call the callback", ->
  262. @callback.called.should.equal true
  263. it "should return an error", ->
  264. @callback.calledWith(sinon.match.has('message')).should.equal true
  265. describe "when the archive has not started or completed", ->
  266. beforeEach ->
  267. @db.docHistoryIndex =
  268. findOne: sinon.stub().callsArgWith(2, null, {})
  269. @PackManager.checkArchiveNotInProgress @project_id, @doc_id, @pack_id, @callback
  270. it "should call the callback with no error", ->
  271. @callback.called.should.equal true
  272. it "should return with no error", ->
  273. (typeof @callback.lastCall.args[0]).should.equal 'undefined'
  274. # describe "setTTLOnArchivedPack", ->
  275. # beforeEach ->
  276. # @pack_id = "somepackid"
  277. # @onedayinms = 86400000
  278. # @db.docHistory =
  279. # findAndModify : sinon.stub().callsArgWith(1)
  280. # it "should set expires to 1 day", (done)->
  281. # #@PackManager._getOneDayInFutureWithRandomDelay = sinon.stub().returns(@onedayinms)
  282. # @PackManager.setTTLOnArchivedPack @project_id, @doc_id, @pack_id, =>
  283. # args = @db.docHistory.findAndModify.args[0][0]
  284. # args.query._id.should.equal @pack_id
  285. # args.update['$set'].expiresAt.should.equal @onedayinms
  286. # done()
  287. # describe "_getOneDayInFutureWithRandomDelay", ->
  288. # beforeEach ->
  289. # @onedayinms = 86400000
  290. # @thirtyMins = 1000 * 60 * 30
  291. # it "should give 1 day + 30 mins random time", (done)->
  292. # loops = 10000
  293. # while --loops > 0
  294. # randomDelay = @PackManager._getOneDayInFutureWithRandomDelay() - new Date(Date.now() + @onedayinms)
  295. # randomDelay.should.be.above(0)
  296. # randomDelay.should.be.below(@thirtyMins + 1)
  297. # done()