mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Test] Enzyme test for related events button (#74411)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
4ae6746c0b
commit
dfad75ff1a
6 changed files with 199 additions and 23 deletions
|
@ -10,7 +10,10 @@ import {
|
|||
ResolverEntityIndex,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { mockEndpointEvent } from '../../store/mocks/endpoint_event';
|
||||
import { mockTreeWithNoAncestorsAnd2Children } from '../../store/mocks/resolver_tree';
|
||||
import {
|
||||
mockTreeWithNoAncestorsAnd2Children,
|
||||
withRelatedEventsOnOrigin,
|
||||
} from '../../store/mocks/resolver_tree';
|
||||
import { DataAccessLayer } from '../../types';
|
||||
|
||||
interface Metadata {
|
||||
|
@ -40,11 +43,24 @@ interface Metadata {
|
|||
/**
|
||||
* A simple mock dataAccessLayer possible that returns a tree with 0 ancestors and 2 direct children. 1 related event is returned. The parameter to `entities` is ignored.
|
||||
*/
|
||||
export function oneAncestorTwoChildren(): { dataAccessLayer: DataAccessLayer; metadata: Metadata } {
|
||||
export function oneAncestorTwoChildren(
|
||||
{ withRelatedEvents }: { withRelatedEvents: Iterable<[string, string]> | null } = {
|
||||
withRelatedEvents: null,
|
||||
}
|
||||
): { dataAccessLayer: DataAccessLayer; metadata: Metadata } {
|
||||
const metadata: Metadata = {
|
||||
databaseDocumentID: '_id',
|
||||
entityIDs: { origin: 'origin', firstChild: 'firstChild', secondChild: 'secondChild' },
|
||||
};
|
||||
const baseTree = mockTreeWithNoAncestorsAnd2Children({
|
||||
originID: metadata.entityIDs.origin,
|
||||
firstChildID: metadata.entityIDs.firstChild,
|
||||
secondChildID: metadata.entityIDs.secondChild,
|
||||
});
|
||||
const composedTree = withRelatedEvents
|
||||
? withRelatedEventsOnOrigin(baseTree, withRelatedEvents)
|
||||
: baseTree;
|
||||
|
||||
return {
|
||||
metadata,
|
||||
dataAccessLayer: {
|
||||
|
@ -54,13 +70,17 @@ export function oneAncestorTwoChildren(): { dataAccessLayer: DataAccessLayer; me
|
|||
relatedEvents(entityID: string): Promise<ResolverRelatedEvents> {
|
||||
return Promise.resolve({
|
||||
entityID,
|
||||
events: [
|
||||
mockEndpointEvent({
|
||||
entityID,
|
||||
name: 'event',
|
||||
timestamp: 0,
|
||||
}),
|
||||
],
|
||||
events:
|
||||
/* Respond with the mocked related events when the origin's related events are fetched*/ withRelatedEvents &&
|
||||
entityID === metadata.entityIDs.origin
|
||||
? composedTree.relatedEvents.events
|
||||
: [
|
||||
mockEndpointEvent({
|
||||
entityID,
|
||||
name: 'event',
|
||||
timestamp: 0,
|
||||
}),
|
||||
],
|
||||
nextEvent: null,
|
||||
});
|
||||
},
|
||||
|
@ -69,13 +89,7 @@ export function oneAncestorTwoChildren(): { dataAccessLayer: DataAccessLayer; me
|
|||
* Fetch a ResolverTree for a entityID
|
||||
*/
|
||||
resolverTree(): Promise<ResolverTree> {
|
||||
return Promise.resolve(
|
||||
mockTreeWithNoAncestorsAnd2Children({
|
||||
originID: metadata.entityIDs.origin,
|
||||
firstChildID: metadata.entityIDs.firstChild,
|
||||
secondChildID: metadata.entityIDs.secondChild,
|
||||
})
|
||||
);
|
||||
return Promise.resolve(composedTree);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { EndpointEvent } from '../../../../common/endpoint/types';
|
||||
|
||||
/**
|
||||
* Simple mock related event.
|
||||
*/
|
||||
export function mockRelatedEvent({
|
||||
entityID,
|
||||
timestamp,
|
||||
category,
|
||||
type,
|
||||
id,
|
||||
}: {
|
||||
entityID: string;
|
||||
timestamp: number;
|
||||
category: string;
|
||||
type: string;
|
||||
id?: string;
|
||||
}): EndpointEvent {
|
||||
return {
|
||||
'@timestamp': timestamp,
|
||||
event: {
|
||||
kind: 'event',
|
||||
type,
|
||||
category,
|
||||
id: id ?? 'xyz',
|
||||
},
|
||||
process: {
|
||||
entity_id: entityID,
|
||||
},
|
||||
} as EndpointEvent;
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { mockEndpointEvent } from './endpoint_event';
|
||||
import { mockRelatedEvent } from './related_event';
|
||||
import { ResolverTree, ResolverEvent } from '../../../../common/endpoint/types';
|
||||
|
||||
export function mockTreeWith2AncestorsAndNoChildren({
|
||||
|
@ -109,6 +110,58 @@ export function mockTreeWithAllProcessesTerminated({
|
|||
} as unknown) as ResolverTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* A valid category for a related event. E.g. "registry", "network", "file"
|
||||
*/
|
||||
type RelatedEventCategory = string;
|
||||
/**
|
||||
* A valid type for a related event. E.g. "start", "end", "access"
|
||||
*/
|
||||
type RelatedEventType = string;
|
||||
|
||||
/**
|
||||
* Add/replace related event info (on origin node) for any mock ResolverTree
|
||||
*
|
||||
* @param treeToAddRelatedEventsTo the ResolverTree to modify
|
||||
* @param relatedEventsToAddByCategoryAndType Iterable of `[category, type]` pairs describing related events. e.g. [['dns','info'],['registry','access']]
|
||||
*/
|
||||
export function withRelatedEventsOnOrigin(
|
||||
treeToAddRelatedEventsTo: ResolverTree,
|
||||
relatedEventsToAddByCategoryAndType: Iterable<[RelatedEventCategory, RelatedEventType]>
|
||||
): ResolverTree {
|
||||
const events = [];
|
||||
const byCategory: Record<string, number> = {};
|
||||
const stats = {
|
||||
totalAlerts: 0,
|
||||
events: {
|
||||
total: 0,
|
||||
byCategory,
|
||||
},
|
||||
};
|
||||
for (const [category, type] of relatedEventsToAddByCategoryAndType) {
|
||||
events.push(
|
||||
mockRelatedEvent({
|
||||
entityID: treeToAddRelatedEventsTo.entityID,
|
||||
timestamp: 1,
|
||||
category,
|
||||
type,
|
||||
})
|
||||
);
|
||||
stats.events.total++;
|
||||
stats.events.byCategory[category] = stats.events.byCategory[category]
|
||||
? stats.events.byCategory[category] + 1
|
||||
: 1;
|
||||
}
|
||||
return {
|
||||
...treeToAddRelatedEventsTo,
|
||||
stats,
|
||||
relatedEvents: {
|
||||
events,
|
||||
nextEvent: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mockTreeWithNoAncestorsAnd2Children({
|
||||
originID,
|
||||
firstChildID,
|
||||
|
|
|
@ -220,6 +220,28 @@ export class Simulator {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all contents of the outer ReactWrapper (to be `console.log`ged as appropriate)
|
||||
* This will include both DOM (div, span, etc.) and React/JSX (MyComponent, MyGrid, etc.)
|
||||
*/
|
||||
public debugWrapper() {
|
||||
return this.wrapper.debug();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an Enzyme ReactWrapper that includes the Related Events host button for a given process node
|
||||
*
|
||||
* @param entityID The entity ID of the proocess node to select in
|
||||
*/
|
||||
public processNodeRelatedEventButton(entityID: string): ReactWrapper {
|
||||
return this.processNodeElements({ entityID }).findWhere(
|
||||
(wrapper) =>
|
||||
// Filter out React components
|
||||
typeof wrapper.type() === 'string' &&
|
||||
wrapper.prop('data-test-subj') === 'resolver:submenu:button'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the selected node query string values.
|
||||
*/
|
||||
|
|
|
@ -9,14 +9,14 @@ import { Simulator } from '../test_utilities/simulator';
|
|||
// Extend jest with a custom matcher
|
||||
import '../test_utilities/extend_jest';
|
||||
|
||||
let simulator: Simulator;
|
||||
let databaseDocumentID: string;
|
||||
let entityIDs: { origin: string; firstChild: string; secondChild: string };
|
||||
|
||||
// the resolver component instance ID, used by the react code to distinguish piece of global state from those used by other resolver instances
|
||||
const resolverComponentInstanceID = 'resolverComponentInstanceID';
|
||||
|
||||
describe('Resolver, when analyzing a tree that has 1 ancestor and 2 children', () => {
|
||||
let simulator: Simulator;
|
||||
let databaseDocumentID: string;
|
||||
let entityIDs: { origin: string; firstChild: string; secondChild: string };
|
||||
|
||||
// the resolver component instance ID, used by the react code to distinguish piece of global state from those used by other resolver instances
|
||||
const resolverComponentInstanceID = 'resolverComponentInstanceID';
|
||||
|
||||
beforeEach(async () => {
|
||||
// create a mock data access layer
|
||||
const { metadata: dataAccessLayerMetadata, dataAccessLayer } = oneAncestorTwoChildren();
|
||||
|
@ -79,6 +79,7 @@ describe('Resolver, when analyzing a tree that has 1 ancestor and 2 children', (
|
|||
simulator
|
||||
.processNodeElements({ entityID: entityIDs.secondChild })
|
||||
.find('button')
|
||||
.first()
|
||||
.simulate('click');
|
||||
});
|
||||
it('should render the second child node as selected, and the first child not as not selected, and the query string should indicate that the second child is selected', async () => {
|
||||
|
@ -107,3 +108,52 @@ describe('Resolver, when analyzing a tree that has 1 ancestor and 2 children', (
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resolver, when analyzing a tree that has some related events', () => {
|
||||
beforeEach(async () => {
|
||||
// create a mock data access layer with related events
|
||||
const { metadata: dataAccessLayerMetadata, dataAccessLayer } = oneAncestorTwoChildren({
|
||||
withRelatedEvents: [
|
||||
['registry', 'access'],
|
||||
['registry', 'access'],
|
||||
],
|
||||
});
|
||||
|
||||
// save a reference to the entity IDs exposed by the mock data layer
|
||||
entityIDs = dataAccessLayerMetadata.entityIDs;
|
||||
|
||||
// save a reference to the `_id` supported by the mock data layer
|
||||
databaseDocumentID = dataAccessLayerMetadata.databaseDocumentID;
|
||||
|
||||
// create a resolver simulator, using the data access layer and an arbitrary component instance ID
|
||||
simulator = new Simulator({ databaseDocumentID, dataAccessLayer, resolverComponentInstanceID });
|
||||
});
|
||||
|
||||
describe('when it has loaded', () => {
|
||||
beforeEach(async () => {
|
||||
await expect(
|
||||
simulator.mapStateTransitions(() => ({
|
||||
graphElements: simulator.graphElement().length,
|
||||
graphLoadingElements: simulator.graphLoadingElement().length,
|
||||
graphErrorElements: simulator.graphErrorElement().length,
|
||||
originNode: simulator.processNodeElements({ entityID: entityIDs.origin }).length,
|
||||
}))
|
||||
).toYieldEqualTo({
|
||||
graphElements: 1,
|
||||
graphLoadingElements: 0,
|
||||
graphErrorElements: 0,
|
||||
originNode: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a related events button', async () => {
|
||||
await expect(
|
||||
simulator.mapStateTransitions(() => ({
|
||||
relatedEventButtons: simulator.processNodeRelatedEventButton(entityIDs.origin).length,
|
||||
}))
|
||||
).toYieldEqualTo({
|
||||
relatedEventButtons: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -233,6 +233,7 @@ const NodeSubMenuComponents = React.memo(
|
|||
iconType={menuIsOpen ? 'arrowUp' : 'arrowDown'}
|
||||
iconSide="right"
|
||||
tabIndex={-1}
|
||||
data-test-subj="resolver:submenu:button"
|
||||
>
|
||||
{count ? <EuiI18nNumber value={count} /> : ''} {menuTitle}
|
||||
</EuiButton>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue