ApplyUpdateTests.js 12 KB

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