import _ from 'lodash' ; import expect from '@kbn/expect' ; import { firstNonNullValue } from '../../../../plugins/security_solution/common/endpoint/models/ecs_safety_helpers' ; import { NodeID } from '../../../../plugins/security_solution/server/endpoint/routes/resolver/tree/utils' ; import { SafeResolverEvent , ResolverNode , ResolverSchema , } from '../../../../plugins/security_solution/common/endpoint/types' ; import { parentEntityIDSafeVersion , entityIDSafeVersion , eventIDSafeVersion , timestampSafeVersion , timestampAsDateSafeVersion , } from '../../../../plugins/security_solution/common/endpoint/models/event' ; import { Event , Tree , TreeNode , RelatedEventInfo , categoryMapping , } from '../../../../plugins/security_solution/common/endpoint/generate_data' ; /**
 * Schema used for the /tree api post requests that uses the ancestry
 */ export const schemaWithAncestry = { ancestry : 'process.Ext.ancestry' , id : 'process.entity_id' , parent : 'process.parent.entity_id' , } ; /**
 * Schema used for the /tree api post requests that does not use the ancestry
 */ export const schemaWithoutAncestry = { id : 'process.entity_id' , parent : 'process.parent.entity_id' , } ; /**
 * Schema used for the /tree api post requests that uses the name field
 */ export const schemaWithName = { id : 'process.entity_id' , parent : 'process.parent.entity_id' , name : 'process.name' , } ; const createLevels = ( { descendantsByParent , levels , currentNodes , schema , } ) : Array < Map < NodeID , ResolverNode > > => { if ( ! currentNodes || currentNodes . size === 0 ) { return levels ; } levels . push ( currentNodes ) ; const nextLevel = new Map ( ) ; for ( const node of currentNodes . values ( ) ) { const id = getID ( node , schema ) ; const children = descendantsByParent . get ( id ) ; if ( children ) { for ( const child of children . values ( ) ) { const childID = getID ( child , schema ) ; nextLevel . set ( childID , child ) ; } } } return createLevels ( { descendantsByParent , levels , currentNodes : nextLevel , schema } ) ; } ; interface TreeExpectation { origin : NodeID ; nodeExpectations : NodeExpectations ; } interface NodeExpectations { ancestors ? : number ; descendants ? : number ; descendantLevels ? : number ; } interface APITree { ancestors : ResolverNode [ ] ; origin : ResolverNode | undefined ; descendantLevels : Array < Map < NodeID , ResolverNode > > ; nodeExpectations : NodeExpectations ; } /**
 * Represents a utility structure for making it easier to perform expect calls on the response
 * from the /tree api. This can represent multiple trees, since the tree api can return multiple trees.
 */ export interface APIResponse { nodesByID : Map < NodeID , ResolverNode > ; trees : Map < NodeID , APITree > ; allNodes : ResolverNode [ ] ; } param node param schema export const getID = ( node , schema ) : NodeID => { const id = firstNonNullValue ( node ?. data [ schema . id ] ) ; if ( ! id ) { throw new Error ( `Unable to find id ${ schema . id } in node: ${ JSON . stringify ( node ) }` ) ; } return id ; } ; const getParentInternal = ( node , schema ) : NodeID | undefined => { if ( node ) { return firstNonNullValue ( node ?. data [ schema . parent ] ) ; } return undefined ; } ; param node param schema export const getParent = ( node , schema ) : NodeID => { const parent = getParentInternal ( node , schema ) ; if ( ! parent ) { throw new Error ( `Unable to find parent ${ schema . parent } in node: ${ JSON . stringify ( node ) }` ) ; } return parent ; } ; param treeExpectations param nodes param schema const createTreeFromResponse = ( treeExpectations , nodes , schema ) => { const nodesByID = new Map < NodeID , ResolverNode > ( ) ; const nodesByParent = new Map < NodeID , Map < NodeID , ResolverNode > > ( ) ; for ( const node of nodes ) { const id = getID ( node , schema ) ; const parent = getParentInternal ( node , schema ) ; nodesByID . set ( id , node ) ; if ( parent ) { let groupedChildren = nodesByParent . get ( parent ) ; if ( ! groupedChildren ) { groupedChildren = new Map ( ) ; nodesByParent . set ( parent , groupedChildren ) ; } groupedChildren . set ( id , node ) ; } } const trees = new Map ( ) ; for ( const expectation of treeExpectations ) { const descendantLevels = createLevels ( { descendantsByParent : nodesByParent , levels : [ ] , currentNodes : nodesByParent . get ( expectation . origin ) , schema , } ) ; const ancestors = [ ] ; const originNode = nodesByID . get ( expectation . origin ) ; if ( originNode ) { let currentID = getParentInternal ( originNode , schema ) ; while ( currentID !== undefined ) { const parentNode = nodesByID . get ( currentID ) ; if ( parentNode ) { ancestors . push ( parentNode ) ; } currentID = getParentInternal ( parentNode , schema ) ; } } trees . set ( expectation . origin , { ancestors , origin : originNode , descendantLevels , nodeExpectations : expectation . nodeExpectations , } ) ; } return { nodesByID , trees , allNodes : nodes , } ; } ; const verifyAncestry = ( { responseTrees , schema , genTree , } ) => { const allGenNodes = new Map < string , TreeNode > ( [ ... genTree . ancestry , ... genTree . children , [ genTree . origin . id , genTree . origin ] , ] ) ; for ( const tree of responseTrees . trees . values ( ) ) { if ( tree . nodeExpectations . ancestors !== undefined ) { expect ( tree . ancestors . length ) . to . be ( tree . nodeExpectations . ancestors ) ; } if ( tree . origin !== undefined ) { const originID = getID ( tree . origin , schema ) ; const originParentID = getParent ( tree . origin , schema ) ; expect ( tree . origin . id ) . to . be ( originID ) ; expect ( tree . origin . parent ) . to . be ( originParentID ) ; expect ( allGenNodes . get ( String ( originID ) ) ?. id ) . to . be ( String ( originID ) ) ; expect ( allGenNodes . get ( String ( originParentID ) ) ?. id ) . to . be ( String ( originParentID ) ) ; expect ( originID ) . to . be ( entityIDSafeVersion ( allGenNodes . get ( String ( originID ) ) ! . lifecycle [ 0 ] ) ) ; expect ( originParentID ) . to . be ( parentEntityIDSafeVersion ( allGenNodes . get ( String ( originID ) ) ! . lifecycle [ 0 ] ) ) ; const originLifecycleSorted = [ ... allGenNodes . get ( String ( originID ) ) ! . lifecycle ] . sort ( ( a , b ) => { const aTime = timestampSafeVersion ( a ) ; const bTime = timestampSafeVersion ( b ) ; if ( aTime !== undefined && bTime !== undefined ) { return aTime - bTime ; } else { return 0 ; } } ) ; const ts = timestampAsDateSafeVersion ( tree . origin ?. data ) ; expect ( ts ) . to . not . be ( undefined ) ; expect ( ts ) . to . eql ( timestampAsDateSafeVersion ( originLifecycleSorted [ 0 ] ) ) ; } for ( let i = 0 ; i < tree . ancestors . length ; i ++ ) { const id = getID ( tree . ancestors [ i ] , schema ) ; const parent = getParentInternal ( tree . ancestors [ i ] , schema ) ; if ( i < tree . ancestors . length - 1 ) { expect ( parent ) . to . be ( getID ( tree . ancestors [ i + 1 ] , schema ) ) ; expect ( parent ) . to . not . be ( undefined ) ; expect ( tree . ancestors [ i ] . parent ) . to . not . be ( undefined ) ; expect ( tree . ancestors [ i ] . parent ) . to . be ( parent ) ; } expect ( allGenNodes . get ( String ( id ) ) ?. id ) . to . be ( id ) ; expect ( tree . ancestors [ i ] . id ) . to . be ( id ) ; } } } ; const verifyChildren = ( { responseTrees , schema , genTree , } ) => { const allGenNodes = new Map < string , TreeNode > ( [ ... genTree . ancestry , ... genTree . children , [ genTree . origin . id , genTree . origin ] , ] ) ; for ( const tree of responseTrees . trees . values ( ) ) { if ( tree . nodeExpectations . descendantLevels !== undefined ) { expect ( tree . nodeExpectations . descendantLevels ) . to . be ( tree . descendantLevels . length ) ; } let totalDescendants = 0 ; for ( const level of tree . descendantLevels ) { for ( const node of level . values ( ) ) { totalDescendants += 1 ; const id = getID ( node , schema ) ; const parent = getParent ( node , schema ) ; const genNode = allGenNodes . get ( String ( id ) ) ; expect ( id ) . to . be ( node . id ) ; expect ( parent ) . to . be ( node . parent ) ; expect ( node . parent ) . to . not . be ( undefined ) ; expect ( id ) . to . be ( entityIDSafeVersion ( genNode ! . lifecycle [ 0 ] ) ) ; expect ( parent ) . to . be ( parentEntityIDSafeVersion ( genNode ! . lifecycle [ 0 ] ) ) ; } } if ( tree . nodeExpectations . descendants !== undefined ) { expect ( tree . nodeExpectations . descendants ) . to . be ( totalDescendants ) ; } } } ; const verifyStats = ( { responseTrees , relatedEventsCategories , } ) => { for ( const node of responseTrees . allNodes ) { let totalExpEvents = 0 ; for ( const cat of relatedEventsCategories ) { const ecsCategories = categoryMapping [ cat . category ] ; if ( Array . isArray ( ecsCategories ) ) { for ( const ecsCat of ecsCategories ) { expect ( node . stats . byCategory [ ecsCat ] ) . to . be ( cat . count ) ; } } else { expect ( node . stats . byCategory [ ecsCategories ] ) . to . be ( cat . count ) ; } totalExpEvents += cat . count ; } expect ( node . stats . total ) . to . be ( totalExpEvents ) ; } } ; param expectations param response param schema param genTree param relatedEventsCategories export const verifyTree = ( { expectations , response , schema , genTree , relatedEventsCategories , } ) => { const responseTrees = createTreeFromResponse ( expectations , response , schema ) ; verifyAncestry ( { responseTrees , schema , genTree } ) ; verifyChildren ( { responseTrees , schema , genTree } ) ; if ( relatedEventsCategories !== undefined ) { verifyStats ( { responseTrees , relatedEventsCategories } ) ; } } ; param events export const createAncestryArray = ( events ) => { const ancestry = [ ] ; for ( const event of events ) { const entityID = entityIDSafeVersion ( event ) ; if ( entityID ) { ancestry . push ( entityID ) ; } } return ancestry ; } ; param expected param toTest param lengthCheck export const compareArrays = ( expected , toTest , lengthCheck = false ) => { if ( lengthCheck ) { expect ( expected . length ) . to . eql ( toTest . length ) ; } toTest . forEach ( ( toTestEvent ) => { expect ( expected . find ( ( arrEvent ) => { return eventIDSafeVersion ( toTestEvent ) === eventIDSafeVersion ( arrEvent ) ; } ) ) . to . be . ok ( ) ; } ) ; } ;