RangesManagerTests.js 15 KB

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