ReceiveUpdateTests.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /* eslint-disable
  2. camelcase,
  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 { expect } = require('chai')
  14. const RealTimeClient = require('./helpers/RealTimeClient')
  15. const MockWebServer = require('./helpers/MockWebServer')
  16. const FixturesManager = require('./helpers/FixturesManager')
  17. const async = require('async')
  18. const settings = require('@overleaf/settings')
  19. const redis = require('@overleaf/redis-wrapper')
  20. const rclient = redis.createClient(settings.redis.pubsub)
  21. describe('receiveUpdate', function () {
  22. beforeEach(function (done) {
  23. this.lines = ['test', 'doc', 'lines']
  24. this.version = 42
  25. this.ops = ['mock', 'doc', 'ops']
  26. return async.series(
  27. [
  28. cb => {
  29. return FixturesManager.setUpProject(
  30. {
  31. privilegeLevel: 'owner',
  32. project: { name: 'Test Project' },
  33. },
  34. (error, { user_id, project_id }) => {
  35. if (error) return done(error)
  36. this.user_id = user_id
  37. this.project_id = project_id
  38. return cb()
  39. }
  40. )
  41. },
  42. cb => {
  43. return FixturesManager.setUpDoc(
  44. this.project_id,
  45. { lines: this.lines, version: this.version, ops: this.ops },
  46. (e, { doc_id }) => {
  47. this.doc_id = doc_id
  48. return cb(e)
  49. }
  50. )
  51. },
  52. cb => {
  53. this.clientA = RealTimeClient.connect()
  54. return this.clientA.on('connectionAccepted', cb)
  55. },
  56. cb => {
  57. this.clientB = RealTimeClient.connect()
  58. return this.clientB.on('connectionAccepted', cb)
  59. },
  60. cb => {
  61. return this.clientA.emit(
  62. 'joinProject',
  63. {
  64. project_id: this.project_id,
  65. },
  66. cb
  67. )
  68. },
  69. cb => {
  70. return this.clientA.emit('joinDoc', this.doc_id, cb)
  71. },
  72. cb => {
  73. return this.clientB.emit(
  74. 'joinProject',
  75. {
  76. project_id: this.project_id,
  77. },
  78. cb
  79. )
  80. },
  81. cb => {
  82. return this.clientB.emit('joinDoc', this.doc_id, cb)
  83. },
  84. cb => {
  85. return FixturesManager.setUpProject(
  86. {
  87. privilegeLevel: 'owner',
  88. project: { name: 'Test Project' },
  89. },
  90. (
  91. error,
  92. { user_id: user_id_second, project_id: project_id_second }
  93. ) => {
  94. if (error) return done(error)
  95. this.user_id_second = user_id_second
  96. this.project_id_second = project_id_second
  97. return cb()
  98. }
  99. )
  100. },
  101. cb => {
  102. return FixturesManager.setUpDoc(
  103. this.project_id_second,
  104. { lines: this.lines, version: this.version, ops: this.ops },
  105. (e, { doc_id: doc_id_second }) => {
  106. this.doc_id_second = doc_id_second
  107. return cb(e)
  108. }
  109. )
  110. },
  111. cb => {
  112. this.clientC = RealTimeClient.connect()
  113. return this.clientC.on('connectionAccepted', cb)
  114. },
  115. cb => {
  116. return this.clientC.emit(
  117. 'joinProject',
  118. {
  119. project_id: this.project_id_second,
  120. },
  121. cb
  122. )
  123. },
  124. cb => {
  125. return this.clientC.emit('joinDoc', this.doc_id_second, cb)
  126. },
  127. cb => {
  128. this.clientAUpdates = []
  129. this.clientA.on('otUpdateApplied', update =>
  130. this.clientAUpdates.push(update)
  131. )
  132. this.clientBUpdates = []
  133. this.clientB.on('otUpdateApplied', update =>
  134. this.clientBUpdates.push(update)
  135. )
  136. this.clientCUpdates = []
  137. this.clientC.on('otUpdateApplied', update =>
  138. this.clientCUpdates.push(update)
  139. )
  140. this.clientAErrors = []
  141. this.clientA.on('otUpdateError', error =>
  142. this.clientAErrors.push(error)
  143. )
  144. this.clientBErrors = []
  145. this.clientB.on('otUpdateError', error =>
  146. this.clientBErrors.push(error)
  147. )
  148. this.clientCErrors = []
  149. this.clientC.on('otUpdateError', error =>
  150. this.clientCErrors.push(error)
  151. )
  152. return cb()
  153. },
  154. ],
  155. done
  156. )
  157. })
  158. afterEach(function () {
  159. if (this.clientA != null) {
  160. this.clientA.disconnect()
  161. }
  162. if (this.clientB != null) {
  163. this.clientB.disconnect()
  164. }
  165. return this.clientC != null ? this.clientC.disconnect() : undefined
  166. })
  167. describe('with an update from clientA', function () {
  168. beforeEach(function (done) {
  169. this.update = {
  170. doc_id: this.doc_id,
  171. op: {
  172. meta: {
  173. source: this.clientA.publicId,
  174. },
  175. v: this.version,
  176. doc: this.doc_id,
  177. op: [{ i: 'foo', p: 50 }],
  178. },
  179. }
  180. rclient.publish('applied-ops', JSON.stringify(this.update))
  181. return setTimeout(done, 200)
  182. }) // Give clients time to get message
  183. it('should send the full op to clientB', function () {
  184. return this.clientBUpdates.should.deep.equal([this.update.op])
  185. })
  186. it('should send an ack to clientA', function () {
  187. return this.clientAUpdates.should.deep.equal([
  188. {
  189. v: this.version,
  190. doc: this.doc_id,
  191. },
  192. ])
  193. })
  194. return it('should send nothing to clientC', function () {
  195. return this.clientCUpdates.should.deep.equal([])
  196. })
  197. })
  198. describe('with an update from clientC', function () {
  199. beforeEach(function (done) {
  200. this.update = {
  201. doc_id: this.doc_id_second,
  202. op: {
  203. meta: {
  204. source: this.clientC.publicId,
  205. },
  206. v: this.version,
  207. doc: this.doc_id_second,
  208. op: [{ i: 'update from clientC', p: 50 }],
  209. },
  210. }
  211. rclient.publish('applied-ops', JSON.stringify(this.update))
  212. return setTimeout(done, 200)
  213. }) // Give clients time to get message
  214. it('should send nothing to clientA', function () {
  215. return this.clientAUpdates.should.deep.equal([])
  216. })
  217. it('should send nothing to clientB', function () {
  218. return this.clientBUpdates.should.deep.equal([])
  219. })
  220. return it('should send an ack to clientC', function () {
  221. return this.clientCUpdates.should.deep.equal([
  222. {
  223. v: this.version,
  224. doc: this.doc_id_second,
  225. },
  226. ])
  227. })
  228. })
  229. describe('with an update from a remote client for project 1', function () {
  230. beforeEach(function (done) {
  231. this.update = {
  232. doc_id: this.doc_id,
  233. op: {
  234. meta: {
  235. source: 'this-is-a-remote-client-id',
  236. },
  237. v: this.version,
  238. doc: this.doc_id,
  239. op: [{ i: 'foo', p: 50 }],
  240. },
  241. }
  242. rclient.publish('applied-ops', JSON.stringify(this.update))
  243. return setTimeout(done, 200)
  244. }) // Give clients time to get message
  245. it('should send the full op to clientA', function () {
  246. return this.clientAUpdates.should.deep.equal([this.update.op])
  247. })
  248. it('should send the full op to clientB', function () {
  249. return this.clientBUpdates.should.deep.equal([this.update.op])
  250. })
  251. return it('should send nothing to clientC', function () {
  252. return this.clientCUpdates.should.deep.equal([])
  253. })
  254. })
  255. describe('with an error for the first project', function () {
  256. beforeEach(function (done) {
  257. rclient.publish(
  258. 'applied-ops',
  259. JSON.stringify({
  260. doc_id: this.doc_id,
  261. error: (this.error = 'something went wrong'),
  262. })
  263. )
  264. return setTimeout(done, 200)
  265. }) // Give clients time to get message
  266. it('should send the error to the clients in the first project', function () {
  267. this.clientAErrors.should.deep.equal([this.error])
  268. return this.clientBErrors.should.deep.equal([this.error])
  269. })
  270. it('should not send any errors to the client in the second project', function () {
  271. return this.clientCErrors.should.deep.equal([])
  272. })
  273. it('should disconnect the clients of the first project', function () {
  274. this.clientA.socket.connected.should.equal(false)
  275. return this.clientB.socket.connected.should.equal(false)
  276. })
  277. return it('should not disconnect the client in the second project', function () {
  278. return this.clientC.socket.connected.should.equal(true)
  279. })
  280. })
  281. return describe('with an error for the second project', function () {
  282. beforeEach(function (done) {
  283. rclient.publish(
  284. 'applied-ops',
  285. JSON.stringify({
  286. doc_id: this.doc_id_second,
  287. error: (this.error = 'something went wrong'),
  288. })
  289. )
  290. return setTimeout(done, 200)
  291. }) // Give clients time to get message
  292. it('should not send any errors to the clients in the first project', function () {
  293. this.clientAErrors.should.deep.equal([])
  294. return this.clientBErrors.should.deep.equal([])
  295. })
  296. it('should send the error to the client in the second project', function () {
  297. return this.clientCErrors.should.deep.equal([this.error])
  298. })
  299. it('should not disconnect the clients of the first project', function () {
  300. this.clientA.socket.connected.should.equal(true)
  301. return this.clientB.socket.connected.should.equal(true)
  302. })
  303. return it('should disconnect the client in the second project', function () {
  304. return this.clientC.socket.connected.should.equal(false)
  305. })
  306. })
  307. })