EarlyDisconnect.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /* eslint-disable
  2. camelcase,
  3. no-return-assign,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const async = require('async')
  14. const { expect } = require('chai')
  15. const RealTimeClient = require('./helpers/RealTimeClient')
  16. const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer')
  17. const MockWebServer = require('./helpers/MockWebServer')
  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.pubsub)
  22. const rclientRT = redis.createClient(settings.redis.realtime)
  23. const KeysRT = settings.redis.realtime.key_schema
  24. describe('EarlyDisconnect', function () {
  25. before(function (done) {
  26. return MockDocUpdaterServer.run(done)
  27. })
  28. describe('when the client disconnects before joinProject completes', function () {
  29. before(function () {
  30. // slow down web-api requests to force the race condition
  31. let joinProject
  32. this.actualWebAPIjoinProject = joinProject = MockWebServer.joinProject
  33. return (MockWebServer.joinProject = (project_id, user_id, cb) =>
  34. setTimeout(() => joinProject(project_id, user_id, cb), 300))
  35. })
  36. after(function () {
  37. return (MockWebServer.joinProject = this.actualWebAPIjoinProject)
  38. })
  39. beforeEach(function (done) {
  40. return async.series(
  41. [
  42. (cb) => {
  43. return FixturesManager.setUpProject(
  44. {
  45. privilegeLevel: 'owner',
  46. project: {
  47. name: 'Test Project'
  48. }
  49. },
  50. (e, { project_id, user_id }) => {
  51. this.project_id = project_id
  52. this.user_id = user_id
  53. return cb()
  54. }
  55. )
  56. },
  57. (cb) => {
  58. this.clientA = RealTimeClient.connect()
  59. return this.clientA.on('connectionAccepted', cb)
  60. },
  61. (cb) => {
  62. this.clientA.emit(
  63. 'joinProject',
  64. { project_id: this.project_id },
  65. () => {}
  66. )
  67. // disconnect before joinProject completes
  68. this.clientA.on('disconnect', () => cb())
  69. return this.clientA.disconnect()
  70. },
  71. (cb) => {
  72. // wait for joinDoc and subscribe
  73. return setTimeout(cb, 500)
  74. }
  75. ],
  76. done
  77. )
  78. })
  79. // we can force the race condition, there is no need to repeat too often
  80. return Array.from(Array.from({ length: 5 }).map((_, i) => i + 1)).map(
  81. (attempt) =>
  82. it(`should not subscribe to the pub/sub channel anymore (race ${attempt})`, function (done) {
  83. rclient.pubsub('CHANNELS', (err, resp) => {
  84. if (err) {
  85. return done(err)
  86. }
  87. expect(resp).to.not.include(`editor-events:${this.project_id}`)
  88. return done()
  89. })
  90. return null
  91. })
  92. )
  93. })
  94. describe('when the client disconnects before joinDoc completes', function () {
  95. beforeEach(function (done) {
  96. return async.series(
  97. [
  98. (cb) => {
  99. return FixturesManager.setUpProject(
  100. {
  101. privilegeLevel: 'owner',
  102. project: {
  103. name: 'Test Project'
  104. }
  105. },
  106. (e, { project_id, user_id }) => {
  107. this.project_id = project_id
  108. this.user_id = user_id
  109. return cb()
  110. }
  111. )
  112. },
  113. (cb) => {
  114. this.clientA = RealTimeClient.connect()
  115. return this.clientA.on('connectionAccepted', cb)
  116. },
  117. (cb) => {
  118. return this.clientA.emit(
  119. 'joinProject',
  120. { project_id: this.project_id },
  121. (error, project, privilegeLevel, protocolVersion) => {
  122. this.project = project
  123. this.privilegeLevel = privilegeLevel
  124. this.protocolVersion = protocolVersion
  125. return cb(error)
  126. }
  127. )
  128. },
  129. (cb) => {
  130. return FixturesManager.setUpDoc(
  131. this.project_id,
  132. { lines: this.lines, version: this.version, ops: this.ops },
  133. (e, { doc_id }) => {
  134. this.doc_id = doc_id
  135. return cb(e)
  136. }
  137. )
  138. },
  139. (cb) => {
  140. this.clientA.emit('joinDoc', this.doc_id, () => {})
  141. // disconnect before joinDoc completes
  142. this.clientA.on('disconnect', () => cb())
  143. return this.clientA.disconnect()
  144. },
  145. (cb) => {
  146. // wait for subscribe and unsubscribe
  147. return setTimeout(cb, 100)
  148. }
  149. ],
  150. done
  151. )
  152. })
  153. // we can not force the race condition, so we have to try many times
  154. return Array.from(Array.from({ length: 20 }).map((_, i) => i + 1)).map(
  155. (attempt) =>
  156. it(`should not subscribe to the pub/sub channels anymore (race ${attempt})`, function (done) {
  157. rclient.pubsub('CHANNELS', (err, resp) => {
  158. if (err) {
  159. return done(err)
  160. }
  161. expect(resp).to.not.include(`editor-events:${this.project_id}`)
  162. return rclient.pubsub('CHANNELS', (err, resp) => {
  163. if (err) {
  164. return done(err)
  165. }
  166. expect(resp).to.not.include(`applied-ops:${this.doc_id}`)
  167. return done()
  168. })
  169. })
  170. return null
  171. })
  172. )
  173. })
  174. return describe('when the client disconnects before clientTracking.updatePosition starts', function () {
  175. beforeEach(function (done) {
  176. return async.series(
  177. [
  178. (cb) => {
  179. return FixturesManager.setUpProject(
  180. {
  181. privilegeLevel: 'owner',
  182. project: {
  183. name: 'Test Project'
  184. }
  185. },
  186. (e, { project_id, user_id }) => {
  187. this.project_id = project_id
  188. this.user_id = user_id
  189. return cb()
  190. }
  191. )
  192. },
  193. (cb) => {
  194. this.clientA = RealTimeClient.connect()
  195. return this.clientA.on('connectionAccepted', cb)
  196. },
  197. (cb) => {
  198. return this.clientA.emit(
  199. 'joinProject',
  200. { project_id: this.project_id },
  201. (error, project, privilegeLevel, protocolVersion) => {
  202. this.project = project
  203. this.privilegeLevel = privilegeLevel
  204. this.protocolVersion = protocolVersion
  205. return cb(error)
  206. }
  207. )
  208. },
  209. (cb) => {
  210. return FixturesManager.setUpDoc(
  211. this.project_id,
  212. { lines: this.lines, version: this.version, ops: this.ops },
  213. (e, { doc_id }) => {
  214. this.doc_id = doc_id
  215. return cb(e)
  216. }
  217. )
  218. },
  219. (cb) => {
  220. return this.clientA.emit('joinDoc', this.doc_id, cb)
  221. },
  222. (cb) => {
  223. this.clientA.emit(
  224. 'clientTracking.updatePosition',
  225. {
  226. row: 42,
  227. column: 36,
  228. doc_id: this.doc_id
  229. },
  230. () => {}
  231. )
  232. // disconnect before updateClientPosition completes
  233. this.clientA.on('disconnect', () => cb())
  234. return this.clientA.disconnect()
  235. },
  236. (cb) => {
  237. // wait for updateClientPosition
  238. return setTimeout(cb, 100)
  239. }
  240. ],
  241. done
  242. )
  243. })
  244. // we can not force the race condition, so we have to try many times
  245. return Array.from(Array.from({ length: 20 }).map((_, i) => i + 1)).map(
  246. (attempt) =>
  247. it(`should not show the client as connected (race ${attempt})`, function (done) {
  248. rclientRT.smembers(
  249. KeysRT.clientsInProject({ project_id: this.project_id }),
  250. (err, results) => {
  251. if (err) {
  252. return done(err)
  253. }
  254. expect(results).to.deep.equal([])
  255. return done()
  256. }
  257. )
  258. return null
  259. })
  260. )
  261. })
  262. })