Skip to content

Commit

Permalink
[Security solution] Threat hunting test coverage improvements (#73276)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic authored Jul 27, 2020
1 parent 94ed783 commit ddff1c9
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { mount } from 'enzyme';
import React from 'react';

import { MarkdownEditor } from '.';
import { TestProviders } from '../../mock';

describe('Markdown Editor', () => {
const onChange = jest.fn();
const onCursorPositionUpdate = jest.fn();
const defaultProps = {
content: 'hello world',
onChange,
onCursorPositionUpdate,
};
beforeEach(() => {
jest.clearAllMocks();
});
test('it calls onChange with correct value', () => {
const wrapper = mount(
<TestProviders>
<MarkdownEditor {...defaultProps} />
</TestProviders>
);
const newValue = 'a new string';
wrapper
.find(`[data-test-subj="textAreaInput"]`)
.first()
.simulate('change', { target: { value: newValue } });
expect(onChange).toBeCalledWith(newValue);
});
test('it calls onCursorPositionUpdate with correct args', () => {
const wrapper = mount(
<TestProviders>
<MarkdownEditor {...defaultProps} />
</TestProviders>
);
wrapper.find(`[data-test-subj="textAreaInput"]`).first().simulate('blur');
expect(onCursorPositionUpdate).toBeCalledWith({
start: 0,
end: 0,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ export const MarkdownEditor = React.memo<{
end: e!.target!.selectionEnd ?? 0,
});
}
return false;
},
[onCursorPositionUpdate]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ const getMockObject = (
): RouteSpyState & TabNavigationProps => ({
detailName,
navTabs: {
case: {
disabled: false,
href: '/app/security/cases',
id: 'case',
name: 'Cases',
urlKey: 'case',
},
hosts: {
disabled: false,
href: '/app/security/hosts',
Expand Down Expand Up @@ -227,6 +234,73 @@ describe('Navigation Breadcrumbs', () => {
{ text: 'Flows', href: '' },
]);
});

test('should return Alerts breadcrumbs when supplied detection pathname', () => {
const breadcrumbs = getBreadcrumbsForRoute(
getMockObject('detections', '/', undefined),
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
{ text: 'Security', href: '/app/security/overview' },
{
text: 'Detections',
href:
"securitySolution:detections?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
},
]);
});
test('should return Cases breadcrumbs when supplied case pathname', () => {
const breadcrumbs = getBreadcrumbsForRoute(
getMockObject('case', '/', undefined),
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
{ text: 'Security', href: '/app/security/overview' },
{
text: 'Cases',
href:
"securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
},
]);
});
test('should return Case details breadcrumbs when supplied case details pathname', () => {
const sampleCase = {
id: 'my-case-id',
name: 'Case name',
};
const breadcrumbs = getBreadcrumbsForRoute(
{
...getMockObject('case', `/${sampleCase.id}`, sampleCase.id),
state: { caseTitle: sampleCase.name },
},
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
{ text: 'Security', href: '/app/security/overview' },
{
text: 'Cases',
href:
"securitySolution:case?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
},
{
text: sampleCase.name,
href: `securitySolution:case/${sampleCase.id}?timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
},
]);
});
test('should return Admin breadcrumbs when supplied admin pathname', () => {
const breadcrumbs = getBreadcrumbsForRoute(
getMockObject('administration', '/', undefined),
getUrlForAppMock
);
expect(breadcrumbs).toEqual([
{ text: 'Security', href: '/app/security/overview' },
{
text: 'Administration',
href: 'securitySolution:administration',
},
]);
});
});

describe('setBreadcrumbs()', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import { useShowTimeline } from './use_show_timeline';
import { globalNode } from '../../mock';

describe('use show timeline', () => {
it('shows timeline for routes on default', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
await waitForNextUpdate();
const uninitializedTimeline = result.current;
expect(uninitializedTimeline).toEqual([true]);
});
});
it('hides timeline for blacklist routes', async () => {
await act(async () => {
Object.defineProperty(globalNode.window, 'location', {
value: {
pathname: `/cases/configure`,
},
});
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
await waitForNextUpdate();
const uninitializedTimeline = result.current;
expect(uninitializedTimeline).toEqual([false]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import { getTimelineDefaults, useTimelineManager, UseTimelineManager } from './';
import { FilterManager } from '../../../../../../../src/plugins/data/public/query/filter_manager';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { TimelineRowAction } from '../timeline/body/actions';

const isStringifiedComparisonEqual = (a: {}, b: {}): boolean =>
JSON.stringify(a) === JSON.stringify(b);

describe('useTimelineManager', () => {
const setupMock = coreMock.createSetup();
const testId = 'coolness';
const timelineDefaults = getTimelineDefaults(testId);
const timelineRowActions = () => [];
const mockFilterManager = new FilterManager(setupMock.uiSettings);
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
it('initilizes an undefined timeline', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
const uninitializedTimeline = result.current.getManageTimelineById(testId);
expect(isStringifiedComparisonEqual(uninitializedTimeline, timelineDefaults)).toBeTruthy();
});
});
it('getIndexToAddById', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
const data = result.current.getIndexToAddById(testId);
expect(data).toEqual(timelineDefaults.indexToAdd);
});
});
it('setIndexToAdd', async () => {
await act(async () => {
const indexToAddArgs = { id: testId, indexToAdd: ['example'] };
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
result.current.initializeTimeline({
id: testId,
timelineRowActions,
});
result.current.setIndexToAdd(indexToAddArgs);
const data = result.current.getIndexToAddById(testId);
expect(data).toEqual(indexToAddArgs.indexToAdd);
});
});
it('setIsTimelineLoading', async () => {
await act(async () => {
const isLoadingArgs = { id: testId, isLoading: true };
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
result.current.initializeTimeline({
id: testId,
timelineRowActions,
});
let timeline = result.current.getManageTimelineById(testId);
expect(timeline.isLoading).toBeFalsy();
result.current.setIsTimelineLoading(isLoadingArgs);
timeline = result.current.getManageTimelineById(testId);
expect(timeline.isLoading).toBeTruthy();
});
});
it('setTimelineRowActions', async () => {
await act(async () => {
const timelineRowActionsEx = () => [
{ id: 'wow', content: 'hey', displayType: 'icon', onClick: () => {} } as TimelineRowAction,
];
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
result.current.initializeTimeline({
id: testId,
timelineRowActions,
});
let timeline = result.current.getManageTimelineById(testId);
expect(timeline.timelineRowActions).toEqual(timelineRowActions);
result.current.setTimelineRowActions({
id: testId,
timelineRowActions: timelineRowActionsEx,
});
timeline = result.current.getManageTimelineById(testId);
expect(timeline.timelineRowActions).toEqual(timelineRowActionsEx);
});
});
it('getTimelineFilterManager undefined on uninitialized', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
const data = result.current.getTimelineFilterManager(testId);
expect(data).toEqual(undefined);
});
});
it('getTimelineFilterManager defined at initialize', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
result.current.initializeTimeline({
id: testId,
timelineRowActions,
filterManager: mockFilterManager,
});
const data = result.current.getTimelineFilterManager(testId);
expect(data).toEqual(mockFilterManager);
});
});
it('isManagedTimeline returns false when unset and then true when set', async () => {
await act(async () => {
const { result, waitForNextUpdate } = renderHook<string, UseTimelineManager>(() =>
useTimelineManager()
);
await waitForNextUpdate();
let data = result.current.isManagedTimeline(testId);
expect(data).toBeFalsy();
result.current.initializeTimeline({
id: testId,
timelineRowActions,
filterManager: mockFilterManager,
});
data = result.current.isManagedTimeline(testId);
expect(data).toBeTruthy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const reducerManageTimeline = (
}
};

interface UseTimelineManager {
export interface UseTimelineManager {
getIndexToAddById: (id: string) => string[] | null;
getManageTimelineById: (id: string) => ManageTimeline;
getTimelineFilterManager: (id: string) => FilterManager | undefined;
Expand All @@ -152,7 +152,9 @@ interface UseTimelineManager {
}) => void;
}

const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseTimelineManager => {
export const useTimelineManager = (
manageTimelineForTesting?: ManageTimelineById
): UseTimelineManager => {
const [state, dispatch] = useReducer<
(state: ManageTimelineById, action: ActionManageTimeline) => ManageTimelineById
>(reducerManageTimeline, manageTimelineForTesting ?? initManageTimeline);
Expand Down Expand Up @@ -241,12 +243,12 @@ const useTimelineManager = (manageTimelineForTesting?: ManageTimelineById): UseT
};

const init = {
getManageTimelineById: (id: string) => getTimelineDefaults(id),
getIndexToAddById: (id: string) => null,
getManageTimelineById: (id: string) => getTimelineDefaults(id),
getTimelineFilterManager: () => undefined,
setIndexToAdd: () => undefined,
isManagedTimeline: () => false,
initializeTimeline: () => noop,
isManagedTimeline: () => false,
setIndexToAdd: () => undefined,
setIsTimelineLoading: () => noop,
setTimelineRowActions: () => noop,
};
Expand Down

0 comments on commit ddff1c9

Please sign in to comment.