RangesTests.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. /* eslint-disable
  2. no-unused-vars,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  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 async = require('async')
  16. const { db, ObjectId } = require('../../../app/js/mongodb')
  17. const MockWebApi = require('./helpers/MockWebApi')
  18. const DocUpdaterClient = require('./helpers/DocUpdaterClient')
  19. const DocUpdaterApp = require('./helpers/DocUpdaterApp')
  20. describe('Ranges', function () {
  21. before(function (done) {
  22. return DocUpdaterApp.ensureRunning(done)
  23. })
  24. describe('tracking changes from ops', function () {
  25. before(function (done) {
  26. this.project_id = DocUpdaterClient.randomId()
  27. this.user_id = DocUpdaterClient.randomId()
  28. this.id_seed = '587357bd35e64f6157'
  29. this.doc = {
  30. id: DocUpdaterClient.randomId(),
  31. lines: ['aaa'],
  32. }
  33. this.updates = [
  34. {
  35. doc: this.doc.id,
  36. op: [{ i: '123', p: 1 }],
  37. v: 0,
  38. meta: { user_id: this.user_id },
  39. },
  40. {
  41. doc: this.doc.id,
  42. op: [{ i: '456', p: 5 }],
  43. v: 1,
  44. meta: { user_id: this.user_id, tc: this.id_seed },
  45. },
  46. {
  47. doc: this.doc.id,
  48. op: [{ d: '12', p: 1 }],
  49. v: 2,
  50. meta: { user_id: this.user_id },
  51. },
  52. ]
  53. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  54. lines: this.doc.lines,
  55. version: 0,
  56. })
  57. const jobs = []
  58. for (const update of Array.from(this.updates)) {
  59. ;(update => {
  60. return jobs.push(callback =>
  61. DocUpdaterClient.sendUpdate(
  62. this.project_id,
  63. this.doc.id,
  64. update,
  65. callback
  66. )
  67. )
  68. })(update)
  69. }
  70. return DocUpdaterApp.ensureRunning(error => {
  71. if (error != null) {
  72. throw error
  73. }
  74. return DocUpdaterClient.preloadDoc(
  75. this.project_id,
  76. this.doc.id,
  77. error => {
  78. if (error != null) {
  79. throw error
  80. }
  81. return async.series(jobs, error => {
  82. if (error != null) {
  83. throw error
  84. }
  85. return done()
  86. })
  87. }
  88. )
  89. })
  90. })
  91. it('should update the ranges', function (done) {
  92. return DocUpdaterClient.getDoc(
  93. this.project_id,
  94. this.doc.id,
  95. (error, res, data) => {
  96. if (error != null) {
  97. throw error
  98. }
  99. const { ranges } = data
  100. const change = ranges.changes[0]
  101. change.op.should.deep.equal({ i: '456', p: 3 })
  102. change.id.should.equal(this.id_seed + '000001')
  103. change.metadata.user_id.should.equal(this.user_id)
  104. return done()
  105. }
  106. )
  107. })
  108. return describe('Adding comments', function () {
  109. describe('standalone', function () {
  110. before(function (done) {
  111. this.project_id = DocUpdaterClient.randomId()
  112. this.user_id = DocUpdaterClient.randomId()
  113. this.doc = {
  114. id: DocUpdaterClient.randomId(),
  115. lines: ['foo bar baz'],
  116. }
  117. this.updates = [
  118. {
  119. doc: this.doc.id,
  120. op: [
  121. { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) },
  122. ],
  123. v: 0,
  124. },
  125. ]
  126. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  127. lines: this.doc.lines,
  128. version: 0,
  129. })
  130. const jobs = []
  131. for (const update of Array.from(this.updates)) {
  132. ;(update => {
  133. return jobs.push(callback =>
  134. DocUpdaterClient.sendUpdate(
  135. this.project_id,
  136. this.doc.id,
  137. update,
  138. callback
  139. )
  140. )
  141. })(update)
  142. }
  143. return DocUpdaterClient.preloadDoc(
  144. this.project_id,
  145. this.doc.id,
  146. error => {
  147. if (error != null) {
  148. throw error
  149. }
  150. return async.series(jobs, error => {
  151. if (error != null) {
  152. throw error
  153. }
  154. return setTimeout(done, 200)
  155. })
  156. }
  157. )
  158. })
  159. return it('should update the ranges', function (done) {
  160. return DocUpdaterClient.getDoc(
  161. this.project_id,
  162. this.doc.id,
  163. (error, res, data) => {
  164. if (error != null) {
  165. throw error
  166. }
  167. const { ranges } = data
  168. const comment = ranges.comments[0]
  169. comment.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid })
  170. comment.id.should.equal(this.tid)
  171. return done()
  172. }
  173. )
  174. })
  175. })
  176. return describe('with conflicting ops needing OT', function () {
  177. before(function (done) {
  178. this.project_id = DocUpdaterClient.randomId()
  179. this.user_id = DocUpdaterClient.randomId()
  180. this.doc = {
  181. id: DocUpdaterClient.randomId(),
  182. lines: ['foo bar baz'],
  183. }
  184. this.updates = [
  185. {
  186. doc: this.doc.id,
  187. op: [{ i: 'ABC', p: 3 }],
  188. v: 0,
  189. meta: { user_id: this.user_id },
  190. },
  191. {
  192. doc: this.doc.id,
  193. op: [
  194. { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) },
  195. ],
  196. v: 0,
  197. },
  198. ]
  199. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  200. lines: this.doc.lines,
  201. version: 0,
  202. })
  203. const jobs = []
  204. for (const update of Array.from(this.updates)) {
  205. ;(update => {
  206. return jobs.push(callback =>
  207. DocUpdaterClient.sendUpdate(
  208. this.project_id,
  209. this.doc.id,
  210. update,
  211. callback
  212. )
  213. )
  214. })(update)
  215. }
  216. return DocUpdaterClient.preloadDoc(
  217. this.project_id,
  218. this.doc.id,
  219. error => {
  220. if (error != null) {
  221. throw error
  222. }
  223. return async.series(jobs, error => {
  224. if (error != null) {
  225. throw error
  226. }
  227. return setTimeout(done, 200)
  228. })
  229. }
  230. )
  231. })
  232. return it('should update the comments with the OT shifted comment', function (done) {
  233. return DocUpdaterClient.getDoc(
  234. this.project_id,
  235. this.doc.id,
  236. (error, res, data) => {
  237. if (error != null) {
  238. throw error
  239. }
  240. const { ranges } = data
  241. const comment = ranges.comments[0]
  242. comment.op.should.deep.equal({ c: 'bar', p: 7, t: this.tid })
  243. return done()
  244. }
  245. )
  246. })
  247. })
  248. })
  249. })
  250. describe('Loading ranges from persistence layer', function () {
  251. before(function (done) {
  252. this.project_id = DocUpdaterClient.randomId()
  253. this.user_id = DocUpdaterClient.randomId()
  254. this.id_seed = '587357bd35e64f6157'
  255. this.doc = {
  256. id: DocUpdaterClient.randomId(),
  257. lines: ['a123aa'],
  258. }
  259. this.update = {
  260. doc: this.doc.id,
  261. op: [{ i: '456', p: 5 }],
  262. v: 0,
  263. meta: { user_id: this.user_id, tc: this.id_seed },
  264. }
  265. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  266. lines: this.doc.lines,
  267. version: 0,
  268. ranges: {
  269. changes: [
  270. {
  271. op: { i: '123', p: 1 },
  272. metadata: {
  273. user_id: this.user_id,
  274. ts: new Date(),
  275. },
  276. },
  277. ],
  278. },
  279. })
  280. return DocUpdaterClient.preloadDoc(
  281. this.project_id,
  282. this.doc.id,
  283. error => {
  284. if (error != null) {
  285. throw error
  286. }
  287. return DocUpdaterClient.sendUpdate(
  288. this.project_id,
  289. this.doc.id,
  290. this.update,
  291. error => {
  292. if (error != null) {
  293. throw error
  294. }
  295. return setTimeout(done, 200)
  296. }
  297. )
  298. }
  299. )
  300. })
  301. it('should have preloaded the existing ranges', function (done) {
  302. return DocUpdaterClient.getDoc(
  303. this.project_id,
  304. this.doc.id,
  305. (error, res, data) => {
  306. if (error != null) {
  307. throw error
  308. }
  309. const { changes } = data.ranges
  310. changes[0].op.should.deep.equal({ i: '123', p: 1 })
  311. changes[1].op.should.deep.equal({ i: '456', p: 5 })
  312. return done()
  313. }
  314. )
  315. })
  316. return it('should flush the ranges to the persistence layer again', function (done) {
  317. return DocUpdaterClient.flushDoc(this.project_id, this.doc.id, error => {
  318. if (error != null) {
  319. throw error
  320. }
  321. return MockWebApi.getDocument(
  322. this.project_id,
  323. this.doc.id,
  324. (error, doc) => {
  325. if (error) return done(error)
  326. const { changes } = doc.ranges
  327. changes[0].op.should.deep.equal({ i: '123', p: 1 })
  328. changes[1].op.should.deep.equal({ i: '456', p: 5 })
  329. return done()
  330. }
  331. )
  332. })
  333. })
  334. })
  335. describe('accepting a change', function () {
  336. beforeEach(function (done) {
  337. sinon.spy(MockWebApi, 'setDocument')
  338. this.project_id = DocUpdaterClient.randomId()
  339. this.user_id = DocUpdaterClient.randomId()
  340. this.id_seed = '587357bd35e64f6157'
  341. this.doc = {
  342. id: DocUpdaterClient.randomId(),
  343. lines: ['aaa'],
  344. }
  345. this.update = {
  346. doc: this.doc.id,
  347. op: [{ i: '456', p: 1 }],
  348. v: 0,
  349. meta: { user_id: this.user_id, tc: this.id_seed },
  350. }
  351. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  352. lines: this.doc.lines,
  353. version: 0,
  354. })
  355. return DocUpdaterClient.preloadDoc(
  356. this.project_id,
  357. this.doc.id,
  358. error => {
  359. if (error != null) {
  360. throw error
  361. }
  362. return DocUpdaterClient.sendUpdate(
  363. this.project_id,
  364. this.doc.id,
  365. this.update,
  366. error => {
  367. if (error != null) {
  368. throw error
  369. }
  370. return setTimeout(() => {
  371. return DocUpdaterClient.getDoc(
  372. this.project_id,
  373. this.doc.id,
  374. (error, res, data) => {
  375. if (error != null) {
  376. throw error
  377. }
  378. const { ranges } = data
  379. const change = ranges.changes[0]
  380. change.op.should.deep.equal({ i: '456', p: 1 })
  381. change.id.should.equal(this.id_seed + '000001')
  382. change.metadata.user_id.should.equal(this.user_id)
  383. return done()
  384. }
  385. )
  386. }, 200)
  387. }
  388. )
  389. }
  390. )
  391. })
  392. afterEach(function () {
  393. MockWebApi.setDocument.restore()
  394. })
  395. it('should remove the change after accepting', function (done) {
  396. return DocUpdaterClient.acceptChange(
  397. this.project_id,
  398. this.doc.id,
  399. this.id_seed + '000001',
  400. error => {
  401. if (error != null) {
  402. throw error
  403. }
  404. return DocUpdaterClient.getDoc(
  405. this.project_id,
  406. this.doc.id,
  407. (error, res, data) => {
  408. if (error != null) {
  409. throw error
  410. }
  411. expect(data.ranges.changes).to.be.undefined
  412. return done()
  413. }
  414. )
  415. }
  416. )
  417. })
  418. it('should persist the ranges after accepting', function (done) {
  419. DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => {
  420. if (err) return done(err)
  421. DocUpdaterClient.acceptChange(
  422. this.project_id,
  423. this.doc.id,
  424. this.id_seed + '000001',
  425. error => {
  426. if (error != null) {
  427. throw error
  428. }
  429. DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => {
  430. if (err) return done(err)
  431. DocUpdaterClient.getDoc(
  432. this.project_id,
  433. this.doc.id,
  434. (error, res, data) => {
  435. if (error != null) {
  436. throw error
  437. }
  438. expect(data.ranges.changes).to.be.undefined
  439. MockWebApi.setDocument
  440. .calledWith(this.project_id, this.doc.id, ['a456aa'], 1, {})
  441. .should.equal(true)
  442. done()
  443. }
  444. )
  445. })
  446. }
  447. )
  448. })
  449. })
  450. })
  451. describe('deleting a comment range', function () {
  452. before(function (done) {
  453. this.project_id = DocUpdaterClient.randomId()
  454. this.user_id = DocUpdaterClient.randomId()
  455. this.doc = {
  456. id: DocUpdaterClient.randomId(),
  457. lines: ['foo bar'],
  458. }
  459. this.update = {
  460. doc: this.doc.id,
  461. op: [{ c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  462. v: 0,
  463. }
  464. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  465. lines: this.doc.lines,
  466. version: 0,
  467. })
  468. return DocUpdaterClient.preloadDoc(
  469. this.project_id,
  470. this.doc.id,
  471. error => {
  472. if (error != null) {
  473. throw error
  474. }
  475. return DocUpdaterClient.sendUpdate(
  476. this.project_id,
  477. this.doc.id,
  478. this.update,
  479. error => {
  480. if (error != null) {
  481. throw error
  482. }
  483. return setTimeout(() => {
  484. return DocUpdaterClient.getDoc(
  485. this.project_id,
  486. this.doc.id,
  487. (error, res, data) => {
  488. if (error != null) {
  489. throw error
  490. }
  491. const { ranges } = data
  492. const change = ranges.comments[0]
  493. change.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid })
  494. change.id.should.equal(this.tid)
  495. return done()
  496. }
  497. )
  498. }, 200)
  499. }
  500. )
  501. }
  502. )
  503. })
  504. return it('should remove the comment range', function (done) {
  505. return DocUpdaterClient.removeComment(
  506. this.project_id,
  507. this.doc.id,
  508. this.tid,
  509. (error, res) => {
  510. if (error != null) {
  511. throw error
  512. }
  513. expect(res.statusCode).to.equal(204)
  514. return DocUpdaterClient.getDoc(
  515. this.project_id,
  516. this.doc.id,
  517. (error, res, data) => {
  518. if (error != null) {
  519. throw error
  520. }
  521. expect(data.ranges.comments).to.be.undefined
  522. return done()
  523. }
  524. )
  525. }
  526. )
  527. })
  528. })
  529. describe('tripping range size limit', function () {
  530. before(function (done) {
  531. this.project_id = DocUpdaterClient.randomId()
  532. this.user_id = DocUpdaterClient.randomId()
  533. this.id_seed = DocUpdaterClient.randomId()
  534. this.doc = {
  535. id: DocUpdaterClient.randomId(),
  536. lines: ['aaa'],
  537. }
  538. this.i = new Array(3 * 1024 * 1024).join('a')
  539. this.updates = [
  540. {
  541. doc: this.doc.id,
  542. op: [{ i: this.i, p: 1 }],
  543. v: 0,
  544. meta: { user_id: this.user_id, tc: this.id_seed },
  545. },
  546. ]
  547. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  548. lines: this.doc.lines,
  549. version: 0,
  550. })
  551. const jobs = []
  552. for (const update of Array.from(this.updates)) {
  553. ;(update => {
  554. return jobs.push(callback =>
  555. DocUpdaterClient.sendUpdate(
  556. this.project_id,
  557. this.doc.id,
  558. update,
  559. callback
  560. )
  561. )
  562. })(update)
  563. }
  564. return DocUpdaterClient.preloadDoc(
  565. this.project_id,
  566. this.doc.id,
  567. error => {
  568. if (error != null) {
  569. throw error
  570. }
  571. return async.series(jobs, error => {
  572. if (error != null) {
  573. throw error
  574. }
  575. return setTimeout(done, 200)
  576. })
  577. }
  578. )
  579. })
  580. return it('should not update the ranges', function (done) {
  581. return DocUpdaterClient.getDoc(
  582. this.project_id,
  583. this.doc.id,
  584. (error, res, data) => {
  585. if (error != null) {
  586. throw error
  587. }
  588. const { ranges } = data
  589. expect(ranges.changes).to.be.undefined
  590. return done()
  591. }
  592. )
  593. })
  594. })
  595. return describe('deleting text surrounding a comment', function () {
  596. before(function (done) {
  597. this.project_id = DocUpdaterClient.randomId()
  598. this.user_id = DocUpdaterClient.randomId()
  599. this.doc_id = DocUpdaterClient.randomId()
  600. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  601. lines: ['foo bar baz'],
  602. version: 0,
  603. ranges: {
  604. comments: [
  605. {
  606. op: {
  607. c: 'a',
  608. p: 5,
  609. tid: (this.tid = DocUpdaterClient.randomId()),
  610. },
  611. metadata: {
  612. user_id: this.user_id,
  613. ts: new Date(),
  614. },
  615. },
  616. ],
  617. },
  618. })
  619. this.updates = [
  620. {
  621. doc: this.doc_id,
  622. op: [{ d: 'foo ', p: 0 }],
  623. v: 0,
  624. meta: { user_id: this.user_id },
  625. },
  626. {
  627. doc: this.doc_id,
  628. op: [{ d: 'bar ', p: 0 }],
  629. v: 1,
  630. meta: { user_id: this.user_id },
  631. },
  632. ]
  633. const jobs = []
  634. for (const update of Array.from(this.updates)) {
  635. ;(update => {
  636. return jobs.push(callback =>
  637. DocUpdaterClient.sendUpdate(
  638. this.project_id,
  639. this.doc_id,
  640. update,
  641. callback
  642. )
  643. )
  644. })(update)
  645. }
  646. return DocUpdaterClient.preloadDoc(
  647. this.project_id,
  648. this.doc_id,
  649. error => {
  650. if (error != null) {
  651. throw error
  652. }
  653. return async.series(jobs, function (error) {
  654. if (error != null) {
  655. throw error
  656. }
  657. return setTimeout(() => {
  658. return DocUpdaterClient.getDoc(
  659. this.project_id,
  660. this.doc_id,
  661. (error, res, data) => {
  662. if (error != null) {
  663. throw error
  664. }
  665. return done()
  666. }
  667. )
  668. }, 200)
  669. })
  670. }
  671. )
  672. })
  673. return it('should write a snapshot from before the destructive change', function (done) {
  674. return DocUpdaterClient.getDoc(
  675. this.project_id,
  676. this.doc_id,
  677. (error, res, data) => {
  678. if (error != null) {
  679. return done(error)
  680. }
  681. db.docSnapshots
  682. .find({
  683. project_id: ObjectId(this.project_id),
  684. doc_id: ObjectId(this.doc_id),
  685. })
  686. .toArray((error, docSnapshots) => {
  687. if (error != null) {
  688. return done(error)
  689. }
  690. expect(docSnapshots.length).to.equal(1)
  691. expect(docSnapshots[0].version).to.equal(1)
  692. expect(docSnapshots[0].lines).to.deep.equal(['bar baz'])
  693. expect(docSnapshots[0].ranges.comments[0].op).to.deep.equal({
  694. c: 'a',
  695. p: 1,
  696. tid: this.tid,
  697. })
  698. return done()
  699. })
  700. }
  701. )
  702. })
  703. })
  704. })