WebApiManagerTests.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import { strict as esmock } from 'esmock'
  4. import { RequestFailedError } from '@overleaf/fetch-utils'
  5. const MODULE_PATH = '../../../../app/js/WebApiManager.js'
  6. describe('WebApiManager', function () {
  7. beforeEach(async function () {
  8. this.settings = {
  9. apis: {
  10. web: {
  11. url: 'http://example.com',
  12. user: 'overleaf',
  13. pass: 'password',
  14. },
  15. },
  16. }
  17. this.userId = 'mock-user-id'
  18. this.projectId = 'mock-project-id'
  19. this.project = { features: 'mock-features' }
  20. this.olProjectId = 12345
  21. this.Metrics = { inc: sinon.stub() }
  22. this.RedisManager = {
  23. promises: {
  24. getCachedHistoryId: sinon.stub(),
  25. setCachedHistoryId: sinon.stub().resolves(),
  26. },
  27. }
  28. this.FetchUtils = {
  29. fetchNothing: sinon.stub().resolves(),
  30. fetchJson: sinon.stub(),
  31. RequestFailedError,
  32. }
  33. this.WebApiManager = await esmock(MODULE_PATH, {
  34. '@overleaf/fetch-utils': this.FetchUtils,
  35. '@overleaf/settings': this.settings,
  36. '@overleaf/metrics': this.Metrics,
  37. '../../../../app/js/RedisManager.js': this.RedisManager,
  38. })
  39. this.WebApiManager.setRetryTimeoutMs(100)
  40. })
  41. describe('getHistoryId', function () {
  42. describe('when there is no cached value and the web request is successful', function () {
  43. beforeEach(function () {
  44. this.RedisManager.promises.getCachedHistoryId
  45. .withArgs(this.projectId) // first call, no cached value returned
  46. .onCall(0)
  47. .resolves(null)
  48. this.RedisManager.promises.getCachedHistoryId
  49. .withArgs(this.projectId) // subsequent calls, return cached value
  50. .resolves(this.olProjectId)
  51. this.RedisManager.promises.getCachedHistoryId
  52. .withArgs('mock-project-id-2') // no cached value for other project
  53. .resolves(null)
  54. this.FetchUtils.fetchJson.resolves({
  55. overleaf: { history: { id: this.olProjectId } },
  56. })
  57. })
  58. it('should only request project details once per project', async function () {
  59. for (let i = 0; i < 5; i++) {
  60. await this.WebApiManager.promises.getHistoryId(this.projectId)
  61. }
  62. this.FetchUtils.fetchJson.should.have.been.calledOnce
  63. await this.WebApiManager.promises.getHistoryId('mock-project-id-2')
  64. this.FetchUtils.fetchJson.should.have.been.calledTwice
  65. })
  66. it('should cache the history id', async function () {
  67. const olProjectId = await this.WebApiManager.promises.getHistoryId(
  68. this.projectId
  69. )
  70. this.RedisManager.promises.setCachedHistoryId
  71. .calledWith(this.projectId, olProjectId)
  72. .should.equal(true)
  73. })
  74. it("should return the project's history id", async function () {
  75. const olProjectId = await this.WebApiManager.promises.getHistoryId(
  76. this.projectId
  77. )
  78. expect(this.FetchUtils.fetchJson).to.have.been.calledWithMatch(
  79. `${this.settings.apis.web.url}/project/${this.projectId}/details`,
  80. {
  81. basicAuth: {
  82. user: this.settings.apis.web.user,
  83. password: this.settings.apis.web.pass,
  84. },
  85. }
  86. )
  87. expect(olProjectId).to.equal(this.olProjectId)
  88. })
  89. })
  90. describe('when the web API returns an error', function () {
  91. beforeEach(function () {
  92. this.error = new Error('something went wrong')
  93. this.FetchUtils.fetchJson.rejects(this.error)
  94. this.RedisManager.promises.getCachedHistoryId.resolves(null)
  95. })
  96. it('should throw an error', async function () {
  97. await expect(
  98. this.WebApiManager.promises.getHistoryId(this.projectId)
  99. ).to.be.rejectedWith(this.error)
  100. })
  101. })
  102. describe('when web returns a 404', function () {
  103. beforeEach(function () {
  104. this.FetchUtils.fetchJson.rejects(
  105. new RequestFailedError(
  106. 'http://some-url',
  107. {},
  108. { status: 404 },
  109. 'Not found'
  110. )
  111. )
  112. this.RedisManager.promises.getCachedHistoryId.resolves(null)
  113. })
  114. it('should throw an error', async function () {
  115. await expect(
  116. this.WebApiManager.promises.getHistoryId(this.projectId)
  117. ).to.be.rejectedWith('got a 404 from web api')
  118. })
  119. })
  120. describe('when web returns a failure error code', function () {
  121. beforeEach(function () {
  122. this.RedisManager.promises.getCachedHistoryId.resolves(null)
  123. this.FetchUtils.fetchJson.rejects(
  124. new RequestFailedError(
  125. 'http://some-url',
  126. {},
  127. { status: 500 },
  128. 'Error'
  129. )
  130. )
  131. })
  132. it('should throw an error', async function () {
  133. await expect(
  134. this.WebApiManager.promises.getHistoryId(this.projectId)
  135. ).to.be.rejectedWith(RequestFailedError)
  136. })
  137. })
  138. })
  139. })