fetch-json.test.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import { expect } from 'chai'
  2. import fetchMock from 'fetch-mock'
  3. import { Response } from 'node-fetch'
  4. import {
  5. deleteJSON,
  6. FetchError,
  7. getUserFacingMessage,
  8. getJSON,
  9. postJSON,
  10. putJSON,
  11. } from '../../../frontend/js/infrastructure/fetch-json'
  12. describe('fetchJSON', function () {
  13. before(function () {
  14. fetchMock.restore()
  15. })
  16. afterEach(function () {
  17. fetchMock.restore()
  18. })
  19. const headers = {
  20. Accept: 'application/json',
  21. 'Content-Type': 'application/json',
  22. }
  23. it('handles GET requests', function () {
  24. fetchMock.once(
  25. { method: 'GET', url: '/test', headers },
  26. { status: 200, body: { result: 'success' } }
  27. )
  28. return expect(getJSON('/test')).to.eventually.deep.equal({
  29. result: 'success',
  30. })
  31. })
  32. it('handles 4xx responses', function () {
  33. fetchMock.get('/test', {
  34. status: 400,
  35. body: { message: 'The request was invalid' },
  36. })
  37. return expect(getJSON('/test'))
  38. .to.eventually.be.rejectedWith('Bad Request')
  39. .and.be.an.instanceOf(FetchError)
  40. .to.nested.include({
  41. message: 'Bad Request',
  42. 'data.message': 'The request was invalid',
  43. 'response.status': 400,
  44. 'info.statusCode': 400,
  45. })
  46. })
  47. it('handles 5xx responses', async function () {
  48. fetchMock.get('/test', { status: 500 })
  49. return expect(getJSON('/test'))
  50. .to.eventually.be.rejectedWith('Internal Server Error')
  51. .and.be.an.instanceOf(FetchError)
  52. .to.nested.include({
  53. 'response.status': 500,
  54. 'info.statusCode': 500,
  55. })
  56. })
  57. it('handles JSON error responses', async function () {
  58. fetchMock.get('/test', {
  59. status: 500,
  60. headers: {
  61. 'Content-Type': 'application/json',
  62. },
  63. body: { message: 'lorem ipsum' },
  64. })
  65. return expect(getJSON('/test'))
  66. .to.eventually.be.rejectedWith('Internal Server Error')
  67. .and.be.an.instanceOf(FetchError)
  68. .to.nested.include({
  69. 'data.message': 'lorem ipsum',
  70. })
  71. })
  72. it('handles text error responses', async function () {
  73. fetchMock.get('/test', {
  74. status: 500,
  75. headers: {
  76. 'Content-Type': 'text/plain',
  77. },
  78. body: 'lorem ipsum',
  79. })
  80. return expect(getJSON('/test'))
  81. .to.eventually.be.rejectedWith('Internal Server Error')
  82. .and.be.an.instanceOf(FetchError)
  83. .to.nested.include({
  84. 'data.message': 'lorem ipsum',
  85. })
  86. })
  87. it('handles text error responses sent as HTML', async function () {
  88. fetchMock.get('/test', {
  89. status: 500,
  90. headers: {
  91. 'Content-Type': 'text/html',
  92. },
  93. body: 'lorem ipsum',
  94. })
  95. return expect(getJSON('/test'))
  96. .to.eventually.be.rejectedWith('Internal Server Error')
  97. .and.be.an.instanceOf(FetchError)
  98. .to.nested.include({
  99. 'data.message': 'lorem ipsum',
  100. })
  101. })
  102. it('handles (ignores) HTML error responses sent as HTML', async function () {
  103. fetchMock.get('/test', {
  104. status: 500,
  105. headers: {
  106. 'Content-Type': 'text/html',
  107. },
  108. body: '<!doctype html><html lang="en"><body><p>lorem ipsum</p></body></html>',
  109. })
  110. const promise = getJSON('/test')
  111. expect(promise)
  112. .to.eventually.be.rejectedWith('Internal Server Error')
  113. .and.be.an.instanceOf(FetchError)
  114. try {
  115. await promise
  116. } catch (error) {
  117. expect(error.data).to.eql({})
  118. }
  119. })
  120. it('handles 5xx responses without a status message', async function () {
  121. // It's hard to make a Response object with statusText=null,
  122. // so we need to do some monkey-work to make it happen
  123. const response = new Response('weird scary error', {
  124. ok: false,
  125. status: 599,
  126. })
  127. Object.defineProperty(response, 'statusText', {
  128. get: () => null,
  129. set: () => {},
  130. })
  131. fetchMock.get('/test', response)
  132. return expect(getJSON('/test'))
  133. .to.eventually.be.rejectedWith('Unexpected Error: 599')
  134. .and.be.an.instanceOf(FetchError)
  135. .to.nested.include({
  136. 'response.status': 599,
  137. 'info.statusCode': 599,
  138. message: 'Unexpected Error: 599',
  139. })
  140. })
  141. it('handles POST requests', function () {
  142. const body = { example: true }
  143. fetchMock.once(
  144. { method: 'POST', url: '/test', headers, body },
  145. { status: 200, body: { result: 'success' } }
  146. )
  147. return expect(postJSON('/test', { body })).to.eventually.deep.equal({
  148. result: 'success',
  149. })
  150. })
  151. it('handles PUT requests', function () {
  152. const body = { example: true }
  153. fetchMock.once(
  154. { method: 'PUT', url: '/test', headers, body },
  155. { status: 200, body: { result: 'success' } }
  156. )
  157. return expect(putJSON('/test', { body })).to.eventually.deep.equal({
  158. result: 'success',
  159. })
  160. })
  161. it('handles DELETE requests', function () {
  162. fetchMock.once({ method: 'DELETE', url: '/test', headers }, { status: 204 })
  163. return expect(deleteJSON('/test')).to.eventually.deep.equal({})
  164. })
  165. describe('getUserFacingMessage()', function () {
  166. it('returns the error facing message for FetchError instances', function () {
  167. const error = new FetchError(
  168. '403 error',
  169. 'http:/example.com',
  170. {},
  171. { status: 403 }
  172. )
  173. expect(getUserFacingMessage(error)).to.equal(
  174. 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.'
  175. )
  176. })
  177. it('returns `message` for Error instances different than FetchError', function () {
  178. const error = new Error('403 error')
  179. expect(getUserFacingMessage(error)).to.equal('403 error')
  180. })
  181. it('returns `undefined` for non-Error instances', function () {
  182. expect(getUserFacingMessage(undefined)).to.be.undefined
  183. expect(getUserFacingMessage(null)).to.be.undefined
  184. expect(getUserFacingMessage('error')).to.be.undefined
  185. })
  186. })
  187. })