RangesManagerTests.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. /* eslint-disable
  2. no-return-assign,
  3. no-unused-vars,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  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 { expect } = require('chai')
  15. const modulePath = '../../../../app/js/RangesManager.js'
  16. const SandboxedModule = require('sandboxed-module')
  17. describe('RangesManager', function () {
  18. beforeEach(function () {
  19. this.RangesManager = SandboxedModule.require(modulePath, {
  20. requires: {
  21. '@overleaf/metrics': (this.Metrics = { histogram: sinon.stub() }),
  22. },
  23. })
  24. this.doc_id = 'doc-id-123'
  25. this.project_id = 'project-id-123'
  26. this.user_id = 'user-id-123'
  27. return (this.callback = sinon.stub())
  28. })
  29. describe('applyUpdate', function () {
  30. beforeEach(function () {
  31. this.updates = [
  32. {
  33. meta: {
  34. user_id: this.user_id,
  35. },
  36. op: [
  37. {
  38. i: 'two ',
  39. p: 4,
  40. },
  41. ],
  42. },
  43. ]
  44. this.entries = {
  45. comments: [
  46. {
  47. op: {
  48. c: 'three ',
  49. p: 4,
  50. },
  51. metadata: {
  52. user_id: this.user_id,
  53. },
  54. },
  55. ],
  56. changes: [
  57. {
  58. op: {
  59. i: 'five',
  60. p: 15,
  61. },
  62. metadata: {
  63. user_id: this.user_id,
  64. },
  65. },
  66. ],
  67. }
  68. return (this.newDocLines = ['one two three four five'])
  69. }) // old is "one three four five"
  70. describe('successfully', function () {
  71. beforeEach(function () {
  72. return this.RangesManager.applyUpdate(
  73. this.project_id,
  74. this.doc_id,
  75. this.entries,
  76. this.updates,
  77. this.newDocLines,
  78. this.callback
  79. )
  80. })
  81. return it('should return the modified the comments and changes', function () {
  82. this.callback.called.should.equal(true)
  83. const [error, entries, rangesWereCollapsed] = Array.from(
  84. this.callback.args[0]
  85. )
  86. expect(error).to.be.null
  87. expect(rangesWereCollapsed).to.equal(false)
  88. entries.comments[0].op.should.deep.equal({
  89. c: 'three ',
  90. p: 8,
  91. })
  92. return entries.changes[0].op.should.deep.equal({
  93. i: 'five',
  94. p: 19,
  95. })
  96. })
  97. })
  98. describe('with empty comments', function () {
  99. beforeEach(function () {
  100. this.entries.comments = []
  101. return this.RangesManager.applyUpdate(
  102. this.project_id,
  103. this.doc_id,
  104. this.entries,
  105. this.updates,
  106. this.newDocLines,
  107. this.callback
  108. )
  109. })
  110. return it('should return an object with no comments', function () {
  111. // Save space in redis and don't store just {}
  112. this.callback.called.should.equal(true)
  113. const [error, entries] = Array.from(this.callback.args[0])
  114. expect(error).to.be.null
  115. return expect(entries.comments).to.be.undefined
  116. })
  117. })
  118. describe('with empty changes', function () {
  119. beforeEach(function () {
  120. this.entries.changes = []
  121. return this.RangesManager.applyUpdate(
  122. this.project_id,
  123. this.doc_id,
  124. this.entries,
  125. this.updates,
  126. this.newDocLines,
  127. this.callback
  128. )
  129. })
  130. return it('should return an object with no changes', function () {
  131. // Save space in redis and don't store just {}
  132. this.callback.called.should.equal(true)
  133. const [error, entries] = Array.from(this.callback.args[0])
  134. expect(error).to.be.null
  135. return expect(entries.changes).to.be.undefined
  136. })
  137. })
  138. describe('with too many comments', function () {
  139. beforeEach(function () {
  140. this.RangesManager.MAX_COMMENTS = 2
  141. this.updates = [
  142. {
  143. meta: {
  144. user_id: this.user_id,
  145. },
  146. op: [
  147. {
  148. c: 'one',
  149. p: 0,
  150. t: 'thread-id-1',
  151. },
  152. ],
  153. },
  154. ]
  155. this.entries = {
  156. comments: [
  157. {
  158. op: {
  159. c: 'three ',
  160. p: 4,
  161. t: 'thread-id-2',
  162. },
  163. metadata: {
  164. user_id: this.user_id,
  165. },
  166. },
  167. {
  168. op: {
  169. c: 'four ',
  170. p: 10,
  171. t: 'thread-id-3',
  172. },
  173. metadata: {
  174. user_id: this.user_id,
  175. },
  176. },
  177. ],
  178. changes: [],
  179. }
  180. return this.RangesManager.applyUpdate(
  181. this.project_id,
  182. this.doc_id,
  183. this.entries,
  184. this.updates,
  185. this.newDocLines,
  186. this.callback
  187. )
  188. })
  189. return it('should return an error', function () {
  190. this.callback.called.should.equal(true)
  191. const [error, entries] = Array.from(this.callback.args[0])
  192. expect(error).to.not.be.null
  193. return expect(error.message).to.equal(
  194. 'too many comments or tracked changes'
  195. )
  196. })
  197. })
  198. describe('with too many changes', function () {
  199. beforeEach(function () {
  200. this.RangesManager.MAX_CHANGES = 2
  201. this.updates = [
  202. {
  203. meta: {
  204. user_id: this.user_id,
  205. tc: 'track-changes-id-yes',
  206. },
  207. op: [
  208. {
  209. i: 'one ',
  210. p: 0,
  211. },
  212. ],
  213. },
  214. ]
  215. this.entries = {
  216. changes: [
  217. {
  218. op: {
  219. i: 'three',
  220. p: 4,
  221. },
  222. metadata: {
  223. user_id: this.user_id,
  224. },
  225. },
  226. {
  227. op: {
  228. i: 'four',
  229. p: 10,
  230. },
  231. metadata: {
  232. user_id: this.user_id,
  233. },
  234. },
  235. ],
  236. comments: [],
  237. }
  238. this.newDocLines = ['one two three four']
  239. return this.RangesManager.applyUpdate(
  240. this.project_id,
  241. this.doc_id,
  242. this.entries,
  243. this.updates,
  244. this.newDocLines,
  245. this.callback
  246. )
  247. })
  248. return it('should return an error', function () {
  249. // Save space in redis and don't store just {}
  250. this.callback.called.should.equal(true)
  251. const [error, entries] = Array.from(this.callback.args[0])
  252. expect(error).to.not.be.null
  253. return expect(error.message).to.equal(
  254. 'too many comments or tracked changes'
  255. )
  256. })
  257. })
  258. describe('inconsistent changes', function () {
  259. beforeEach(function () {
  260. this.updates = [
  261. {
  262. meta: {
  263. user_id: this.user_id,
  264. },
  265. op: [
  266. {
  267. c: "doesn't match",
  268. p: 0,
  269. },
  270. ],
  271. },
  272. ]
  273. return this.RangesManager.applyUpdate(
  274. this.project_id,
  275. this.doc_id,
  276. this.entries,
  277. this.updates,
  278. this.newDocLines,
  279. this.callback
  280. )
  281. })
  282. return it('should return an error', function () {
  283. // Save space in redis and don't store just {}
  284. this.callback.called.should.equal(true)
  285. const [error, entries] = Array.from(this.callback.args[0])
  286. expect(error).to.not.be.null
  287. return expect(error.message).to.equal(
  288. 'Change ({"op":{"i":"five","p":15},"metadata":{"user_id":"user-id-123"}}) doesn\'t match text ("our ")'
  289. )
  290. })
  291. })
  292. describe('with an update that collapses a range', function () {
  293. beforeEach(function () {
  294. this.updates = [
  295. {
  296. meta: {
  297. user_id: this.user_id,
  298. },
  299. op: [
  300. {
  301. d: 'one',
  302. p: 0,
  303. t: 'thread-id-1',
  304. },
  305. ],
  306. },
  307. ]
  308. this.entries = {
  309. comments: [
  310. {
  311. op: {
  312. c: 'n',
  313. p: 1,
  314. t: 'thread-id-2',
  315. },
  316. metadata: {
  317. user_id: this.user_id,
  318. },
  319. },
  320. ],
  321. changes: [],
  322. }
  323. return this.RangesManager.applyUpdate(
  324. this.project_id,
  325. this.doc_id,
  326. this.entries,
  327. this.updates,
  328. this.newDocLines,
  329. this.callback
  330. )
  331. })
  332. return it('should return ranges_were_collapsed == true', function () {
  333. this.callback.called.should.equal(true)
  334. const [error, entries, rangesWereCollapsed] = Array.from(
  335. this.callback.args[0]
  336. )
  337. return expect(rangesWereCollapsed).to.equal(true)
  338. })
  339. })
  340. describe('with an update that deletes ranges', function () {
  341. beforeEach(function () {
  342. this.updates = [
  343. {
  344. meta: {
  345. user_id: this.user_id,
  346. },
  347. op: [
  348. {
  349. d: 'one two three four five',
  350. p: 0,
  351. },
  352. ],
  353. },
  354. ]
  355. this.entries = {
  356. comments: [
  357. {
  358. op: {
  359. c: 'n',
  360. p: 1,
  361. t: 'thread-id-2',
  362. },
  363. metadata: {
  364. user_id: this.user_id,
  365. },
  366. },
  367. ],
  368. changes: [
  369. {
  370. op: {
  371. i: 'hello',
  372. p: 1,
  373. t: 'thread-id-2',
  374. },
  375. metadata: {
  376. user_id: this.user_id,
  377. },
  378. },
  379. ],
  380. }
  381. return this.RangesManager.applyUpdate(
  382. this.project_id,
  383. this.doc_id,
  384. this.entries,
  385. this.updates,
  386. this.newDocLines,
  387. this.callback
  388. )
  389. })
  390. it('should increment the range-delta histogram', function () {
  391. this.Metrics.histogram.called.should.equal(true)
  392. })
  393. return it('should return ranges_were_collapsed == true', function () {
  394. this.callback.called.should.equal(true)
  395. const [error, entries, rangesWereCollapsed] = Array.from(
  396. this.callback.args[0]
  397. )
  398. return expect(rangesWereCollapsed).to.equal(true)
  399. })
  400. })
  401. })
  402. return describe('acceptChanges', function () {
  403. beforeEach(function () {
  404. this.RangesManager = SandboxedModule.require(modulePath, {
  405. requires: {
  406. '@overleaf/ranges-tracker': (this.RangesTracker =
  407. SandboxedModule.require('@overleaf/ranges-tracker')),
  408. '@overleaf/metrics': {},
  409. },
  410. })
  411. this.ranges = {
  412. comments: [],
  413. changes: [
  414. {
  415. id: 'a1',
  416. op: {
  417. i: 'lorem',
  418. p: 0,
  419. },
  420. },
  421. {
  422. id: 'a2',
  423. op: {
  424. i: 'ipsum',
  425. p: 10,
  426. },
  427. },
  428. {
  429. id: 'a3',
  430. op: {
  431. i: 'dolor',
  432. p: 20,
  433. },
  434. },
  435. {
  436. id: 'a4',
  437. op: {
  438. i: 'sit',
  439. p: 30,
  440. },
  441. },
  442. {
  443. id: 'a5',
  444. op: {
  445. i: 'amet',
  446. p: 40,
  447. },
  448. },
  449. ],
  450. }
  451. return (this.removeChangeIdsSpy = sinon.spy(
  452. this.RangesTracker.prototype,
  453. 'removeChangeIds'
  454. ))
  455. })
  456. describe('successfully with a single change', function () {
  457. beforeEach(function (done) {
  458. this.change_ids = [this.ranges.changes[1].id]
  459. return this.RangesManager.acceptChanges(
  460. this.change_ids,
  461. this.ranges,
  462. (err, ranges) => {
  463. if (err) return done(err)
  464. this.rangesResponse = ranges
  465. return done()
  466. }
  467. )
  468. })
  469. it('should log the call with the correct number of changes', function () {
  470. return this.logger.debug
  471. .calledWith('accepting 1 changes in ranges')
  472. .should.equal(true)
  473. })
  474. it('should delegate the change removal to the ranges tracker', function () {
  475. return this.removeChangeIdsSpy
  476. .calledWith(this.change_ids)
  477. .should.equal(true)
  478. })
  479. it('should remove the change', function () {
  480. return expect(
  481. this.rangesResponse.changes.find(
  482. change => change.id === this.ranges.changes[1].id
  483. )
  484. ).to.be.undefined
  485. })
  486. it('should return the original number of changes minus 1', function () {
  487. return this.rangesResponse.changes.length.should.equal(
  488. this.ranges.changes.length - 1
  489. )
  490. })
  491. return it('should not touch other changes', function () {
  492. return [0, 2, 3, 4].map(i =>
  493. expect(
  494. this.rangesResponse.changes.find(
  495. change => change.id === this.ranges.changes[i].id
  496. )
  497. ).to.deep.equal(this.ranges.changes[i])
  498. )
  499. })
  500. })
  501. return describe('successfully with multiple changes', function () {
  502. beforeEach(function (done) {
  503. this.change_ids = [
  504. this.ranges.changes[1].id,
  505. this.ranges.changes[3].id,
  506. this.ranges.changes[4].id,
  507. ]
  508. return this.RangesManager.acceptChanges(
  509. this.change_ids,
  510. this.ranges,
  511. (err, ranges) => {
  512. if (err) return done(err)
  513. this.rangesResponse = ranges
  514. return done()
  515. }
  516. )
  517. })
  518. it('should log the call with the correct number of changes', function () {
  519. return this.logger.debug
  520. .calledWith(`accepting ${this.change_ids.length} changes in ranges`)
  521. .should.equal(true)
  522. })
  523. it('should delegate the change removal to the ranges tracker', function () {
  524. return this.removeChangeIdsSpy
  525. .calledWith(this.change_ids)
  526. .should.equal(true)
  527. })
  528. it('should remove the changes', function () {
  529. return [1, 3, 4].map(
  530. i =>
  531. expect(
  532. this.rangesResponse.changes.find(
  533. change => change.id === this.ranges.changes[1].id
  534. )
  535. ).to.be.undefined
  536. )
  537. })
  538. it('should return the original number of changes minus the number of accepted changes', function () {
  539. return this.rangesResponse.changes.length.should.equal(
  540. this.ranges.changes.length - 3
  541. )
  542. })
  543. return it('should not touch other changes', function () {
  544. return [0, 2].map(i =>
  545. expect(
  546. this.rangesResponse.changes.find(
  547. change => change.id === this.ranges.changes[i].id
  548. )
  549. ).to.deep.equal(this.ranges.changes[i])
  550. )
  551. })
  552. })
  553. })
  554. })