Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security solution] Threat hunting test coverage improvements #73276

Merged
merged 8 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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