ApplyUpdateTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS201: Simplify complex destructure assignments
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const async = require('async')
  16. const { expect } = require('chai')
  17. const RealTimeClient = require('./helpers/RealTimeClient')
  18. const FixturesManager = require('./helpers/FixturesManager')
  19. const settings = require('@overleaf/settings')
  20. const redis = require('@overleaf/redis-wrapper')
  21. const rclient = redis.createClient(settings.redis.documentupdater)
  22. const redisSettings = settings.redis
  23. const PENDING_UPDATES_LIST_KEYS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(n => {
  24. let key = 'pending-updates-list'
  25. if (n !== 0) {
  26. key += `-${n}`
  27. }
  28. return key
  29. })
  30. function getPendingUpdatesList(cb) {
  31. Promise.all(PENDING_UPDATES_LIST_KEYS.map(key => rclient.lrange(key, 0, -1)))
  32. .then(results => {
  33. cb(
  34. null,
  35. results.reduce((acc, more) => {
  36. if (more.length) {
  37. acc.push(...more)
  38. }
  39. return acc
  40. }, [])
  41. )
  42. })
  43. .catch(cb)
  44. }
  45. function clearPendingUpdatesList(cb) {
  46. Promise.all(PENDING_UPDATES_LIST_KEYS.map(key => rclient.del(key)))
  47. .then(() => cb(null))
  48. .catch(cb)
  49. }
  50. describe('applyOtUpdate', function () {
  51. before(function () {
  52. return (this.update = {
  53. op: [{ i: 'foo', p: 42 }],
  54. })
  55. })
  56. describe('when authorized', function () {
  57. before(function (done) {
  58. return async.series(
  59. [
  60. cb => {
  61. return FixturesManager.setUpProject(
  62. {
  63. privilegeLevel: 'readAndWrite',
  64. },
  65. (e, { project_id, user_id }) => {
  66. this.project_id = project_id
  67. this.user_id = user_id
  68. return cb(e)
  69. }
  70. )
  71. },
  72. cb => {
  73. return FixturesManager.setUpDoc(
  74. this.project_id,
  75. { lines: this.lines, version: this.version, ops: this.ops },
  76. (e, { doc_id }) => {
  77. this.doc_id = doc_id
  78. return cb(e)
  79. }
  80. )
  81. },
  82. cb => {
  83. this.client = RealTimeClient.connect()
  84. return this.client.on('connectionAccepted', cb)
  85. },
  86. cb => {
  87. return this.client.emit(
  88. 'joinProject',
  89. { project_id: this.project_id },
  90. cb
  91. )
  92. },
  93. cb => {
  94. return this.client.emit('joinDoc', this.doc_id, cb)
  95. },
  96. cb => {
  97. return this.client.emit(
  98. 'applyOtUpdate',
  99. this.doc_id,
  100. this.update,
  101. cb
  102. )
  103. },
  104. ],
  105. done
  106. )
  107. })
  108. it('should push the doc into the pending updates list', function (done) {
  109. getPendingUpdatesList((error, ...rest) => {
  110. const [doc_id] = Array.from(rest[0])
  111. doc_id.should.equal(`${this.project_id}:${this.doc_id}`)
  112. return done()
  113. })
  114. return null
  115. })
  116. it('should push the update into redis', function (done) {
  117. rclient.lrange(
  118. redisSettings.documentupdater.key_schema.pendingUpdates({
  119. doc_id: this.doc_id,
  120. }),
  121. 0,
  122. -1,
  123. (error, ...rest) => {
  124. let [update] = Array.from(rest[0])
  125. update = JSON.parse(update)
  126. update.op.should.deep.equal(this.update.op)
  127. update.meta.should.deep.equal({
  128. source: this.client.publicId,
  129. user_id: this.user_id,
  130. })
  131. return done()
  132. }
  133. )
  134. return null
  135. })
  136. return after(function (done) {
  137. return async.series(
  138. [
  139. cb => clearPendingUpdatesList(cb),
  140. cb =>
  141. rclient.del(
  142. 'DocsWithPendingUpdates',
  143. `${this.project_id}:${this.doc_id}`,
  144. cb
  145. ),
  146. cb =>
  147. rclient.del(
  148. redisSettings.documentupdater.key_schema.pendingUpdates(
  149. this.doc_id
  150. ),
  151. cb
  152. ),
  153. ],
  154. done
  155. )
  156. })
  157. })
  158. describe('when authorized with a huge edit update', function () {
  159. before(function (done) {
  160. this.update = {
  161. op: {
  162. p: 12,
  163. t: 'update is too large'.repeat(1024 * 400), // >7MB
  164. },
  165. }
  166. return async.series(
  167. [
  168. cb => {
  169. return FixturesManager.setUpProject(
  170. {
  171. privilegeLevel: 'readAndWrite',
  172. },
  173. (e, { project_id, user_id }) => {
  174. this.project_id = project_id
  175. this.user_id = user_id
  176. return cb(e)
  177. }
  178. )
  179. },
  180. cb => {
  181. return FixturesManager.setUpDoc(
  182. this.project_id,
  183. { lines: this.lines, version: this.version, ops: this.ops },
  184. (e, { doc_id }) => {
  185. this.doc_id = doc_id
  186. return cb(e)
  187. }
  188. )
  189. },
  190. cb => {
  191. this.client = RealTimeClient.connect()
  192. this.client.on('connectionAccepted', cb)
  193. return this.client.on('otUpdateError', otUpdateError => {
  194. this.otUpdateError = otUpdateError
  195. })
  196. },
  197. cb => {
  198. return this.client.emit(
  199. 'joinProject',
  200. { project_id: this.project_id },
  201. cb
  202. )
  203. },
  204. cb => {
  205. return this.client.emit('joinDoc', this.doc_id, cb)
  206. },
  207. cb => {
  208. return this.client.emit(
  209. 'applyOtUpdate',
  210. this.doc_id,
  211. this.update,
  212. error => {
  213. this.error = error
  214. return cb()
  215. }
  216. )
  217. },
  218. ],
  219. done
  220. )
  221. })
  222. it('should not return an error', function () {
  223. return expect(this.error).to.not.exist
  224. })
  225. it('should send an otUpdateError to the client', function (done) {
  226. return setTimeout(() => {
  227. expect(this.otUpdateError).to.exist
  228. return done()
  229. }, 300)
  230. })
  231. it('should disconnect the client', function (done) {
  232. return setTimeout(() => {
  233. this.client.socket.connected.should.equal(false)
  234. return done()
  235. }, 300)
  236. })
  237. return it('should not put the update in redis', function (done) {
  238. rclient.llen(
  239. redisSettings.documentupdater.key_schema.pendingUpdates({
  240. doc_id: this.doc_id,
  241. }),
  242. (error, len) => {
  243. len.should.equal(0)
  244. return done()
  245. }
  246. )
  247. return null
  248. })
  249. })
  250. describe('when authorized to read-only with an edit update', function () {
  251. before(function (done) {
  252. return async.series(
  253. [
  254. cb => {
  255. return FixturesManager.setUpProject(
  256. {
  257. privilegeLevel: 'readOnly',
  258. },
  259. (e, { project_id, user_id }) => {
  260. this.project_id = project_id
  261. this.user_id = user_id
  262. return cb(e)
  263. }
  264. )
  265. },
  266. cb => {
  267. return FixturesManager.setUpDoc(
  268. this.project_id,
  269. { lines: this.lines, version: this.version, ops: this.ops },
  270. (e, { doc_id }) => {
  271. this.doc_id = doc_id
  272. return cb(e)
  273. }
  274. )
  275. },
  276. cb => {
  277. this.client = RealTimeClient.connect()
  278. return this.client.on('connectionAccepted', cb)
  279. },
  280. cb => {
  281. return this.client.emit(
  282. 'joinProject',
  283. { project_id: this.project_id },
  284. cb
  285. )
  286. },
  287. cb => {
  288. return this.client.emit('joinDoc', this.doc_id, cb)
  289. },
  290. cb => {
  291. return this.client.emit(
  292. 'applyOtUpdate',
  293. this.doc_id,
  294. this.update,
  295. error => {
  296. this.error = error
  297. return cb()
  298. }
  299. )
  300. },
  301. ],
  302. done
  303. )
  304. })
  305. it('should return an error', function () {
  306. return expect(this.error).to.exist
  307. })
  308. it('should disconnect the client', function (done) {
  309. return setTimeout(() => {
  310. this.client.socket.connected.should.equal(false)
  311. return done()
  312. }, 300)
  313. })
  314. return it('should not put the update in redis', function (done) {
  315. rclient.llen(
  316. redisSettings.documentupdater.key_schema.pendingUpdates({
  317. doc_id: this.doc_id,
  318. }),
  319. (error, len) => {
  320. len.should.equal(0)
  321. return done()
  322. }
  323. )
  324. return null
  325. })
  326. })
  327. return describe('when authorized to read-only with a comment update', function () {
  328. before(function (done) {
  329. this.comment_update = {
  330. op: [{ c: 'foo', p: 42 }],
  331. }
  332. return async.series(
  333. [
  334. cb => {
  335. return FixturesManager.setUpProject(
  336. {
  337. privilegeLevel: 'readOnly',
  338. },
  339. (e, { project_id, user_id }) => {
  340. this.project_id = project_id
  341. this.user_id = user_id
  342. return cb(e)
  343. }
  344. )
  345. },
  346. cb => {
  347. return FixturesManager.setUpDoc(
  348. this.project_id,
  349. { lines: this.lines, version: this.version, ops: this.ops },
  350. (e, { doc_id }) => {
  351. this.doc_id = doc_id
  352. return cb(e)
  353. }
  354. )
  355. },
  356. cb => {
  357. this.client = RealTimeClient.connect()
  358. return this.client.on('connectionAccepted', cb)
  359. },
  360. cb => {
  361. return this.client.emit(
  362. 'joinProject',
  363. { project_id: this.project_id },
  364. cb
  365. )
  366. },
  367. cb => {
  368. return this.client.emit('joinDoc', this.doc_id, cb)
  369. },
  370. cb => {
  371. return this.client.emit(
  372. 'applyOtUpdate',
  373. this.doc_id,
  374. this.comment_update,
  375. cb
  376. )
  377. },
  378. ],
  379. done
  380. )
  381. })
  382. it('should push the doc into the pending updates list', function (done) {
  383. getPendingUpdatesList((error, ...rest) => {
  384. const [doc_id] = Array.from(rest[0])
  385. doc_id.should.equal(`${this.project_id}:${this.doc_id}`)
  386. return done()
  387. })
  388. return null
  389. })
  390. it('should push the update into redis', function (done) {
  391. rclient.lrange(
  392. redisSettings.documentupdater.key_schema.pendingUpdates({
  393. doc_id: this.doc_id,
  394. }),
  395. 0,
  396. -1,
  397. (error, ...rest) => {
  398. let [update] = Array.from(rest[0])
  399. update = JSON.parse(update)
  400. update.op.should.deep.equal(this.comment_update.op)
  401. update.meta.should.deep.equal({
  402. source: this.client.publicId,
  403. user_id: this.user_id,
  404. })
  405. return done()
  406. }
  407. )
  408. return null
  409. })
  410. return after(function (done) {
  411. return async.series(
  412. [
  413. cb => clearPendingUpdatesList(cb),
  414. cb =>
  415. rclient.del(
  416. 'DocsWithPendingUpdates',
  417. `${this.project_id}:${this.doc_id}`,
  418. cb
  419. ),
  420. cb =>
  421. rclient.del(
  422. redisSettings.documentupdater.key_schema.pendingUpdates({
  423. doc_id: this.doc_id,
  424. }),
  425. cb
  426. ),
  427. ],
  428. done
  429. )
  430. })
  431. })
  432. })