AppendingUpdatesTests.js 16 KB

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