RangesTests.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  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. DocUpdaterClient.enableHistoryRangesSupport(this.doc.id, (error, res) => {
  424. if (error) {
  425. throw error
  426. }
  427. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  428. lines: this.doc.lines,
  429. version: 0,
  430. })
  431. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  432. if (error != null) {
  433. throw error
  434. }
  435. this.id_seed_1 = 'tc_1'
  436. this.id_seed_2 = 'tc_2'
  437. this.id_seed_3 = 'tc_3'
  438. this.updates = [
  439. {
  440. doc: this.doc.id,
  441. op: [{ d: 'bbb', p: 4 }],
  442. v: 0,
  443. meta: {
  444. user_id: this.user_id,
  445. tc: this.id_seed_1,
  446. },
  447. },
  448. {
  449. doc: this.doc.id,
  450. op: [{ d: 'ccc', p: 5 }],
  451. v: 1,
  452. meta: {
  453. user_id: this.user_id,
  454. tc: this.id_seed_2,
  455. },
  456. },
  457. {
  458. doc: this.doc.id,
  459. op: [{ d: 'ddd', p: 6 }],
  460. v: 2,
  461. meta: {
  462. user_id: this.user_id,
  463. tc: this.id_seed_3,
  464. },
  465. },
  466. ]
  467. DocUpdaterClient.sendUpdates(
  468. this.project_id,
  469. this.doc.id,
  470. this.updates,
  471. error => {
  472. if (error != null) {
  473. throw error
  474. }
  475. setTimeout(() => {
  476. DocUpdaterClient.getDoc(
  477. this.project_id,
  478. this.doc.id,
  479. (error, res, data) => {
  480. if (error != null) {
  481. throw error
  482. }
  483. const { ranges } = data
  484. const changeOps = ranges.changes
  485. .map(change => change.op)
  486. .flat()
  487. changeOps.should.deep.equal([
  488. { d: 'bbb', p: 4 },
  489. { d: 'ccc', p: 5 },
  490. { d: 'ddd', p: 6 },
  491. ])
  492. done()
  493. }
  494. )
  495. }, 200)
  496. }
  497. )
  498. })
  499. })
  500. })
  501. afterEach(function () {
  502. sandbox.restore()
  503. })
  504. it('accepting changes in order', function (done) {
  505. DocUpdaterClient.acceptChanges(
  506. this.project_id,
  507. this.doc.id,
  508. [
  509. this.id_seed_1 + '000001',
  510. this.id_seed_2 + '000001',
  511. this.id_seed_3 + '000001',
  512. ],
  513. error => {
  514. if (error != null) {
  515. throw error
  516. }
  517. const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0]
  518. expect(historyUpdates[0]).to.deep.equal({
  519. doc: this.doc.id,
  520. meta: {
  521. pathname: '/a/b/c.tex',
  522. doc_length: 10,
  523. history_doc_length: 19,
  524. ts: historyUpdates[0].meta.ts,
  525. user_id: this.user_id,
  526. },
  527. op: [{ p: 4, d: 'bbb' }],
  528. })
  529. expect(historyUpdates[1]).to.deep.equal({
  530. doc: this.doc.id,
  531. meta: {
  532. pathname: '/a/b/c.tex',
  533. doc_length: 10,
  534. history_doc_length: 16,
  535. ts: historyUpdates[1].meta.ts,
  536. user_id: this.user_id,
  537. },
  538. op: [{ p: 5, d: 'ccc' }],
  539. })
  540. expect(historyUpdates[2]).to.deep.equal({
  541. doc: this.doc.id,
  542. meta: {
  543. pathname: '/a/b/c.tex',
  544. doc_length: 10,
  545. history_doc_length: 13,
  546. ts: historyUpdates[2].meta.ts,
  547. user_id: this.user_id,
  548. },
  549. op: [{ p: 6, d: 'ddd' }],
  550. })
  551. done()
  552. }
  553. )
  554. })
  555. it('accepting changes in reverse order', function (done) {
  556. DocUpdaterClient.acceptChanges(
  557. this.project_id,
  558. this.doc.id,
  559. [
  560. this.id_seed_3 + '000001',
  561. this.id_seed_2 + '000001',
  562. this.id_seed_1 + '000001',
  563. ],
  564. error => {
  565. if (error != null) {
  566. throw error
  567. }
  568. const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0]
  569. expect(historyUpdates[0]).to.deep.equal({
  570. doc: this.doc.id,
  571. meta: {
  572. pathname: '/a/b/c.tex',
  573. doc_length: 10,
  574. history_doc_length: 19,
  575. ts: historyUpdates[0].meta.ts,
  576. user_id: this.user_id,
  577. },
  578. op: [{ p: 4, d: 'bbb' }],
  579. })
  580. expect(historyUpdates[1]).to.deep.equal({
  581. doc: this.doc.id,
  582. meta: {
  583. pathname: '/a/b/c.tex',
  584. doc_length: 10,
  585. history_doc_length: 16,
  586. ts: historyUpdates[1].meta.ts,
  587. user_id: this.user_id,
  588. },
  589. op: [{ p: 5, d: 'ccc' }],
  590. })
  591. expect(historyUpdates[2]).to.deep.equal({
  592. doc: this.doc.id,
  593. meta: {
  594. pathname: '/a/b/c.tex',
  595. doc_length: 10,
  596. history_doc_length: 13,
  597. ts: historyUpdates[2].meta.ts,
  598. user_id: this.user_id,
  599. },
  600. op: [{ p: 6, d: 'ddd' }],
  601. })
  602. done()
  603. }
  604. )
  605. })
  606. })
  607. describe('deleting a comment range', function () {
  608. before(function (done) {
  609. this.project_id = DocUpdaterClient.randomId()
  610. this.user_id = DocUpdaterClient.randomId()
  611. this.doc = {
  612. id: DocUpdaterClient.randomId(),
  613. lines: ['foo bar'],
  614. }
  615. this.update = {
  616. doc: this.doc.id,
  617. op: [{ c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }],
  618. v: 0,
  619. }
  620. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  621. lines: this.doc.lines,
  622. version: 0,
  623. })
  624. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  625. if (error != null) {
  626. throw error
  627. }
  628. DocUpdaterClient.sendUpdate(
  629. this.project_id,
  630. this.doc.id,
  631. this.update,
  632. error => {
  633. if (error != null) {
  634. throw error
  635. }
  636. setTimeout(() => {
  637. DocUpdaterClient.getDoc(
  638. this.project_id,
  639. this.doc.id,
  640. (error, res, data) => {
  641. if (error != null) {
  642. throw error
  643. }
  644. const { ranges } = data
  645. const change = ranges.comments[0]
  646. change.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid })
  647. change.id.should.equal(this.tid)
  648. done()
  649. }
  650. )
  651. }, 200)
  652. }
  653. )
  654. })
  655. })
  656. it('should remove the comment range', function (done) {
  657. DocUpdaterClient.removeComment(
  658. this.project_id,
  659. this.doc.id,
  660. this.tid,
  661. (error, res) => {
  662. if (error != null) {
  663. throw error
  664. }
  665. expect(res.statusCode).to.equal(204)
  666. DocUpdaterClient.getDoc(
  667. this.project_id,
  668. this.doc.id,
  669. (error, res, data) => {
  670. if (error != null) {
  671. throw error
  672. }
  673. expect(data.ranges.comments).to.be.undefined
  674. done()
  675. }
  676. )
  677. }
  678. )
  679. })
  680. })
  681. describe('tripping range size limit', function () {
  682. before(function (done) {
  683. this.project_id = DocUpdaterClient.randomId()
  684. this.user_id = DocUpdaterClient.randomId()
  685. this.id_seed = DocUpdaterClient.randomId()
  686. this.doc = {
  687. id: DocUpdaterClient.randomId(),
  688. lines: ['aaa'],
  689. }
  690. this.i = new Array(3 * 1024 * 1024).join('a')
  691. this.updates = [
  692. {
  693. doc: this.doc.id,
  694. op: [{ i: this.i, p: 1 }],
  695. v: 0,
  696. meta: { user_id: this.user_id, tc: this.id_seed },
  697. },
  698. ]
  699. MockWebApi.insertDoc(this.project_id, this.doc.id, {
  700. lines: this.doc.lines,
  701. version: 0,
  702. })
  703. const jobs = []
  704. for (const update of this.updates) {
  705. jobs.push(callback =>
  706. DocUpdaterClient.sendUpdate(
  707. this.project_id,
  708. this.doc.id,
  709. update,
  710. callback
  711. )
  712. )
  713. }
  714. DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => {
  715. if (error != null) {
  716. throw error
  717. }
  718. async.series(jobs, error => {
  719. if (error != null) {
  720. throw error
  721. }
  722. setTimeout(done, 200)
  723. })
  724. })
  725. })
  726. it('should not update the ranges', function (done) {
  727. DocUpdaterClient.getDoc(
  728. this.project_id,
  729. this.doc.id,
  730. (error, res, data) => {
  731. if (error != null) {
  732. throw error
  733. }
  734. const { ranges } = data
  735. expect(ranges.changes).to.be.undefined
  736. done()
  737. }
  738. )
  739. })
  740. })
  741. describe('deleting text surrounding a comment', function () {
  742. before(function (done) {
  743. this.project_id = DocUpdaterClient.randomId()
  744. this.user_id = DocUpdaterClient.randomId()
  745. this.doc_id = DocUpdaterClient.randomId()
  746. MockWebApi.insertDoc(this.project_id, this.doc_id, {
  747. lines: ['foo bar baz'],
  748. version: 0,
  749. ranges: {
  750. comments: [
  751. {
  752. op: {
  753. c: 'a',
  754. p: 5,
  755. tid: (this.tid = DocUpdaterClient.randomId()),
  756. },
  757. metadata: {
  758. user_id: this.user_id,
  759. ts: new Date(),
  760. },
  761. },
  762. ],
  763. },
  764. })
  765. this.updates = [
  766. {
  767. doc: this.doc_id,
  768. op: [{ d: 'foo ', p: 0 }],
  769. v: 0,
  770. meta: { user_id: this.user_id },
  771. },
  772. {
  773. doc: this.doc_id,
  774. op: [{ d: 'bar ', p: 0 }],
  775. v: 1,
  776. meta: { user_id: this.user_id },
  777. },
  778. ]
  779. const jobs = []
  780. for (const update of this.updates) {
  781. jobs.push(callback =>
  782. DocUpdaterClient.sendUpdate(
  783. this.project_id,
  784. this.doc_id,
  785. update,
  786. callback
  787. )
  788. )
  789. }
  790. DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => {
  791. if (error != null) {
  792. throw error
  793. }
  794. async.series(jobs, function (error) {
  795. if (error != null) {
  796. throw error
  797. }
  798. setTimeout(() => {
  799. DocUpdaterClient.getDoc(
  800. this.project_id,
  801. this.doc_id,
  802. (error, res, data) => {
  803. if (error != null) {
  804. throw error
  805. }
  806. done()
  807. }
  808. )
  809. }, 200)
  810. })
  811. })
  812. })
  813. it('should write a snapshot from before the destructive change', function (done) {
  814. DocUpdaterClient.getDoc(
  815. this.project_id,
  816. this.doc_id,
  817. (error, res, data) => {
  818. if (error != null) {
  819. return done(error)
  820. }
  821. db.docSnapshots
  822. .find({
  823. project_id: new ObjectId(this.project_id),
  824. doc_id: new ObjectId(this.doc_id),
  825. })
  826. .toArray((error, docSnapshots) => {
  827. if (error != null) {
  828. return done(error)
  829. }
  830. expect(docSnapshots.length).to.equal(1)
  831. expect(docSnapshots[0].version).to.equal(1)
  832. expect(docSnapshots[0].lines).to.deep.equal(['bar baz'])
  833. expect(docSnapshots[0].ranges.comments[0].op).to.deep.equal({
  834. c: 'a',
  835. p: 1,
  836. tid: this.tid,
  837. })
  838. done()
  839. })
  840. }
  841. )
  842. })
  843. })
  844. })