parseResponse.test.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import fetchMock from 'fetch-mock';
  20. import callApi from '../../../src/connection/callApi/callApi';
  21. import parseResponse from '../../../src/connection/callApi/parseResponse';
  22. import { LOGIN_GLOB } from '../fixtures/constants';
  23. describe('parseResponse()', () => {
  24. beforeAll(() => {
  25. fetchMock.get(LOGIN_GLOB, { result: '1234' });
  26. });
  27. afterAll(() => fetchMock.restore());
  28. const mockGetUrl = '/mock/get/url';
  29. const mockPostUrl = '/mock/post/url';
  30. const mockErrorUrl = '/mock/error/url';
  31. const mockNoParseUrl = '/mock/noparse/url';
  32. const mockGetPayload = { get: 'payload' };
  33. const mockPostPayload = { post: 'payload' };
  34. const mockErrorPayload = { status: 500, statusText: 'Internal error' };
  35. beforeEach(() => {
  36. fetchMock.get(mockGetUrl, mockGetPayload);
  37. fetchMock.post(mockPostUrl, mockPostPayload);
  38. fetchMock.get(mockErrorUrl, () => Promise.reject(mockErrorPayload));
  39. fetchMock.get(mockNoParseUrl, new Response('test response'));
  40. });
  41. afterEach(() => fetchMock.reset());
  42. it('returns a Promise', () => {
  43. const apiPromise = callApi({ url: mockGetUrl, method: 'GET' });
  44. const parsedResponsePromise = parseResponse(apiPromise);
  45. expect(parsedResponsePromise).toBeInstanceOf(Promise);
  46. });
  47. it('resolves to { json, response } if the request succeeds', async () => {
  48. expect.assertions(4);
  49. const args = await parseResponse(
  50. callApi({ url: mockGetUrl, method: 'GET' }),
  51. );
  52. expect(fetchMock.calls(mockGetUrl)).toHaveLength(1);
  53. const keys = Object.keys(args);
  54. expect(keys).toContain('response');
  55. expect(keys).toContain('json');
  56. expect(args.json).toEqual(
  57. expect.objectContaining(mockGetPayload) as typeof args.json,
  58. );
  59. });
  60. it('throws if `parseMethod=json` and .json() fails', async () => {
  61. expect.assertions(3);
  62. const mockTextUrl = '/mock/text/url';
  63. const mockTextResponse =
  64. '<html><head></head><body>I could be a stack trace or something</body></html>';
  65. fetchMock.get(mockTextUrl, mockTextResponse);
  66. let error;
  67. try {
  68. await parseResponse(callApi({ url: mockTextUrl, method: 'GET' }));
  69. } catch (err) {
  70. error = err as Error;
  71. } finally {
  72. expect(fetchMock.calls(mockTextUrl)).toHaveLength(1);
  73. expect(error?.stack).toBeDefined();
  74. expect(error?.message).toContain('Unexpected token');
  75. }
  76. });
  77. it('resolves to { text, response } if the `parseMethod=text`', async () => {
  78. expect.assertions(4);
  79. // test with json + bigint to ensure that it was not first parsed as json
  80. const mockTextParseUrl = '/mock/textparse/url';
  81. const mockTextJsonResponse = '{ "value": 9223372036854775807 }';
  82. fetchMock.get(mockTextParseUrl, mockTextJsonResponse);
  83. const args = await parseResponse(
  84. callApi({ url: mockTextParseUrl, method: 'GET' }),
  85. 'text',
  86. );
  87. expect(fetchMock.calls(mockTextParseUrl)).toHaveLength(1);
  88. const keys = Object.keys(args);
  89. expect(keys).toContain('response');
  90. expect(keys).toContain('text');
  91. expect(args.text).toBe(mockTextJsonResponse);
  92. });
  93. it('throws if parseMethod is not null|json|text', async () => {
  94. expect.assertions(1);
  95. let error;
  96. try {
  97. await parseResponse(
  98. callApi({ url: mockNoParseUrl, method: 'GET' }),
  99. 'something-else' as never,
  100. );
  101. } catch (err) {
  102. error = err;
  103. } finally {
  104. expect(error.message).toEqual(
  105. expect.stringContaining('Expected parseResponse=json'),
  106. );
  107. }
  108. });
  109. it('resolves to unmodified `Response` object if `parseMethod=null|raw`', async () => {
  110. expect.assertions(3);
  111. const responseNull = await parseResponse(
  112. callApi({ url: mockNoParseUrl, method: 'GET' }),
  113. null,
  114. );
  115. const responseRaw = await parseResponse(
  116. callApi({ url: mockNoParseUrl, method: 'GET' }),
  117. 'raw',
  118. );
  119. expect(fetchMock.calls(mockNoParseUrl)).toHaveLength(2);
  120. expect(responseNull.bodyUsed).toBe(false);
  121. expect(responseRaw.bodyUsed).toBe(false);
  122. });
  123. it('resolves to big number value if `parseMethod=json-bigint`', async () => {
  124. const mockBigIntUrl = '/mock/get/bigInt';
  125. const mockGetBigIntPayload = `{
  126. "value": 9223372036854775807, "minus": { "value": -483729382918228373892, "str": "something" },
  127. "number": 1234, "floatValue": { "plus": 0.3452211361231223, "minus": -0.3452211361231223, "even": 1234567890123456.0000000 },
  128. "string.constructor": "data.constructor",
  129. "constructor": "constructor"
  130. }`;
  131. fetchMock.get(mockBigIntUrl, mockGetBigIntPayload);
  132. const responseBigNumber = await parseResponse(
  133. callApi({ url: mockBigIntUrl, method: 'GET' }),
  134. 'json-bigint',
  135. );
  136. expect(`${responseBigNumber.json.value}`).toEqual('9223372036854775807');
  137. expect(`${responseBigNumber.json.minus.value}`).toEqual(
  138. '-483729382918228373892',
  139. );
  140. expect(responseBigNumber.json.number).toEqual(1234);
  141. expect(responseBigNumber.json.floatValue.plus).toEqual(0.3452211361231223);
  142. expect(responseBigNumber.json.floatValue.minus).toEqual(
  143. -0.3452211361231223,
  144. );
  145. expect(responseBigNumber.json.floatValue.even).toEqual(1234567890123456);
  146. expect(
  147. responseBigNumber.json.floatValue.plus +
  148. responseBigNumber.json.floatValue.minus,
  149. ).toEqual(0);
  150. expect(
  151. responseBigNumber.json.floatValue.plus /
  152. responseBigNumber.json.floatValue.minus,
  153. ).toEqual(-1);
  154. expect(Math.min(responseBigNumber.json.floatValue.plus, 0)).toEqual(0);
  155. expect(Math.abs(responseBigNumber.json.floatValue.minus)).toEqual(
  156. responseBigNumber.json.floatValue.plus,
  157. );
  158. expect(responseBigNumber.json['string.constructor']).toEqual(
  159. 'data.constructor',
  160. );
  161. expect(responseBigNumber.json.constructor).toEqual('constructor');
  162. });
  163. it('rejects if request.ok=false', async () => {
  164. expect.assertions(3);
  165. const mockNotOkayUrl = '/mock/notokay/url';
  166. fetchMock.get(mockNotOkayUrl, 404); // 404s result in not response.ok=false
  167. const apiPromise = callApi({ url: mockNotOkayUrl, method: 'GET' });
  168. let error;
  169. try {
  170. await parseResponse(apiPromise);
  171. } catch (err) {
  172. error = err as { ok: boolean; status: number };
  173. } finally {
  174. expect(fetchMock.calls(mockNotOkayUrl)).toHaveLength(1);
  175. expect(error?.ok).toBe(false);
  176. expect(error?.status).toBe(404);
  177. }
  178. });
  179. });