/** * 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 sinon from 'sinon'; import * as featureFlags from 'src/featureFlags'; import initAsyncEvents from 'src/middleware/asyncEvent'; jest.useFakeTimers(); describe('asyncEvent middleware', () => { const next = sinon.spy(); const state = { charts: { 123: { id: 123, status: 'loading', asyncJobId: 'foo123', }, 345: { id: 345, status: 'loading', asyncJobId: 'foo345', }, }, }; const events = [ { status: 'done', result_url: '/api/v1/chart/data/cache-key-1', job_id: 'foo123', channel_id: '999', errors: [], }, { status: 'done', result_url: '/api/v1/chart/data/cache-key-2', job_id: 'foo345', channel_id: '999', errors: [], }, ]; const mockStore = { getState: () => state, dispatch: sinon.stub(), }; const action = { type: 'GENERIC_ACTION', }; const EVENTS_ENDPOINT = 'glob:*/api/v1/async_event/*'; const CACHED_DATA_ENDPOINT = 'glob:*/api/v1/chart/data/*'; const config = { GLOBAL_ASYNC_QUERIES_TRANSPORT: 'polling', GLOBAL_ASYNC_QUERIES_POLLING_DELAY: 500, }; let featureEnabledStub: any; function setup() { const getPendingComponents = sinon.stub(); const successAction = sinon.spy(); const errorAction = sinon.spy(); const testCallback = sinon.stub(); const testCallbackPromise = sinon.stub(); testCallbackPromise.returns( new Promise(resolve => { testCallback.callsFake(resolve); }), ); return { getPendingComponents, successAction, errorAction, testCallback, testCallbackPromise, }; } beforeEach(() => { fetchMock.get(EVENTS_ENDPOINT, { status: 200, body: { result: [] }, }); fetchMock.get(CACHED_DATA_ENDPOINT, { status: 200, body: { result: { some: 'data' } }, }); featureEnabledStub = sinon.stub(featureFlags, 'isFeatureEnabled'); featureEnabledStub.withArgs('GLOBAL_ASYNC_QUERIES').returns(true); }); afterEach(() => { fetchMock.reset(); next.resetHistory(); featureEnabledStub.restore(); }); afterAll(fetchMock.reset); it('should initialize and call next', () => { const { getPendingComponents, successAction, errorAction } = setup(); getPendingComponents.returns([]); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, }); asyncEventMiddleware(mockStore)(next)(action); expect(next.callCount).toBe(1); }); it('should fetch events when there are pending components', () => { const { getPendingComponents, successAction, errorAction, testCallback, testCallbackPromise, } = setup(); getPendingComponents.returns(Object.values(state.charts)); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, processEventsCallback: testCallback, }); asyncEventMiddleware(mockStore)(next)(action); return testCallbackPromise().then(() => { expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1); }); }); it('should fetch cached when there are successful events', () => { const { getPendingComponents, successAction, errorAction, testCallback, testCallbackPromise, } = setup(); fetchMock.reset(); fetchMock.get(EVENTS_ENDPOINT, { status: 200, body: { result: events }, }); fetchMock.get(CACHED_DATA_ENDPOINT, { status: 200, body: { result: { some: 'data' } }, }); getPendingComponents.returns(Object.values(state.charts)); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, processEventsCallback: testCallback, }); asyncEventMiddleware(mockStore)(next)(action); return testCallbackPromise().then(() => { expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1); expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(2); expect(successAction.callCount).toBe(2); }); }); it('should call errorAction for cache fetch error responses', () => { const { getPendingComponents, successAction, errorAction, testCallback, testCallbackPromise, } = setup(); fetchMock.reset(); fetchMock.get(EVENTS_ENDPOINT, { status: 200, body: { result: events }, }); fetchMock.get(CACHED_DATA_ENDPOINT, { status: 400, body: { errors: ['error'] }, }); getPendingComponents.returns(Object.values(state.charts)); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, processEventsCallback: testCallback, }); asyncEventMiddleware(mockStore)(next)(action); return testCallbackPromise().then(() => { expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1); expect(fetchMock.calls(CACHED_DATA_ENDPOINT)).toHaveLength(2); expect(errorAction.callCount).toBe(2); }); }); it('should handle event fetching error responses', () => { const { getPendingComponents, successAction, errorAction, testCallback, testCallbackPromise, } = setup(); fetchMock.reset(); fetchMock.get(EVENTS_ENDPOINT, { status: 400, body: { message: 'error' }, }); getPendingComponents.returns(Object.values(state.charts)); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, processEventsCallback: testCallback, }); asyncEventMiddleware(mockStore)(next)(action); return testCallbackPromise().then(() => { expect(fetchMock.calls(EVENTS_ENDPOINT)).toHaveLength(1); }); }); it('should not fetch events when async queries are disabled', () => { featureEnabledStub.restore(); featureEnabledStub = sinon.stub(featureFlags, 'isFeatureEnabled'); featureEnabledStub.withArgs('GLOBAL_ASYNC_QUERIES').returns(false); const { getPendingComponents, successAction, errorAction } = setup(); getPendingComponents.returns(Object.values(state.charts)); const asyncEventMiddleware = initAsyncEvents({ config, getPendingComponents, successAction, errorAction, }); asyncEventMiddleware(mockStore)(next)(action); expect(getPendingComponents.called).toBe(false); }); });