RetryManagerTests.js 4.2 KB

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