UpdatesManagerTests.coffee 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/UpdatesManager.js"
  6. SandboxedModule = require('sandboxed-module')
  7. describe "UpdatesManager", ->
  8. beforeEach ->
  9. @UpdatesManager = SandboxedModule.require modulePath, requires:
  10. "./UpdateCompressor": @UpdateCompressor = {}
  11. "./MongoManager" : @MongoManager = {}
  12. "./RedisManager" : @RedisManager = {}
  13. "./LockManager" : @LockManager = {}
  14. "./WebApiManager": @WebApiManager = {}
  15. "./UpdateTrimmer": @UpdateTrimmer = {}
  16. "logger-sharelatex": { log: sinon.stub(), error: sinon.stub() }
  17. @doc_id = "doc-id-123"
  18. @project_id = "project-id-123"
  19. @callback = sinon.stub()
  20. describe "compressAndSaveRawUpdates", ->
  21. describe "when there are no raw ops", ->
  22. beforeEach ->
  23. @MongoManager.popLastCompressedUpdate = sinon.stub()
  24. @MongoManager.insertCompressedUpdates = sinon.stub()
  25. @UpdatesManager.compressAndSaveRawUpdates @project_id, @doc_id, [], @callback
  26. it "should not need to access the database", ->
  27. @MongoManager.popLastCompressedUpdate.called.should.equal false
  28. @MongoManager.insertCompressedUpdates.called.should.equal false
  29. it "should call the callback", ->
  30. @callback.called.should.equal true
  31. describe "when there is no compressed history to begin with", ->
  32. beforeEach ->
  33. @rawUpdates = [{ v: 12, op: "mock-op-12" }, { v: 13, op: "mock-op-13" }]
  34. @compressedUpdates = { v: 13, op: "compressed-op-12" }
  35. @MongoManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, null)
  36. @MongoManager.insertCompressedUpdates = sinon.stub().callsArg(3)
  37. @UpdateCompressor.compressRawUpdates = sinon.stub().returns(@compressedUpdates)
  38. @UpdatesManager.compressAndSaveRawUpdates @project_id, @doc_id, @rawUpdates, @callback
  39. it "should try to pop the last compressed op", ->
  40. @MongoManager.popLastCompressedUpdate
  41. .calledWith(@doc_id)
  42. .should.equal true
  43. it "should compress the raw ops", ->
  44. @UpdateCompressor.compressRawUpdates
  45. .calledWith(null, @rawUpdates)
  46. .should.equal true
  47. it "should save the compressed ops", ->
  48. @MongoManager.insertCompressedUpdates
  49. .calledWith(@project_id, @doc_id, @compressedUpdates)
  50. .should.equal true
  51. it "should call the callback", ->
  52. @callback.called.should.equal true
  53. describe "when the raw ops need appending to existing history", ->
  54. beforeEach ->
  55. @lastCompressedUpdate = { v: 11, op: "compressed-op-11" }
  56. @compressedUpdates = { v: 13, op: "compressed-op-12" }
  57. @MongoManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @lastCompressedUpdate)
  58. @MongoManager.insertCompressedUpdates = sinon.stub().callsArg(3)
  59. @UpdateCompressor.compressRawUpdates = sinon.stub().returns(@compressedUpdates)
  60. describe "when the raw ops start where the existing history ends", ->
  61. beforeEach ->
  62. @rawUpdates = [{ v: 12, op: "mock-op-12" }, { v: 13, op: "mock-op-13" }]
  63. @UpdatesManager.compressAndSaveRawUpdates @project_id, @doc_id, @rawUpdates, @callback
  64. it "should try to pop the last compressed op", ->
  65. @MongoManager.popLastCompressedUpdate
  66. .calledWith(@doc_id)
  67. .should.equal true
  68. it "should compress the last compressed op and the raw ops", ->
  69. @UpdateCompressor.compressRawUpdates
  70. .calledWith(@lastCompressedUpdate, @rawUpdates)
  71. .should.equal true
  72. it "should save the compressed ops", ->
  73. @MongoManager.insertCompressedUpdates
  74. .calledWith(@project_id, @doc_id, @compressedUpdates)
  75. .should.equal true
  76. it "should call the callback", ->
  77. @callback.called.should.equal true
  78. describe "when some raw ops are passed that have already been compressed", ->
  79. beforeEach ->
  80. @rawUpdates = [{ v: 10, op: "mock-op-10" }, { v: 11, op: "mock-op-11"}, { v: 12, op: "mock-op-12" }, { v: 13, op: "mock-op-13" }]
  81. @UpdatesManager.compressAndSaveRawUpdates @project_id, @doc_id, @rawUpdates, @callback
  82. it "should only compress the more recent raw ops", ->
  83. @UpdateCompressor.compressRawUpdates
  84. .calledWith(@lastCompressedUpdate, @rawUpdates.slice(-2))
  85. .should.equal true
  86. describe "when the raw ops do not follow from the last compressed op version", ->
  87. beforeEach ->
  88. @rawUpdates = [{ v: 13, op: "mock-op-13" }]
  89. @UpdatesManager.compressAndSaveRawUpdates @project_id, @doc_id, @rawUpdates, @callback
  90. it "should call the callback with an error", ->
  91. @callback
  92. .calledWith(new Error("Tried to apply raw op at version 13 to last compressed update with version 11"))
  93. .should.equal true
  94. it "should put the popped update back into mongo", ->
  95. @MongoManager.insertCompressedUpdates.calledOnce.should.equal true
  96. @MongoManager.insertCompressedUpdates
  97. .calledWith(@project_id, @doc_id, [@lastCompressedUpdate])
  98. .should.equal true
  99. describe "processUncompressedUpdates", ->
  100. beforeEach ->
  101. @UpdatesManager.compressAndSaveRawUpdates = sinon.stub().callsArgWith(3)
  102. @RedisManager.deleteOldestRawUpdates = sinon.stub().callsArg(3)
  103. @MongoManager.backportProjectId = sinon.stub().callsArg(2)
  104. describe "when there is fewer than one batch to send", ->
  105. beforeEach ->
  106. @updates = ["mock-update"]
  107. @RedisManager.getOldestRawUpdates = sinon.stub().callsArgWith(2, null, @updates)
  108. @UpdatesManager.processUncompressedUpdates @project_id, @doc_id, @callback
  109. it "should backport the project id", ->
  110. @MongoManager.backportProjectId
  111. .calledWith(@project_id, @doc_id)
  112. .should.equal true
  113. it "should get the oldest updates", ->
  114. @RedisManager.getOldestRawUpdates
  115. .calledWith(@doc_id, @UpdatesManager.REDIS_READ_BATCH_SIZE)
  116. .should.equal true
  117. it "should compress and save the updates", ->
  118. @UpdatesManager.compressAndSaveRawUpdates
  119. .calledWith(@project_id, @doc_id, @updates)
  120. .should.equal true
  121. it "should delete the batch of uncompressed updates that was just processed", ->
  122. @RedisManager.deleteOldestRawUpdates
  123. .calledWith(@project_id, @doc_id, @updates.length)
  124. .should.equal true
  125. it "should call the callback", ->
  126. @callback.called.should.equal true
  127. describe "when there are multiple batches to send", ->
  128. beforeEach (done) ->
  129. @UpdatesManager.REDIS_READ_BATCH_SIZE = 2
  130. @updates = ["mock-update-0", "mock-update-1", "mock-update-2", "mock-update-3", "mock-update-4"]
  131. @redisArray = @updates.slice()
  132. @RedisManager.getOldestRawUpdates = (doc_id, batchSize, callback = (error, updates) ->) =>
  133. updates = @redisArray.slice(0, batchSize)
  134. @redisArray = @redisArray.slice(batchSize)
  135. callback null, updates
  136. sinon.spy @RedisManager, "getOldestRawUpdates"
  137. @UpdatesManager.processUncompressedUpdates @project_id, @doc_id, (args...) =>
  138. @callback(args...)
  139. done()
  140. it "should get the oldest updates in three batches ", ->
  141. @RedisManager.getOldestRawUpdates.callCount.should.equal 3
  142. it "should compress and save the updates in batches", ->
  143. @UpdatesManager.compressAndSaveRawUpdates
  144. .calledWith(@project_id, @doc_id, @updates.slice(0,2))
  145. .should.equal true
  146. @UpdatesManager.compressAndSaveRawUpdates
  147. .calledWith(@project_id, @doc_id, @updates.slice(2,4))
  148. .should.equal true
  149. @UpdatesManager.compressAndSaveRawUpdates
  150. .calledWith(@project_id, @doc_id, @updates.slice(4,5))
  151. .should.equal true
  152. it "should delete the batches of uncompressed updates", ->
  153. @RedisManager.deleteOldestRawUpdates.callCount.should.equal 3
  154. it "should call the callback", ->
  155. @callback.called.should.equal true
  156. describe "processCompressedUpdatesWithLock", ->
  157. beforeEach ->
  158. @UpdatesManager.processUncompressedUpdates = sinon.stub().callsArg(2)
  159. @LockManager.runWithLock = sinon.stub().callsArg(2)
  160. @UpdatesManager.processUncompressedUpdatesWithLock @project_id, @doc_id, @callback
  161. it "should run processUncompressedUpdates with the lock", ->
  162. @LockManager.runWithLock
  163. .calledWith(
  164. "HistoryLock:#{@doc_id}"
  165. )
  166. .should.equal true
  167. it "should call the callback", ->
  168. @callback.called.should.equal true
  169. describe "getDocUpdates", ->
  170. beforeEach ->
  171. @updates = ["mock-updates"]
  172. @options = { to: "mock-to", limit: "mock-limit" }
  173. @MongoManager.getDocUpdates = sinon.stub().callsArgWith(2, null, @updates)
  174. @UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
  175. @UpdatesManager.getDocUpdates @project_id, @doc_id, @options, @callback
  176. it "should process outstanding updates", ->
  177. @UpdatesManager.processUncompressedUpdatesWithLock
  178. .calledWith(@project_id, @doc_id)
  179. .should.equal true
  180. it "should get the updates from the database", ->
  181. @MongoManager.getDocUpdates
  182. .calledWith(@doc_id, @options)
  183. .should.equal true
  184. it "should return the updates", ->
  185. @callback
  186. .calledWith(null, @updates)
  187. .should.equal true
  188. describe "getDocUpdatesWithUserInfo", ->
  189. beforeEach ->
  190. @updates = ["mock-updates"]
  191. @options = { to: "mock-to", limit: "mock-limit" }
  192. @updatesWithUserInfo = ["updates-with-user-info"]
  193. @UpdatesManager.getDocUpdates = sinon.stub().callsArgWith(3, null, @updates)
  194. @UpdatesManager.fillUserInfo = sinon.stub().callsArgWith(1, null, @updatesWithUserInfo)
  195. @UpdatesManager.getDocUpdatesWithUserInfo @project_id, @doc_id, @options, @callback
  196. it "should get the updates", ->
  197. @UpdatesManager.getDocUpdates
  198. .calledWith(@project_id, @doc_id, @options)
  199. .should.equal true
  200. it "should file the updates with the user info", ->
  201. @UpdatesManager.fillUserInfo
  202. .calledWith(@updates)
  203. .should.equal true
  204. it "should return the updates with the filled details", ->
  205. @callback.calledWith(null, @updatesWithUserInfo).should.equal true
  206. describe "getProjectUpdates", ->
  207. beforeEach ->
  208. @updates = ["mock-updates"]
  209. @options = { before: "mock-before", limit: "mock-limit" }
  210. @MongoManager.getProjectUpdates = sinon.stub().callsArgWith(2, null, @updates)
  211. @UpdatesManager.processUncompressedUpdatesForProject = sinon.stub().callsArg(1)
  212. @UpdatesManager.getProjectUpdates @project_id, @options, @callback
  213. it "should process any outstanding updates", ->
  214. @UpdatesManager.processUncompressedUpdatesForProject
  215. .calledWith(@project_id)
  216. .should.equal true
  217. it "should get the updates from the database", ->
  218. @MongoManager.getProjectUpdates
  219. .calledWith(@project_id, @options)
  220. .should.equal true
  221. it "should return the updates", ->
  222. @callback
  223. .calledWith(null, @updates)
  224. .should.equal true
  225. describe "processUncompressedUpdatesForProject", ->
  226. beforeEach (done) ->
  227. @doc_ids = ["mock-id-1", "mock-id-2"]
  228. @UpdatesManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(2)
  229. @RedisManager.getDocIdsWithHistoryOps = sinon.stub().callsArgWith(1, null, @doc_ids)
  230. @UpdateTrimmer.deleteOldProjectUpdates = sinon.stub().callsArg(1)
  231. @UpdatesManager.processUncompressedUpdatesForProject @project_id, () =>
  232. @callback()
  233. done()
  234. it "should get all the docs with history ops", ->
  235. @RedisManager.getDocIdsWithHistoryOps
  236. .calledWith(@project_id)
  237. .should.equal true
  238. it "should process the doc ops for the each doc_id", ->
  239. for doc_id in @doc_ids
  240. @UpdatesManager.processUncompressedUpdatesWithLock
  241. .calledWith(@project_id, doc_id)
  242. .should.equal true
  243. it "should delete old updates for the project", ->
  244. @UpdateTrimmer.deleteOldProjectUpdates
  245. .calledWith(@project_id)
  246. .should.equal true
  247. it "should call the callback", ->
  248. @callback.called.should.equal true
  249. describe "getProjectUpdatesWithUserInfo", ->
  250. beforeEach ->
  251. @updates = ["mock-updates"]
  252. @options = { before: "mock-before", limit: "mock-limit" }
  253. @updatesWithUserInfo = ["updates-with-user-info"]
  254. @UpdatesManager.getProjectUpdates = sinon.stub().callsArgWith(2, null, @updates)
  255. @UpdatesManager.fillUserInfo = sinon.stub().callsArgWith(1, null, @updatesWithUserInfo)
  256. @UpdatesManager.getProjectUpdatesWithUserInfo @project_id, @options, @callback
  257. it "should get the updates", ->
  258. @UpdatesManager.getProjectUpdates
  259. .calledWith(@project_id, @options)
  260. .should.equal true
  261. it "should file the updates with the user info", ->
  262. @UpdatesManager.fillUserInfo
  263. .calledWith(@updates)
  264. .should.equal true
  265. it "should return the updates with the filled details", ->
  266. @callback.calledWith(null, @updatesWithUserInfo).should.equal true
  267. describe "_extendBatchOfSummarizedUpdates", ->
  268. beforeEach ->
  269. @before = Date.now()
  270. @min_count = 2
  271. @existingSummarizedUpdates = ["summarized-updates-3"]
  272. @summarizedUpdates = ["summarized-updates-3", "summarized-update-2", "summarized-update-1"]
  273. describe "when there are updates to get", ->
  274. beforeEach ->
  275. @updates = [
  276. {op: "mock-op-1", meta: end_ts: @before - 10},
  277. {op: "mock-op-1", meta: end_ts: @nextBeforeTimestamp = @before - 20}
  278. ]
  279. @existingSummarizedUpdates = ["summarized-updates-3"]
  280. @summarizedUpdates = ["summarized-updates-3", "summarized-update-2", "summarized-update-1"]
  281. @UpdatesManager._summarizeUpdates = sinon.stub().returns(@summarizedUpdates)
  282. @UpdatesManager.getProjectUpdatesWithUserInfo = sinon.stub().callsArgWith(2, null, @updates)
  283. @UpdatesManager._extendBatchOfSummarizedUpdates @project_id, @existingSummarizedUpdates, @before, @min_count, @callback
  284. it "should get the updates", ->
  285. @UpdatesManager.getProjectUpdatesWithUserInfo
  286. .calledWith(@project_id, { before: @before, limit: 3 * @min_count })
  287. .should.equal true
  288. it "should summarize the updates", ->
  289. @UpdatesManager._summarizeUpdates
  290. .calledWith(@updates, @existingSummarizedUpdates)
  291. .should.equal true
  292. it "should call the callback with the summarized updates and the next before timestamp", ->
  293. @callback.calledWith(null, @summarizedUpdates, @nextBeforeTimestamp).should.equal true
  294. describe "when there are no more updates", ->
  295. beforeEach ->
  296. @updates = []
  297. @UpdatesManager._summarizeUpdates = sinon.stub().returns(@summarizedUpdates)
  298. @UpdatesManager.getProjectUpdatesWithUserInfo = sinon.stub().callsArgWith(2, null, @updates)
  299. @UpdatesManager._extendBatchOfSummarizedUpdates @project_id, @existingSummarizedUpdates, @before, @min_count, @callback
  300. it "should call the callback with the summarized updates and null for nextBeforeTimestamp", ->
  301. @callback.calledWith(null, @summarizedUpdates, null).should.equal true
  302. describe "getSummarizedProjectUpdates", ->
  303. describe "when one batch of updates is enough to meet the limit", ->
  304. beforeEach ->
  305. @before = Date.now()
  306. @min_count = 2
  307. @updates = ["summarized-updates-3", "summarized-updates-2"]
  308. @nextBeforeTimestamp = @before - 100
  309. @UpdatesManager._extendBatchOfSummarizedUpdates = sinon.stub().callsArgWith(4, null, @updates, @nextBeforeTimestamp)
  310. @UpdatesManager.getSummarizedProjectUpdates @project_id, { before: @before, min_count: @min_count }, @callback
  311. it "should get the batch of summarized updates", ->
  312. @UpdatesManager._extendBatchOfSummarizedUpdates
  313. .calledWith(@project_id, [], @before, @min_count)
  314. .should.equal true
  315. it "should call the callback with the updates", ->
  316. @callback.calledWith(null, @updates, @nextBeforeTimestamp).should.equal true
  317. describe "when multiple batches are needed to meet the limit", ->
  318. beforeEach ->
  319. @before = Date.now()
  320. @min_count = 4
  321. @firstBatch = [{ toV: 6, fromV: 6 }, { toV: 5, fromV: 5 }]
  322. @nextBeforeTimestamp = @before - 100
  323. @secondBatch = [{ toV: 4, fromV: 4 }, { toV: 3, fromV: 3 }]
  324. @nextNextBeforeTimestamp = @before - 200
  325. @UpdatesManager._extendBatchOfSummarizedUpdates = (project_id, existingUpdates, before, desiredLength, callback) =>
  326. if existingUpdates.length == 0
  327. callback null, @firstBatch, @nextBeforeTimestamp
  328. else
  329. callback null, @firstBatch.concat(@secondBatch), @nextNextBeforeTimestamp
  330. sinon.spy @UpdatesManager, "_extendBatchOfSummarizedUpdates"
  331. @UpdatesManager.getSummarizedProjectUpdates @project_id, { before: @before, min_count: @min_count }, @callback
  332. it "should get the first batch of summarized updates", ->
  333. @UpdatesManager._extendBatchOfSummarizedUpdates
  334. .calledWith(@project_id, [], @before, @min_count)
  335. .should.equal true
  336. it "should get the second batch of summarized updates", ->
  337. @UpdatesManager._extendBatchOfSummarizedUpdates
  338. .calledWith(@project_id, @firstBatch, @nextBeforeTimestamp, @min_count)
  339. .should.equal true
  340. it "should call the callback with all the updates", ->
  341. @callback.calledWith(null, @firstBatch.concat(@secondBatch), @nextNextBeforeTimestamp).should.equal true
  342. describe "when the end of the database is hit", ->
  343. beforeEach ->
  344. @before = Date.now()
  345. @min_count = 4
  346. @updates = [{ toV: 6, fromV: 6 }, { toV: 5, fromV: 5 }]
  347. @UpdatesManager._extendBatchOfSummarizedUpdates = sinon.stub().callsArgWith(4, null, @updates, null)
  348. @UpdatesManager.getSummarizedProjectUpdates @project_id, { before: @before, min_count: @min_count }, @callback
  349. it "should get the batch of summarized updates", ->
  350. @UpdatesManager._extendBatchOfSummarizedUpdates
  351. .calledWith(@project_id, [], @before, @min_count)
  352. .should.equal true
  353. it "should call the callback with the updates", ->
  354. @callback.calledWith(null, @updates, null).should.equal true
  355. describe "fillUserInfo", ->
  356. describe "with valid users", ->
  357. beforeEach (done) ->
  358. {ObjectId} = require "mongojs"
  359. @user_id_1 = ObjectId().toString()
  360. @user_id_2 = ObjectId().toString()
  361. @updates = [{
  362. meta:
  363. user_id: @user_id_1
  364. op: "mock-op-1"
  365. }, {
  366. meta:
  367. user_id: @user_id_1
  368. op: "mock-op-2"
  369. }, {
  370. meta:
  371. user_id: @user_id_2
  372. op: "mock-op-3"
  373. }]
  374. @user_info = {}
  375. @user_info[@user_id_1] = email: "user1@sharelatex.com"
  376. @user_info[@user_id_2] = email: "user2@sharelatex.com"
  377. @WebApiManager.getUserInfo = (user_id, callback = (error, userInfo) ->) =>
  378. callback null, @user_info[user_id]
  379. sinon.spy @WebApiManager, "getUserInfo"
  380. @UpdatesManager.fillUserInfo @updates, (error, @results) =>
  381. done()
  382. it "should only call getUserInfo once for each user_id", ->
  383. @WebApiManager.getUserInfo.calledTwice.should.equal true
  384. @WebApiManager.getUserInfo
  385. .calledWith(@user_id_1)
  386. .should.equal true
  387. @WebApiManager.getUserInfo
  388. .calledWith(@user_id_2)
  389. .should.equal true
  390. it "should return the updates with the user info filled", ->
  391. expect(@results).to.deep.equal [{
  392. meta:
  393. user:
  394. email: "user1@sharelatex.com"
  395. op: "mock-op-1"
  396. }, {
  397. meta:
  398. user:
  399. email: "user1@sharelatex.com"
  400. op: "mock-op-2"
  401. }, {
  402. meta:
  403. user:
  404. email: "user2@sharelatex.com"
  405. op: "mock-op-3"
  406. }]
  407. describe "with invalid user ids", ->
  408. beforeEach (done) ->
  409. @updates = [{
  410. meta:
  411. user_id: null
  412. op: "mock-op-1"
  413. }, {
  414. meta:
  415. user_id: "anonymous-user"
  416. op: "mock-op-2"
  417. }]
  418. @WebApiManager.getUserInfo = (user_id, callback = (error, userInfo) ->) =>
  419. callback null, @user_info[user_id]
  420. sinon.spy @WebApiManager, "getUserInfo"
  421. @UpdatesManager.fillUserInfo @updates, (error, @results) =>
  422. done()
  423. it "should not call getUserInfo", ->
  424. @WebApiManager.getUserInfo.called.should.equal false
  425. it "should return the updates without the user info filled", ->
  426. expect(@results).to.deep.equal [{
  427. meta: {}
  428. op: "mock-op-1"
  429. }, {
  430. meta: {}
  431. op: "mock-op-2"
  432. }]
  433. describe "_summarizeUpdates", ->
  434. beforeEach ->
  435. @now = Date.now()
  436. @user_1 = { id: "mock-user-1" }
  437. @user_2 = { id: "mock-user-2" }
  438. it "should concat updates that are close in time", ->
  439. result = @UpdatesManager._summarizeUpdates [{
  440. doc_id: "doc-id-1"
  441. meta:
  442. user: @user_1
  443. start_ts: @now + 20
  444. end_ts: @now + 30
  445. v: 5
  446. }, {
  447. doc_id: "doc-id-1"
  448. meta:
  449. user: @user_2
  450. start_ts: @now
  451. end_ts: @now + 10
  452. v: 4
  453. }]
  454. expect(result).to.deep.equal [{
  455. docs:
  456. "doc-id-1":
  457. fromV: 4
  458. toV: 5
  459. meta:
  460. users: [@user_1, @user_2]
  461. start_ts: @now
  462. end_ts: @now + 30
  463. }]
  464. it "should leave updates that are far apart in time", ->
  465. oneDay = 1000 * 60 * 60 * 24
  466. result = @UpdatesManager._summarizeUpdates [{
  467. doc_id: "doc-id-1"
  468. meta:
  469. user: @user_2
  470. start_ts: @now + oneDay
  471. end_ts: @now + oneDay + 10
  472. v: 5
  473. }, {
  474. doc_id: "doc-id-1"
  475. meta:
  476. user: @user_1
  477. start_ts: @now
  478. end_ts: @now + 10
  479. v: 4
  480. }]
  481. expect(result).to.deep.equal [{
  482. docs:
  483. "doc-id-1":
  484. fromV: 5
  485. toV: 5
  486. meta:
  487. users: [@user_2]
  488. start_ts: @now + oneDay
  489. end_ts: @now + oneDay + 10
  490. }, {
  491. docs:
  492. "doc-id-1":
  493. fromV: 4
  494. toV: 4
  495. meta:
  496. users: [@user_1]
  497. start_ts: @now
  498. end_ts: @now + 10
  499. }]
  500. it "should concat onto existing summarized updates", ->
  501. result = @UpdatesManager._summarizeUpdates [{
  502. doc_id: "doc-id-2"
  503. meta:
  504. user: @user_1
  505. start_ts: @now + 20
  506. end_ts: @now + 30
  507. v: 5
  508. }, {
  509. doc_id: "doc-id-2"
  510. meta:
  511. user: @user_2
  512. start_ts: @now
  513. end_ts: @now + 10
  514. v: 4
  515. }], [{
  516. docs:
  517. "doc-id-1":
  518. fromV: 6
  519. toV: 8
  520. meta:
  521. users: [@user_1]
  522. start_ts: @now + 40
  523. end_ts: @now + 50
  524. }]
  525. expect(result).to.deep.equal [{
  526. docs:
  527. "doc-id-1":
  528. toV: 8
  529. fromV: 6
  530. "doc-id-2":
  531. toV: 5
  532. fromV: 4
  533. meta:
  534. users: [@user_1, @user_2]
  535. start_ts: @now
  536. end_ts: @now + 50
  537. }]