superset/superset-frontend/spec/javascripts/middleware/asyncEvent_spec.ts

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);
});
});
});