ShareJsUpdateManagerTests.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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[`${this.project_id}:${this.doc_id}`] =
  77. this.appliedOps = ['mock-ops']
  78. return this.ShareJsUpdateManager.applyUpdate(
  79. this.project_id,
  80. this.doc_id,
  81. this.update,
  82. this.lines,
  83. this.version,
  84. (err, docLines, version, appliedOps) => {
  85. this.callback(err, docLines, version, appliedOps)
  86. return done()
  87. }
  88. )
  89. })
  90. it('should create a new ShareJs model', function () {
  91. return this.ShareJsUpdateManager.getNewShareJsModel
  92. .calledWith(this.project_id, this.doc_id, this.lines, this.version)
  93. .should.equal(true)
  94. })
  95. it('should listen for ops on the model', function () {
  96. return this.ShareJsUpdateManager._listenForOps
  97. .calledWith(this.model)
  98. .should.equal(true)
  99. })
  100. it('should send the update to ShareJs', function () {
  101. return this.model.applyOp
  102. .calledWith(`${this.project_id}:${this.doc_id}`, this.update)
  103. .should.equal(true)
  104. })
  105. it('should get the updated doc lines', function () {
  106. return this.model.getSnapshot
  107. .calledWith(`${this.project_id}:${this.doc_id}`)
  108. .should.equal(true)
  109. })
  110. return it('should return the updated doc lines, version and ops', function () {
  111. return this.callback
  112. .calledWith(null, this.updatedDocLines, this.version, this.appliedOps)
  113. .should.equal(true)
  114. })
  115. })
  116. describe('when applyOp fails', function () {
  117. beforeEach(function (done) {
  118. this.error = new Error('Something went wrong')
  119. this.model.applyOp = sinon.stub().callsArgWith(2, this.error)
  120. return this.ShareJsUpdateManager.applyUpdate(
  121. this.project_id,
  122. this.doc_id,
  123. this.update,
  124. this.lines,
  125. this.version,
  126. (err, docLines, version) => {
  127. this.callback(err, docLines, version)
  128. return done()
  129. }
  130. )
  131. })
  132. return it('should call the callback with the error', function () {
  133. return this.callback.calledWith(this.error).should.equal(true)
  134. })
  135. })
  136. describe('when getSnapshot fails', function () {
  137. beforeEach(function (done) {
  138. this.error = new Error('Something went wrong')
  139. this.model.getSnapshot.callsArgWith(1, this.error)
  140. return this.ShareJsUpdateManager.applyUpdate(
  141. this.project_id,
  142. this.doc_id,
  143. this.update,
  144. this.lines,
  145. this.version,
  146. (err, docLines, version) => {
  147. this.callback(err, docLines, version)
  148. return done()
  149. }
  150. )
  151. })
  152. return it('should call the callback with the error', function () {
  153. return this.callback.calledWith(this.error).should.equal(true)
  154. })
  155. })
  156. return describe('with an invalid hash', function () {
  157. beforeEach(function (done) {
  158. this.error = new Error('invalid hash')
  159. this.model.getSnapshot.callsArgWith(1, null, {
  160. snapshot: 'unexpected content',
  161. v: this.version,
  162. })
  163. this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] =
  164. this.appliedOps = ['mock-ops']
  165. return this.ShareJsUpdateManager.applyUpdate(
  166. this.project_id,
  167. this.doc_id,
  168. this.update,
  169. this.lines,
  170. this.version,
  171. (err, docLines, version, appliedOps) => {
  172. this.callback(err, docLines, version, appliedOps)
  173. return done()
  174. }
  175. )
  176. })
  177. return it('should call the callback with the error', function () {
  178. return this.callback
  179. .calledWith(sinon.match.instanceOf(Error))
  180. .should.equal(true)
  181. })
  182. })
  183. })
  184. return describe('_listenForOps', function () {
  185. beforeEach(function () {
  186. this.model = {
  187. on: (event, callback) => {
  188. return (this.callback = callback)
  189. },
  190. }
  191. sinon.spy(this.model, 'on')
  192. return this.ShareJsUpdateManager._listenForOps(this.model)
  193. })
  194. it('should listen to the model for updates', function () {
  195. return this.model.on.calledWith('applyOp').should.equal(true)
  196. })
  197. return describe('the callback', function () {
  198. beforeEach(function () {
  199. this.opData = {
  200. op: { t: 'foo', p: 1 },
  201. meta: { source: 'bar' },
  202. }
  203. this.RealTimeRedisManager.sendData = sinon.stub()
  204. return this.callback(`${this.project_id}:${this.doc_id}`, this.opData)
  205. })
  206. return it('should publish the op to redis', function () {
  207. return this.RealTimeRedisManager.sendData
  208. .calledWith({
  209. project_id: this.project_id,
  210. doc_id: this.doc_id,
  211. op: this.opData,
  212. })
  213. .should.equal(true)
  214. })
  215. })
  216. })
  217. })