mirror of
https://github.com/apache/superset.git
synced 2024-09-19 12:09:42 -04:00
248 lines
7.3 KiB
TypeScript
248 lines
7.3 KiB
TypeScript
/**
|
|
* 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 WS from 'jest-websocket-mock';
|
|
import sinon from 'sinon';
|
|
import * as featureFlags from 'src/featureFlags';
|
|
import { parseErrorJson } from 'src/utils/getClientErrorObject';
|
|
import * as asyncEvent from 'src/middleware/asyncEvent';
|
|
|
|
describe('asyncEvent middleware', () => {
|
|
const asyncPendingEvent = {
|
|
status: 'pending',
|
|
result_url: null,
|
|
job_id: 'foo123',
|
|
channel_id: '999',
|
|
errors: [],
|
|
};
|
|
const asyncDoneEvent = {
|
|
id: '1518951480106-0',
|
|
status: 'done',
|
|
result_url: '/api/v1/chart/data/cache-key-1',
|
|
job_id: 'foo123',
|
|
channel_id: '999',
|
|
errors: [],
|
|
};
|
|
const asyncErrorEvent = {
|
|
id: '1518951480107-0',
|
|
status: 'error',
|
|
result_url: null,
|
|
job_id: 'foo123',
|
|
channel_id: '999',
|
|
errors: [{ message: "Error: relation 'foo' does not exist" }],
|
|
};
|
|
const chartData = {
|
|
result: [
|
|
{
|
|
cache_key: '199f01f81f99c98693694821e4458111',
|
|
cached_dttm: null,
|
|
cache_timeout: 86400,
|
|
annotation_data: {},
|
|
error: null,
|
|
is_cached: false,
|
|
query:
|
|
'SELECT product_line AS product_line,\n sum(sales) AS "(Sales)"\nFROM cleaned_sales_data\nGROUP BY product_line\nLIMIT 50000',
|
|
status: 'success',
|
|
stacktrace: null,
|
|
rowcount: 7,
|
|
colnames: ['product_line', '(Sales)'],
|
|
coltypes: [1, 0],
|
|
data: [
|
|
{
|
|
product_line: 'Classic Cars',
|
|
'(Sales)': 3919615.66,
|
|
},
|
|
],
|
|
applied_filters: [
|
|
{
|
|
column: '__time_range',
|
|
},
|
|
],
|
|
rejected_filters: [],
|
|
},
|
|
],
|
|
};
|
|
|
|
const EVENTS_ENDPOINT = 'glob:*/api/v1/async_event/*';
|
|
const CACHED_DATA_ENDPOINT = 'glob:*/api/v1/chart/data/*';
|
|
let featureEnabledStub: any;
|
|
|
|
beforeEach(async () => {
|
|
featureEnabledStub = sinon.stub(featureFlags, 'isFeatureEnabled');
|
|
featureEnabledStub.withArgs('GLOBAL_ASYNC_QUERIES').returns(true);
|
|
});
|
|
|
|
afterEach(() => {
|
|
fetchMock.reset();
|
|
featureEnabledStub.restore();
|
|
});
|
|
|
|
afterAll(fetchMock.reset);
|
|
|
|
describe('polling transport', () => {
|
|
const config = {
|
|
GLOBAL_ASYNC_QUERIES_TRANSPORT: 'polling',
|
|
GLOBAL_ASYNC_QUERIES_POLLING_DELAY: 50,
|
|
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL: '',
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
fetchMock.get(EVENTS_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: [asyncDoneEvent] },
|
|
});
|
|
fetchMock.get(CACHED_DATA_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: chartData },
|
|
});
|
|
asyncEvent.init(config);
|
|
});
|
|
|
|
it('resolves with chart data on event done status', async () => {
|
|
await expect(
|
|
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
|
).resolves.toEqual([chartData]);
|
|
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(1);
|
|
});
|
|
|
|
it('rejects on event error status', async () => {
|
|
fetchMock.reset();
|
|
fetchMock.get(EVENTS_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: [asyncErrorEvent] },
|
|
});
|
|
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
|
await expect(
|
|
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
|
).rejects.toEqual(errorResponse);
|
|
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(0);
|
|
});
|
|
|
|
it('rejects on cached data fetch error', async () => {
|
|
fetchMock.reset();
|
|
fetchMock.get(EVENTS_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: [asyncDoneEvent] },
|
|
});
|
|
fetchMock.get(CACHED_DATA_ENDPOINT, {
|
|
status: 400,
|
|
});
|
|
|
|
const errorResponse = [{ error: 'Bad Request' }];
|
|
await expect(
|
|
asyncEvent.waitForAsyncData(asyncPendingEvent),
|
|
).rejects.toEqual(errorResponse);
|
|
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('ws transport', () => {
|
|
let wsServer: WS;
|
|
const config = {
|
|
GLOBAL_ASYNC_QUERIES_TRANSPORT: 'ws',
|
|
GLOBAL_ASYNC_QUERIES_POLLING_DELAY: 50,
|
|
GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL: 'ws://127.0.0.1:8080/',
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
fetchMock.get(EVENTS_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: [asyncDoneEvent] },
|
|
});
|
|
fetchMock.get(CACHED_DATA_ENDPOINT, {
|
|
status: 200,
|
|
body: { result: chartData },
|
|
});
|
|
|
|
wsServer = new WS(config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL);
|
|
asyncEvent.init(config);
|
|
});
|
|
|
|
afterEach(() => {
|
|
WS.clean();
|
|
});
|
|
|
|
it('resolves with chart data on event done status', async () => {
|
|
await wsServer.connected;
|
|
|
|
const promise = asyncEvent.waitForAsyncData(asyncPendingEvent);
|
|
|
|
wsServer.send(JSON.stringify(asyncDoneEvent));
|
|
|
|
await expect(promise).resolves.toEqual([chartData]);
|
|
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(0);
|
|
});
|
|
|
|
it('rejects on event error status', async () => {
|
|
await wsServer.connected;
|
|
|
|
const promise = asyncEvent.waitForAsyncData(asyncPendingEvent);
|
|
|
|
wsServer.send(JSON.stringify(asyncErrorEvent));
|
|
|
|
const errorResponse = await parseErrorJson(asyncErrorEvent);
|
|
|
|
await expect(promise).rejects.toEqual(errorResponse);
|
|
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(0);
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(0);
|
|
});
|
|
|
|
it('rejects on cached data fetch error', async () => {
|
|
fetchMock.reset();
|
|
fetchMock.get(CACHED_DATA_ENDPOINT, {
|
|
status: 400,
|
|
});
|
|
|
|
await wsServer.connected;
|
|
|
|
const promise = asyncEvent.waitForAsyncData(asyncPendingEvent);
|
|
|
|
wsServer.send(JSON.stringify(asyncDoneEvent));
|
|
|
|
const errorResponse = [{ error: 'Bad Request' }];
|
|
|
|
await expect(promise).rejects.toEqual(errorResponse);
|
|
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(0);
|
|
});
|
|
|
|
it('resolves when events are received before listener', async () => {
|
|
await wsServer.connected;
|
|
|
|
wsServer.send(JSON.stringify(asyncDoneEvent));
|
|
|
|
const promise = asyncEvent.waitForAsyncData(asyncPendingEvent);
|
|
await expect(promise).resolves.toEqual([chartData]);
|
|
|
|
expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(1);
|
|
expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|