RetryManagerTests.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import mongodb from 'mongodb-legacy'
  4. import { strict as esmock } from 'esmock'
  5. const { ObjectId } = mongodb
  6. const MODULE_PATH = '../../../../app/js/RetryManager.js'
  7. describe('RetryManager', function () {
  8. beforeEach(async function () {
  9. this.projectId1 = new ObjectId().toString()
  10. this.projectId2 = new ObjectId().toString()
  11. this.projectId3 = new ObjectId().toString()
  12. this.projectId4 = new ObjectId().toString()
  13. this.historyId = 12345
  14. this.WebApiManager = {
  15. promises: {
  16. getHistoryId: sinon.stub().resolves(this.historyId),
  17. },
  18. }
  19. this.RedisManager = {
  20. promises: {
  21. countUnprocessedUpdates: sinon.stub().resolves(0),
  22. },
  23. }
  24. this.ErrorRecorder = {
  25. promises: {
  26. getFailedProjects: sinon.stub().resolves([
  27. {
  28. project_id: this.projectId1,
  29. error: 'Error: Timeout',
  30. attempts: 1,
  31. },
  32. {
  33. project_id: this.projectId2,
  34. error: 'Error: Timeout',
  35. attempts: 25,
  36. },
  37. {
  38. project_id: this.projectId3,
  39. error: 'sync ongoing',
  40. attempts: 10,
  41. resyncAttempts: 1,
  42. },
  43. {
  44. project_id: this.projectId4,
  45. error: 'sync ongoing',
  46. attempts: 10,
  47. resyncAttempts: 2,
  48. },
  49. ]),
  50. getFailureRecord: sinon.stub().resolves(),
  51. },
  52. }
  53. this.SyncManager = {
  54. promises: {
  55. startResync: sinon.stub().resolves(),
  56. startHardResync: sinon.stub().resolves(),
  57. },
  58. }
  59. this.UpdatesProcessor = {
  60. promises: {
  61. processUpdatesForProject: sinon.stub().resolves(),
  62. },
  63. }
  64. this.settings = {
  65. redis: {
  66. lock: {
  67. key_schema: {
  68. projectHistoryLock({ projectId }) {
  69. return `ProjectHistoryLock:${projectId}`
  70. },
  71. },
  72. },
  73. },
  74. }
  75. this.request = {}
  76. this.RetryManager = await esmock(MODULE_PATH, {
  77. '../../../../app/js/WebApiManager.js': this.WebApiManager,
  78. '../../../../app/js/RedisManager.js': this.RedisManager,
  79. '../../../../app/js/ErrorRecorder.js': this.ErrorRecorder,
  80. '../../../../app/js/SyncManager.js': this.SyncManager,
  81. '../../../../app/js/UpdatesProcessor.js': this.UpdatesProcessor,
  82. '@overleaf/settings': this.settings,
  83. request: this.request,
  84. })
  85. })
  86. describe('RetryManager', function () {
  87. describe('for a soft failure', function () {
  88. beforeEach(async function () {
  89. await this.RetryManager.promises.retryFailures({ failureType: 'soft' })
  90. })
  91. it('should flush the queue', function () {
  92. expect(
  93. this.UpdatesProcessor.promises.processUpdatesForProject
  94. ).to.have.been.calledWith(this.projectId1)
  95. })
  96. })
  97. describe('for a hard failure', function () {
  98. beforeEach(async function () {
  99. await this.RetryManager.promises.retryFailures({ failureType: 'hard' })
  100. })
  101. it('should check the overleaf project id', function () {
  102. expect(
  103. this.WebApiManager.promises.getHistoryId
  104. ).to.have.been.calledWith(this.projectId2)
  105. })
  106. it("should start a soft resync when a resync hasn't been tried yet", function () {
  107. expect(this.SyncManager.promises.startResync).to.have.been.calledWith(
  108. this.projectId2
  109. )
  110. })
  111. it('should start a hard resync when a resync has already been tried', function () {
  112. expect(
  113. this.SyncManager.promises.startHardResync
  114. ).to.have.been.calledWith(this.projectId3)
  115. })
  116. it("shouldn't try a resync after a hard resync attempt failed", function () {
  117. expect(
  118. this.SyncManager.promises.startHardResync
  119. ).not.to.have.been.calledWith(this.projectId4)
  120. })
  121. it('should count the unprocessed updates', function () {
  122. expect(
  123. this.RedisManager.promises.countUnprocessedUpdates
  124. ).to.have.been.calledWith(this.projectId2)
  125. })
  126. it('should check the failure record', function () {
  127. expect(
  128. this.ErrorRecorder.promises.getFailureRecord
  129. ).to.have.been.calledWith(this.projectId2)
  130. })
  131. })
  132. })
  133. })