WebApiManager.test.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { vi, describe, beforeEach, it } from 'vitest'
  2. import sinon from 'sinon'
  3. import { RequestFailedError } from '@overleaf/fetch-utils'
  4. const modulePath = '../../../app/js/WebApiManager.js'
  5. describe('WebApiManager', function () {
  6. beforeEach(async function (ctx) {
  7. ctx.project_id = 'project-id-123'
  8. ctx.user_id = 'user-id-123'
  9. ctx.user = { _id: ctx.user_id }
  10. ctx.callback = sinon.stub()
  11. ctx.fetchUtils = {
  12. fetchJson: sinon.stub(),
  13. RequestFailedError,
  14. }
  15. vi.doMock('@overleaf/fetch-utils', () => ctx.fetchUtils)
  16. vi.doMock('@overleaf/settings', () => ({
  17. default: (ctx.settings = {
  18. apis: {
  19. web: {
  20. url: 'http://web.example.com',
  21. user: 'username',
  22. pass: 'password',
  23. },
  24. },
  25. }),
  26. }))
  27. ctx.WebApiManager = (await import(modulePath)).default
  28. })
  29. describe('joinProject', function () {
  30. describe('successfully', function () {
  31. beforeEach(function (ctx) {
  32. ctx.response = {
  33. project: { name: 'Test project' },
  34. privilegeLevel: 'owner',
  35. isRestrictedUser: true,
  36. isTokenMember: true,
  37. isInvitedMember: true,
  38. }
  39. ctx.fetchUtils.fetchJson.resolves(ctx.response)
  40. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user, ctx.callback)
  41. })
  42. it('should send a request to web to join the project', function (ctx) {
  43. ctx.fetchUtils.fetchJson.should.have.been.calledWith(
  44. new URL(`/project/${ctx.project_id}/join`, ctx.settings.apis.web.url),
  45. {
  46. method: 'POST',
  47. basicAuth: {
  48. user: ctx.settings.apis.web.user,
  49. password: ctx.settings.apis.web.pass,
  50. },
  51. json: {
  52. userId: ctx.user_id,
  53. anonymousAccessToken: undefined,
  54. },
  55. }
  56. )
  57. })
  58. it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
  59. ctx.callback
  60. .calledWith(null, ctx.response.project, ctx.response.privilegeLevel, {
  61. isRestrictedUser: ctx.response.isRestrictedUser,
  62. isTokenMember: ctx.response.isTokenMember,
  63. isInvitedMember: ctx.response.isInvitedMember,
  64. })
  65. .should.equal(true)
  66. })
  67. })
  68. describe('with anon user', function () {
  69. beforeEach(function (ctx) {
  70. ctx.user_id = 'anonymous-user'
  71. ctx.token = 'a-ro-token'
  72. ctx.user = {
  73. _id: ctx.user_id,
  74. anonymousAccessToken: ctx.token,
  75. }
  76. ctx.response = {
  77. project: { name: 'Test project' },
  78. privilegeLevel: 'readOnly',
  79. isRestrictedUser: true,
  80. isTokenMember: false,
  81. isInvitedMember: false,
  82. }
  83. ctx.fetchUtils.fetchJson.resolves(ctx.response)
  84. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user, ctx.callback)
  85. })
  86. it('should send a request to web to join the project', function (ctx) {
  87. ctx.fetchUtils.fetchJson.should.have.been.calledWith(
  88. new URL(`/project/${ctx.project_id}/join`, ctx.settings.apis.web.url),
  89. {
  90. method: 'POST',
  91. basicAuth: {
  92. user: ctx.settings.apis.web.user,
  93. password: ctx.settings.apis.web.pass,
  94. },
  95. json: {
  96. userId: ctx.user_id,
  97. anonymousAccessToken: ctx.token,
  98. },
  99. }
  100. )
  101. })
  102. it('should return the project, privilegeLevel, and restricted flag', function (ctx) {
  103. ctx.callback.should.have.been.calledWith(
  104. null,
  105. ctx.response.project,
  106. ctx.response.privilegeLevel,
  107. {
  108. isRestrictedUser: ctx.response.isRestrictedUser,
  109. isTokenMember: ctx.response.isTokenMember,
  110. isInvitedMember: ctx.response.isInvitedMember,
  111. }
  112. )
  113. })
  114. })
  115. describe('when web replies with a 403', function () {
  116. beforeEach(function (ctx) {
  117. ctx.fetchUtils.fetchJson.rejects(
  118. new RequestFailedError(
  119. `/project/${ctx.project_id}/join`,
  120. { method: 'POST' },
  121. { status: 403 }
  122. )
  123. )
  124. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
  125. })
  126. it('should call the callback with an error', function (ctx) {
  127. ctx.callback
  128. .calledWith(
  129. sinon.match({
  130. message: 'not authorized',
  131. })
  132. )
  133. .should.equal(true)
  134. })
  135. })
  136. describe('when web replies with a 404', function () {
  137. beforeEach(function (ctx) {
  138. ctx.fetchUtils.fetchJson.rejects(
  139. new RequestFailedError(
  140. `/project/${ctx.project_id}/join`,
  141. { method: 'POST' },
  142. { status: 404 }
  143. )
  144. )
  145. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
  146. })
  147. it('should call the callback with an error', function (ctx) {
  148. ctx.callback
  149. .calledWith(
  150. sinon.match({
  151. message: 'project not found',
  152. info: { code: 'ProjectNotFound' },
  153. })
  154. )
  155. .should.equal(true)
  156. })
  157. })
  158. describe('with an error from web', function () {
  159. beforeEach(function (ctx) {
  160. ctx.fetchUtils.fetchJson.rejects(
  161. new RequestFailedError(
  162. `/project/${ctx.project_id}/join`,
  163. { method: 'POST' },
  164. { status: 500 }
  165. )
  166. )
  167. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
  168. })
  169. it('should call the callback with an error', function (ctx) {
  170. ctx.callback
  171. .calledWith(
  172. sinon.match({
  173. message: 'non-success status code from web',
  174. info: { statusCode: 500 },
  175. })
  176. )
  177. .should.equal(true)
  178. })
  179. })
  180. describe('with no data from web', function () {
  181. beforeEach(function (ctx) {
  182. ctx.fetchUtils.fetchJson.resolves(null)
  183. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
  184. })
  185. it('should call the callback with an error', function (ctx) {
  186. ctx.callback
  187. .calledWith(
  188. sinon.match({
  189. message: 'no data returned from joinProject request',
  190. })
  191. )
  192. .should.equal(true)
  193. })
  194. })
  195. describe('when the project is over its rate limit', function () {
  196. beforeEach(function (ctx) {
  197. ctx.fetchUtils.fetchJson.rejects(
  198. new RequestFailedError(
  199. `/project/${ctx.project_id}/join`,
  200. { method: 'POST' },
  201. { status: 429 }
  202. )
  203. )
  204. ctx.WebApiManager.joinProject(ctx.project_id, ctx.user_id, ctx.callback)
  205. })
  206. it('should call the callback with a TooManyRequests error code', function (ctx) {
  207. ctx.callback
  208. .calledWith(
  209. sinon.match({
  210. message: 'rate-limit hit when joining project',
  211. info: {
  212. code: 'TooManyRequests',
  213. },
  214. })
  215. )
  216. .should.equal(true)
  217. })
  218. })
  219. })
  220. })