RangesTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const async = require('async')
  4. const { db, ObjectId } = require('../../../app/js/mongodb')
  5. const MockWebApi = require('./helpers/MockWebApi')
  6. const DocUpdaterClient = require('./helpers/DocUpdaterClient')
  7. const DocUpdaterApp = require('./helpers/DocUpdaterApp')
  8. const RangesManager = require('../../../app/js/RangesManager')
  9. const sandbox = sinon.createSandbox()
  10. describe('Ranges', function () {
  11. before(function (done) {
  12. DocUpdaterApp.ensureRunning(done)
  13. })
  14. describe('tracking changes from ops', function () {
  15. before(function (done) {
  16. this.project_id = DocUpdaterClient.randomId()
  17. this.user_id = DocUpdaterClient.randomId()
  18. this.id_seed = '587357bd35e64f6157'
  19. this.doc = {
  20. id: DocUpdaterClient.randomId(),
  21. lines: ['aaa'],
  22. }
  23. this.updates = [
  24. {
  25. doc: this.doc.id,
  26. op: [{ i: '123', p: 1 }],
  27. v: 0,
  28. meta: { user_id: this.user_id },
  29. },
  30. {
  31. doc: this.doc.id,
  32. op: [{ i: '456', p: 5 }],
  33. v: 1,
  34. meta: { user_id: this.user_id, tc: this.id_seed },
  35. },
  36. {
  37. doc: this.doc.id,
  38. op: [{ d: '12', p: 1 }],
  39. v: 2,
  40. meta: { user_id: this.user_id },
  41. },
  42. ]
  43. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  44. lines: this.doc.lines,
  45. version: 0,
  46. })
  47. const jobs = []
  48. for (const update of this.updates) {
  49. jobs.push(callback =>
  50. DocUpdaterClient.sendUpdate(
  51. this.project_id,
  52. this.doc.id,
  53. update,
  54. callback
  55. )
  56. )
  57. }
  58. DocUpdaterApp.ensureRunning(error => {
  59. if (error != null) {
  60. throw error
  61. }
  62. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  63. if (error != null) {
  64. throw error
  65. }
  66. async.series(jobs, error => {
  67. if (error != null) {
  68. throw error
  69. }
  70. done()
  71. })
  72. })
  73. })
  74. })
  75. it('should update the ranges', function (done) {
  76. DocUpdaterClient.getDoc(
  77. this.project_id,
  78. this.doc.id,
  79. (error, res, data) => {
  80. if (error != null) {
  81. throw error
  82. }
  83. const { ranges } = data
  84. const change = ranges.changes[0]
  85. change.op.should.deep.equal({ i: '456', p: 3 })
  86. change.id.should.equal(this.id_seed + '000001')
  87. change.metadata.user_id.should.equal(this.user_id)
  88. done()
  89. }
  90. )
  91. })
  92. describe('Adding comments', function () {
  93. describe('standalone', function () {
  94. before(function (done) {
  95. this.project_id = DocUpdaterClient.randomId()
  96. this.user_id = DocUpdaterClient.randomId()
  97. this.doc = {
  98. id: DocUpdaterClient.randomId(),
  99. lines: ['foo bar baz'],
  100. }
  101. this.updates = [
  102. {
  103. doc: this.doc.id,
  104. op: [
  105. { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) },
  106. ],
  107. v: 0,
  108. },
  109. ]
  110. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  111. lines: this.doc.lines,
  112. version: 0,
  113. })
  114. const jobs = []
  115. for (const update of this.updates) {
  116. jobs.push(callback =>
  117. DocUpdaterClient.sendUpdate(
  118. this.project_id,
  119. this.doc.id,
  120. update,
  121. callback
  122. )
  123. )
  124. }
  125. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  126. if (error != null) {
  127. throw error
  128. }
  129. async.series(jobs, error => {
  130. if (error != null) {
  131. throw error
  132. }
  133. setTimeout(done, 200)
  134. })
  135. })
  136. })
  137. it('should update the ranges', function (done) {
  138. DocUpdaterClient.getDoc(
  139. this.project_id,
  140. this.doc.id,
  141. (error, res, data) => {
  142. if (error != null) {
  143. throw error
  144. }
  145. const { ranges } = data
  146. const comment = ranges.comments[0]
  147. comment.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid })
  148. comment.id.should.equal(this.tid)
  149. done()
  150. }
  151. )
  152. })
  153. })
  154. describe('with conflicting ops needing OT', function () {
  155. before(function (done) {
  156. this.project_id = DocUpdaterClient.randomId()
  157. this.user_id = DocUpdaterClient.randomId()
  158. this.doc = {
  159. id: DocUpdaterClient.randomId(),
  160. lines: ['foo bar baz'],
  161. }
  162. this.updates = [
  163. {
  164. doc: this.doc.id,
  165. op: [{ i: 'ABC', p: 3 }],
  166. v: 0,
  167. meta: { user_id: this.user_id },
  168. },
  169. {
  170. doc: this.doc.id,
  171. op: [
  172. { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) },
  173. ],
  174. v: 0,
  175. },
  176. ]
  177. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  178. lines: this.doc.lines,
  179. version: 0,
  180. })
  181. const jobs = []
  182. for (const update of this.updates) {
  183. jobs.push(callback =>
  184. DocUpdaterClient.sendUpdate(
  185. this.project_id,
  186. this.doc.id,
  187. update,
  188. callback
  189. )
  190. )
  191. }
  192. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  193. if (error != null) {
  194. throw error
  195. }
  196. async.series(jobs, error => {
  197. if (error != null) {
  198. throw error
  199. }
  200. setTimeout(done, 200)
  201. })
  202. })
  203. })
  204. it('should update the comments with the OT shifted comment', function (done) {
  205. DocUpdaterClient.getDoc(
  206. this.project_id,
  207. this.doc.id,
  208. (error, res, data) => {
  209. if (error != null) {
  210. throw error
  211. }
  212. const { ranges } = data
  213. const comment = ranges.comments[0]
  214. comment.op.should.deep.equal({ c: 'bar', p: 7, t: this.tid })
  215. done()
  216. }
  217. )
  218. })
  219. })
  220. })
  221. })
  222. describe('Loading ranges from persistence layer', function () {
  223. before(function (done) {
  224. this.project_id = DocUpdaterClient.randomId()
  225. this.user_id = DocUpdaterClient.randomId()
  226. this.id_seed = '587357bd35e64f6157'
  227. this.doc = {
  228. id: DocUpdaterClient.randomId(),
  229. lines: ['a123aa'],
  230. }
  231. this.update = {
  232. doc: this.doc.id,
  233. op: [{ i: '456', p: 5 }],
  234. v: 0,
  235. meta: { user_id: this.user_id, tc: this.id_seed },
  236. }
  237. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  238. lines: this.doc.lines,
  239. version: 0,
  240. ranges: {
  241. changes: [
  242. {
  243. op: { i: '123', p: 1 },
  244. metadata: {
  245. user_id: this.user_id,
  246. ts: new Date(),
  247. },
  248. },
  249. ],
  250. },
  251. })
  252. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  253. if (error != null) {
  254. throw error
  255. }
  256. DocUpdaterClient.sendUpdate(
  257. this.project_id,
  258. this.doc.id,
  259. this.update,
  260. error => {
  261. if (error != null) {
  262. throw error
  263. }
  264. setTimeout(done, 200)
  265. }
  266. )
  267. })
  268. })
  269. it('should have preloaded the existing ranges', function (done) {
  270. DocUpdaterClient.getDoc(
  271. this.project_id,
  272. this.doc.id,
  273. (error, res, data) => {
  274. if (error != null) {
  275. throw error
  276. }
  277. const { changes } = data.ranges
  278. changes[0].op.should.deep.equal({ i: '123', p: 1 })
  279. changes[1].op.should.deep.equal({ i: '456', p: 5 })
  280. done()
  281. }
  282. )
  283. })
  284. it('should flush the ranges to the persistence layer again', function (done) {
  285. DocUpdaterClient.flushDoc(this.project_id, this.doc.id, error => {
  286. if (error != null) {
  287. throw error
  288. }
  289. MockWebApi.getDocument(this.project_id, this.doc.id, (error, doc) => {
  290. if (error) return done(error)
  291. const { changes } = doc.ranges
  292. changes[0].op.should.deep.equal({ i: '123', p: 1 })
  293. changes[1].op.should.deep.equal({ i: '456', p: 5 })
  294. done()
  295. })
  296. })
  297. })
  298. })
  299. describe('accepting a change', function () {
  300. beforeEach(function (done) {
  301. sandbox.spy(MockWebApi, 'setDocument')
  302. this.project_id = DocUpdaterClient.randomId()
  303. this.user_id = DocUpdaterClient.randomId()
  304. this.id_seed = '587357bd35e64f6157'
  305. this.doc = {
  306. id: DocUpdaterClient.randomId(),
  307. lines: ['aaa'],
  308. }
  309. this.update = {
  310. doc: this.doc.id,
  311. op: [{ i: '456', p: 1 }],
  312. v: 0,
  313. meta: { user_id: this.user_id, tc: this.id_seed },
  314. }
  315. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  316. lines: this.doc.lines,
  317. version: 0,
  318. })
  319. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  320. if (error != null) {
  321. throw error
  322. }
  323. DocUpdaterClient.sendUpdate(
  324. this.project_id,
  325. this.doc.id,
  326. this.update,
  327. error => {
  328. if (error != null) {
  329. throw error
  330. }
  331. setTimeout(() => {
  332. DocUpdaterClient.getDoc(
  333. this.project_id,
  334. this.doc.id,
  335. (error, res, data) => {
  336. if (error != null) {
  337. throw error
  338. }
  339. const { ranges } = data
  340. const change = ranges.changes[0]
  341. change.op.should.deep.equal({ i: '456', p: 1 })
  342. change.id.should.equal(this.id_seed + '000001')
  343. change.metadata.user_id.should.equal(this.user_id)
  344. done()
  345. }
  346. )
  347. }, 200)
  348. }
  349. )
  350. })
  351. })
  352. afterEach(function () {
  353. sandbox.restore()
  354. })
  355. it('should remove the change after accepting', function (done) {
  356. DocUpdaterClient.acceptChange(
  357. this.project_id,
  358. this.doc.id,
  359. this.id_seed + '000001',
  360. error => {
  361. if (error != null) {
  362. throw error
  363. }
  364. DocUpdaterClient.getDoc(
  365. this.project_id,
  366. this.doc.id,
  367. (error, res, data) => {
  368. if (error != null) {
  369. throw error
  370. }
  371. expect(data.ranges.changes).to.be.undefined
  372. done()
  373. }
  374. )
  375. }
  376. )
  377. })
  378. it('should persist the ranges after accepting', function (done) {
  379. DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => {
  380. if (err) return done(err)
  381. DocUpdaterClient.acceptChange(
  382. this.project_id,
  383. this.doc.id,
  384. this.id_seed + '000001',
  385. error => {
  386. if (error != null) {
  387. throw error
  388. }
  389. DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => {
  390. if (err) return done(err)
  391. DocUpdaterClient.getDoc(
  392. this.project_id,
  393. this.doc.id,
  394. (error, res, data) => {
  395. if (error != null) {
  396. throw error
  397. }
  398. expect(data.ranges.changes).to.be.undefined
  399. MockWebApi.setDocument
  400. .calledWith(this.project_id, this.doc.id, ['a456aa'], 1, {})
  401. .should.equal(true)
  402. done()
  403. }
  404. )
  405. })
  406. }
  407. )
  408. })
  409. })
  410. })
  411. describe('accepting multiple changes', function () {
  412. beforeEach(function (done) {
  413. this.getHistoryUpdatesSpy = sandbox.spy(
  414. RangesManager,
  415. 'getHistoryUpdatesForAcceptedChanges'
  416. )
  417. this.project_id = DocUpdaterClient.randomId()
  418. this.user_id = DocUpdaterClient.randomId()
  419. this.doc = {
  420. id: DocUpdaterClient.randomId(),
  421. lines: ['aaa', 'bbb', 'ccc', 'ddd', 'eee'],
  422. }
  423. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  424. lines: this.doc.lines,
  425. version: 0,
  426. historyRangesSupport: true,
  427. })
  428. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  429. if (error != null) {
  430. throw error
  431. }
  432. this.id_seed_1 = 'tc_1'
  433. this.id_seed_2 = 'tc_2'
  434. this.id_seed_3 = 'tc_3'
  435. this.updates = [
  436. {
  437. doc: this.doc.id,
  438. op: [{ d: 'bbb', p: 4 }],
  439. v: 0,
  440. meta: {
  441. user_id: this.user_id,
  442. tc: this.id_seed_1,
  443. },
  444. },
  445. {
  446. doc: this.doc.id,
  447. op: [{ d: 'ccc', p: 5 }],
  448. v: 1,
  449. meta: {
  450. user_id: this.user_id,
  451. tc: this.id_seed_2,
  452. },
  453. },
  454. {
  455. doc: this.doc.id,
  456. op: [{ d: 'ddd', p: 6 }],
  457. v: 2,
  458. meta: {
  459. user_id: this.user_id,
  460. tc: this.id_seed_3,
  461. },
  462. },
  463. ]
  464. DocUpdaterClient.sendUpdates(
  465. this.project_id,
  466. this.doc.id,
  467. this.updates,
  468. error => {
  469. if (error != null) {
  470. throw error
  471. }
  472. setTimeout(() => {
  473. DocUpdaterClient.getDoc(
  474. this.project_id,
  475. this.doc.id,
  476. (error, res, data) => {
  477. if (error != null) {
  478. throw error
  479. }
  480. const { ranges } = data
  481. const changeOps = ranges.changes
  482. .map(change => change.op)
  483. .flat()
  484. changeOps.should.deep.equal([
  485. { d: 'bbb', p: 4 },
  486. { d: 'ccc', p: 5 },
  487. { d: 'ddd', p: 6 },
  488. ])
  489. done()
  490. }
  491. )
  492. }, 200)
  493. }
  494. )
  495. })
  496. })
  497. afterEach(function () {
  498. sandbox.restore()
  499. })
  500. it('accepting changes in order', function (done) {
  501. DocUpdaterClient.acceptChanges(
  502. this.project_id,
  503. this.doc.id,
  504. [
  505. this.id_seed_1 + '000001',
  506. this.id_seed_2 + '000001',
  507. this.id_seed_3 + '000001',
  508. ],
  509. error => {
  510. if (error != null) {
  511. throw error
  512. }
  513. const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0]
  514. expect(historyUpdates[0]).to.deep.equal({
  515. doc: this.doc.id,
  516. meta: {
  517. pathname: '/a/b/c.tex',
  518. doc_length: 10,
  519. history_doc_length: 19,
  520. ts: historyUpdates[0].meta.ts,
  521. user_id: this.user_id,
  522. },
  523. op: [{ p: 4, d: 'bbb' }],
  524. })
  525. expect(historyUpdates[1]).to.deep.equal({
  526. doc: this.doc.id,
  527. meta: {
  528. pathname: '/a/b/c.tex',
  529. doc_length: 10,
  530. history_doc_length: 16,
  531. ts: historyUpdates[1].meta.ts,
  532. user_id: this.user_id,
  533. },
  534. op: [{ p: 5, d: 'ccc' }],
  535. })
  536. expect(historyUpdates[2]).to.deep.equal({
  537. doc: this.doc.id,
  538. meta: {
  539. pathname: '/a/b/c.tex',
  540. doc_length: 10,
  541. history_doc_length: 13,
  542. ts: historyUpdates[2].meta.ts,
  543. user_id: this.user_id,
  544. },
  545. op: [{ p: 6, d: 'ddd' }],
  546. })
  547. done()
  548. }
  549. )
  550. })
  551. it('accepting changes in reverse order', function (done) {
  552. DocUpdaterClient.acceptChanges(
  553. this.project_id,
  554. this.doc.id,
  555. [
  556. this.id_seed_3 + '000001',
  557. this.id_seed_2 + '000001',
  558. this.id_seed_1 + '000001',
  559. ],
  560. error => {
  561. if (error != null) {
  562. throw error
  563. }
  564. const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0]
  565. expect(historyUpdates[0]).to.deep.equal({
  566. doc: this.doc.id,
  567. meta: {
  568. pathname: '/a/b/c.tex',
  569. doc_length: 10,
  570. history_doc_length: 19,
  571. ts: historyUpdates[0].meta.ts,
  572. user_id: this.user_id,
  573. },
  574. op: [{ p: 4, d: 'bbb' }],
  575. })
  576. expect(historyUpdates[1]).to.deep.equal({
  577. doc: this.doc.id,
  578. meta: {
  579. pathname: '/a/b/c.tex',
  580. doc_length: 10,
  581. history_doc_length: 16,
  582. ts: historyUpdates[1].meta.ts,
  583. user_id: this.user_id,
  584. },
  585. op: [{ p: 5, d: 'ccc' }],
  586. })
  587. expect(historyUpdates[2]).to.deep.equal({
  588. doc: this.doc.id,
  589. meta: {
  590. pathname: '/a/b/c.tex',
  591. doc_length: 10,
  592. history_doc_length: 13,
  593. ts: historyUpdates[2].meta.ts,
  594. user_id: this.user_id,
  595. },
  596. op: [{ p: 6, d: 'ddd' }],
  597. })
  598. done()
  599. }
  600. )
  601. })
  602. })
  603. describe('deleting a comment range', function () {
  604. before(function (done) {
  605. this.project_id = DocUpdaterClient.randomId()
  606. this.user_id = DocUpdaterClient.randomId()
  607. this.doc = {
  608. id: DocUpdaterClient.randomId(),
  609. lines: ['foo bar'],
  610. }
  611. this.update = {
  612. doc: this.doc.id,
  613. op: [{ c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  614. v: 0,
  615. }
  616. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  617. lines: this.doc.lines,
  618. version: 0,
  619. })
  620. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  621. if (error != null) {
  622. throw error
  623. }
  624. DocUpdaterClient.sendUpdate(
  625. this.project_id,
  626. this.doc.id,
  627. this.update,
  628. error => {
  629. if (error != null) {
  630. throw error
  631. }
  632. setTimeout(() => {
  633. DocUpdaterClient.getDoc(
  634. this.project_id,
  635. this.doc.id,
  636. (error, res, data) => {
  637. if (error != null) {
  638. throw error
  639. }
  640. const { ranges } = data
  641. const change = ranges.comments[0]
  642. change.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid })
  643. change.id.should.equal(this.tid)
  644. done()
  645. }
  646. )
  647. }, 200)
  648. }
  649. )
  650. })
  651. })
  652. it('should remove the comment range', function (done) {
  653. DocUpdaterClient.removeComment(
  654. this.project_id,
  655. this.doc.id,
  656. this.tid,
  657. (error, res) => {
  658. if (error != null) {
  659. throw error
  660. }
  661. expect(res.statusCode).to.equal(204)
  662. DocUpdaterClient.getDoc(
  663. this.project_id,
  664. this.doc.id,
  665. (error, res, data) => {
  666. if (error != null) {
  667. throw error
  668. }
  669. expect(data.ranges.comments).to.be.undefined
  670. done()
  671. }
  672. )
  673. }
  674. )
  675. })
  676. })
  677. describe('tripping range size limit', function () {
  678. before(function (done) {
  679. this.project_id = DocUpdaterClient.randomId()
  680. this.user_id = DocUpdaterClient.randomId()
  681. this.id_seed = DocUpdaterClient.randomId()
  682. this.doc = {
  683. id: DocUpdaterClient.randomId(),
  684. lines: ['aaa'],
  685. }
  686. this.i = new Array(3 * 1024 * 1024).join('a')
  687. this.updates = [
  688. {
  689. doc: this.doc.id,
  690. op: [{ i: this.i, p: 1 }],
  691. v: 0,
  692. meta: { user_id: this.user_id, tc: this.id_seed },
  693. },
  694. ]
  695. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  696. lines: this.doc.lines,
  697. version: 0,
  698. })
  699. const jobs = []
  700. for (const update of this.updates) {
  701. jobs.push(callback =>
  702. DocUpdaterClient.sendUpdate(
  703. this.project_id,
  704. this.doc.id,
  705. update,
  706. callback
  707. )
  708. )
  709. }
  710. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  711. if (error != null) {
  712. throw error
  713. }
  714. async.series(jobs, error => {
  715. if (error != null) {
  716. throw error
  717. }
  718. setTimeout(done, 200)
  719. })
  720. })
  721. })
  722. it('should not update the ranges', function (done) {
  723. DocUpdaterClient.getDoc(
  724. this.project_id,
  725. this.doc.id,
  726. (error, res, data) => {
  727. if (error != null) {
  728. throw error
  729. }
  730. const { ranges } = data
  731. expect(ranges.changes).to.be.undefined
  732. done()
  733. }
  734. )
  735. })
  736. })
  737. describe('deleting text surrounding a comment', function () {
  738. before(function (done) {
  739. this.project_id = DocUpdaterClient.randomId()
  740. this.user_id = DocUpdaterClient.randomId()
  741. this.doc_id = DocUpdaterClient.randomId()
  742. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  743. lines: ['foo bar baz'],
  744. version: 0,
  745. ranges: {
  746. comments: [
  747. {
  748. op: {
  749. c: 'a',
  750. p: 5,
  751. tid: (this.tid = DocUpdaterClient.randomId()),
  752. },
  753. metadata: {
  754. user_id: this.user_id,
  755. ts: new Date(),
  756. },
  757. },
  758. ],
  759. },
  760. })
  761. this.updates = [
  762. {
  763. doc: this.doc_id,
  764. op: [{ d: 'foo ', p: 0 }],
  765. v: 0,
  766. meta: { user_id: this.user_id },
  767. },
  768. {
  769. doc: this.doc_id,
  770. op: [{ d: 'bar ', p: 0 }],
  771. v: 1,
  772. meta: { user_id: this.user_id },
  773. },
  774. ]
  775. const jobs = []
  776. for (const update of this.updates) {
  777. jobs.push(callback =>
  778. DocUpdaterClient.sendUpdate(
  779. this.project_id,
  780. this.doc_id,
  781. update,
  782. callback
  783. )
  784. )
  785. }
  786. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  787. if (error != null) {
  788. throw error
  789. }
  790. async.series(jobs, function (error) {
  791. if (error != null) {
  792. throw error
  793. }
  794. setTimeout(() => {
  795. DocUpdaterClient.getDoc(
  796. this.project_id,
  797. this.doc_id,
  798. (error, res, data) => {
  799. if (error != null) {
  800. throw error
  801. }
  802. done()
  803. }
  804. )
  805. }, 200)
  806. })
  807. })
  808. })
  809. it('should write a snapshot from before the destructive change', function (done) {
  810. DocUpdaterClient.getDoc(
  811. this.project_id,
  812. this.doc_id,
  813. (error, res, data) => {
  814. if (error != null) {
  815. return done(error)
  816. }
  817. db.docSnapshots
  818. .find({
  819. project_id: new ObjectId(this.project_id),
  820. doc_id: new ObjectId(this.doc_id),
  821. })
  822. .toArray((error, docSnapshots) => {
  823. if (error != null) {
  824. return done(error)
  825. }
  826. expect(docSnapshots.length).to.equal(1)
  827. expect(docSnapshots[0].version).to.equal(1)
  828. expect(docSnapshots[0].lines).to.deep.equal(['bar baz'])
  829. expect(docSnapshots[0].ranges.comments[0].op).to.deep.equal({
  830. c: 'a',
  831. p: 1,
  832. tid: this.tid,
  833. })
  834. done()
  835. })
  836. }
  837. )
  838. })
  839. })
  840. })