ShareJsUpdateManagerTests.js 7.4 KB

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