DispatchManagerTests.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /* eslint-disable
  2. handle-callback-err,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const sinon = require('sinon')
  15. const chai = require('chai')
  16. const should = chai.should()
  17. const modulePath = '../../../../app/js/DispatchManager.js'
  18. const SandboxedModule = require('sandboxed-module')
  19. const Errors = require('../../../../app/js/Errors.js')
  20. describe('DispatchManager', function () {
  21. beforeEach(function () {
  22. let Timer
  23. this.timeout(3000)
  24. this.DispatchManager = SandboxedModule.require(modulePath, {
  25. requires: {
  26. './UpdateManager': (this.UpdateManager = {}),
  27. 'logger-sharelatex': (this.logger = {
  28. log: sinon.stub(),
  29. error: sinon.stub(),
  30. warn: sinon.stub()
  31. }),
  32. 'settings-sharelatex': (this.settings = {
  33. redis: {
  34. documentupdater: {}
  35. }
  36. }),
  37. '@overleaf/redis-wrapper': (this.redis = {}),
  38. './RateLimitManager': {},
  39. './Errors': Errors,
  40. './Metrics': (this.Metrics = {
  41. Timer: (Timer = (function () {
  42. Timer = class Timer {
  43. static initClass() {
  44. this.prototype.done = sinon.stub()
  45. }
  46. }
  47. Timer.initClass()
  48. return Timer
  49. })())
  50. })
  51. }
  52. })
  53. this.callback = sinon.stub()
  54. return (this.RateLimiter = {
  55. run(task, cb) {
  56. return task(cb)
  57. }
  58. })
  59. }) // run task without rate limit
  60. return describe('each worker', function () {
  61. beforeEach(function () {
  62. this.client = { auth: sinon.stub() }
  63. this.redis.createClient = sinon.stub().returns(this.client)
  64. return (this.worker = this.DispatchManager.createDispatcher(
  65. this.RateLimiter,
  66. 0
  67. ))
  68. })
  69. it('should create a new redis client', function () {
  70. return this.redis.createClient.called.should.equal(true)
  71. })
  72. describe('_waitForUpdateThenDispatchWorker', function () {
  73. beforeEach(function () {
  74. this.project_id = 'project-id-123'
  75. this.doc_id = 'doc-id-123'
  76. this.doc_key = `${this.project_id}:${this.doc_id}`
  77. return (this.client.blpop = sinon
  78. .stub()
  79. .callsArgWith(2, null, ['pending-updates-list', this.doc_key]))
  80. })
  81. describe('in the normal case', function () {
  82. beforeEach(function () {
  83. this.UpdateManager.processOutstandingUpdatesWithLock = sinon
  84. .stub()
  85. .callsArg(2)
  86. return this.worker._waitForUpdateThenDispatchWorker(this.callback)
  87. })
  88. it('should call redis with BLPOP', function () {
  89. return this.client.blpop
  90. .calledWith('pending-updates-list', 0)
  91. .should.equal(true)
  92. })
  93. it('should call processOutstandingUpdatesWithLock', function () {
  94. return this.UpdateManager.processOutstandingUpdatesWithLock
  95. .calledWith(this.project_id, this.doc_id)
  96. .should.equal(true)
  97. })
  98. it('should not log any errors', function () {
  99. this.logger.error.called.should.equal(false)
  100. return this.logger.warn.called.should.equal(false)
  101. })
  102. return it('should call the callback', function () {
  103. return this.callback.called.should.equal(true)
  104. })
  105. })
  106. describe('with an error', function () {
  107. beforeEach(function () {
  108. this.UpdateManager.processOutstandingUpdatesWithLock = sinon
  109. .stub()
  110. .callsArgWith(2, new Error('a generic error'))
  111. return this.worker._waitForUpdateThenDispatchWorker(this.callback)
  112. })
  113. it('should log an error', function () {
  114. return this.logger.error.called.should.equal(true)
  115. })
  116. return it('should call the callback', function () {
  117. return this.callback.called.should.equal(true)
  118. })
  119. })
  120. describe("with a 'Delete component' error", function () {
  121. beforeEach(function () {
  122. this.UpdateManager.processOutstandingUpdatesWithLock = sinon
  123. .stub()
  124. .callsArgWith(2, new Errors.DeleteMismatchError())
  125. return this.worker._waitForUpdateThenDispatchWorker(this.callback)
  126. })
  127. it('should log a warning', function () {
  128. return this.logger.warn.called.should.equal(true)
  129. })
  130. return it('should call the callback', function () {
  131. return this.callback.called.should.equal(true)
  132. })
  133. })
  134. describe('pending updates list with shard key', function () {
  135. beforeEach(function (done) {
  136. this.client = {
  137. auth: sinon.stub(),
  138. blpop: sinon.stub().callsArgWith(2)
  139. }
  140. this.redis.createClient = sinon.stub().returns(this.client)
  141. this.queueShardNumber = 7
  142. this.worker = this.DispatchManager.createDispatcher(
  143. this.RateLimiter,
  144. this.queueShardNumber
  145. )
  146. this.worker._waitForUpdateThenDispatchWorker(done)
  147. })
  148. it('should call redis with BLPOP with the correct key', function () {
  149. this.client.blpop
  150. .calledWith(`pending-updates-list-${this.queueShardNumber}`, 0)
  151. .should.equal(true)
  152. })
  153. })
  154. })
  155. return describe('run', function () {
  156. return it('should call _waitForUpdateThenDispatchWorker until shutting down', function (done) {
  157. let callCount = 0
  158. this.worker._waitForUpdateThenDispatchWorker = (callback) => {
  159. if (callback == null) {
  160. callback = function (error) {}
  161. }
  162. callCount++
  163. if (callCount === 3) {
  164. this.settings.shuttingDown = true
  165. }
  166. return setTimeout(() => callback(), 10)
  167. }
  168. sinon.spy(this.worker, '_waitForUpdateThenDispatchWorker')
  169. this.worker.run()
  170. var checkStatus = () => {
  171. if (!this.settings.shuttingDown) {
  172. // retry until shutdown
  173. setTimeout(checkStatus, 100)
  174. } else {
  175. this.worker._waitForUpdateThenDispatchWorker.callCount.should.equal(
  176. 3
  177. )
  178. return done()
  179. }
  180. }
  181. return checkStatus()
  182. })
  183. })
  184. })
  185. })