Skip to content

Commit

Permalink
[Security Solution][Test] Enzyme test for related events button (#74411
Browse files Browse the repository at this point in the history
…) (#74520)

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
bkimmel and elasticmachine authored Aug 6, 2020
1 parent b4610d0 commit 42cb0b0
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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: {
Expand All @@ -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,
});
},
Expand All @@ -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);
},

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { mockEndpointEvent } from './endpoint_event';
import { mockRelatedEvent } from './related_event';
import { ResolverTree, ResolverEvent } from '../../../../common/endpoint/types';

export function mockTreeWith2AncestorsAndNoChildren({
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import { Simulator } from '../test_utilities/simulator';
// Extend jest with a custom matcher
import '../test_utilities/extend_jest';

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 };
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';
// 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', () => {
beforeEach(async () => {
// create a mock data access layer
const { metadata: dataAccessLayerMetadata, dataAccessLayer } = oneAncestorTwoChildren();
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit 42cb0b0

Please sign in to comment.