AppendingUpdatesTests.js 16 KB

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