DispatchManagerTests.js 6.2 KB

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