From ef156d12886f05e289994ceec15c7eef3f0d48b5 Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Mon, 6 Apr 2020 22:53:52 +0300
Subject: [PATCH] [7.x] [SIEM][CASE] Configuration page tests (#61093) (#62661)
* Test ClosureOptionsRadio component
* Test ClosureOptions component
* Test ConnectorsDropdown component
* Test Connectors
* Test FieldMappingRow
* Test FieldMapping
* Create utils functions and refactor to be able to test
* Test Mapping
* Improve tests
* Test ConfigureCases
* Refactor tests
* Fix flacky tests
* Remove snapshots
* Refactor tests
* Test button
* Test reducer
* Move test
* Better structure
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../configure_cases/__mock__/index.tsx | 122 +++
.../configure_cases/button.test.tsx | 114 +++
.../components/configure_cases/button.tsx | 10 +-
.../configure_cases/closure_options.test.tsx | 67 ++
.../configure_cases/closure_options.tsx | 10 +-
.../closure_options_radio.test.tsx | 79 ++
.../configure_cases/closure_options_radio.tsx | 3 +-
.../configure_cases/connectors.test.tsx | 90 +++
.../components/configure_cases/connectors.tsx | 16 +-
.../connectors_dropdown.test.tsx | 86 ++
.../configure_cases/connectors_dropdown.tsx | 7 +-
.../configure_cases/field_mapping.test.tsx | 84 ++
.../configure_cases/field_mapping.tsx | 31 +-
.../field_mapping_row.test.tsx | 106 +++
.../configure_cases/field_mapping_row.tsx | 4 +-
.../components/configure_cases/index.test.tsx | 748 ++++++++++++++++++
.../case/components/configure_cases/index.tsx | 16 +-
.../configure_cases/mapping.test.tsx | 65 ++
.../components/configure_cases/mapping.tsx | 13 +-
.../configure_cases/reducer.test.ts | 68 ++
.../components/configure_cases/utils.test.tsx | 63 ++
.../case/components/configure_cases/utils.ts | 44 ++
22 files changed, 1807 insertions(+), 39 deletions(-)
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx
create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx
new file mode 100644
index 0000000000000..a3df3664398ad
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx
@@ -0,0 +1,122 @@
+/*
+ * 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 {
+ Connector,
+ CasesConfigurationMapping,
+} from '../../../../../containers/case/configure/types';
+import { State } from '../reducer';
+import { ReturnConnectors } from '../../../../../containers/case/configure/use_connectors';
+import { ReturnUseCaseConfigure } from '../../../../../containers/case/configure/use_configure';
+import { createUseKibanaMock } from '../../../../../mock/kibana_react';
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock';
+
+export const connectors: Connector[] = [
+ {
+ id: '123',
+ actionTypeId: '.servicenow',
+ name: 'My Connector',
+ config: {
+ apiUrl: 'https://instance1.service-now.com',
+ casesConfiguration: {
+ mapping: [
+ {
+ source: 'title',
+ target: 'short_description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'description',
+ target: 'description',
+ actionType: 'append',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: '456',
+ actionTypeId: '.servicenow',
+ name: 'My Connector 2',
+ config: {
+ apiUrl: 'https://instance2.service-now.com',
+ casesConfiguration: {
+ mapping: [
+ {
+ source: 'title',
+ target: 'short_description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'description',
+ target: 'description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+ ],
+ },
+ },
+ },
+];
+
+export const mapping: CasesConfigurationMapping[] = [
+ {
+ source: 'title',
+ target: 'short_description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'description',
+ target: 'description',
+ actionType: 'append',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+];
+
+export const searchURL =
+ '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))';
+
+export const initialState: State = {
+ connectorId: 'none',
+ closureType: 'close-by-user',
+ mapping: null,
+ currentConfiguration: { connectorId: 'none', closureType: 'close-by-user' },
+};
+
+export const useCaseConfigureResponse: ReturnUseCaseConfigure = {
+ loading: false,
+ persistLoading: false,
+ refetchCaseConfigure: jest.fn(),
+ persistCaseConfigure: jest.fn(),
+};
+
+export const useConnectorsResponse: ReturnConnectors = {
+ loading: false,
+ connectors,
+ refetchConnectors: jest.fn(),
+};
+
+export const kibanaMockImplementationArgs = {
+ services: {
+ ...createUseKibanaMock()().services,
+ triggers_actions_ui: { actionTypeRegistry: actionTypeRegistryMock.create() },
+ },
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx
new file mode 100644
index 0000000000000..cf52fef94ed17
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 React from 'react';
+import { ReactWrapper, mount } from 'enzyme';
+import { EuiText } from '@elastic/eui';
+
+import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button';
+import { TestProviders } from '../../../../mock';
+import { searchURL } from './__mock__';
+
+describe('Configuration button', () => {
+ let wrapper: ReactWrapper;
+ const props: ConfigureCaseButtonProps = {
+ isDisabled: false,
+ label: 'My label',
+ msgTooltip: <>>,
+ showToolTip: false,
+ titleTooltip: '',
+ urlSearch: searchURL,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders without the tooltip', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="configure-case-button"]')
+ .first()
+ .exists()
+ ).toBe(true);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="configure-case-tooltip"]')
+ .first()
+ .exists()
+ ).toBe(false);
+ });
+
+ test('it pass the correct props to the button', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="configure-case-button"]')
+ .first()
+ .props()
+ ).toMatchObject({
+ href: `#/link-to/case/configure${searchURL}`,
+ iconType: 'controlsHorizontal',
+ isDisabled: false,
+ 'aria-label': 'My label',
+ children: 'My label',
+ });
+ });
+
+ test('it renders the tooltip', () => {
+ const msgTooltip = {'My message tooltip'};
+
+ const newWrapper = mount(
+ ,
+ {
+ wrappingComponent: TestProviders,
+ }
+ );
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="configure-case-tooltip"]')
+ .first()
+ .exists()
+ ).toBe(true);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="configure-case-button"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the tooltip when hovering the button', () => {
+ const msgTooltip = 'My message tooltip';
+ const titleTooltip = 'My title';
+
+ const newWrapper = mount(
+ {msgTooltip}>}
+ />,
+ {
+ wrappingComponent: TestProviders,
+ }
+ );
+
+ newWrapper
+ .find('[data-test-subj="configure-case-button"]')
+ .first()
+ .simulate('mouseOver');
+
+ expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`);
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx
index b0bea83148bda..844ffea28415f 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx
@@ -8,7 +8,7 @@ import { EuiButton, EuiToolTip } from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import { getConfigureCasesUrl } from '../../../../components/link_to';
-interface ConfigureCaseButtonProps {
+export interface ConfigureCaseButtonProps {
label: string;
isDisabled: boolean;
msgTooltip: JSX.Element;
@@ -32,6 +32,7 @@ const ConfigureCaseButtonComponent: React.FC = ({
iconType="controlsHorizontal"
isDisabled={isDisabled}
aria-label={label}
+ data-test-subj="configure-case-button"
>
{label}
@@ -39,7 +40,12 @@ const ConfigureCaseButtonComponent: React.FC = ({
[label, isDisabled, urlSearch]
);
return showToolTip ? (
- {msgTooltip}
}>
+ {msgTooltip}}
+ data-test-subj="configure-case-tooltip"
+ >
{configureCaseButton}
) : (
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx
new file mode 100644
index 0000000000000..209dce9aedffc
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx
@@ -0,0 +1,67 @@
+/*
+ * 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 React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+
+import { ClosureOptions, ClosureOptionsProps } from './closure_options';
+import { TestProviders } from '../../../../mock';
+import { ClosureOptionsRadio } from './closure_options_radio';
+
+describe('ClosureOptions', () => {
+ let wrapper: ReactWrapper;
+ const onChangeClosureType = jest.fn();
+ const props: ClosureOptionsProps = {
+ disabled: false,
+ closureTypeSelected: 'close-by-user',
+ onChangeClosureType,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it shows the closure options form group', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-closure-options-form-group"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the closure options form row', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-closure-options-form-row"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows closure options', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-closure-options-radio"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it pass the correct props to child', () => {
+ const closureOptionsRadioComponent = wrapper.find(ClosureOptionsRadio);
+ expect(closureOptionsRadioComponent.props().disabled).toEqual(false);
+ expect(closureOptionsRadioComponent.props().closureTypeSelected).toEqual('close-by-user');
+ expect(closureOptionsRadioComponent.props().onChangeClosureType).toEqual(onChangeClosureType);
+ });
+
+ test('the closure type is changed successfully', () => {
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+
+ expect(onChangeClosureType).toHaveBeenCalled();
+ expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing');
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx
index 9879b9149059a..6fa97818dd0ce 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx
@@ -11,7 +11,7 @@ import { ClosureType } from '../../../../containers/case/configure/types';
import { ClosureOptionsRadio } from './closure_options_radio';
import * as i18n from './translations';
-interface ClosureOptionsProps {
+export interface ClosureOptionsProps {
closureTypeSelected: ClosureType;
disabled: boolean;
onChangeClosureType: (newClosureType: ClosureType) => void;
@@ -27,12 +27,18 @@ const ClosureOptionsComponent: React.FC = ({
fullWidth
title={{i18n.CASE_CLOSURE_OPTIONS_TITLE}
}
description={i18n.CASE_CLOSURE_OPTIONS_DESC}
+ data-test-subj="case-closure-options-form-group"
>
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx
new file mode 100644
index 0000000000000..f2ef2c2d55c28
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 React from 'react';
+import { ReactWrapper, mount } from 'enzyme';
+
+import { ClosureOptionsRadio, ClosureOptionsRadioComponentProps } from './closure_options_radio';
+import { TestProviders } from '../../../../mock';
+
+describe('ClosureOptionsRadio', () => {
+ let wrapper: ReactWrapper;
+ const onChangeClosureType = jest.fn();
+ const props: ClosureOptionsRadioComponentProps = {
+ disabled: false,
+ closureTypeSelected: 'close-by-user',
+ onChangeClosureType,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="closure-options-radio-group"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the correct number of radio buttons', () => {
+ expect(wrapper.find('input[name="closure_options"]')).toHaveLength(2);
+ });
+
+ test('it renders close by user radio button', () => {
+ expect(wrapper.find('input[id="close-by-user"]').exists()).toBeTruthy();
+ });
+
+ test('it renders close by pushing radio button', () => {
+ expect(wrapper.find('input[id="close-by-pushing"]').exists()).toBeTruthy();
+ });
+
+ test('it disables the close by user radio button', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find('input[id="close-by-user"]').prop('disabled')).toEqual(true);
+ });
+
+ test('it disables correctly the close by pushing radio button', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find('input[id="close-by-pushing"]').prop('disabled')).toEqual(true);
+ });
+
+ test('it selects the correct radio button', () => {
+ const newWrapper = mount(
+ ,
+ {
+ wrappingComponent: TestProviders,
+ }
+ );
+ expect(newWrapper.find('input[id="close-by-pushing"]').prop('checked')).toEqual(true);
+ });
+
+ test('it calls the onChangeClosureType function', () => {
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+ expect(onChangeClosureType).toHaveBeenCalled();
+ expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing');
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx
index f32f867b2471d..d2cdb7ecda7ba 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx
@@ -26,7 +26,7 @@ const radios: ClosureRadios[] = [
},
];
-interface ClosureOptionsRadioComponentProps {
+export interface ClosureOptionsRadioComponentProps {
closureTypeSelected: ClosureType;
disabled: boolean;
onChangeClosureType: (newClosureType: ClosureType) => void;
@@ -51,6 +51,7 @@ const ClosureOptionsRadioComponent: React.FC
idSelected={closureTypeSelected}
onChange={onChangeLocal}
name="closure_options"
+ data-test-subj="closure-options-radio-group"
/>
);
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx
new file mode 100644
index 0000000000000..5fb52c374b482
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx
@@ -0,0 +1,90 @@
+/*
+ * 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 React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+
+import { Connectors, Props } from './connectors';
+import { TestProviders } from '../../../../mock';
+import { ConnectorsDropdown } from './connectors_dropdown';
+import { connectors } from './__mock__';
+
+describe('Connectors', () => {
+ let wrapper: ReactWrapper;
+ const onChangeConnector = jest.fn();
+ const handleShowAddFlyout = jest.fn();
+ const props: Props = {
+ disabled: false,
+ connectors,
+ selectedConnector: 'none',
+ isLoading: false,
+ onChangeConnector,
+ handleShowAddFlyout,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it shows the connectors from group', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-connectors-form-group"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the connectors form row', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-connectors-form-row"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the connectors dropdown', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-connectors-dropdown"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it pass the correct props to child', () => {
+ const connectorsDropdownProps = wrapper.find(ConnectorsDropdown).props();
+ expect(connectorsDropdownProps).toMatchObject({
+ disabled: false,
+ isLoading: false,
+ connectors,
+ selectedConnector: 'none',
+ onChange: props.onChangeConnector,
+ });
+ });
+
+ test('the connector is changed successfully', () => {
+ wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
+
+ expect(onChangeConnector).toHaveBeenCalled();
+ expect(onChangeConnector).toHaveBeenCalledWith('456');
+ });
+
+ test('the connector is changed successfully to none', () => {
+ onChangeConnector.mockClear();
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ newWrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ newWrapper.find('button[data-test-subj="dropdown-connector-no-connector"]').simulate('click');
+
+ expect(onChangeConnector).toHaveBeenCalled();
+ expect(onChangeConnector).toHaveBeenCalledWith('none');
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
index 8fb1cfb1aa6cc..de6d5f76cfad0 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
@@ -28,7 +28,7 @@ const EuiFormRowExtended = styled(EuiFormRow)`
}
`;
-interface Props {
+export interface Props {
connectors: Connector[];
disabled: boolean;
isLoading: boolean;
@@ -48,7 +48,11 @@ const ConnectorsComponent: React.FC = ({
{i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL}
-
+
{i18n.ADD_NEW_CONNECTOR}
@@ -61,14 +65,20 @@ const ConnectorsComponent: React.FC = ({
fullWidth
title={{i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}
}
description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC}
+ data-test-subj="case-connectors-form-group"
>
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx
new file mode 100644
index 0000000000000..044108962efc7
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx
@@ -0,0 +1,86 @@
+/*
+ * 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 React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+import { EuiSuperSelect } from '@elastic/eui';
+
+import { ConnectorsDropdown, Props } from './connectors_dropdown';
+import { TestProviders } from '../../../../mock';
+import { connectors } from './__mock__';
+
+describe('ConnectorsDropdown', () => {
+ let wrapper: ReactWrapper;
+ const props: Props = {
+ disabled: false,
+ connectors,
+ isLoading: false,
+ onChange: jest.fn(),
+ selectedConnector: 'none',
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="dropdown-connectors"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it formats the connectors correctly', () => {
+ const selectProps = wrapper.find(EuiSuperSelect).props();
+
+ expect(selectProps.options).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ value: 'none',
+ 'data-test-subj': 'dropdown-connector-no-connector',
+ }),
+ expect.objectContaining({ value: '123', 'data-test-subj': 'dropdown-connector-123' }),
+ expect.objectContaining({ value: '456', 'data-test-subj': 'dropdown-connector-456' }),
+ ])
+ );
+ });
+
+ test('it disables the dropdown', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="dropdown-connectors"]')
+ .first()
+ .prop('disabled')
+ ).toEqual(true);
+ });
+
+ test('it loading correctly', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="dropdown-connectors"]')
+ .first()
+ .prop('isLoading')
+ ).toEqual(true);
+ });
+
+ test('it selects the correct connector', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find('button span').text()).toEqual('My Connector');
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx
index a0a0ad6cd3e7f..15066e73eee82 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx
@@ -12,7 +12,7 @@ import { Connector } from '../../../../containers/case/configure/types';
import { connectors as connectorsDefinition } from '../../../../lib/connectors/config';
import * as i18n from './translations';
-interface Props {
+export interface Props {
connectors: Connector[];
disabled: boolean;
isLoading: boolean;
@@ -34,7 +34,7 @@ const noConnectorOption = {
{i18n.NO_CONNECTOR}
>
),
- 'data-test-subj': 'no-connector',
+ 'data-test-subj': 'dropdown-connector-no-connector',
};
const ConnectorsDropdownComponent: React.FC = ({
@@ -60,7 +60,7 @@ const ConnectorsDropdownComponent: React.FC = ({
{connector.name}
>
),
- 'data-test-subj': connector.id,
+ 'data-test-subj': `dropdown-connector-${connector.id}`,
},
],
[noConnectorOption]
@@ -76,6 +76,7 @@ const ConnectorsDropdownComponent: React.FC = ({
valueOfSelected={selectedConnector}
fullWidth
onChange={onChange}
+ data-test-subj="dropdown-connectors"
/>
);
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx
new file mode 100644
index 0000000000000..9ab752bb589c0
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx
@@ -0,0 +1,84 @@
+/*
+ * 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 React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+
+import { FieldMapping, FieldMappingProps } from './field_mapping';
+import { mapping } from './__mock__';
+import { FieldMappingRow } from './field_mapping_row';
+import { defaultMapping } from '../../../../lib/connectors/config';
+import { TestProviders } from '../../../../mock';
+
+describe('FieldMappingRow', () => {
+ let wrapper: ReactWrapper;
+ const onChangeMapping = jest.fn();
+ const props: FieldMappingProps = {
+ disabled: false,
+ mapping,
+ onChangeMapping,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-field-mapping-cols"]')
+ .first()
+ .exists()
+ ).toBe(true);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-field-mapping-row-wrapper"]')
+ .first()
+ .exists()
+ ).toBe(true);
+
+ expect(wrapper.find(FieldMappingRow).length).toEqual(3);
+ });
+
+ test('it shows the correct number of FieldMappingRow with default mapping', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find(FieldMappingRow).length).toEqual(3);
+ });
+
+ test('it pass the corrects props to mapping row', () => {
+ const rows = wrapper.find(FieldMappingRow);
+ rows.forEach((row, index) => {
+ expect(row.prop('siemField')).toEqual(mapping[index].source);
+ expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType);
+ expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target);
+ });
+ });
+
+ test('it pass the default mapping when mapping is null', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ const rows = newWrapper.find(FieldMappingRow);
+ rows.forEach((row, index) => {
+ expect(row.prop('siemField')).toEqual(defaultMapping[index].source);
+ expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType);
+ expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target);
+ });
+ });
+
+ test('it should show zero rows on empty array', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find(FieldMappingRow).length).toEqual(0);
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx
index 0c0dc14f1c218..2934b1056e29c 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx
@@ -18,6 +18,7 @@ import { FieldMappingRow } from './field_mapping_row';
import * as i18n from './translations';
import { defaultMapping } from '../../../../lib/connectors/config';
+import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';
const FieldRowWrapper = styled.div`
margin-top: 8px;
@@ -28,22 +29,26 @@ const supportedThirdPartyFields: Array> =
{
value: 'not_mapped',
inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED},
+ 'data-test-subj': 'third-party-field-not-mapped',
},
{
value: 'short_description',
inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC},
+ 'data-test-subj': 'third-party-field-short-description',
},
{
value: 'comments',
inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS},
+ 'data-test-subj': 'third-party-field-comments',
},
{
value: 'description',
inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC},
+ 'data-test-subj': 'third-party-field-description',
},
];
-interface FieldMappingProps {
+export interface FieldMappingProps {
disabled: boolean;
mapping: CasesConfigurationMapping[] | null;
onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void;
@@ -57,14 +62,7 @@ const FieldMappingComponent: React.FC = ({
const onChangeActionType = useCallback(
(caseField: CaseField, newActionType: ActionType) => {
const myMapping = mapping ?? defaultMapping;
- const findItemIndex = myMapping.findIndex(item => item.source === caseField);
- if (findItemIndex >= 0) {
- onChangeMapping([
- ...myMapping.slice(0, findItemIndex),
- { ...myMapping[findItemIndex], actionType: newActionType },
- ...myMapping.slice(findItemIndex + 1),
- ]);
- }
+ onChangeMapping(setActionTypeToMapping(caseField, newActionType, myMapping));
},
[mapping]
);
@@ -72,22 +70,13 @@ const FieldMappingComponent: React.FC = ({
const onChangeThirdParty = useCallback(
(caseField: CaseField, newThirdPartyField: ThirdPartyField) => {
const myMapping = mapping ?? defaultMapping;
- onChangeMapping(
- myMapping.map(item => {
- if (item.source !== caseField && item.target === newThirdPartyField) {
- return { ...item, target: 'not_mapped' };
- } else if (item.source === caseField) {
- return { ...item, target: newThirdPartyField };
- }
- return item;
- })
- );
+ onChangeMapping(setThirdPartyToMapping(caseField, newThirdPartyField, myMapping));
},
[mapping]
);
return (
<>
-
+
{i18n.FIELD_MAPPING_FIRST_COL}
@@ -100,7 +89,7 @@ const FieldMappingComponent: React.FC = ({
-
+
{(mapping ?? defaultMapping).map(item => (
> = [
+ {
+ value: 'short_description',
+ inputDisplay: {'Short Description'},
+ 'data-test-subj': 'third-party-short-desc',
+ },
+ {
+ value: 'description',
+ inputDisplay: {'Description'},
+ 'data-test-subj': 'third-party-desc',
+ },
+];
+
+describe('FieldMappingRow', () => {
+ let wrapper: ReactWrapper;
+ const onChangeActionType = jest.fn();
+ const onChangeThirdParty = jest.fn();
+
+ const props: RowProps = {
+ disabled: false,
+ siemField: 'title',
+ thirdPartyOptions,
+ onChangeActionType,
+ onChangeThirdParty,
+ selectedActionType: 'nothing',
+ selectedThirdParty: 'short_description',
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-third-party-select"]')
+ .first()
+ .exists()
+ ).toBe(true);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-type-select"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it passes thirdPartyOptions correctly', () => {
+ const selectProps = wrapper
+ .find(EuiSuperSelect)
+ .first()
+ .props();
+
+ expect(selectProps.options).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ value: 'short_description',
+ 'data-test-subj': 'third-party-short-desc',
+ }),
+ expect.objectContaining({
+ value: 'description',
+ 'data-test-subj': 'third-party-desc',
+ }),
+ ])
+ );
+ });
+
+ test('it passes the correct actionTypeOptions', () => {
+ const selectProps = wrapper
+ .find(EuiSuperSelect)
+ .at(1)
+ .props();
+
+ expect(selectProps.options).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ value: 'nothing',
+ 'data-test-subj': 'edit-update-option-nothing',
+ }),
+ expect.objectContaining({
+ value: 'overwrite',
+ 'data-test-subj': 'edit-update-option-overwrite',
+ }),
+ expect.objectContaining({
+ value: 'append',
+ 'data-test-subj': 'edit-update-option-append',
+ }),
+ ])
+ );
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx
index 62e43c86af8d9..732a11a58d35a 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx
@@ -21,7 +21,7 @@ import {
ThirdPartyField,
} from '../../../../containers/case/configure/types';
-interface RowProps {
+export interface RowProps {
disabled: boolean;
siemField: CaseField;
thirdPartyOptions: Array>;
@@ -77,6 +77,7 @@ const FieldMappingRowComponent: React.FC = ({
options={thirdPartyOptions}
valueOfSelected={selectedThirdParty}
onChange={onChangeThirdParty.bind(null, siemField)}
+ data-test-subj={'case-configure-third-party-select'}
/>
@@ -85,6 +86,7 @@ const FieldMappingRowComponent: React.FC = ({
options={actionTypeOptions}
valueOfSelected={selectedActionType}
onChange={onChangeActionType.bind(null, siemField)}
+ data-test-subj={'case-configure-action-type-select'}
/>
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx
new file mode 100644
index 0000000000000..5ea3f500c0349
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx
@@ -0,0 +1,748 @@
+/*
+ * 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 React, { useEffect } from 'react';
+import { ReactWrapper, mount } from 'enzyme';
+
+import { useKibana } from '../../../../lib/kibana';
+import { useConnectors } from '../../../../containers/case/configure/use_connectors';
+import { useCaseConfigure } from '../../../../containers/case/configure/use_configure';
+import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search';
+
+import {
+ connectors,
+ searchURL,
+ useCaseConfigureResponse,
+ useConnectorsResponse,
+ kibanaMockImplementationArgs,
+} from './__mock__';
+
+jest.mock('../../../../lib/kibana');
+jest.mock('../../../../containers/case/configure/use_connectors');
+jest.mock('../../../../containers/case/configure/use_configure');
+jest.mock('../../../../components/navigation/use_get_url_search');
+
+const useKibanaMock = useKibana as jest.Mock;
+const useConnectorsMock = useConnectors as jest.Mock;
+const useCaseConfigureMock = useCaseConfigure as jest.Mock;
+const useGetUrlSearchMock = useGetUrlSearch as jest.Mock;
+
+import { ConfigureCases } from './';
+import { TestProviders } from '../../../../mock';
+import { Connectors } from './connectors';
+import { ClosureOptions } from './closure_options';
+import { Mapping } from './mapping';
+import {
+ ActionsConnectorsContextProvider,
+ ConnectorAddFlyout,
+ ConnectorEditFlyout,
+} from '../../../../../../../../plugins/triggers_actions_ui/public';
+import { EuiBottomBar } from '@elastic/eui';
+
+describe('rendering', () => {
+ let wrapper: ReactWrapper;
+ beforeEach(() => {
+ jest.resetAllMocks();
+ useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
+ useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] }));
+ useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs);
+ useGetUrlSearchMock.mockImplementation(() => searchURL);
+
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders the Connectors', () => {
+ expect(wrapper.find('[data-test-subj="case-connectors-form-group"]').exists()).toBeTruthy();
+ });
+
+ test('it renders the ClosureType', () => {
+ expect(
+ wrapper.find('[data-test-subj="case-closure-options-form-group"]').exists()
+ ).toBeTruthy();
+ });
+
+ test('it renders the Mapping', () => {
+ expect(wrapper.find('[data-test-subj="case-mapping-form-group"]').exists()).toBeTruthy();
+ });
+
+ test('it renders the ActionsConnectorsContextProvider', () => {
+ // Components from triggers_actions_ui do not have a data-test-subj
+ expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy();
+ });
+
+ test('it renders the ConnectorAddFlyout', () => {
+ // Components from triggers_actions_ui do not have a data-test-subj
+ expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy();
+ });
+
+ test('it does NOT render the ConnectorEditFlyout', () => {
+ // Components from triggers_actions_ui do not have a data-test-subj
+ expect(wrapper.find(ConnectorEditFlyout).exists()).toBeFalsy();
+ });
+
+ test('it does NOT render the EuiCallOut', () => {
+ expect(wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists()).toBeFalsy();
+ });
+
+ test('it does NOT render the EuiBottomBar', () => {
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+ });
+});
+
+describe('ConfigureCases - Unhappy path', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
+ useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] }));
+ useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs);
+ useGetUrlSearchMock.mockImplementation(() => searchURL);
+ });
+
+ test('it shows the warning callout when configuration is invalid', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('not-id'), []);
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const wrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists()
+ ).toBeTruthy();
+ });
+});
+
+describe('ConfigureCases - Happy path', () => {
+ let wrapper: ReactWrapper;
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('123'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return useCaseConfigureResponse;
+ }
+ );
+ useConnectorsMock.mockImplementation(() => useConnectorsResponse);
+ useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs);
+ useGetUrlSearchMock.mockImplementation(() => searchURL);
+
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it renders the ConnectorEditFlyout', () => {
+ expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy();
+ });
+
+ test('it renders with correct props', () => {
+ // Connector
+ expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors);
+ expect(wrapper.find(Connectors).prop('disabled')).toBe(false);
+ expect(wrapper.find(Connectors).prop('isLoading')).toBe(false);
+ expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('123');
+
+ // ClosureOptions
+ expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false);
+ expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user');
+
+ // Mapping
+ expect(wrapper.find(Mapping).prop('disabled')).toBe(true);
+ expect(wrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(false);
+ expect(wrapper.find(Mapping).prop('mapping')).toEqual(
+ connectors[0].config.casesConfiguration.mapping
+ );
+
+ // Flyouts
+ expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false);
+ expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([
+ {
+ id: '.servicenow',
+ name: 'ServiceNow',
+ enabled: true,
+ enabledInConfig: true,
+ enabledInLicense: true,
+ minimumLicenseRequired: 'platinum',
+ },
+ ]);
+
+ expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false);
+ expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]);
+ });
+
+ test('it disables correctly when the user cannot crud', () => {
+ const newWrapper = mount(, {
+ wrappingComponent: TestProviders,
+ });
+
+ expect(newWrapper.find(Connectors).prop('disabled')).toBe(true);
+ expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true);
+ expect(newWrapper.find(Mapping).prop('disabled')).toBe(true);
+ expect(newWrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(true);
+ });
+
+ test('it disables correctly Connector when loading connectors', () => {
+ useConnectorsMock.mockImplementation(() => ({
+ ...useConnectorsResponse,
+ loading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Connectors).prop('disabled')).toBe(true);
+ });
+
+ test('it disables correctly Connector when saving configuration', () => {
+ useCaseConfigureMock.mockImplementation(() => ({
+ ...useCaseConfigureResponse,
+ persistLoading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Connectors).prop('disabled')).toBe(true);
+ });
+
+ test('it pass the correct value to isLoading attribute on Connector', () => {
+ useConnectorsMock.mockImplementation(() => ({
+ ...useConnectorsResponse,
+ loading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Connectors).prop('isLoading')).toBe(true);
+ });
+
+ test('it set correctly the selected connector', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Connectors).prop('selectedConnector')).toBe('456');
+ });
+
+ test('it show the add flyout when pressing the add connector button', () => {
+ wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click');
+ wrapper.update();
+
+ expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true);
+ expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy();
+ });
+
+ test('it disables correctly ClosureOptions when loading connectors', () => {
+ useConnectorsMock.mockImplementation(() => ({
+ ...useConnectorsResponse,
+ loading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true);
+ });
+
+ test('it disables correctly ClosureOptions when saving configuration', () => {
+ useCaseConfigureMock.mockImplementation(() => ({
+ ...useCaseConfigureResponse,
+ persistLoading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true);
+ });
+
+ test('it disables correctly ClosureOptions when the connector is set to none', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('none'), []);
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the mapping permanently', () => {
+ expect(wrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the update connector button when loading the connectors', () => {
+ useConnectorsMock.mockImplementation(() => ({
+ ...useConnectorsResponse,
+ loading: true,
+ }));
+
+ expect(wrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the update connector button when loading the configuration', () => {
+ useCaseConfigureMock.mockImplementation(() => ({
+ ...useCaseConfigureResponse,
+ loading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the update connector button when saving the configuration', () => {
+ useCaseConfigureMock.mockImplementation(() => ({
+ ...useCaseConfigureResponse,
+ persistLoading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the update connector button when the connectorId is invalid', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('not-id'), []);
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it disables the update connector button when the connectorId is set to none', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('none'), []);
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(newWrapper.find(Mapping).prop('disabled')).toBe(true);
+ });
+
+ test('it show the edit flyout when pressing the update connector button', () => {
+ wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click');
+ wrapper.update();
+
+ expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true);
+ expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy();
+ });
+
+ test('it sets the mapping of a connector correctly', () => {
+ expect(wrapper.find(Mapping).prop('mapping')).toEqual(
+ connectors[0].config.casesConfiguration.mapping
+ );
+ });
+
+ // TODO: When mapping is enabled the test.todo should be implemented.
+ test.todo('the mapping is changed successfully when changing the third party');
+ test.todo('the mapping is changed successfully when changing the action type');
+
+ test('it does not shows the action bar when there is no change', () => {
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+ });
+
+ test('it shows the action bar when the connector is changed', () => {
+ wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ wrapper.update();
+ wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeTruthy();
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]')
+ .first()
+ .text()
+ ).toBe('1 unsaved changes');
+ });
+
+ test('it shows the action bar when the closure type is changed', () => {
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeTruthy();
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]')
+ .first()
+ .text()
+ ).toBe('1 unsaved changes');
+ });
+
+ test('it tracks the changes successfully', () => {
+ wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ wrapper.update();
+ wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
+ wrapper.update();
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeTruthy();
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]')
+ .first()
+ .text()
+ ).toBe('2 unsaved changes');
+ });
+
+ test('it tracks and reverts the changes successfully ', () => {
+ // change settings
+ wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ wrapper.update();
+ wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
+ wrapper.update();
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+
+ // revert back to initial settings
+ wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
+ wrapper.update();
+ wrapper.find('button[data-test-subj="dropdown-connector-123"]').simulate('click');
+ wrapper.update();
+ wrapper.find('input[id="close-by-user"]').simulate('change');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+ });
+
+ test('it close and restores the action bar when the add connector button is pressed', () => {
+ // Change closure type
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+
+ // Press add connector button
+ wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+
+ expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true);
+
+ // Close the add flyout
+ wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeTruthy();
+
+ expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]')
+ .first()
+ .text()
+ ).toBe('1 unsaved changes');
+ });
+
+ test('it close and restores the action bar when the update connector button is pressed', () => {
+ // Change closure type
+ wrapper.find('input[id="close-by-pushing"]').simulate('change');
+ wrapper.update();
+
+ // Press update connector button
+ wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+
+ expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true);
+
+ // Close the edit flyout
+ wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click');
+ wrapper.update();
+
+ expect(
+ wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeTruthy();
+
+ expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false);
+
+ expect(
+ wrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]')
+ .first()
+ .text()
+ ).toBe('1 unsaved changes');
+ });
+
+ test('it disables the buttons of action bar when loading connectors', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return useCaseConfigureResponse;
+ }
+ );
+
+ useConnectorsMock.mockImplementation(() => ({
+ ...useConnectorsResponse,
+ loading: true,
+ }));
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+ });
+
+ test('it disables the buttons of action bar when loading configuration', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return { ...useCaseConfigureResponse, loading: true };
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+ });
+
+ test('it disables the buttons of action bar when saving configuration', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return { ...useCaseConfigureResponse, persistLoading: true };
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .prop('isDisabled')
+ ).toBe(true);
+ });
+
+ test('it shows the loading spinner when saving configuration', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return { ...useCaseConfigureResponse, persistLoading: true };
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]')
+ .first()
+ .prop('isLoading')
+ ).toBe(true);
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .prop('isLoading')
+ ).toBe(true);
+ });
+
+ test('it closes the action bar when pressing save', () => {
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return useCaseConfigureResponse;
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .simulate('click');
+
+ newWrapper.update();
+
+ expect(
+ newWrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
+ ).toBeFalsy();
+ });
+
+ test('it submits the configuration correctly', () => {
+ const persistCaseConfigure = jest.fn();
+
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-pushing',
+ }),
+ []
+ );
+ return { ...useCaseConfigureResponse, persistCaseConfigure };
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]')
+ .first()
+ .simulate('click');
+
+ newWrapper.update();
+
+ expect(persistCaseConfigure).toHaveBeenCalled();
+ expect(persistCaseConfigure).toHaveBeenCalledWith({
+ connectorId: '456',
+ connectorName: 'My Connector 2',
+ closureType: 'close-by-user',
+ });
+ });
+
+ test('it has the correct url on cancel button', () => {
+ const persistCaseConfigure = jest.fn();
+
+ useCaseConfigureMock.mockImplementation(
+ ({ setConnector, setClosureType, setCurrentConfiguration }) => {
+ useEffect(() => setConnector('456'), []);
+ useEffect(() => setClosureType('close-by-user'), []);
+ useEffect(
+ () =>
+ setCurrentConfiguration({
+ connectorId: '123',
+ closureType: 'close-by-user',
+ }),
+ []
+ );
+ return { ...useCaseConfigureResponse, persistCaseConfigure };
+ }
+ );
+
+ const newWrapper = mount(, { wrappingComponent: TestProviders });
+
+ expect(
+ newWrapper
+ .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]')
+ .first()
+ .prop('href')
+ ).toBe(`#/link-to/case${searchURL}`);
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
index b8cf5a3880801..241dcef14a145 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
@@ -140,6 +140,7 @@ const ConfigureCasesComponent: React.FC = ({ userC
setClosureType,
setCurrentConfiguration,
});
+
const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors();
// ActionsConnectorsContextProvider reloadConnectors prop expects a Promise.
@@ -251,7 +252,12 @@ const ConfigureCasesComponent: React.FC = ({ userC
{!connectorIsValid && (
-
+
{i18n.WARNING_NO_CONNECTOR_MESSAGE}
@@ -283,11 +289,13 @@ const ConfigureCasesComponent: React.FC = ({ userC
/>
{actionBarVisible && (
-
+
- {i18n.UNSAVED_CHANGES(totalConfigurationChanges)}
+
+ {i18n.UNSAVED_CHANGES(totalConfigurationChanges)}
+
@@ -300,6 +308,7 @@ const ConfigureCasesComponent: React.FC = ({ userC
isLoading={persistLoading}
aria-label={i18n.CANCEL}
href={getCaseUrl(search)}
+ data-test-subj="case-configure-action-bottom-bar-cancel-button"
>
{i18n.CANCEL}
@@ -313,6 +322,7 @@ const ConfigureCasesComponent: React.FC = ({ userC
isDisabled={isLoadingAny}
isLoading={persistLoading}
onClick={handleSubmit}
+ data-test-subj="case-configure-action-bottom-bar-save-button"
>
{i18n.SAVE_CHANGES}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx
new file mode 100644
index 0000000000000..fefcb2ca8cf6a
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 React from 'react';
+import { mount, ReactWrapper } from 'enzyme';
+
+import { TestProviders } from '../../../../mock';
+import { Mapping, MappingProps } from './mapping';
+import { mapping } from './__mock__';
+
+describe('Mapping', () => {
+ let wrapper: ReactWrapper;
+ const onChangeMapping = jest.fn();
+ const setEditFlyoutVisibility = jest.fn();
+ const props: MappingProps = {
+ disabled: false,
+ mapping,
+ updateConnectorDisabled: false,
+ onChangeMapping,
+ setEditFlyoutVisibility,
+ };
+
+ beforeAll(() => {
+ wrapper = mount(, { wrappingComponent: TestProviders });
+ });
+
+ test('it shows mapping form group', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-mapping-form-group"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows mapping form row', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-mapping-form-row"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the update button', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-mapping-update-connector-button"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+
+ test('it shows the field mapping', () => {
+ expect(
+ wrapper
+ .find('[data-test-subj="case-mapping-field"]')
+ .first()
+ .exists()
+ ).toBe(true);
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx
index 8cba73d1249df..7340a49f6d0bb 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx
@@ -20,7 +20,7 @@ import * as i18n from './translations';
import { FieldMapping } from './field_mapping';
import { CasesConfigurationMapping } from '../../../../containers/case/configure/types';
-interface MappingProps {
+export interface MappingProps {
disabled: boolean;
updateConnectorDisabled: boolean;
mapping: CasesConfigurationMapping[] | null;
@@ -45,20 +45,27 @@ const MappingComponent: React.FC = ({
fullWidth
title={{i18n.FIELD_MAPPING_TITLE}
}
description={i18n.FIELD_MAPPING_DESC}
+ data-test-subj="case-mapping-form-group"
>
-
+
{i18n.UPDATE_CONNECTOR}
-
+
);
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts
new file mode 100644
index 0000000000000..df958b75dc6b8
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { configureCasesReducer, Action, State } from './reducer';
+import { initialState, mapping } from './__mock__';
+
+describe('Reducer', () => {
+ let reducer: (state: State, action: Action) => State;
+
+ beforeAll(() => {
+ reducer = configureCasesReducer();
+ });
+
+ test('it should set the correct configuration', () => {
+ const action: Action = {
+ type: 'setCurrentConfiguration',
+ currentConfiguration: { connectorId: '123', closureType: 'close-by-user' },
+ };
+ const state = reducer(initialState, action);
+
+ expect(state).toEqual({
+ ...state,
+ currentConfiguration: action.currentConfiguration,
+ });
+ });
+
+ test('it should set the correct connector id', () => {
+ const action: Action = {
+ type: 'setConnectorId',
+ connectorId: '456',
+ };
+ const state = reducer(initialState, action);
+
+ expect(state).toEqual({
+ ...state,
+ connectorId: action.connectorId,
+ });
+ });
+
+ test('it should set the closure type', () => {
+ const action: Action = {
+ type: 'setClosureType',
+ closureType: 'close-by-pushing',
+ };
+ const state = reducer(initialState, action);
+
+ expect(state).toEqual({
+ ...state,
+ closureType: action.closureType,
+ });
+ });
+
+ test('it should set the mapping', () => {
+ const action: Action = {
+ type: 'setMapping',
+ mapping,
+ };
+ const state = reducer(initialState, action);
+
+ expect(state).toEqual({
+ ...state,
+ mapping: action.mapping,
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx
new file mode 100644
index 0000000000000..1c6fc9b2d405f
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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 { mapping } from './__mock__';
+import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';
+import { CasesConfigurationMapping } from '../../../../containers/case/configure/types';
+
+describe('FieldMappingRow', () => {
+ test('it should change the action type', () => {
+ const newMapping = setActionTypeToMapping('title', 'nothing', mapping);
+ expect(newMapping[0].actionType).toBe('nothing');
+ });
+
+ test('it should not change other fields', () => {
+ const [newTitle, description, comments] = setActionTypeToMapping('title', 'nothing', mapping);
+ expect(newTitle).not.toEqual(mapping[0]);
+ expect(description).toEqual(mapping[1]);
+ expect(comments).toEqual(mapping[2]);
+ });
+
+ test('it should return a new array when changing action type', () => {
+ const newMapping = setActionTypeToMapping('title', 'nothing', mapping);
+ expect(newMapping).not.toBe(mapping);
+ });
+
+ test('it should change the third party', () => {
+ const newMapping = setThirdPartyToMapping('title', 'description', mapping);
+ expect(newMapping[0].target).toBe('description');
+ });
+
+ test('it should not change other fields when there is not a conflict', () => {
+ const tempMapping: CasesConfigurationMapping[] = [
+ {
+ source: 'title',
+ target: 'short_description',
+ actionType: 'overwrite',
+ },
+ {
+ source: 'comments',
+ target: 'comments',
+ actionType: 'append',
+ },
+ ];
+
+ const [newTitle, comments] = setThirdPartyToMapping('title', 'description', tempMapping);
+
+ expect(newTitle).not.toEqual(mapping[0]);
+ expect(comments).toEqual(tempMapping[1]);
+ });
+
+ test('it should return a new array when changing third party', () => {
+ const newMapping = setThirdPartyToMapping('title', 'description', mapping);
+ expect(newMapping).not.toBe(mapping);
+ });
+
+ test('it should change the target of the conflicting third party field to not_mapped', () => {
+ const newMapping = setThirdPartyToMapping('title', 'description', mapping);
+ expect(newMapping[1].target).toBe('not_mapped');
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts
new file mode 100644
index 0000000000000..2ac6cc1a38587
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 {
+ CaseField,
+ ActionType,
+ CasesConfigurationMapping,
+ ThirdPartyField,
+} from '../../../../containers/case/configure/types';
+
+export const setActionTypeToMapping = (
+ caseField: CaseField,
+ newActionType: ActionType,
+ mapping: CasesConfigurationMapping[]
+): CasesConfigurationMapping[] => {
+ const findItemIndex = mapping.findIndex(item => item.source === caseField);
+
+ if (findItemIndex >= 0) {
+ return [
+ ...mapping.slice(0, findItemIndex),
+ { ...mapping[findItemIndex], actionType: newActionType },
+ ...mapping.slice(findItemIndex + 1),
+ ];
+ }
+
+ return [...mapping];
+};
+
+export const setThirdPartyToMapping = (
+ caseField: CaseField,
+ newThirdPartyField: ThirdPartyField,
+ mapping: CasesConfigurationMapping[]
+): CasesConfigurationMapping[] =>
+ mapping.map(item => {
+ if (item.source !== caseField && item.target === newThirdPartyField) {
+ return { ...item, target: 'not_mapped' };
+ } else if (item.source === caseField) {
+ return { ...item, target: newThirdPartyField };
+ }
+ return item;
+ });