PackManagerTests.js 22 KB

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