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. sendCanaryAppliedOp: sinon.stub(),
  37. }),
  38. './Metrics': (this.metrics = { inc: sinon.stub() }),
  39. },
  40. globals: {
  41. clearTimeout: (this.clearTimeout = sinon.stub()),
  42. },
  43. }))
  44. })
  45. describe('applyUpdate', function () {
  46. beforeEach(function () {
  47. this.lines = ['one', 'two']
  48. this.version = 34
  49. this.updatedDocLines = ['onefoo', 'two']
  50. const content = this.updatedDocLines.join('\n')
  51. this.hash = crypto
  52. .createHash('sha1')
  53. .update('blob ' + content.length + '\x00')
  54. .update(content, 'utf8')
  55. .digest('hex')
  56. this.update = { p: 4, t: 'foo', v: this.version, hash: this.hash }
  57. this.model = {
  58. applyOp: sinon.stub().callsArg(2),
  59. getSnapshot: sinon.stub(),
  60. db: {
  61. appliedOps: {},
  62. },
  63. }
  64. this.ShareJsUpdateManager.getNewShareJsModel = sinon
  65. .stub()
  66. .returns(this.model)
  67. this.ShareJsUpdateManager._listenForOps = sinon.stub()
  68. return (this.ShareJsUpdateManager.removeDocFromCache = sinon
  69. .stub()
  70. .callsArg(1))
  71. })
  72. describe('successfully', function () {
  73. beforeEach(function (done) {
  74. this.model.getSnapshot.callsArgWith(1, null, {
  75. snapshot: this.updatedDocLines.join('\n'),
  76. v: this.version,
  77. })
  78. this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] =
  79. this.appliedOps = ['mock-ops']
  80. return this.ShareJsUpdateManager.applyUpdate(
  81. this.project_id,
  82. this.doc_id,
  83. this.update,
  84. this.lines,
  85. this.version,
  86. (err, docLines, version, appliedOps) => {
  87. this.callback(err, docLines, version, appliedOps)
  88. return done()
  89. }
  90. )
  91. })
  92. it('should create a new ShareJs model', function () {
  93. return this.ShareJsUpdateManager.getNewShareJsModel
  94. .calledWith(this.project_id, this.doc_id, this.lines, this.version)
  95. .should.equal(true)
  96. })
  97. it('should listen for ops on the model', function () {
  98. return this.ShareJsUpdateManager._listenForOps
  99. .calledWith(this.model)
  100. .should.equal(true)
  101. })
  102. it('should send the update to ShareJs', function () {
  103. return this.model.applyOp
  104. .calledWith(`${this.project_id}:${this.doc_id}`, this.update)
  105. .should.equal(true)
  106. })
  107. it('should get the updated doc lines', function () {
  108. return this.model.getSnapshot
  109. .calledWith(`${this.project_id}:${this.doc_id}`)
  110. .should.equal(true)
  111. })
  112. return it('should return the updated doc lines, version and ops', function () {
  113. return this.callback
  114. .calledWith(null, this.updatedDocLines, this.version, this.appliedOps)
  115. .should.equal(true)
  116. })
  117. })
  118. describe('when applyOp fails', function () {
  119. beforeEach(function (done) {
  120. this.error = new Error('Something went wrong')
  121. this.model.applyOp = sinon.stub().callsArgWith(2, this.error)
  122. return this.ShareJsUpdateManager.applyUpdate(
  123. this.project_id,
  124. this.doc_id,
  125. this.update,
  126. this.lines,
  127. this.version,
  128. (err, docLines, version) => {
  129. this.callback(err, docLines, version)
  130. return done()
  131. }
  132. )
  133. })
  134. return it('should call the callback with the error', function () {
  135. return this.callback.calledWith(this.error).should.equal(true)
  136. })
  137. })
  138. describe('when getSnapshot fails', function () {
  139. beforeEach(function (done) {
  140. this.error = new Error('Something went wrong')
  141. this.model.getSnapshot.callsArgWith(1, this.error)
  142. return this.ShareJsUpdateManager.applyUpdate(
  143. this.project_id,
  144. this.doc_id,
  145. this.update,
  146. this.lines,
  147. this.version,
  148. (err, docLines, version) => {
  149. this.callback(err, docLines, version)
  150. return done()
  151. }
  152. )
  153. })
  154. return it('should call the callback with the error', function () {
  155. return this.callback.calledWith(this.error).should.equal(true)
  156. })
  157. })
  158. return describe('with an invalid hash', function () {
  159. beforeEach(function (done) {
  160. this.error = new Error('invalid hash')
  161. this.model.getSnapshot.callsArgWith(1, null, {
  162. snapshot: 'unexpected content',
  163. v: this.version,
  164. })
  165. this.model.db.appliedOps[`${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. })