makeApi.test.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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 { JsonValue, SupersetClientClass } from '@superset-ui/core';
  21. import { makeApi, SupersetApiError } from '../../../../src/query';
  22. import setupClientForTest from '../setupClientForTest';
  23. describe('makeApi()', () => {
  24. beforeAll(() => setupClientForTest());
  25. afterEach(() => fetchMock.restore());
  26. it('should expose method and endpoint', () => {
  27. const api = makeApi({
  28. method: 'GET',
  29. endpoint: '/test',
  30. });
  31. expect(api.method).toEqual('GET');
  32. expect(api.endpoint).toEqual('/test');
  33. expect(api.requestType).toEqual('search');
  34. });
  35. it('should allow custom path', async () => {
  36. expect.assertions(2);
  37. const api = makeApi({
  38. method: 'GET',
  39. endpoint: '/test-custom-client',
  40. });
  41. const client = new SupersetClientClass({ appRoot: '/foo' });
  42. const mockResponse = { yes: 'ok' };
  43. const mockRequest = jest.fn(() =>
  44. Promise.resolve(
  45. new Response(JSON.stringify(mockResponse), {
  46. headers: { 'Content-Type': 'application/json' },
  47. }),
  48. ),
  49. );
  50. Object.assign(client, {
  51. request: mockRequest,
  52. });
  53. const result = await api(null, { client });
  54. expect(result).toEqual(mockResponse);
  55. expect(mockRequest).toHaveBeenCalledTimes(1);
  56. });
  57. it('should obtain json response by default', async () => {
  58. expect.assertions(1);
  59. const api = makeApi({
  60. method: 'GET',
  61. endpoint: '/test',
  62. });
  63. fetchMock.get('glob:*/test', { yes: 'ok' });
  64. expect(await api({})).toEqual({ yes: 'ok' });
  65. });
  66. it('should allow custom parseResponse', async () => {
  67. expect.assertions(2);
  68. const responseJson = { items: [1, 2, 3] };
  69. fetchMock.post('glob:*/test', responseJson);
  70. const api = makeApi({
  71. method: 'POST',
  72. endpoint: '/test',
  73. processResponse: (json: typeof responseJson) =>
  74. json.items.reduce((a: number, b: number) => a + b),
  75. });
  76. expect(api.method).toEqual('POST');
  77. expect(await api({})).toBe(6);
  78. });
  79. it('should post FormData when requestType=form', async () => {
  80. expect.assertions(3);
  81. const api = makeApi({
  82. method: 'POST',
  83. endpoint: '/test-formdata',
  84. requestType: 'form',
  85. });
  86. fetchMock.post('glob:*/test-formdata', { test: 'ok' });
  87. expect(await api({ request: 'test' })).toEqual({ test: 'ok' });
  88. const expected = new FormData();
  89. expected.append('request', JSON.stringify('test'));
  90. const received = fetchMock.lastOptions()?.body as FormData;
  91. expect(received).toBeInstanceOf(FormData);
  92. expect(received.get('request')).toEqual(expected.get('request'));
  93. });
  94. it('should use searchParams for method=GET (`requestType=search` implied)', async () => {
  95. expect.assertions(1);
  96. const api = makeApi({
  97. method: 'GET',
  98. endpoint: '/test-get-search',
  99. });
  100. fetchMock.get('glob:*/test-get-search*', { search: 'get' });
  101. await api({ p1: 1, p2: 2, p3: [1, 2] });
  102. expect(fetchMock.lastUrl()).toContain(
  103. '/test-get-search?p1=1&p2=2&p3=1%2C2',
  104. );
  105. });
  106. it('should serialize rison for method=GET, requestType=rison', async () => {
  107. expect.assertions(1);
  108. const api = makeApi({
  109. method: 'GET',
  110. endpoint: '/test-post-search',
  111. requestType: 'rison',
  112. });
  113. fetchMock.get('glob:*/test-post-search*', { rison: 'get' });
  114. await api({ p1: 1, p3: [1, 2] });
  115. expect(fetchMock.lastUrl()).toContain(
  116. '/test-post-search?q=(p1:1,p3:!(1,2))',
  117. );
  118. });
  119. it('should use searchParams for method=POST, requestType=search', async () => {
  120. expect.assertions(1);
  121. const api = makeApi({
  122. method: 'POST',
  123. endpoint: '/test-post-search',
  124. requestType: 'search',
  125. });
  126. fetchMock.post('glob:*/test-post-search*', { search: 'post' });
  127. await api({ p1: 1, p3: [1, 2] });
  128. expect(fetchMock.lastUrl()).toContain('/test-post-search?p1=1&p3=1%2C2');
  129. });
  130. it('should throw when requestType is invalid', () => {
  131. expect(() => {
  132. makeApi({
  133. method: 'POST',
  134. endpoint: '/test-formdata',
  135. // @ts-ignore
  136. requestType: 'text',
  137. });
  138. }).toThrow('Invalid request payload type');
  139. });
  140. it('should handle errors', async () => {
  141. expect.assertions(1);
  142. const api = makeApi({
  143. method: 'POST',
  144. endpoint: '/test-formdata',
  145. requestType: 'form',
  146. });
  147. let error;
  148. fetchMock.post('glob:*/test-formdata', { test: 'ok' });
  149. try {
  150. await api('<This is an invalid JSON string>');
  151. } catch (err) {
  152. error = err;
  153. } finally {
  154. expect((error as SupersetApiError).message).toContain('Invalid payload');
  155. }
  156. });
  157. it('should handle error on 200 response', async () => {
  158. expect.assertions(1);
  159. const api = makeApi({
  160. method: 'POST',
  161. endpoint: '/test-200-error',
  162. requestType: 'json',
  163. });
  164. fetchMock.post('glob:*/test-200-error', { error: 'not ok' });
  165. let error;
  166. try {
  167. await api({});
  168. } catch (err) {
  169. error = err;
  170. } finally {
  171. expect((error as SupersetApiError).message).toContain('not ok');
  172. }
  173. });
  174. it('should parse text response when responseType=text', async () => {
  175. expect.assertions(1);
  176. const api = makeApi<JsonValue, string, 'text'>({
  177. method: 'PUT',
  178. endpoint: '/test-parse-text',
  179. requestType: 'form',
  180. responseType: 'text',
  181. processResponse: text => `${text}?`,
  182. });
  183. fetchMock.put('glob:*/test-parse-text', 'ok');
  184. const result = await api({ field1: 11 });
  185. expect(result).toBe('ok?');
  186. });
  187. it('should return raw response when responseType=raw', async () => {
  188. expect.assertions(2);
  189. const api = makeApi<JsonValue, number, 'raw'>({
  190. method: 'DELETE',
  191. endpoint: '/test-raw-response',
  192. responseType: 'raw',
  193. processResponse: response => response.status,
  194. });
  195. fetchMock.delete('glob:*/test-raw-response?*', 'ok');
  196. const result = await api({ field1: 11 }, {});
  197. expect(result).toEqual(200);
  198. expect(fetchMock.lastUrl()).toContain('/test-raw-response?field1=11');
  199. });
  200. });