PackManagerTests.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. /* eslint-disable
  2. mocha/no-identical-title,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const sinon = require('sinon')
  14. const { assert, expect } = require('chai')
  15. const modulePath = '../../../../app/js/PackManager.js'
  16. const SandboxedModule = require('sandboxed-module')
  17. const { ObjectId } = require('mongodb')
  18. const _ = require('underscore')
  19. const tk = require('timekeeper')
  20. describe('PackManager', function () {
  21. beforeEach(function () {
  22. tk.freeze(new Date())
  23. this.PackManager = SandboxedModule.require(modulePath, {
  24. requires: {
  25. bson: require('bson'),
  26. './mongodb': { db: (this.db = {}), ObjectId },
  27. './LockManager': {},
  28. './MongoAWS': {},
  29. '@overleaf/metrics': { inc() {} },
  30. './ProjectIterator': require('../../../../app/js/ProjectIterator.js'), // Cache for speed
  31. '@overleaf/settings': {
  32. redis: { lock: { key_schema: {} } },
  33. },
  34. },
  35. })
  36. this.callback = sinon.stub()
  37. this.doc_id = ObjectId().toString()
  38. this.project_id = ObjectId().toString()
  39. return (this.PackManager.MAX_COUNT = 512)
  40. })
  41. afterEach(function () {
  42. return tk.reset()
  43. })
  44. describe('insertCompressedUpdates', function () {
  45. beforeEach(function () {
  46. this.lastUpdate = {
  47. _id: '12345',
  48. pack: [
  49. { op: 'op-1', meta: 'meta-1', v: 1 },
  50. { op: 'op-2', meta: 'meta-2', v: 2 },
  51. ],
  52. n: 2,
  53. sz: 100,
  54. }
  55. this.newUpdates = [
  56. { op: 'op-3', meta: 'meta-3', v: 3 },
  57. { op: 'op-4', meta: 'meta-4', v: 4 },
  58. ]
  59. return (this.db.docHistory = {
  60. insertOne: sinon.stub().yields(),
  61. insert: sinon.stub().callsArg(1),
  62. updateOne: sinon.stub().yields(),
  63. findAndModify: sinon.stub().callsArg(1),
  64. })
  65. })
  66. describe('with no last update', function () {
  67. beforeEach(function () {
  68. this.PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  69. return this.PackManager.insertCompressedUpdates(
  70. this.project_id,
  71. this.doc_id,
  72. null,
  73. this.newUpdates,
  74. true,
  75. this.callback
  76. )
  77. })
  78. describe('for a small update', function () {
  79. it('should insert the update into a new pack', function () {
  80. return this.PackManager.insertUpdatesIntoNewPack
  81. .calledWith(this.project_id, this.doc_id, this.newUpdates, true)
  82. .should.equal(true)
  83. })
  84. return it('should call the callback', function () {
  85. return this.callback.called.should.equal(true)
  86. })
  87. })
  88. return describe('for many small updates', function () {
  89. beforeEach(function () {
  90. this.newUpdates = __range__(0, 2048, true).map(i => ({
  91. op: `op-${i}`,
  92. meta: `meta-${i}`,
  93. v: i,
  94. }))
  95. return this.PackManager.insertCompressedUpdates(
  96. this.project_id,
  97. this.doc_id,
  98. null,
  99. this.newUpdates,
  100. false,
  101. this.callback
  102. )
  103. })
  104. it('should append the initial updates to the existing pack', function () {
  105. return this.PackManager.insertUpdatesIntoNewPack
  106. .calledWith(
  107. this.project_id,
  108. this.doc_id,
  109. this.newUpdates.slice(0, 512),
  110. false
  111. )
  112. .should.equal(true)
  113. })
  114. it('should insert the first set remaining updates as a new pack', function () {
  115. return this.PackManager.insertUpdatesIntoNewPack
  116. .calledWith(
  117. this.project_id,
  118. this.doc_id,
  119. this.newUpdates.slice(512, 1024),
  120. false
  121. )
  122. .should.equal(true)
  123. })
  124. it('should insert the second set of remaining updates as a new pack', function () {
  125. return this.PackManager.insertUpdatesIntoNewPack
  126. .calledWith(
  127. this.project_id,
  128. this.doc_id,
  129. this.newUpdates.slice(1024, 1536),
  130. false
  131. )
  132. .should.equal(true)
  133. })
  134. it('should insert the third set of remaining updates as a new pack', function () {
  135. return this.PackManager.insertUpdatesIntoNewPack
  136. .calledWith(
  137. this.project_id,
  138. this.doc_id,
  139. this.newUpdates.slice(1536, 2048),
  140. false
  141. )
  142. .should.equal(true)
  143. })
  144. it('should insert the final set of remaining updates as a new pack', function () {
  145. return this.PackManager.insertUpdatesIntoNewPack
  146. .calledWith(
  147. this.project_id,
  148. this.doc_id,
  149. this.newUpdates.slice(2048, 2049),
  150. false
  151. )
  152. .should.equal(true)
  153. })
  154. return it('should call the callback', function () {
  155. return this.callback.called.should.equal(true)
  156. })
  157. })
  158. })
  159. describe('with an existing pack as the last update', function () {
  160. beforeEach(function () {
  161. this.PackManager.appendUpdatesToExistingPack = sinon.stub().callsArg(5)
  162. this.PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  163. return this.PackManager.insertCompressedUpdates(
  164. this.project_id,
  165. this.doc_id,
  166. this.lastUpdate,
  167. this.newUpdates,
  168. false,
  169. this.callback
  170. )
  171. })
  172. describe('for a small update', function () {
  173. it('should append the update to the existing pack', function () {
  174. return this.PackManager.appendUpdatesToExistingPack
  175. .calledWith(
  176. this.project_id,
  177. this.doc_id,
  178. this.lastUpdate,
  179. this.newUpdates,
  180. false
  181. )
  182. .should.equal(true)
  183. })
  184. it('should not insert any new packs', function () {
  185. return this.PackManager.insertUpdatesIntoNewPack.called.should.equal(
  186. false
  187. )
  188. })
  189. return it('should call the callback', function () {
  190. return this.callback.called.should.equal(true)
  191. })
  192. })
  193. describe('for many small updates', function () {
  194. beforeEach(function () {
  195. this.newUpdates = __range__(0, 2048, true).map(i => ({
  196. op: `op-${i}`,
  197. meta: `meta-${i}`,
  198. v: i,
  199. }))
  200. return this.PackManager.insertCompressedUpdates(
  201. this.project_id,
  202. this.doc_id,
  203. this.lastUpdate,
  204. this.newUpdates,
  205. false,
  206. this.callback
  207. )
  208. })
  209. it('should append the initial updates to the existing pack', function () {
  210. return this.PackManager.appendUpdatesToExistingPack
  211. .calledWith(
  212. this.project_id,
  213. this.doc_id,
  214. this.lastUpdate,
  215. this.newUpdates.slice(0, 510),
  216. false
  217. )
  218. .should.equal(true)
  219. })
  220. it('should insert the first set remaining updates as a new pack', function () {
  221. return this.PackManager.insertUpdatesIntoNewPack
  222. .calledWith(
  223. this.project_id,
  224. this.doc_id,
  225. this.newUpdates.slice(510, 1022),
  226. false
  227. )
  228. .should.equal(true)
  229. })
  230. it('should insert the second set of remaining updates as a new pack', function () {
  231. return this.PackManager.insertUpdatesIntoNewPack
  232. .calledWith(
  233. this.project_id,
  234. this.doc_id,
  235. this.newUpdates.slice(1022, 1534),
  236. false
  237. )
  238. .should.equal(true)
  239. })
  240. it('should insert the third set of remaining updates as a new pack', function () {
  241. return this.PackManager.insertUpdatesIntoNewPack
  242. .calledWith(
  243. this.project_id,
  244. this.doc_id,
  245. this.newUpdates.slice(1534, 2046),
  246. false
  247. )
  248. .should.equal(true)
  249. })
  250. it('should insert the final set of remaining updates as a new pack', function () {
  251. return this.PackManager.insertUpdatesIntoNewPack
  252. .calledWith(
  253. this.project_id,
  254. this.doc_id,
  255. this.newUpdates.slice(2046, 2049),
  256. false
  257. )
  258. .should.equal(true)
  259. })
  260. return it('should call the callback', function () {
  261. return this.callback.called.should.equal(true)
  262. })
  263. })
  264. return describe('for many big updates', function () {
  265. beforeEach(function () {
  266. const longString = __range__(
  267. 0,
  268. 0.75 * this.PackManager.MAX_SIZE,
  269. true
  270. )
  271. .map(j => 'a')
  272. .join('')
  273. this.newUpdates = [0, 1, 2, 3, 4].map(i => ({
  274. op: `op-${i}-${longString}`,
  275. meta: `meta-${i}`,
  276. v: i,
  277. }))
  278. return this.PackManager.insertCompressedUpdates(
  279. this.project_id,
  280. this.doc_id,
  281. this.lastUpdate,
  282. this.newUpdates,
  283. false,
  284. this.callback
  285. )
  286. })
  287. it('should append the initial updates to the existing pack', function () {
  288. return this.PackManager.appendUpdatesToExistingPack
  289. .calledWith(
  290. this.project_id,
  291. this.doc_id,
  292. this.lastUpdate,
  293. this.newUpdates.slice(0, 1),
  294. false
  295. )
  296. .should.equal(true)
  297. })
  298. it('should insert the first set remaining updates as a new pack', function () {
  299. return this.PackManager.insertUpdatesIntoNewPack
  300. .calledWith(
  301. this.project_id,
  302. this.doc_id,
  303. this.newUpdates.slice(1, 2),
  304. false
  305. )
  306. .should.equal(true)
  307. })
  308. it('should insert the second set of remaining updates as a new pack', function () {
  309. return this.PackManager.insertUpdatesIntoNewPack
  310. .calledWith(
  311. this.project_id,
  312. this.doc_id,
  313. this.newUpdates.slice(2, 3),
  314. false
  315. )
  316. .should.equal(true)
  317. })
  318. it('should insert the third set of remaining updates as a new pack', function () {
  319. return this.PackManager.insertUpdatesIntoNewPack
  320. .calledWith(
  321. this.project_id,
  322. this.doc_id,
  323. this.newUpdates.slice(3, 4),
  324. false
  325. )
  326. .should.equal(true)
  327. })
  328. it('should insert the final set of remaining updates as a new pack', function () {
  329. return this.PackManager.insertUpdatesIntoNewPack
  330. .calledWith(
  331. this.project_id,
  332. this.doc_id,
  333. this.newUpdates.slice(4, 5),
  334. false
  335. )
  336. .should.equal(true)
  337. })
  338. return it('should call the callback', function () {
  339. return this.callback.called.should.equal(true)
  340. })
  341. })
  342. })
  343. describe('flushCompressedUpdates', function () {
  344. return describe('when there is no previous update', function () {
  345. beforeEach(function () {
  346. return this.PackManager.flushCompressedUpdates(
  347. this.project_id,
  348. this.doc_id,
  349. null,
  350. this.newUpdates,
  351. true,
  352. this.callback
  353. )
  354. })
  355. return describe('for a small update that will expire', function () {
  356. it('should insert the update into mongo', function () {
  357. return this.db.docHistory.insertOne
  358. .calledWithMatch({
  359. pack: this.newUpdates,
  360. project_id: ObjectId(this.project_id),
  361. doc_id: ObjectId(this.doc_id),
  362. n: this.newUpdates.length,
  363. v: this.newUpdates[0].v,
  364. v_end: this.newUpdates[this.newUpdates.length - 1].v,
  365. })
  366. .should.equal(true)
  367. })
  368. it('should set an expiry time in the future', function () {
  369. return this.db.docHistory.insertOne
  370. .calledWithMatch({
  371. expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000),
  372. })
  373. .should.equal(true)
  374. })
  375. return it('should call the callback', function () {
  376. return this.callback.called.should.equal(true)
  377. })
  378. })
  379. })
  380. })
  381. describe('when there is a recent previous update in mongo that expires', function () {
  382. beforeEach(function () {
  383. this.lastUpdate = {
  384. _id: '12345',
  385. pack: [
  386. { op: 'op-1', meta: 'meta-1', v: 1 },
  387. { op: 'op-2', meta: 'meta-2', v: 2 },
  388. ],
  389. n: 2,
  390. sz: 100,
  391. meta: { start_ts: Date.now() - 6 * 3600 * 1000 },
  392. expiresAt: new Date(Date.now()),
  393. }
  394. return this.PackManager.flushCompressedUpdates(
  395. this.project_id,
  396. this.doc_id,
  397. this.lastUpdate,
  398. this.newUpdates,
  399. true,
  400. this.callback
  401. )
  402. })
  403. return describe('for a small update that will expire', function () {
  404. it('should append the update in mongo', function () {
  405. return this.db.docHistory.updateOne
  406. .calledWithMatch(
  407. { _id: this.lastUpdate._id },
  408. {
  409. $push: { pack: { $each: this.newUpdates } },
  410. $set: { v_end: this.newUpdates[this.newUpdates.length - 1].v },
  411. }
  412. )
  413. .should.equal(true)
  414. })
  415. it('should set an expiry time in the future', function () {
  416. return this.db.docHistory.updateOne
  417. .calledWithMatch(sinon.match.any, {
  418. $set: { expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000) },
  419. })
  420. .should.equal(true)
  421. })
  422. return it('should call the callback', function () {
  423. return this.callback.called.should.equal(true)
  424. })
  425. })
  426. })
  427. describe('when there is a recent previous update in mongo that expires', function () {
  428. beforeEach(function () {
  429. this.PackManager.updateIndex = sinon.stub().callsArg(2)
  430. this.lastUpdate = {
  431. _id: '12345',
  432. pack: [
  433. { op: 'op-1', meta: 'meta-1', v: 1 },
  434. { op: 'op-2', meta: 'meta-2', v: 2 },
  435. ],
  436. n: 2,
  437. sz: 100,
  438. meta: { start_ts: Date.now() - 6 * 3600 * 1000 },
  439. expiresAt: new Date(Date.now()),
  440. }
  441. return this.PackManager.flushCompressedUpdates(
  442. this.project_id,
  443. this.doc_id,
  444. this.lastUpdate,
  445. this.newUpdates,
  446. false,
  447. this.callback
  448. )
  449. })
  450. return describe('for a small update that will not expire', function () {
  451. it('should insert the update into mongo', function () {
  452. return this.db.docHistory.insertOne
  453. .calledWithMatch({
  454. pack: this.newUpdates,
  455. project_id: ObjectId(this.project_id),
  456. doc_id: ObjectId(this.doc_id),
  457. n: this.newUpdates.length,
  458. v: this.newUpdates[0].v,
  459. v_end: this.newUpdates[this.newUpdates.length - 1].v,
  460. })
  461. .should.equal(true)
  462. })
  463. it('should not set any expiry time', function () {
  464. return this.db.docHistory.insertOne
  465. .neverCalledWithMatch(sinon.match.has('expiresAt'))
  466. .should.equal(true)
  467. })
  468. return it('should call the callback', function () {
  469. return this.callback.called.should.equal(true)
  470. })
  471. })
  472. })
  473. return describe('when there is an old previous update in mongo', function () {
  474. beforeEach(function () {
  475. this.lastUpdate = {
  476. _id: '12345',
  477. pack: [
  478. { op: 'op-1', meta: 'meta-1', v: 1 },
  479. { op: 'op-2', meta: 'meta-2', v: 2 },
  480. ],
  481. n: 2,
  482. sz: 100,
  483. meta: { start_ts: Date.now() - 30 * 24 * 3600 * 1000 },
  484. expiresAt: new Date(Date.now() - 30 * 24 * 3600 * 1000),
  485. }
  486. return this.PackManager.flushCompressedUpdates(
  487. this.project_id,
  488. this.doc_id,
  489. this.lastUpdate,
  490. this.newUpdates,
  491. true,
  492. this.callback
  493. )
  494. })
  495. return describe('for a small update that will expire', function () {
  496. it('should insert the update into mongo', function () {
  497. return this.db.docHistory.insertOne
  498. .calledWithMatch({
  499. pack: this.newUpdates,
  500. project_id: ObjectId(this.project_id),
  501. doc_id: ObjectId(this.doc_id),
  502. n: this.newUpdates.length,
  503. v: this.newUpdates[0].v,
  504. v_end: this.newUpdates[this.newUpdates.length - 1].v,
  505. })
  506. .should.equal(true)
  507. })
  508. it('should set an expiry time in the future', function () {
  509. return this.db.docHistory.insertOne
  510. .calledWithMatch({
  511. expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000),
  512. })
  513. .should.equal(true)
  514. })
  515. return it('should call the callback', function () {
  516. return this.callback.called.should.equal(true)
  517. })
  518. })
  519. })
  520. })
  521. describe('getOpsByVersionRange', function () {})
  522. describe('loadPacksByVersionRange', function () {})
  523. describe('fetchPacksIfNeeded', function () {})
  524. describe('makeProjectIterator', function () {})
  525. describe('getPackById', function () {})
  526. describe('increaseTTL', function () {})
  527. describe('getIndex', function () {})
  528. describe('getPackFromIndex', function () {})
  529. // getLastPackFromIndex:
  530. // getIndexWithKeys
  531. // initialiseIndex
  532. // updateIndex
  533. // findCompletedPacks
  534. // findUnindexedPacks
  535. // insertPacksIntoIndexWithLock
  536. // _insertPacksIntoIndex
  537. // archivePack
  538. // checkArchivedPack
  539. // processOldPack
  540. // updateIndexIfNeeded
  541. // findUnarchivedPacks
  542. return describe('checkArchiveNotInProgress', function () {
  543. describe('when an archive is in progress', function () {
  544. beforeEach(function () {
  545. this.db.docHistoryIndex = {
  546. findOne: sinon.stub().callsArgWith(2, null, { inS3: false }),
  547. }
  548. return this.PackManager.checkArchiveNotInProgress(
  549. this.project_id,
  550. this.doc_id,
  551. this.pack_id,
  552. this.callback
  553. )
  554. })
  555. it('should call the callback', function () {
  556. return this.callback.called.should.equal(true)
  557. })
  558. return it('should return an error', function () {
  559. return this.callback
  560. .calledWith(sinon.match.has('message'))
  561. .should.equal(true)
  562. })
  563. })
  564. describe('when an archive is completed', function () {
  565. beforeEach(function () {
  566. this.db.docHistoryIndex = {
  567. findOne: sinon.stub().callsArgWith(2, null, { inS3: true }),
  568. }
  569. return this.PackManager.checkArchiveNotInProgress(
  570. this.project_id,
  571. this.doc_id,
  572. this.pack_id,
  573. this.callback
  574. )
  575. })
  576. it('should call the callback', function () {
  577. return this.callback.called.should.equal(true)
  578. })
  579. return it('should return an error', function () {
  580. return this.callback
  581. .calledWith(sinon.match.has('message'))
  582. .should.equal(true)
  583. })
  584. })
  585. return describe('when the archive has not started or completed', function () {
  586. beforeEach(function () {
  587. this.db.docHistoryIndex = {
  588. findOne: sinon.stub().callsArgWith(2, null, {}),
  589. }
  590. return this.PackManager.checkArchiveNotInProgress(
  591. this.project_id,
  592. this.doc_id,
  593. this.pack_id,
  594. this.callback
  595. )
  596. })
  597. it('should call the callback with no error', function () {
  598. return this.callback.called.should.equal(true)
  599. })
  600. return it('should return with no error', function () {
  601. return (typeof this.callback.lastCall.args[0]).should.equal('undefined')
  602. })
  603. })
  604. })
  605. })
  606. // describe "setTTLOnArchivedPack", ->
  607. // beforeEach ->
  608. // @pack_id = "somepackid"
  609. // @onedayinms = 86400000
  610. // @db.docHistory =
  611. // findAndModify : sinon.stub().callsArgWith(1)
  612. // it "should set expires to 1 day", (done)->
  613. // #@PackManager._getOneDayInFutureWithRandomDelay = sinon.stub().returns(@onedayinms)
  614. // @PackManager.setTTLOnArchivedPack @project_id, @doc_id, @pack_id, =>
  615. // args = @db.docHistory.findAndModify.args[0][0]
  616. // args.query._id.should.equal @pack_id
  617. // args.update['$set'].expiresAt.should.equal @onedayinms
  618. // done()
  619. // describe "_getOneDayInFutureWithRandomDelay", ->
  620. // beforeEach ->
  621. // @onedayinms = 86400000
  622. // @thirtyMins = 1000 * 60 * 30
  623. // it "should give 1 day + 30 mins random time", (done)->
  624. // loops = 10000
  625. // while --loops > 0
  626. // randomDelay = @PackManager._getOneDayInFutureWithRandomDelay() - new Date(Date.now() + @onedayinms)
  627. // randomDelay.should.be.above(0)
  628. // randomDelay.should.be.below(@thirtyMins + 1)
  629. // done()
  630. function __range__(left, right, inclusive) {
  631. const range = []
  632. const ascending = left < right
  633. const end = !inclusive ? right : ascending ? right + 1 : right - 1
  634. for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
  635. range.push(i)
  636. }
  637. return range
  638. }