Skip to content

Commit

Permalink
[Security Solution][Detections] Adds exception modal tests (#74596)
Browse files Browse the repository at this point in the history
  • Loading branch information
dplumlee committed Aug 18, 2020
1 parent 8ea2bb3 commit eddf9cc
Show file tree
Hide file tree
Showing 5 changed files with 615 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const AddExceptionComments = memo(function AddExceptionComments({
<EuiFlexItem grow={1}>
<EuiTextArea
placeholder={i18n.ADD_COMMENT_PLACEHOLDER}
aria-label="Use aria labels when no actual label is in use"
aria-label="Comment Input"
value={newCommentValue}
onChange={handleOnChange}
fullWidth={true}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
/*
* 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 { ThemeProvider } from 'styled-components';
import { mount, ReactWrapper } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { act } from 'react-dom/test-utils';

import { AddExceptionModal } from './';
import { useKibana, useCurrentUser } from '../../../../common/lib/kibana';
import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock';
import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub';
import { useAddOrUpdateException } from '../use_add_exception';
import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list';
import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index';
import { createUseKibanaMock } from '../../../mock/kibana_react';
import { TimelineNonEcsData, Ecs } from '../../../../graphql/types';
import * as builder from '../builder';
import * as helpers from '../helpers';
import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
import { EntriesArray } from '../../../../../../lists/common/schemas/types';
import { ExceptionListItemSchema } from '../../../../../../lists/common';

jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index');
jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../detections/containers/detection_engine/rules');
jest.mock('../use_add_exception');
jest.mock('../use_fetch_or_create_rule_exception_list');
jest.mock('../builder');

const useKibanaMock = useKibana as jest.Mock;

describe('When the add exception modal is opened', () => {
const ruleName = 'test rule';
let defaultEndpointItems: jest.SpyInstance<ReturnType<
typeof helpers.defaultEndpointExceptionItems
>>;
let ExceptionBuilderComponent: jest.SpyInstance<ReturnType<
typeof builder.ExceptionBuilderComponent
>>;
beforeEach(() => {
defaultEndpointItems = jest.spyOn(helpers, 'defaultEndpointExceptionItems');
ExceptionBuilderComponent = jest
.spyOn(builder, 'ExceptionBuilderComponent')
.mockReturnValue(<></>);

const kibanaMock = createUseKibanaMock()();
useKibanaMock.mockImplementation(() => ({
...kibanaMock,
}));
(useAddOrUpdateException as jest.Mock).mockImplementation(() => [
{ isLoading: false },
jest.fn(),
]);
(useFetchOrCreateRuleExceptionList as jest.Mock).mockImplementation(() => [
false,
getExceptionListSchemaMock(),
]);
(useSignalIndex as jest.Mock).mockImplementation(() => ({
loading: false,
signalIndexName: 'mock-siem-signals-index',
}));
(useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
{
isLoading: false,
indexPatterns: stubIndexPattern,
},
]);
(useCurrentUser as jest.Mock).mockReturnValue({ username: 'test-username' });
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

describe('when the modal is loading', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
// Mocks one of the hooks as loading
(useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
{
isLoading: true,
indexPatterns: stubIndexPattern,
},
]);
wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AddExceptionModal
ruleId={'123'}
ruleIndices={[]}
ruleName={ruleName}
exceptionListType={'endpoint'}
onCancel={jest.fn()}
onConfirm={jest.fn()}
/>
</ThemeProvider>
);
});
it('should show the loading spinner', () => {
expect(wrapper.find('[data-test-subj="loadingAddExceptionModal"]').exists()).toBeTruthy();
});
});

describe('when there is no alert data passed to an endpoint list exception', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AddExceptionModal
ruleId={'123'}
ruleIndices={['filebeat-*']}
ruleName={ruleName}
exceptionListType={'endpoint'}
onCancel={jest.fn()}
onConfirm={jest.fn()}
/>
</ThemeProvider>
);
const callProps = ExceptionBuilderComponent.mock.calls[0][0];
act(() => callProps.onChange({ exceptionItems: [] }));
});
it('has the add exception button disabled', () => {
expect(
wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode()
).toBeDisabled();
});
it('should render the exception builder', () => {
expect(wrapper.find('[data-test-subj="alert-exception-builder"]').exists()).toBeTruthy();
});
it('should not render the close on add exception checkbox', () => {
expect(
wrapper.find('[data-test-subj="close-alert-on-add-add-exception-checkbox"]').exists()
).toBeFalsy();
});
it('should contain the endpoint specific documentation text', () => {
expect(wrapper.find('[data-test-subj="add-exception-endpoint-text"]').exists()).toBeTruthy();
});
});

describe('when there is alert data passed to an endpoint list exception', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const alertDataMock: { ecsData: Ecs; nonEcsData: TimelineNonEcsData[] } = {
ecsData: { _id: 'test-id' },
nonEcsData: [{ field: 'file.path', value: ['test/path'] }],
};
wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AddExceptionModal
ruleId={'123'}
ruleIndices={['filebeat-*']}
ruleName={ruleName}
exceptionListType={'endpoint'}
onCancel={jest.fn()}
onConfirm={jest.fn()}
alertData={alertDataMock}
/>
</ThemeProvider>
);
const callProps = ExceptionBuilderComponent.mock.calls[0][0];
act(() => callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }));
});
it('has the add exception button enabled', () => {
expect(
wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode()
).not.toBeDisabled();
});
it('should render the exception builder', () => {
expect(wrapper.find('[data-test-subj="alert-exception-builder"]').exists()).toBeTruthy();
});
it('should prepopulate endpoint items', () => {
expect(defaultEndpointItems).toHaveBeenCalled();
});
it('should render the close on add exception checkbox', () => {
expect(
wrapper.find('[data-test-subj="close-alert-on-add-add-exception-checkbox"]').exists()
).toBeTruthy();
});
it('should have the bulk close checkbox disabled', () => {
expect(
wrapper
.find('input[data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"]')
.getDOMNode()
).toBeDisabled();
});
it('should contain the endpoint specific documentation text', () => {
expect(wrapper.find('[data-test-subj="add-exception-endpoint-text"]').exists()).toBeTruthy();
});
});

describe('when there is alert data passed to a detection list exception', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const alertDataMock: { ecsData: Ecs; nonEcsData: TimelineNonEcsData[] } = {
ecsData: { _id: 'test-id' },
nonEcsData: [{ field: 'file.path', value: ['test/path'] }],
};
wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AddExceptionModal
ruleId={'123'}
ruleIndices={['filebeat-*']}
ruleName={ruleName}
exceptionListType={'detection'}
onCancel={jest.fn()}
onConfirm={jest.fn()}
alertData={alertDataMock}
/>
</ThemeProvider>
);
const callProps = ExceptionBuilderComponent.mock.calls[0][0];
act(() => callProps.onChange({ exceptionItems: [getExceptionListItemSchemaMock()] }));
});
it('has the add exception button enabled', () => {
expect(
wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode()
).not.toBeDisabled();
});
it('should render the exception builder', () => {
expect(wrapper.find('[data-test-subj="alert-exception-builder"]').exists()).toBeTruthy();
});
it('should not prepopulate endpoint items', () => {
expect(defaultEndpointItems).not.toHaveBeenCalled();
});
it('should render the close on add exception checkbox', () => {
expect(
wrapper.find('[data-test-subj="close-alert-on-add-add-exception-checkbox"]').exists()
).toBeTruthy();
});
it('should have the bulk close checkbox disabled', () => {
expect(
wrapper
.find('input[data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"]')
.getDOMNode()
).toBeDisabled();
});
});

describe('when there is bulk-closeable alert data passed to an endpoint list exception', () => {
let wrapper: ReactWrapper;
let callProps: {
onChange: (props: { exceptionItems: ExceptionListItemSchema[] }) => void;
exceptionListItems: ExceptionListItemSchema[];
};
beforeEach(() => {
// Mocks the index patterns to contain the pre-populated endpoint fields so that the exception qualifies as bulk closable
(useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
{
isLoading: false,
indexPatterns: {
...stubIndexPattern,
fields: [
{ name: 'file.path.text', type: 'string' },
{ name: 'subject_name', type: 'string' },
{ name: 'trusted', type: 'string' },
{ name: 'file.hash.sha256', type: 'string' },
{ name: 'event.code', type: 'string' },
],
},
},
]);
const alertDataMock: { ecsData: Ecs; nonEcsData: TimelineNonEcsData[] } = {
ecsData: { _id: 'test-id' },
nonEcsData: [{ field: 'file.path', value: ['test/path'] }],
};
wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AddExceptionModal
ruleId={'123'}
ruleIndices={['filebeat-*']}
ruleName={ruleName}
exceptionListType={'endpoint'}
onCancel={jest.fn()}
onConfirm={jest.fn()}
alertData={alertDataMock}
/>
</ThemeProvider>
);
callProps = ExceptionBuilderComponent.mock.calls[0][0];
act(() => callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }));
});
it('has the add exception button enabled', () => {
expect(
wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode()
).not.toBeDisabled();
});
it('should render the exception builder', () => {
expect(wrapper.find('[data-test-subj="alert-exception-builder"]').exists()).toBeTruthy();
});
it('should prepopulate endpoint items', () => {
expect(defaultEndpointItems).toHaveBeenCalled();
});
it('should render the close on add exception checkbox', () => {
expect(
wrapper.find('[data-test-subj="close-alert-on-add-add-exception-checkbox"]').exists()
).toBeTruthy();
});
it('should contain the endpoint specific documentation text', () => {
expect(wrapper.find('[data-test-subj="add-exception-endpoint-text"]').exists()).toBeTruthy();
});
it('should have the bulk close checkbox enabled', () => {
expect(
wrapper
.find('input[data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"]')
.getDOMNode()
).not.toBeDisabled();
});
describe('when a "is in list" entry is added', () => {
it('should have the bulk close checkbox disabled', () => {
act(() =>
callProps.onChange({
exceptionItems: [
...callProps.exceptionListItems,
{
...getExceptionListItemSchemaMock(),
entries: [
{ field: 'event.code', operator: 'included', type: 'list' },
] as EntriesArray,
},
],
})
);

expect(
wrapper
.find('input[data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"]')
.getDOMNode()
).toBeDisabled();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
setShouldDisableBulkClose(
entryHasListType(exceptionItemsToAdd) ||
entryHasNonEcsType(exceptionItemsToAdd, signalIndexPatterns) ||
exceptionItemsToAdd.length === 0
exceptionItemsToAdd.every((item) => item.entries.length === 0)
);
}
}, [
Expand Down Expand Up @@ -344,6 +344,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
{alertData !== undefined && alertStatus !== 'closed' && (
<EuiFormRow fullWidth>
<EuiCheckbox
data-test-subj="close-alert-on-add-add-exception-checkbox"
id="close-alert-on-add-add-exception-checkbox"
label="Close this alert"
checked={shouldCloseAlert}
Expand All @@ -353,6 +354,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
)}
<EuiFormRow fullWidth>
<EuiCheckbox
data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"
id="bulk-close-alert-on-add-add-exception-checkbox"
label={
shouldDisableBulkClose
Expand All @@ -367,7 +369,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
{exceptionListType === 'endpoint' && (
<>
<EuiSpacer />
<EuiText color="subdued" size="s">
<EuiText data-test-subj="add-exception-endpoint-text" color="subdued" size="s">
{i18n.ENDPOINT_QUARANTINE_TEXT}
</EuiText>
</>
Expand All @@ -380,6 +382,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({
<EuiButtonEmpty onClick={onCancel}>{i18n.CANCEL}</EuiButtonEmpty>

<EuiButton
data-test-subj="add-exception-confirm-button"
onClick={onAddExceptionConfirm}
isLoading={addExceptionIsLoading}
isDisabled={isSubmitButtonDisabled}
Expand Down
Loading

0 comments on commit eddf9cc

Please sign in to comment.