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 _ = 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. './mongojs': { 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. save: sinon.stub().callsArg(1),
  65. insert: sinon.stub().callsArg(1),
  66. findAndModify: sinon.stub().callsArg(1)
  67. })
  68. })
  69. describe('with no last update', function () {
  70. beforeEach(function () {
  71. this.PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  72. return this.PackManager.insertCompressedUpdates(
  73. this.project_id,
  74. this.doc_id,
  75. null,
  76. this.newUpdates,
  77. true,
  78. this.callback
  79. )
  80. })
  81. describe('for a small update', function () {
  82. it('should insert the update into a new pack', function () {
  83. return this.PackManager.insertUpdatesIntoNewPack
  84. .calledWith(this.project_id, this.doc_id, this.newUpdates, true)
  85. .should.equal(true)
  86. })
  87. return it('should call the callback', function () {
  88. return this.callback.called.should.equal(true)
  89. })
  90. })
  91. return describe('for many small updates', function () {
  92. beforeEach(function () {
  93. this.newUpdates = __range__(0, 2048, true).map((i) => ({
  94. op: `op-${i}`,
  95. meta: `meta-${i}`,
  96. v: i
  97. }))
  98. return this.PackManager.insertCompressedUpdates(
  99. this.project_id,
  100. this.doc_id,
  101. null,
  102. this.newUpdates,
  103. false,
  104. this.callback
  105. )
  106. })
  107. it('should append the initial updates to the existing pack', function () {
  108. return this.PackManager.insertUpdatesIntoNewPack
  109. .calledWith(
  110. this.project_id,
  111. this.doc_id,
  112. this.newUpdates.slice(0, 512),
  113. false
  114. )
  115. .should.equal(true)
  116. })
  117. it('should insert the first set remaining updates as a new pack', function () {
  118. return this.PackManager.insertUpdatesIntoNewPack
  119. .calledWith(
  120. this.project_id,
  121. this.doc_id,
  122. this.newUpdates.slice(512, 1024),
  123. false
  124. )
  125. .should.equal(true)
  126. })
  127. it('should insert the second set of remaining updates as a new pack', function () {
  128. return this.PackManager.insertUpdatesIntoNewPack
  129. .calledWith(
  130. this.project_id,
  131. this.doc_id,
  132. this.newUpdates.slice(1024, 1536),
  133. false
  134. )
  135. .should.equal(true)
  136. })
  137. it('should insert the third set of remaining updates as a new pack', function () {
  138. return this.PackManager.insertUpdatesIntoNewPack
  139. .calledWith(
  140. this.project_id,
  141. this.doc_id,
  142. this.newUpdates.slice(1536, 2048),
  143. false
  144. )
  145. .should.equal(true)
  146. })
  147. it('should insert the final set of remaining updates as a new pack', function () {
  148. return this.PackManager.insertUpdatesIntoNewPack
  149. .calledWith(
  150. this.project_id,
  151. this.doc_id,
  152. this.newUpdates.slice(2048, 2049),
  153. false
  154. )
  155. .should.equal(true)
  156. })
  157. return it('should call the callback', function () {
  158. return this.callback.called.should.equal(true)
  159. })
  160. })
  161. })
  162. describe('with an existing pack as the last update', function () {
  163. beforeEach(function () {
  164. this.PackManager.appendUpdatesToExistingPack = sinon.stub().callsArg(5)
  165. this.PackManager.insertUpdatesIntoNewPack = sinon.stub().callsArg(4)
  166. return this.PackManager.insertCompressedUpdates(
  167. this.project_id,
  168. this.doc_id,
  169. this.lastUpdate,
  170. this.newUpdates,
  171. false,
  172. this.callback
  173. )
  174. })
  175. describe('for a small update', function () {
  176. it('should append the update to the existing pack', function () {
  177. return this.PackManager.appendUpdatesToExistingPack
  178. .calledWith(
  179. this.project_id,
  180. this.doc_id,
  181. this.lastUpdate,
  182. this.newUpdates,
  183. false
  184. )
  185. .should.equal(true)
  186. })
  187. it('should not insert any new packs', function () {
  188. return this.PackManager.insertUpdatesIntoNewPack.called.should.equal(
  189. false
  190. )
  191. })
  192. return it('should call the callback', function () {
  193. return this.callback.called.should.equal(true)
  194. })
  195. })
  196. describe('for many small updates', function () {
  197. beforeEach(function () {
  198. this.newUpdates = __range__(0, 2048, true).map((i) => ({
  199. op: `op-${i}`,
  200. meta: `meta-${i}`,
  201. v: i
  202. }))
  203. return this.PackManager.insertCompressedUpdates(
  204. this.project_id,
  205. this.doc_id,
  206. this.lastUpdate,
  207. this.newUpdates,
  208. false,
  209. this.callback
  210. )
  211. })
  212. it('should append the initial updates to the existing pack', function () {
  213. return this.PackManager.appendUpdatesToExistingPack
  214. .calledWith(
  215. this.project_id,
  216. this.doc_id,
  217. this.lastUpdate,
  218. this.newUpdates.slice(0, 510),
  219. false
  220. )
  221. .should.equal(true)
  222. })
  223. it('should insert the first set remaining updates as a new pack', function () {
  224. return this.PackManager.insertUpdatesIntoNewPack
  225. .calledWith(
  226. this.project_id,
  227. this.doc_id,
  228. this.newUpdates.slice(510, 1022),
  229. false
  230. )
  231. .should.equal(true)
  232. })
  233. it('should insert the second set of remaining updates as a new pack', function () {
  234. return this.PackManager.insertUpdatesIntoNewPack
  235. .calledWith(
  236. this.project_id,
  237. this.doc_id,
  238. this.newUpdates.slice(1022, 1534),
  239. false
  240. )
  241. .should.equal(true)
  242. })
  243. it('should insert the third set of remaining updates as a new pack', function () {
  244. return this.PackManager.insertUpdatesIntoNewPack
  245. .calledWith(
  246. this.project_id,
  247. this.doc_id,
  248. this.newUpdates.slice(1534, 2046),
  249. false
  250. )
  251. .should.equal(true)
  252. })
  253. it('should insert the final set of remaining updates as a new pack', function () {
  254. return this.PackManager.insertUpdatesIntoNewPack
  255. .calledWith(
  256. this.project_id,
  257. this.doc_id,
  258. this.newUpdates.slice(2046, 2049),
  259. false
  260. )
  261. .should.equal(true)
  262. })
  263. return it('should call the callback', function () {
  264. return this.callback.called.should.equal(true)
  265. })
  266. })
  267. return describe('for many big updates', function () {
  268. beforeEach(function () {
  269. const longString = __range__(
  270. 0,
  271. 0.75 * this.PackManager.MAX_SIZE,
  272. true
  273. )
  274. .map((j) => 'a')
  275. .join('')
  276. this.newUpdates = [0, 1, 2, 3, 4].map((i) => ({
  277. op: `op-${i}-${longString}`,
  278. meta: `meta-${i}`,
  279. v: i
  280. }))
  281. return this.PackManager.insertCompressedUpdates(
  282. this.project_id,
  283. this.doc_id,
  284. this.lastUpdate,
  285. this.newUpdates,
  286. false,
  287. this.callback
  288. )
  289. })
  290. it('should append the initial updates to the existing pack', function () {
  291. return this.PackManager.appendUpdatesToExistingPack
  292. .calledWith(
  293. this.project_id,
  294. this.doc_id,
  295. this.lastUpdate,
  296. this.newUpdates.slice(0, 1),
  297. false
  298. )
  299. .should.equal(true)
  300. })
  301. it('should insert the first set remaining updates as a new pack', function () {
  302. return this.PackManager.insertUpdatesIntoNewPack
  303. .calledWith(
  304. this.project_id,
  305. this.doc_id,
  306. this.newUpdates.slice(1, 2),
  307. false
  308. )
  309. .should.equal(true)
  310. })
  311. it('should insert the second set of remaining updates as a new pack', function () {
  312. return this.PackManager.insertUpdatesIntoNewPack
  313. .calledWith(
  314. this.project_id,
  315. this.doc_id,
  316. this.newUpdates.slice(2, 3),
  317. false
  318. )
  319. .should.equal(true)
  320. })
  321. it('should insert the third set of remaining updates as a new pack', function () {
  322. return this.PackManager.insertUpdatesIntoNewPack
  323. .calledWith(
  324. this.project_id,
  325. this.doc_id,
  326. this.newUpdates.slice(3, 4),
  327. false
  328. )
  329. .should.equal(true)
  330. })
  331. it('should insert the final set of remaining updates as a new pack', function () {
  332. return this.PackManager.insertUpdatesIntoNewPack
  333. .calledWith(
  334. this.project_id,
  335. this.doc_id,
  336. this.newUpdates.slice(4, 5),
  337. false
  338. )
  339. .should.equal(true)
  340. })
  341. return it('should call the callback', function () {
  342. return this.callback.called.should.equal(true)
  343. })
  344. })
  345. })
  346. describe('flushCompressedUpdates', function () {
  347. return describe('when there is no previous update', function () {
  348. beforeEach(function () {
  349. return this.PackManager.flushCompressedUpdates(
  350. this.project_id,
  351. this.doc_id,
  352. null,
  353. this.newUpdates,
  354. true,
  355. this.callback
  356. )
  357. })
  358. return describe('for a small update that will expire', function () {
  359. it('should insert the update into mongo', function () {
  360. return this.db.docHistory.save
  361. .calledWithMatch({
  362. pack: this.newUpdates,
  363. project_id: ObjectId(this.project_id),
  364. doc_id: ObjectId(this.doc_id),
  365. n: this.newUpdates.length,
  366. v: this.newUpdates[0].v,
  367. v_end: this.newUpdates[this.newUpdates.length - 1].v
  368. })
  369. .should.equal(true)
  370. })
  371. it('should set an expiry time in the future', function () {
  372. return this.db.docHistory.save
  373. .calledWithMatch({
  374. expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000)
  375. })
  376. .should.equal(true)
  377. })
  378. return it('should call the callback', function () {
  379. return this.callback.called.should.equal(true)
  380. })
  381. })
  382. })
  383. })
  384. describe('when there is a recent previous update in mongo that expires', function () {
  385. beforeEach(function () {
  386. this.lastUpdate = {
  387. _id: '12345',
  388. pack: [
  389. { op: 'op-1', meta: 'meta-1', v: 1 },
  390. { op: 'op-2', meta: 'meta-2', v: 2 }
  391. ],
  392. n: 2,
  393. sz: 100,
  394. meta: { start_ts: Date.now() - 6 * 3600 * 1000 },
  395. expiresAt: new Date(Date.now())
  396. }
  397. return this.PackManager.flushCompressedUpdates(
  398. this.project_id,
  399. this.doc_id,
  400. this.lastUpdate,
  401. this.newUpdates,
  402. true,
  403. this.callback
  404. )
  405. })
  406. return describe('for a small update that will expire', function () {
  407. it('should append the update in mongo', function () {
  408. return this.db.docHistory.findAndModify
  409. .calledWithMatch({
  410. query: { _id: this.lastUpdate._id },
  411. update: {
  412. $push: { pack: { $each: this.newUpdates } },
  413. $set: { v_end: this.newUpdates[this.newUpdates.length - 1].v }
  414. }
  415. })
  416. .should.equal(true)
  417. })
  418. it('should set an expiry time in the future', function () {
  419. return this.db.docHistory.findAndModify
  420. .calledWithMatch({
  421. update: {
  422. $set: { expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000) }
  423. }
  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. }