UpdatesManagerTests.coffee 22 KB

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