blob: c2e1036c786b6b7fdc6ced363e18c56848127966 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import fetchMock from 'fetch-mock';
import { JsonValue, SupersetClientClass } from '@superset-ui/core/src/connection';
import { makeApi, SupersetApiError } from '@superset-ui/core/src/query';
import setupClientForTest from '../setupClientForTest';
describe('makeApi()', () => {
beforeAll(setupClientForTest);
afterEach(fetchMock.restore);
it('should expose method and endpoint', () => {
const api = makeApi({
method: 'GET',
endpoint: '/test',
});
expect(api.method).toEqual('GET');
expect(api.endpoint).toEqual('/test');
expect(api.requestType).toEqual('search');
});
it('should allow custom client', async () => {
expect.assertions(2);
const api = makeApi({
method: 'GET',
endpoint: '/test-custom-client',
});
const client = new SupersetClientClass({ baseUrl: 'http://foo/' });
const mockResponse = { yes: 'ok' };
const mockRequest = jest.fn(() =>
Promise.resolve(
new Response(JSON.stringify(mockResponse), {
headers: { 'Content-Type': 'application/json' },
}),
),
);
Object.assign(client, {
request: mockRequest,
});
const result = await api(null, { client });
expect(result).toEqual(mockResponse);
expect(mockRequest).toHaveBeenCalledTimes(1);
});
it('should obtain json response by default', async () => {
expect.assertions(1);
const api = makeApi({
method: 'GET',
endpoint: '/test',
});
fetchMock.get('glob:*/test', { yes: 'ok' });
expect(await api({})).toEqual({ yes: 'ok' });
});
it('should allow custom parseResponse', async () => {
expect.assertions(2);
const responseJson = { items: [1, 2, 3] };
fetchMock.post('glob:*/test', responseJson);
const api = makeApi({
method: 'POST',
endpoint: '/test',
processResponse: (json: typeof responseJson) =>
json.items.reduce((a: number, b: number) => a + b),
});
expect(api.method).toEqual('POST');
expect(await api({})).toBe(6);
});
it('should post FormData when requestType=form', async () => {
expect.assertions(3);
const api = makeApi({
method: 'POST',
endpoint: '/test-formdata',
requestType: 'form',
});
fetchMock.post('glob:*/test-formdata', { test: 'ok' });
expect(await api({ request: 'test' })).toEqual({ test: 'ok' });
const expected = new FormData();
expected.append('request', JSON.stringify('test'));
const received = fetchMock.lastOptions().body as FormData;
expect(received).toBeInstanceOf(FormData);
expect(received.get('request')).toEqual(expected.get('request'));
});
it('should use searchParams for method=GET (`requestType=search` implied)', async () => {
expect.assertions(1);
const api = makeApi({
method: 'GET',
endpoint: '/test-get-search',
});
fetchMock.get('glob:*/test-get-search*', { search: 'get' });
await api({ p1: 1, p2: 2, p3: [1, 2] });
expect(fetchMock.lastUrl()).toContain('/test-get-search?p1=1&p2=2&p3=1%2C2');
});
it('should serialize rison for method=GET, requestType=rison', async () => {
expect.assertions(1);
const api = makeApi({
method: 'GET',
endpoint: '/test-post-search',
requestType: 'rison',
});
fetchMock.get('glob:*/test-post-search*', { rison: 'get' });
await api({ p1: 1, p3: [1, 2] });
expect(fetchMock.lastUrl()).toContain('/test-post-search?q=(p1:1,p3:!(1,2))');
});
it('should use searchParams for method=POST, requestType=search', async () => {
expect.assertions(1);
const api = makeApi({
method: 'POST',
endpoint: '/test-post-search',
requestType: 'search',
});
fetchMock.post('glob:*/test-post-search*', { search: 'post' });
await api({ p1: 1, p3: [1, 2] });
expect(fetchMock.lastUrl()).toContain('/test-post-search?p1=1&p3=1%2C2');
});
it('should throw when requestType is invalid', () => {
expect(() => {
makeApi({
method: 'POST',
endpoint: '/test-formdata',
// @ts-ignore
requestType: 'text',
});
}).toThrow('Invalid request payload type');
});
it('should handle errors', async () => {
expect.assertions(1);
const api = makeApi({
method: 'POST',
endpoint: '/test-formdata',
requestType: 'form',
});
let error;
fetchMock.post('glob:*/test-formdata', { test: 'ok' });
try {
await api('<This is an invalid JSON string>');
} catch (err) {
error = err;
} finally {
expect((error as SupersetApiError).message).toContain('Invalid payload');
}
});
it('should handle error on 200 response', async () => {
expect.assertions(1);
const api = makeApi({
method: 'POST',
endpoint: '/test-200-error',
requestType: 'json',
});
fetchMock.post('glob:*/test-200-error', { error: 'not ok' });
let error;
try {
await api({});
} catch (err) {
error = err;
} finally {
expect((error as SupersetApiError).message).toContain('not ok');
}
});
it('should parse text response when responseType=text', async () => {
expect.assertions(1);
const api = makeApi<JsonValue, string, 'text'>({
method: 'PUT',
endpoint: '/test-parse-text',
requestType: 'form',
responseType: 'text',
processResponse: text => `${text}?`,
});
fetchMock.put('glob:*/test-parse-text', 'ok');
const result = await api({ field1: 11 });
expect(result).toBe('ok?');
});
it('should return raw resposnse when responseType=raw', async () => {
expect.assertions(2);
const api = makeApi<JsonValue, number, 'raw'>({
method: 'DELETE',
endpoint: '/test-raw-response',
responseType: 'raw',
processResponse: response => response.status,
});
fetchMock.delete('glob:*/test-raw-response?*', 'ok');
const result = await api({ field1: 11 }, {});
expect(result).toEqual(200);
expect(fetchMock.lastUrl()).toContain('/test-raw-response?field1=11');
});
});