AppendingUpdatesTests.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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. * 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 chai = require('chai')
  15. chai.should()
  16. const { expect } = chai
  17. const { ObjectId } = require('../../../app/js/mongodb')
  18. const Settings = require('settings-sharelatex')
  19. const request = require('request')
  20. const rclient = require('redis').createClient(Settings.redis.history) // Only works locally for now
  21. const TrackChangesApp = require('./helpers/TrackChangesApp')
  22. const TrackChangesClient = require('./helpers/TrackChangesClient')
  23. const MockWebApi = require('./helpers/MockWebApi')
  24. describe('Appending doc ops to the history', function () {
  25. before(function (done) {
  26. return TrackChangesApp.ensureRunning(done)
  27. })
  28. describe('when the history does not exist yet', function () {
  29. before(function (done) {
  30. this.project_id = ObjectId().toString()
  31. this.doc_id = ObjectId().toString()
  32. this.user_id = ObjectId().toString()
  33. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  34. TrackChangesClient.pushRawUpdates(
  35. this.project_id,
  36. this.doc_id,
  37. [
  38. {
  39. op: [{ i: 'f', p: 3 }],
  40. meta: { ts: Date.now(), user_id: this.user_id },
  41. v: 3
  42. },
  43. {
  44. op: [{ i: 'o', p: 4 }],
  45. meta: { ts: Date.now(), user_id: this.user_id },
  46. v: 4
  47. },
  48. {
  49. op: [{ i: 'o', p: 5 }],
  50. meta: { ts: Date.now(), user_id: this.user_id },
  51. v: 5
  52. }
  53. ],
  54. (error) => {
  55. if (error != null) {
  56. throw error
  57. }
  58. return TrackChangesClient.flushAndGetCompressedUpdates(
  59. this.project_id,
  60. this.doc_id,
  61. (error, updates) => {
  62. this.updates = updates
  63. if (error != null) {
  64. throw error
  65. }
  66. return done()
  67. }
  68. )
  69. }
  70. )
  71. return null
  72. })
  73. it('should insert the compressed op into mongo', function () {
  74. return expect(this.updates[0].pack[0].op).to.deep.equal([
  75. {
  76. p: 3,
  77. i: 'foo'
  78. }
  79. ])
  80. })
  81. it('should insert the correct version number into mongo', function () {
  82. return expect(this.updates[0].v).to.equal(5)
  83. })
  84. it('should store the doc id', function () {
  85. return expect(this.updates[0].doc_id.toString()).to.equal(this.doc_id)
  86. })
  87. it('should store the project id', function () {
  88. return expect(this.updates[0].project_id.toString()).to.equal(
  89. this.project_id
  90. )
  91. })
  92. return it('should clear the doc from the DocsWithHistoryOps set', function (done) {
  93. rclient.sismember(
  94. `DocsWithHistoryOps:${this.project_id}`,
  95. this.doc_id,
  96. (error, member) => {
  97. member.should.equal(0)
  98. return done()
  99. }
  100. )
  101. return null
  102. })
  103. })
  104. describe('when the history has already been started', function () {
  105. beforeEach(function (done) {
  106. this.project_id = ObjectId().toString()
  107. this.doc_id = ObjectId().toString()
  108. this.user_id = ObjectId().toString()
  109. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  110. TrackChangesClient.pushRawUpdates(
  111. this.project_id,
  112. this.doc_id,
  113. [
  114. {
  115. op: [{ i: 'f', p: 3 }],
  116. meta: { ts: Date.now(), user_id: this.user_id },
  117. v: 3
  118. },
  119. {
  120. op: [{ i: 'o', p: 4 }],
  121. meta: { ts: Date.now(), user_id: this.user_id },
  122. v: 4
  123. },
  124. {
  125. op: [{ i: 'o', p: 5 }],
  126. meta: { ts: Date.now(), user_id: this.user_id },
  127. v: 5
  128. }
  129. ],
  130. (error) => {
  131. if (error != null) {
  132. throw error
  133. }
  134. return TrackChangesClient.flushAndGetCompressedUpdates(
  135. this.project_id,
  136. this.doc_id,
  137. (error, updates) => {
  138. if (error != null) {
  139. throw error
  140. }
  141. return done()
  142. }
  143. )
  144. }
  145. )
  146. return null
  147. })
  148. describe('when the updates are recent and from the same user', function () {
  149. beforeEach(function (done) {
  150. TrackChangesClient.pushRawUpdates(
  151. this.project_id,
  152. this.doc_id,
  153. [
  154. {
  155. op: [{ i: 'b', p: 6 }],
  156. meta: { ts: Date.now(), user_id: this.user_id },
  157. v: 6
  158. },
  159. {
  160. op: [{ i: 'a', p: 7 }],
  161. meta: { ts: Date.now(), user_id: this.user_id },
  162. v: 7
  163. },
  164. {
  165. op: [{ i: 'r', p: 8 }],
  166. meta: { ts: Date.now(), user_id: this.user_id },
  167. v: 8
  168. }
  169. ],
  170. (error) => {
  171. if (error != null) {
  172. throw error
  173. }
  174. return TrackChangesClient.flushAndGetCompressedUpdates(
  175. this.project_id,
  176. this.doc_id,
  177. (error, updates) => {
  178. this.updates = updates
  179. if (error != null) {
  180. throw error
  181. }
  182. return done()
  183. }
  184. )
  185. }
  186. )
  187. return null
  188. })
  189. it('should combine all the updates into one pack', function () {
  190. return expect(this.updates[0].pack[1].op).to.deep.equal([
  191. {
  192. p: 6,
  193. i: 'bar'
  194. }
  195. ])
  196. })
  197. return it('should insert the correct version number into mongo', function () {
  198. return expect(this.updates[0].v_end).to.equal(8)
  199. })
  200. })
  201. return describe('when the updates are far apart', function () {
  202. beforeEach(function (done) {
  203. const oneDay = 24 * 60 * 60 * 1000
  204. TrackChangesClient.pushRawUpdates(
  205. this.project_id,
  206. this.doc_id,
  207. [
  208. {
  209. op: [{ i: 'b', p: 6 }],
  210. meta: { ts: Date.now() + oneDay, user_id: this.user_id },
  211. v: 6
  212. },
  213. {
  214. op: [{ i: 'a', p: 7 }],
  215. meta: { ts: Date.now() + oneDay, user_id: this.user_id },
  216. v: 7
  217. },
  218. {
  219. op: [{ i: 'r', p: 8 }],
  220. meta: { ts: Date.now() + oneDay, user_id: this.user_id },
  221. v: 8
  222. }
  223. ],
  224. (error) => {
  225. if (error != null) {
  226. throw error
  227. }
  228. return TrackChangesClient.flushAndGetCompressedUpdates(
  229. this.project_id,
  230. this.doc_id,
  231. (error, updates) => {
  232. this.updates = updates
  233. if (error != null) {
  234. throw error
  235. }
  236. return done()
  237. }
  238. )
  239. }
  240. )
  241. return null
  242. })
  243. return it('should combine the updates into one pack', function () {
  244. expect(this.updates[0].pack[0].op).to.deep.equal([
  245. {
  246. p: 3,
  247. i: 'foo'
  248. }
  249. ])
  250. return expect(this.updates[0].pack[1].op).to.deep.equal([
  251. {
  252. p: 6,
  253. i: 'bar'
  254. }
  255. ])
  256. })
  257. })
  258. })
  259. describe('when the updates need processing in batches', function () {
  260. before(function (done) {
  261. this.project_id = ObjectId().toString()
  262. this.doc_id = ObjectId().toString()
  263. this.user_id = ObjectId().toString()
  264. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  265. const updates = []
  266. this.expectedOp = [{ p: 0, i: '' }]
  267. for (let i = 0; i <= 250; i++) {
  268. updates.push({
  269. op: [{ i: 'a', p: 0 }],
  270. meta: { ts: Date.now(), user_id: this.user_id },
  271. v: i
  272. })
  273. this.expectedOp[0].i = `a${this.expectedOp[0].i}`
  274. }
  275. TrackChangesClient.pushRawUpdates(
  276. this.project_id,
  277. this.doc_id,
  278. updates,
  279. (error) => {
  280. if (error != null) {
  281. throw error
  282. }
  283. return TrackChangesClient.flushAndGetCompressedUpdates(
  284. this.project_id,
  285. this.doc_id,
  286. (error, updates1) => {
  287. this.updates = updates1
  288. if (error != null) {
  289. throw error
  290. }
  291. return done()
  292. }
  293. )
  294. }
  295. )
  296. return null
  297. })
  298. it('should concat the compressed op into mongo', function () {
  299. return expect(this.updates[0].pack.length).to.deep.equal(3)
  300. }) // batch size is 100
  301. return it('should insert the correct version number into mongo', function () {
  302. return expect(this.updates[0].v_end).to.equal(250)
  303. })
  304. })
  305. describe('when there are multiple ops in each update', function () {
  306. before(function (done) {
  307. this.project_id = ObjectId().toString()
  308. this.doc_id = ObjectId().toString()
  309. this.user_id = ObjectId().toString()
  310. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  311. const oneDay = 24 * 60 * 60 * 1000
  312. TrackChangesClient.pushRawUpdates(
  313. this.project_id,
  314. this.doc_id,
  315. [
  316. {
  317. op: [
  318. { i: 'f', p: 3 },
  319. { i: 'o', p: 4 },
  320. { i: 'o', p: 5 }
  321. ],
  322. meta: { ts: Date.now(), user_id: this.user_id },
  323. v: 3
  324. },
  325. {
  326. op: [
  327. { i: 'b', p: 6 },
  328. { i: 'a', p: 7 },
  329. { i: 'r', p: 8 }
  330. ],
  331. meta: { ts: Date.now() + oneDay, user_id: this.user_id },
  332. v: 4
  333. }
  334. ],
  335. (error) => {
  336. if (error != null) {
  337. throw error
  338. }
  339. return TrackChangesClient.flushAndGetCompressedUpdates(
  340. this.project_id,
  341. this.doc_id,
  342. (error, updates) => {
  343. this.updates = updates
  344. if (error != null) {
  345. throw error
  346. }
  347. return done()
  348. }
  349. )
  350. }
  351. )
  352. return null
  353. })
  354. it('should insert the compressed ops into mongo', function () {
  355. expect(this.updates[0].pack[0].op).to.deep.equal([
  356. {
  357. p: 3,
  358. i: 'foo'
  359. }
  360. ])
  361. return expect(this.updates[0].pack[1].op).to.deep.equal([
  362. {
  363. p: 6,
  364. i: 'bar'
  365. }
  366. ])
  367. })
  368. return it('should insert the correct version numbers into mongo', function () {
  369. expect(this.updates[0].pack[0].v).to.equal(3)
  370. return expect(this.updates[0].pack[1].v).to.equal(4)
  371. })
  372. })
  373. describe('when there is a no-op update', function () {
  374. before(function (done) {
  375. this.project_id = ObjectId().toString()
  376. this.doc_id = ObjectId().toString()
  377. this.user_id = ObjectId().toString()
  378. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  379. const oneDay = 24 * 60 * 60 * 1000
  380. TrackChangesClient.pushRawUpdates(
  381. this.project_id,
  382. this.doc_id,
  383. [
  384. {
  385. op: [],
  386. meta: { ts: Date.now(), user_id: this.user_id },
  387. v: 3
  388. },
  389. {
  390. op: [{ i: 'foo', p: 3 }],
  391. meta: { ts: Date.now() + oneDay, user_id: this.user_id },
  392. v: 4
  393. }
  394. ],
  395. (error) => {
  396. if (error != null) {
  397. throw error
  398. }
  399. return TrackChangesClient.flushAndGetCompressedUpdates(
  400. this.project_id,
  401. this.doc_id,
  402. (error, updates) => {
  403. this.updates = updates
  404. if (error != null) {
  405. throw error
  406. }
  407. return done()
  408. }
  409. )
  410. }
  411. )
  412. return null
  413. })
  414. it('should insert the compressed no-op into mongo', function () {
  415. return expect(this.updates[0].pack[0].op).to.deep.equal([])
  416. })
  417. it('should insert the compressed next update into mongo', function () {
  418. return expect(this.updates[0].pack[1].op).to.deep.equal([
  419. {
  420. p: 3,
  421. i: 'foo'
  422. }
  423. ])
  424. })
  425. return it('should insert the correct version numbers into mongo', function () {
  426. expect(this.updates[0].pack[0].v).to.equal(3)
  427. return expect(this.updates[0].pack[1].v).to.equal(4)
  428. })
  429. })
  430. describe('when there is a comment update', function () {
  431. before(function (done) {
  432. this.project_id = ObjectId().toString()
  433. this.doc_id = ObjectId().toString()
  434. this.user_id = ObjectId().toString()
  435. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  436. TrackChangesClient.pushRawUpdates(
  437. this.project_id,
  438. this.doc_id,
  439. [
  440. {
  441. op: [
  442. { c: 'foo', p: 3 },
  443. { d: 'bar', p: 6 }
  444. ],
  445. meta: { ts: Date.now(), user_id: this.user_id },
  446. v: 3
  447. }
  448. ],
  449. (error) => {
  450. if (error != null) {
  451. throw error
  452. }
  453. return TrackChangesClient.flushAndGetCompressedUpdates(
  454. this.project_id,
  455. this.doc_id,
  456. (error, updates) => {
  457. this.updates = updates
  458. if (error != null) {
  459. throw error
  460. }
  461. return done()
  462. }
  463. )
  464. }
  465. )
  466. return null
  467. })
  468. it('should ignore the comment op', function () {
  469. return expect(this.updates[0].pack[0].op).to.deep.equal([
  470. { d: 'bar', p: 6 }
  471. ])
  472. })
  473. return it('should insert the correct version numbers into mongo', function () {
  474. return expect(this.updates[0].pack[0].v).to.equal(3)
  475. })
  476. })
  477. describe('when the project has versioning enabled', function () {
  478. before(function (done) {
  479. this.project_id = ObjectId().toString()
  480. this.doc_id = ObjectId().toString()
  481. this.user_id = ObjectId().toString()
  482. MockWebApi.projects[this.project_id] = { features: { versioning: true } }
  483. TrackChangesClient.pushRawUpdates(
  484. this.project_id,
  485. this.doc_id,
  486. [
  487. {
  488. op: [{ i: 'f', p: 3 }],
  489. meta: { ts: Date.now(), user_id: this.user_id },
  490. v: 3
  491. }
  492. ],
  493. (error) => {
  494. if (error != null) {
  495. throw error
  496. }
  497. return TrackChangesClient.flushAndGetCompressedUpdates(
  498. this.project_id,
  499. this.doc_id,
  500. (error, updates) => {
  501. this.updates = updates
  502. if (error != null) {
  503. throw error
  504. }
  505. return done()
  506. }
  507. )
  508. }
  509. )
  510. return null
  511. })
  512. return it('should not add a expiresAt entry in the update in mongo', function () {
  513. return expect(this.updates[0].expiresAt).to.be.undefined
  514. })
  515. })
  516. return describe('when the project does not have versioning enabled', function () {
  517. before(function (done) {
  518. this.project_id = ObjectId().toString()
  519. this.doc_id = ObjectId().toString()
  520. this.user_id = ObjectId().toString()
  521. MockWebApi.projects[this.project_id] = { features: { versioning: false } }
  522. TrackChangesClient.pushRawUpdates(
  523. this.project_id,
  524. this.doc_id,
  525. [
  526. {
  527. op: [{ i: 'f', p: 3 }],
  528. meta: { ts: Date.now(), user_id: this.user_id },
  529. v: 3
  530. }
  531. ],
  532. (error) => {
  533. if (error != null) {
  534. throw error
  535. }
  536. return TrackChangesClient.flushAndGetCompressedUpdates(
  537. this.project_id,
  538. this.doc_id,
  539. (error, updates) => {
  540. this.updates = updates
  541. if (error != null) {
  542. throw error
  543. }
  544. return done()
  545. }
  546. )
  547. }
  548. )
  549. return null
  550. })
  551. return it('should add a expiresAt entry in the update in mongo', function () {
  552. return expect(this.updates[0].expiresAt).to.exist
  553. })
  554. })
  555. })