DispatchManagerTests.js 6.3 KB

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