RangesTests.js 19 KB

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