Skip to content

Commit 98f92be

Browse files
[Ingest pipelines] Tests for pipeline debugging enhancement (#75606) (#76282)
1 parent f6c23b3 commit 98f92be

File tree

15 files changed

+735
-60
lines changed

15 files changed

+735
-60
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { Pipeline } from '../../../../../common/types';
8+
import { VerboseTestOutput, Document } from '../types';
9+
10+
export const PROCESSORS: Pick<Pipeline, 'processors'> = {
11+
processors: [
12+
{
13+
set: {
14+
field: 'field1',
15+
value: 'value1',
16+
},
17+
},
18+
],
19+
};
20+
21+
export const DOCUMENTS: Document[] = [
22+
{
23+
_index: 'index',
24+
_id: 'id1',
25+
_source: {
26+
name: 'foo',
27+
},
28+
},
29+
{
30+
_index: 'index',
31+
_id: 'id2',
32+
_source: {
33+
name: 'bar',
34+
},
35+
},
36+
];
37+
38+
export const SIMULATE_RESPONSE: VerboseTestOutput = {
39+
docs: [
40+
{
41+
processor_results: [
42+
{
43+
processor_type: 'set',
44+
status: 'success',
45+
tag: 'some_tag',
46+
doc: {
47+
_index: 'index',
48+
_id: 'id1',
49+
_source: {
50+
name: 'foo',
51+
foo: 'bar',
52+
},
53+
},
54+
},
55+
],
56+
},
57+
{
58+
processor_results: [
59+
{
60+
processor_type: 'set',
61+
status: 'success',
62+
tag: 'some_tag',
63+
doc: {
64+
_index: 'index',
65+
_id: 'id2',
66+
_source: {
67+
name: 'bar',
68+
foo: 'bar',
69+
},
70+
},
71+
},
72+
],
73+
},
74+
],
75+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import sinon, { SinonFakeServer } from 'sinon';
8+
9+
type HttpResponse = Record<string, any> | any[];
10+
11+
// Register helpers to mock HTTP Requests
12+
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
13+
const setSimulatePipelineResponse = (response?: HttpResponse, error?: any) => {
14+
const status = error ? error.status || 400 : 200;
15+
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);
16+
17+
server.respondWith('POST', '/api/ingest_pipelines/simulate', [
18+
status,
19+
{ 'Content-Type': 'application/json' },
20+
body,
21+
]);
22+
};
23+
24+
return {
25+
setSimulatePipelineResponse,
26+
};
27+
};
28+
29+
export const initHttpRequests = () => {
30+
const server = sinon.fakeServer.create();
31+
32+
server.respondImmediately = true;
33+
34+
// Define default response for unhandled requests.
35+
// We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry,
36+
// and we can mock them all with a 200 instead of mocking each one individually.
37+
server.respondWith([200, {}, 'DefaultSinonMockServerResponse']);
38+
39+
const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server);
40+
41+
return {
42+
server,
43+
httpRequestsMockHelpers,
44+
};
45+
};
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { act } from 'react-dom/test-utils';
7+
import React from 'react';
8+
import axios from 'axios';
9+
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
10+
11+
import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks';
12+
13+
import { LocationDescriptorObject } from 'history';
14+
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
15+
/* eslint-disable @kbn/eslint/no-restricted-paths */
16+
import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks';
17+
18+
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
19+
import { stubWebWorker } from '../../../../../../../test_utils/stub_web_worker';
20+
21+
import {
22+
breadcrumbService,
23+
uiMetricService,
24+
documentationService,
25+
apiService,
26+
} from '../../../services';
27+
28+
import {
29+
ProcessorsEditorContextProvider,
30+
Props,
31+
GlobalOnFailureProcessorsEditor,
32+
ProcessorsEditor,
33+
} from '../';
34+
import { TestPipelineActions } from '../';
35+
36+
import { initHttpRequests } from './http_requests.helpers';
37+
38+
stubWebWorker();
39+
40+
jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => {
41+
const original = jest.requireActual('../../../../../../../../src/plugins/kibana_react/public');
42+
return {
43+
...original,
44+
// Mocking CodeEditor, which uses React Monaco under the hood
45+
CodeEditor: (props: any) => (
46+
<input
47+
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
48+
data-currentvalue={props.value}
49+
onChange={(e: any) => {
50+
props.onChange(e.jsonContent);
51+
}}
52+
/>
53+
),
54+
};
55+
});
56+
57+
jest.mock('@elastic/eui', () => {
58+
const original = jest.requireActual('@elastic/eui');
59+
return {
60+
...original,
61+
// Mocking EuiCodeEditor, which uses React Ace under the hood
62+
EuiCodeEditor: (props: any) => (
63+
<input
64+
data-test-subj={props['data-test-subj']}
65+
onChange={(syntheticEvent: any) => {
66+
props.onChange(syntheticEvent.jsonString);
67+
}}
68+
/>
69+
),
70+
};
71+
});
72+
73+
jest.mock('react-virtualized', () => {
74+
const original = jest.requireActual('react-virtualized');
75+
76+
return {
77+
...original,
78+
AutoSizer: ({ children }: { children: any }) => (
79+
<div>{children({ height: 500, width: 500 })}</div>
80+
),
81+
};
82+
});
83+
84+
const history = scopedHistoryMock.create();
85+
history.createHref.mockImplementation((location: LocationDescriptorObject) => {
86+
return `${location.pathname}?${location.search}`;
87+
});
88+
89+
const appServices = {
90+
breadcrumbs: breadcrumbService,
91+
metric: uiMetricService,
92+
documentation: documentationService,
93+
api: apiService,
94+
notifications: notificationServiceMock.createSetupContract(),
95+
history,
96+
uiSettings: {},
97+
};
98+
99+
const testBedSetup = registerTestBed<TestSubject>(
100+
(props: Props) => (
101+
<KibanaContextProvider services={appServices}>
102+
<ProcessorsEditorContextProvider {...props}>
103+
<TestPipelineActions />
104+
<ProcessorsEditor />
105+
<GlobalOnFailureProcessorsEditor />
106+
</ProcessorsEditorContextProvider>
107+
</KibanaContextProvider>
108+
),
109+
{
110+
doMountAsync: false,
111+
}
112+
);
113+
114+
export interface SetupResult extends TestBed<TestSubject> {
115+
actions: ReturnType<typeof createActions>;
116+
}
117+
118+
const createActions = (testBed: TestBed<TestSubject>) => {
119+
const { find, component, form } = testBed;
120+
121+
return {
122+
clickAddDocumentsButton() {
123+
act(() => {
124+
find('addDocumentsButton').simulate('click');
125+
});
126+
component.update();
127+
},
128+
129+
async clickViewOutputButton() {
130+
await act(async () => {
131+
find('viewOutputButton').simulate('click');
132+
});
133+
component.update();
134+
},
135+
136+
closeTestPipelineFlyout() {
137+
act(() => {
138+
find('euiFlyoutCloseButton').simulate('click');
139+
});
140+
component.update();
141+
},
142+
143+
clickProcessorOutputTab() {
144+
act(() => {
145+
find('outputTab').simulate('click');
146+
});
147+
component.update();
148+
},
149+
150+
async clickRefreshOutputButton() {
151+
await act(async () => {
152+
find('refreshOutputButton').simulate('click');
153+
});
154+
component.update();
155+
},
156+
157+
async clickRunPipelineButton() {
158+
await act(async () => {
159+
find('runPipelineButton').simulate('click');
160+
});
161+
component.update();
162+
},
163+
164+
async toggleVerboseSwitch() {
165+
await act(async () => {
166+
form.toggleEuiSwitch('verboseOutputToggle');
167+
});
168+
component.update();
169+
},
170+
171+
addDocumentsJson(jsonString: string) {
172+
find('documentsEditor').simulate('change', {
173+
jsonString,
174+
});
175+
},
176+
177+
async clickProcessor(processorSelector: string) {
178+
await act(async () => {
179+
find(`${processorSelector}.manageItemButton`).simulate('click');
180+
});
181+
component.update();
182+
},
183+
};
184+
};
185+
186+
export const setup = async (props: Props): Promise<SetupResult> => {
187+
const testBed = await testBedSetup(props);
188+
return {
189+
...testBed,
190+
actions: createActions(testBed),
191+
};
192+
};
193+
194+
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
195+
196+
export const setupEnvironment = () => {
197+
// Initialize mock services
198+
uiMetricService.setup(usageCollectionPluginMock.createSetupContract());
199+
// @ts-ignore
200+
apiService.setup(mockHttpClient, uiMetricService);
201+
202+
const { server, httpRequestsMockHelpers } = initHttpRequests();
203+
204+
return {
205+
server,
206+
httpRequestsMockHelpers,
207+
};
208+
};
209+
210+
type TestSubject =
211+
| 'addDocumentsButton'
212+
| 'testPipelineFlyout'
213+
| 'documentsDropdown'
214+
| 'outputTab'
215+
| 'documentsEditor'
216+
| 'runPipelineButton'
217+
| 'documentsTabContent'
218+
| 'outputTabContent'
219+
| 'verboseOutputToggle'
220+
| 'refreshOutputButton'
221+
| 'viewOutputButton'
222+
| 'pipelineExecutionError'
223+
| 'euiFlyoutCloseButton'
224+
| 'processorStatusIcon'
225+
| 'documentsTab'
226+
| 'manageItemButton'
227+
| 'processorSettingsForm'
228+
| 'configurationTab'
229+
| 'outputTab'
230+
| 'processorOutputTabContent'
231+
| string;

0 commit comments

Comments
 (0)