ShareJsUpdateManagerTests.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /* eslint-disable
  2. no-return-assign,
  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. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. const sinon = require('sinon')
  13. const modulePath = '../../../../app/js/ShareJsUpdateManager.js'
  14. const SandboxedModule = require('sandboxed-module')
  15. const crypto = require('crypto')
  16. describe('ShareJsUpdateManager', function () {
  17. beforeEach(function () {
  18. let Model
  19. this.project_id = 'project-id-123'
  20. this.doc_id = 'document-id-123'
  21. this.callback = sinon.stub()
  22. return (this.ShareJsUpdateManager = SandboxedModule.require(modulePath, {
  23. requires: {
  24. './sharejs/server/model': (Model = class Model {
  25. constructor(db) {
  26. this.db = db
  27. }
  28. }),
  29. './ShareJsDB': (this.ShareJsDB = { mockDB: true }),
  30. '@overleaf/redis-wrapper': {
  31. createClient: () => {
  32. return (this.rclient = { auth() {} })
  33. }
  34. },
  35. './RealTimeRedisManager': (this.RealTimeRedisManager = {}),
  36. './Metrics': (this.metrics = { inc: sinon.stub() })
  37. },
  38. globals: {
  39. clearTimeout: (this.clearTimeout = sinon.stub())
  40. }
  41. }))
  42. })
  43. describe('applyUpdate', function () {
  44. beforeEach(function () {
  45. this.lines = ['one', 'two']
  46. this.version = 34
  47. this.updatedDocLines = ['onefoo', 'two']
  48. const content = this.updatedDocLines.join('\n')
  49. this.hash = crypto
  50. .createHash('sha1')
  51. .update('blob ' + content.length + '\x00')
  52. .update(content, 'utf8')
  53. .digest('hex')
  54. this.update = { p: 4, t: 'foo', v: this.version, hash: this.hash }
  55. this.model = {
  56. applyOp: sinon.stub().callsArg(2),
  57. getSnapshot: sinon.stub(),
  58. db: {
  59. appliedOps: {}
  60. }
  61. }
  62. this.ShareJsUpdateManager.getNewShareJsModel = sinon
  63. .stub()
  64. .returns(this.model)
  65. this.ShareJsUpdateManager._listenForOps = sinon.stub()
  66. return (this.ShareJsUpdateManager.removeDocFromCache = sinon
  67. .stub()
  68. .callsArg(1))
  69. })
  70. describe('successfully', function () {
  71. beforeEach(function (done) {
  72. this.model.getSnapshot.callsArgWith(1, null, {
  73. snapshot: this.updatedDocLines.join('\n'),
  74. v: this.version
  75. })
  76. this.model.db.appliedOps[
  77. `${this.project_id}:${this.doc_id}`
  78. ] = this.appliedOps = ['mock-ops']
  79. return this.ShareJsUpdateManager.applyUpdate(
  80. this.project_id,
  81. this.doc_id,
  82. this.update,
  83. this.lines,
  84. this.version,
  85. (err, docLines, version, appliedOps) => {
  86. this.callback(err, docLines, version, appliedOps)
  87. return done()
  88. }
  89. )
  90. })
  91. it('should create a new ShareJs model', function () {
  92. return this.ShareJsUpdateManager.getNewShareJsModel
  93. .calledWith(this.project_id, this.doc_id, this.lines, this.version)
  94. .should.equal(true)
  95. })
  96. it('should listen for ops on the model', function () {
  97. return this.ShareJsUpdateManager._listenForOps
  98. .calledWith(this.model)
  99. .should.equal(true)
  100. })
  101. it('should send the update to ShareJs', function () {
  102. return this.model.applyOp
  103. .calledWith(`${this.project_id}:${this.doc_id}`, this.update)
  104. .should.equal(true)
  105. })
  106. it('should get the updated doc lines', function () {
  107. return this.model.getSnapshot
  108. .calledWith(`${this.project_id}:${this.doc_id}`)
  109. .should.equal(true)
  110. })
  111. return it('should return the updated doc lines, version and ops', function () {
  112. return this.callback
  113. .calledWith(null, this.updatedDocLines, this.version, this.appliedOps)
  114. .should.equal(true)
  115. })
  116. })
  117. describe('when applyOp fails', function () {
  118. beforeEach(function (done) {
  119. this.error = new Error('Something went wrong')
  120. this.model.applyOp = sinon.stub().callsArgWith(2, this.error)
  121. return this.ShareJsUpdateManager.applyUpdate(
  122. this.project_id,
  123. this.doc_id,
  124. this.update,
  125. this.lines,
  126. this.version,
  127. (err, docLines, version) => {
  128. this.callback(err, docLines, version)
  129. return done()
  130. }
  131. )
  132. })
  133. return it('should call the callback with the error', function () {
  134. return this.callback.calledWith(this.error).should.equal(true)
  135. })
  136. })
  137. describe('when getSnapshot fails', function () {
  138. beforeEach(function (done) {
  139. this.error = new Error('Something went wrong')
  140. this.model.getSnapshot.callsArgWith(1, this.error)
  141. return this.ShareJsUpdateManager.applyUpdate(
  142. this.project_id,
  143. this.doc_id,
  144. this.update,
  145. this.lines,
  146. this.version,
  147. (err, docLines, version) => {
  148. this.callback(err, docLines, version)
  149. return done()
  150. }
  151. )
  152. })
  153. return it('should call the callback with the error', function () {
  154. return this.callback.calledWith(this.error).should.equal(true)
  155. })
  156. })
  157. return describe('with an invalid hash', function () {
  158. beforeEach(function (done) {
  159. this.error = new Error('invalid hash')
  160. this.model.getSnapshot.callsArgWith(1, null, {
  161. snapshot: 'unexpected content',
  162. v: this.version
  163. })
  164. this.model.db.appliedOps[
  165. `${this.project_id}:${this.doc_id}`
  166. ] = this.appliedOps = ['mock-ops']
  167. return this.ShareJsUpdateManager.applyUpdate(
  168. this.project_id,
  169. this.doc_id,
  170. this.update,
  171. this.lines,
  172. this.version,
  173. (err, docLines, version, appliedOps) => {
  174. this.callback(err, docLines, version, appliedOps)
  175. return done()
  176. }
  177. )
  178. })
  179. return it('should call the callback with the error', function () {
  180. return this.callback
  181. .calledWith(sinon.match.instanceOf(Error))
  182. .should.equal(true)
  183. })
  184. })
  185. })
  186. return describe('_listenForOps', function () {
  187. beforeEach(function () {
  188. this.model = {
  189. on: (event, callback) => {
  190. return (this.callback = callback)
  191. }
  192. }
  193. sinon.spy(this.model, 'on')
  194. return this.ShareJsUpdateManager._listenForOps(this.model)
  195. })
  196. it('should listen to the model for updates', function () {
  197. return this.model.on.calledWith('applyOp').should.equal(true)
  198. })
  199. return describe('the callback', function () {
  200. beforeEach(function () {
  201. this.opData = {
  202. op: { t: 'foo', p: 1 },
  203. meta: { source: 'bar' }
  204. }
  205. this.RealTimeRedisManager.sendData = sinon.stub()
  206. return this.callback(`${this.project_id}:${this.doc_id}`, this.opData)
  207. })
  208. return it('should publish the op to redis', function () {
  209. return this.RealTimeRedisManager.sendData
  210. .calledWith({
  211. project_id: this.project_id,
  212. doc_id: this.doc_id,
  213. op: this.opData
  214. })
  215. .should.equal(true)
  216. })
  217. })
  218. })
  219. })