Skip to content

Commit

Permalink
[Resolver] UI tests for the panel and bug fix (#74421)
Browse files Browse the repository at this point in the history
* Change the way the resolver simulator works
* refactor resolver tree and data access layer mocks
* Fix bug where timestamp and pid sometimes don't show in the node detail view
* add a few tests for the panel (not done, but worth committing.)
  • Loading branch information
Robert Austin authored Aug 7, 2020
1 parent 5d9f329 commit 7dc33f9
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 237 deletions.
9 changes: 9 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ export interface ResolverRelatedEvents {
nextEvent: string | null;
}

/**
* Safe version of `ResolverRelatedEvents`
*/
export interface SafeResolverRelatedEvents {
entityID: string;
events: SafeResolverEvent[];
nextEvent: string | null;
}

/**
* Response structure for the alerts route.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ import {
ResolverTree,
ResolverEntityIndex,
} from '../../../../common/endpoint/types';
import { mockEndpointEvent } from '../../store/mocks/endpoint_event';
import {
mockTreeWithNoAncestorsAnd2Children,
withRelatedEventsOnOrigin,
} from '../../store/mocks/resolver_tree';
import { mockEndpointEvent } from '../../mocks/endpoint_event';
import { mockTreeWithNoAncestorsAnd2Children } from '../../mocks/resolver_tree';
import { DataAccessLayer } from '../../types';

interface Metadata {
Expand Down Expand Up @@ -43,24 +40,11 @@ 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(
{ withRelatedEvents }: { withRelatedEvents: Iterable<[string, string]> | null } = {
withRelatedEvents: null,
}
): { dataAccessLayer: DataAccessLayer; metadata: Metadata } {
export function noAncestorsTwoChildren(): { 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 @@ -70,17 +54,13 @@ export function oneAncestorTwoChildren(
relatedEvents(entityID: string): Promise<ResolverRelatedEvents> {
return Promise.resolve({
entityID,
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,
}),
],
events: [
mockEndpointEvent({
entityID,
name: 'event',
timestamp: 0,
}),
],
nextEvent: null,
});
},
Expand All @@ -89,7 +69,13 @@ export function oneAncestorTwoChildren(
* Fetch a ResolverTree for a entityID
*/
resolverTree(): Promise<ResolverTree> {
return Promise.resolve(composedTree);
return Promise.resolve(
mockTreeWithNoAncestorsAnd2Children({
originID: metadata.entityIDs.origin,
firstChildID: metadata.entityIDs.firstChild,
secondChildID: metadata.entityIDs.secondChild,
})
);
},

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 { DataAccessLayer } from '../../types';
import { mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin } from '../../mocks/resolver_tree';
import {
ResolverRelatedEvents,
ResolverTree,
ResolverEntityIndex,
} from '../../../../common/endpoint/types';

interface Metadata {
/**
* The `_id` of the document being analyzed.
*/
databaseDocumentID: string;
/**
* A record of entityIDs to be used in tests assertions.
*/
entityIDs: {
/**
* The entityID of the node related to the document being analyzed.
*/
origin: 'origin';
/**
* The entityID of the first child of the origin.
*/
firstChild: 'firstChild';
/**
* The entityID of the second child of the origin.
*/
secondChild: 'secondChild';
};
}

export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): {
dataAccessLayer: DataAccessLayer;
metadata: Metadata;
} {
const metadata: Metadata = {
databaseDocumentID: '_id',
entityIDs: { origin: 'origin', firstChild: 'firstChild', secondChild: 'secondChild' },
};
const tree = mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({
originID: metadata.entityIDs.origin,
firstChildID: metadata.entityIDs.firstChild,
secondChildID: metadata.entityIDs.secondChild,
});

return {
metadata,
dataAccessLayer: {
/**
* Fetch related events for an entity ID
*/
relatedEvents(entityID: string): Promise<ResolverRelatedEvents> {
/**
* Respond with the mocked related events when the origin's related events are fetched.
**/
const events = entityID === metadata.entityIDs.origin ? tree.relatedEvents.events : [];

return Promise.resolve({
entityID,
events,
nextEvent: null,
} as ResolverRelatedEvents);
},

/**
* Fetch a ResolverTree for a entityID
*/
resolverTree(): Promise<ResolverTree> {
return Promise.resolve(tree);
},

/**
* Get an array of index patterns that contain events.
*/
indexPatterns(): string[] {
return ['index pattern'];
},

/**
* Get entities matching a document.
*/
entities(): Promise<ResolverEntityIndex> {
return Promise.resolve([{ entity_id: metadata.entityIDs.origin }]);
},
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EndpointEvent } from '../../../../common/endpoint/types';
import { EndpointEvent } from '../../../common/endpoint/types';

/**
* Simple mock endpoint event that works for tree layouts.
Expand All @@ -28,10 +28,29 @@ export function mockEndpointEvent({
type: lifecycleType ? lifecycleType : 'start',
category: 'process',
},
agent: {
id: 'agent.id',
version: 'agent.version',
type: 'agent.type',
},
ecs: {
version: 'ecs.version',
},
user: {
name: 'user.name',
domain: 'user.domain',
},
process: {
entity_id: entityID,
executable: 'executable',
args: 'args',
name,
pid: 0,
hash: {
md5: 'hash.md5',
},
parent: {
pid: 0,
entity_id: parentEntityId,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
*/

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

export function mockTreeWith2AncestorsAndNoChildren({
originID,
Expand Down Expand Up @@ -125,11 +124,11 @@ type RelatedEventType = string;
* @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(
function withRelatedEventsOnOrigin(
treeToAddRelatedEventsTo: ResolverTree,
relatedEventsToAddByCategoryAndType: Iterable<[RelatedEventCategory, RelatedEventType]>
): ResolverTree {
const events = [];
const events: SafeResolverEvent[] = [];
const byCategory: Record<string, number> = {};
const stats = {
totalAlerts: 0,
Expand All @@ -139,14 +138,18 @@ export function withRelatedEventsOnOrigin(
},
};
for (const [category, type] of relatedEventsToAddByCategoryAndType) {
events.push(
mockRelatedEvent({
entityID: treeToAddRelatedEventsTo.entityID,
timestamp: 1,
category,
events.push({
'@timestamp': 1,
event: {
kind: 'event',
type,
})
);
category,
id: 'xyz',
},
process: {
entity_id: treeToAddRelatedEventsTo.entityID,
},
});
stats.events.total++;
stats.events.byCategory[category] = stats.events.byCategory[category]
? stats.events.byCategory[category] + 1
Expand All @@ -156,7 +159,7 @@ export function withRelatedEventsOnOrigin(
...treeToAddRelatedEventsTo,
stats,
relatedEvents: {
events,
events: events as ResolverEvent[],
nextEvent: null,
},
};
Expand Down Expand Up @@ -309,3 +312,24 @@ export function mockTreeWithNoProcessEvents(): ResolverTree {
},
};
}

export function mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({
originID,
firstChildID,
secondChildID,
}: {
originID: string;
firstChildID: string;
secondChildID: string;
}) {
const baseTree = mockTreeWithNoAncestorsAnd2Children({
originID,
firstChildID,
secondChildID,
});
const withRelatedEvents: Array<[string, string]> = [
['registry', 'access'],
['registry', 'access'],
];
return withRelatedEventsOnOrigin(baseTree, withRelatedEvents);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents,
mockTreeWithAllProcessesTerminated,
mockTreeWithNoProcessEvents,
} from '../mocks/resolver_tree';
} from '../../mocks/resolver_tree';
import { uniquePidForProcess } from '../../models/process_event';
import { EndpointEvent } from '../../../../common/endpoint/types';

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as selectors from './selectors';
import {
mockTreeWith2AncestorsAndNoChildren,
mockTreeWithNoAncestorsAnd2Children,
} from './mocks/resolver_tree';
} from '../mocks/resolver_tree';
import { SafeResolverEvent } from '../../../common/endpoint/types';

describe('resolver selectors', () => {
Expand Down
Loading

0 comments on commit 7dc33f9

Please sign in to comment.