PackManagerTests.js 22 KB

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