WebApiManagerTests.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import async from 'async'
  2. import sinon from 'sinon'
  3. import { expect } from 'chai'
  4. import { strict as esmock } from 'esmock'
  5. const MODULE_PATH = '../../../../app/js/WebApiManager.js'
  6. describe('WebApiManager', function () {
  7. beforeEach(async function () {
  8. this.request = sinon.stub()
  9. this.settings = {
  10. apis: {
  11. web: {
  12. url: 'http://example.com',
  13. user: 'overleaf',
  14. pass: 'password',
  15. },
  16. },
  17. }
  18. this.callback = sinon.stub()
  19. this.userId = 'mock-user-id'
  20. this.projectId = 'mock-project-id'
  21. this.project = { features: 'mock-features' }
  22. this.olProjectId = 12345
  23. this.Metrics = { inc: sinon.stub() }
  24. this.RedisManager = {
  25. getCachedHistoryId: sinon.stub(),
  26. setCachedHistoryId: sinon.stub().yields(),
  27. }
  28. this.WebApiManager = await esmock(MODULE_PATH, {
  29. requestretry: this.request,
  30. '@overleaf/settings': this.settings,
  31. '@overleaf/metrics': this.Metrics,
  32. '../../../../app/js/RedisManager.js': this.RedisManager,
  33. })
  34. })
  35. describe('getHistoryId', function () {
  36. describe('when there is no cached value and the web request is successful', function () {
  37. beforeEach(function () {
  38. this.RedisManager.getCachedHistoryId
  39. .withArgs(this.projectId) // first call, no cached value returned
  40. .onCall(0)
  41. .yields()
  42. this.RedisManager.getCachedHistoryId
  43. .withArgs(this.projectId) // subsequent calls, return cached value
  44. .yields(null, this.olProjectId)
  45. this.RedisManager.getCachedHistoryId
  46. .withArgs('mock-project-id-2') // no cached value for other project
  47. .yields()
  48. this.request.yields(
  49. null,
  50. { statusCode: 200 },
  51. { overleaf: { history: { id: this.olProjectId } } }
  52. )
  53. })
  54. it('should only request project details once per project', function (done) {
  55. async.times(
  56. 5,
  57. (n, cb) => {
  58. this.WebApiManager.getHistoryId(this.projectId, cb)
  59. },
  60. () => {
  61. this.request.callCount.should.equal(1)
  62. this.WebApiManager.getHistoryId('mock-project-id-2', () => {
  63. this.request.callCount.should.equal(2)
  64. done()
  65. })
  66. }
  67. )
  68. })
  69. it('should cache the history id', function (done) {
  70. this.WebApiManager.getHistoryId(
  71. this.projectId,
  72. (error, olProjectId) => {
  73. if (error) return done(error)
  74. this.RedisManager.setCachedHistoryId
  75. .calledWith(this.projectId, olProjectId)
  76. .should.equal(true)
  77. done()
  78. }
  79. )
  80. })
  81. it('should call the callback with the project', function (done) {
  82. this.WebApiManager.getHistoryId(
  83. this.projectId,
  84. (error, olProjectId) => {
  85. expect(error).to.be.null
  86. expect(
  87. this.request.calledWithMatch({
  88. method: 'GET',
  89. url: `${this.settings.apis.web.url}/project/${this.projectId}/details`,
  90. json: true,
  91. auth: {
  92. user: this.settings.apis.web.user,
  93. pass: this.settings.apis.web.pass,
  94. sendImmediately: true,
  95. },
  96. })
  97. ).to.be.true
  98. expect(olProjectId).to.equal(this.olProjectId)
  99. done()
  100. }
  101. )
  102. })
  103. })
  104. describe('when the web API returns an error', function () {
  105. beforeEach(function () {
  106. this.error = new Error('something went wrong')
  107. this.request.yields(this.error)
  108. this.RedisManager.getCachedHistoryId.yields()
  109. this.WebApiManager.getHistoryId(this.projectId, this.callback)
  110. })
  111. it('should return an error to the callback', function () {
  112. this.callback.calledWith(this.error).should.equal(true)
  113. })
  114. })
  115. describe('when web returns a 404', function () {
  116. beforeEach(function () {
  117. this.request.callsArgWith(1, null, { statusCode: 404 }, '')
  118. this.RedisManager.getCachedHistoryId.yields()
  119. this.WebApiManager.getHistoryId(this.projectId, this.callback)
  120. })
  121. it('should return the callback with an error', function () {
  122. this.callback
  123. .calledWith(sinon.match.has('message', 'got a 404 from web api'))
  124. .should.equal(true)
  125. })
  126. })
  127. describe('when web returns a failure error code', function () {
  128. beforeEach(function () {
  129. this.RedisManager.getCachedHistoryId.yields()
  130. this.request.callsArgWith(
  131. 1,
  132. null,
  133. { statusCode: 500, attempts: 42 },
  134. ''
  135. )
  136. this.WebApiManager.getHistoryId(this.projectId, this.callback)
  137. })
  138. it('should return the callback with an error', function () {
  139. this.callback
  140. .calledWith(
  141. sinon.match.has(
  142. 'message',
  143. 'web returned a non-success status code: 500 (attempts: 42)'
  144. )
  145. )
  146. .should.equal(true)
  147. })
  148. })
  149. })
  150. })