Skip to content

Commit

Permalink
[Ingest pipelines] Tests for pipeline debugging enhancement (#75606)
Browse files Browse the repository at this point in the history
  • Loading branch information
alisonelizabeth authored Aug 31, 2020
1 parent 647f397 commit 833d764
Showing 15 changed files with 735 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { Pipeline } from '../../../../../common/types';
import { VerboseTestOutput, Document } from '../types';

export const PROCESSORS: Pick<Pipeline, 'processors'> = {
processors: [
{
set: {
field: 'field1',
value: 'value1',
},
},
],
};

export const DOCUMENTS: Document[] = [
{
_index: 'index',
_id: 'id1',
_source: {
name: 'foo',
},
},
{
_index: 'index',
_id: 'id2',
_source: {
name: 'bar',
},
},
];

export const SIMULATE_RESPONSE: VerboseTestOutput = {
docs: [
{
processor_results: [
{
processor_type: 'set',
status: 'success',
tag: 'some_tag',
doc: {
_index: 'index',
_id: 'id1',
_source: {
name: 'foo',
foo: 'bar',
},
},
},
],
},
{
processor_results: [
{
processor_type: 'set',
status: 'success',
tag: 'some_tag',
doc: {
_index: 'index',
_id: 'id2',
_source: {
name: 'bar',
foo: 'bar',
},
},
},
],
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import sinon, { SinonFakeServer } from 'sinon';

type HttpResponse = Record<string, any> | any[];

// Register helpers to mock HTTP Requests
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const setSimulatePipelineResponse = (response?: HttpResponse, error?: any) => {
const status = error ? error.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);

server.respondWith('POST', '/api/ingest_pipelines/simulate', [
status,
{ 'Content-Type': 'application/json' },
body,
]);
};

return {
setSimulatePipelineResponse,
};
};

export const initHttpRequests = () => {
const server = sinon.fakeServer.create();

server.respondImmediately = true;

// Define default response for unhandled requests.
// We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry,
// and we can mock them all with a 200 instead of mocking each one individually.
server.respondWith([200, {}, 'DefaultSinonMockServerResponse']);

const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server);

return {
server,
httpRequestsMockHelpers,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { act } from 'react-dom/test-utils';
import React from 'react';
import axios from 'axios';
import axiosXhrAdapter from 'axios/lib/adapters/xhr';

import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks';

import { LocationDescriptorObject } from 'history';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
/* eslint-disable @kbn/eslint/no-restricted-paths */
import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks';

import { registerTestBed, TestBed } from '../../../../../../../test_utils';
import { stubWebWorker } from '../../../../../../../test_utils/stub_web_worker';

import {
breadcrumbService,
uiMetricService,
documentationService,
apiService,
} from '../../../services';

import {
ProcessorsEditorContextProvider,
Props,
GlobalOnFailureProcessorsEditor,
ProcessorsEditor,
} from '../';
import { TestPipelineActions } from '../';

import { initHttpRequests } from './http_requests.helpers';

stubWebWorker();

jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => {
const original = jest.requireActual('../../../../../../../../src/plugins/kibana_react/public');
return {
...original,
// Mocking CodeEditor, which uses React Monaco under the hood
CodeEditor: (props: any) => (
<input
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
data-currentvalue={props.value}
onChange={(e: any) => {
props.onChange(e.jsonContent);
}}
/>
),
};
});

jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
return {
...original,
// Mocking EuiCodeEditor, which uses React Ace under the hood
EuiCodeEditor: (props: any) => (
<input
data-test-subj={props['data-test-subj']}
onChange={(syntheticEvent: any) => {
props.onChange(syntheticEvent.jsonString);
}}
/>
),
};
});

jest.mock('react-virtualized', () => {
const original = jest.requireActual('react-virtualized');

return {
...original,
AutoSizer: ({ children }: { children: any }) => (
<div>{children({ height: 500, width: 500 })}</div>
),
};
});

const history = scopedHistoryMock.create();
history.createHref.mockImplementation((location: LocationDescriptorObject) => {
return `${location.pathname}?${location.search}`;
});

const appServices = {
breadcrumbs: breadcrumbService,
metric: uiMetricService,
documentation: documentationService,
api: apiService,
notifications: notificationServiceMock.createSetupContract(),
history,
uiSettings: {},
};

const testBedSetup = registerTestBed<TestSubject>(
(props: Props) => (
<KibanaContextProvider services={appServices}>
<ProcessorsEditorContextProvider {...props}>
<TestPipelineActions />
<ProcessorsEditor />
<GlobalOnFailureProcessorsEditor />
</ProcessorsEditorContextProvider>
</KibanaContextProvider>
),
{
doMountAsync: false,
}
);

export interface SetupResult extends TestBed<TestSubject> {
actions: ReturnType<typeof createActions>;
}

const createActions = (testBed: TestBed<TestSubject>) => {
const { find, component, form } = testBed;

return {
clickAddDocumentsButton() {
act(() => {
find('addDocumentsButton').simulate('click');
});
component.update();
},

async clickViewOutputButton() {
await act(async () => {
find('viewOutputButton').simulate('click');
});
component.update();
},

closeTestPipelineFlyout() {
act(() => {
find('euiFlyoutCloseButton').simulate('click');
});
component.update();
},

clickProcessorOutputTab() {
act(() => {
find('outputTab').simulate('click');
});
component.update();
},

async clickRefreshOutputButton() {
await act(async () => {
find('refreshOutputButton').simulate('click');
});
component.update();
},

async clickRunPipelineButton() {
await act(async () => {
find('runPipelineButton').simulate('click');
});
component.update();
},

async toggleVerboseSwitch() {
await act(async () => {
form.toggleEuiSwitch('verboseOutputToggle');
});
component.update();
},

addDocumentsJson(jsonString: string) {
find('documentsEditor').simulate('change', {
jsonString,
});
},

async clickProcessor(processorSelector: string) {
await act(async () => {
find(`${processorSelector}.manageItemButton`).simulate('click');
});
component.update();
},
};
};

export const setup = async (props: Props): Promise<SetupResult> => {
const testBed = await testBedSetup(props);
return {
...testBed,
actions: createActions(testBed),
};
};

const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });

export const setupEnvironment = () => {
// Initialize mock services
uiMetricService.setup(usageCollectionPluginMock.createSetupContract());
// @ts-ignore
apiService.setup(mockHttpClient, uiMetricService);

const { server, httpRequestsMockHelpers } = initHttpRequests();

return {
server,
httpRequestsMockHelpers,
};
};

type TestSubject =
| 'addDocumentsButton'
| 'testPipelineFlyout'
| 'documentsDropdown'
| 'outputTab'
| 'documentsEditor'
| 'runPipelineButton'
| 'documentsTabContent'
| 'outputTabContent'
| 'verboseOutputToggle'
| 'refreshOutputButton'
| 'viewOutputButton'
| 'pipelineExecutionError'
| 'euiFlyoutCloseButton'
| 'processorStatusIcon'
| 'documentsTab'
| 'manageItemButton'
| 'processorSettingsForm'
| 'configurationTab'
| 'outputTab'
| 'processorOutputTabContent'
| string;
Loading

0 comments on commit 833d764

Please sign in to comment.