import { merge , omit , chunk , isEmpty } from 'lodash' ; import uuid from 'uuid' ; import expect from '@kbn/expect' ; import moment from 'moment' ; import { FtrProviderContext } from '../../ftr_provider_context' ; import { IEvent } from '../../../../plugins/event_log/server' ; import { IValidatedEvent } from '../../../../plugins/event_log/server/types' ; const delay = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ; export default function ( { getService } ) { const supertest = getService ( 'supertest' ) ; const log = getService ( 'log' ) ; const retry = getService ( 'retry' ) ; const spacesService = getService ( 'spaces' ) ; const esArchiver = getService ( 'esArchiver' ) ; describe ( 'Event Log public API' , ( ) => { before ( async ( ) => { await spacesService . create ( { id : 'namespace-a' , name : 'Space A' , disabledFeatures : [ ] , } ) ; } ) ; after ( async ( ) => { await esArchiver . unload ( 'x-pack/test/functional/es_archives/empty_kibana' ) ; } ) ; for ( const namespace of [ undefined , 'namespace-a' ] ) { const namespaceName = namespace === undefined ? 'default' : namespace ; describe ( `namespace: ${ namespaceName }` , ( ) => { it ( 'should allow querying for events by Saved Object' , async ( ) => { const id = uuid . v4 ( ) ; const expectedEvents = [ fakeEvent ( namespace , id ) , fakeEvent ( namespace , id ) ] ; await logTestEvent ( namespace , id , expectedEvents [ 0 ] ) ; await logTestEvent ( namespace , id , expectedEvents [ 1 ] ) ; await retry . try ( async ( ) => { const { body : { data , total } , } = await findEvents ( namespace , id , { } ) ; expect ( data . length ) . to . be ( 2 ) ; expect ( total ) . to . be ( 2 ) ; assertEventsFromApiMatchCreatedEvents ( data , expectedEvents ) ; } ) ; } ) ; it ( 'should support pagination for events' , async ( ) => { const id = uuid . v4 ( ) ; const expectedEvents = await logFakeEvents ( namespace , id , 6 ) ; await retry . try ( async ( ) => { const { body : { data : foundEvents } , } = await findEvents ( namespace , id , { } ) ; expect ( foundEvents . length ) . to . be ( 6 ) ; } ) ; const [ expectedFirstPage , expectedSecondPage ] = chunk ( expectedEvents , 3 ) ; const { body : { data : firstPage } , } = await findEvents ( namespace , id , { per_page : 3 } ) ; expect ( firstPage . length ) . to . be ( 3 ) ; assertEventsFromApiMatchCreatedEvents ( firstPage , expectedFirstPage ) ; const { body : { data : secondPage } , } = await findEvents ( namespace , id , { per_page : 3 , page : 2 } ) ; expect ( secondPage . length ) . to . be ( 3 ) ; assertEventsFromApiMatchCreatedEvents ( secondPage , expectedSecondPage ) ; } ) ; it ( 'should support sorting by event end' , async ( ) => { const id = uuid . v4 ( ) ; const expectedEvents = await logFakeEvents ( namespace , id , 6 ) ; await retry . try ( async ( ) => { const { body : { data : foundEvents } , } = await findEvents ( namespace , id , { sort_field : 'event.end' , sort_order : 'desc' } ) ; expect ( foundEvents . length ) . to . be ( expectedEvents . length ) ; assertEventsFromApiMatchCreatedEvents ( foundEvents , expectedEvents . reverse ( ) ) ; } ) ; } ) ; it ( 'should support date ranges for events' , async ( ) => { const id = uuid . v4 ( ) ; const firstEvent = fakeEvent ( namespace , id ) ; await logTestEvent ( namespace , id , firstEvent ) ; await delay ( 1000 ) ; const start = new Date ( ) . toISOString ( ) ; const expectedEvents = await logFakeEvents ( namespace , id , 6 ) ; const end = new Date ( ) . toISOString ( ) ; await delay ( 1000 ) ; const lastEvent = fakeEvent ( namespace , id ) ; await logTestEvent ( namespace , id , lastEvent ) ; await retry . try ( async ( ) => { const { body : { data : foundEvents , total } , } = await findEvents ( namespace , id , { } ) ; expect ( foundEvents . length ) . to . be ( 8 ) ; expect ( total ) . to . be ( 8 ) ; } ) ; const { body : { data : eventsWithinRange } , } = await findEvents ( namespace , id , { start , end } ) ; expect ( eventsWithinRange . length ) . to . be ( expectedEvents . length ) ; assertEventsFromApiMatchCreatedEvents ( eventsWithinRange , expectedEvents ) ; const { body : { data : eventsFrom } , } = await findEvents ( namespace , id , { start } ) ; expect ( eventsFrom . length ) . to . be ( expectedEvents . length + 1 ) ; assertEventsFromApiMatchCreatedEvents ( eventsFrom , [ ... expectedEvents , lastEvent ] ) ; const { body : { data : eventsUntil } , } = await findEvents ( namespace , id , { end } ) ; expect ( eventsUntil . length ) . to . be ( expectedEvents . length + 1 ) ; assertEventsFromApiMatchCreatedEvents ( eventsUntil , [ firstEvent , ... expectedEvents ] ) ; } ) ; } ) ; } describe ( `Index Lifecycle` , ( ) => { it ( 'should query across indices matching the Event Log data view' , async ( ) => { await esArchiver . load ( 'x-pack/test/functional/es_archives/event_log_multiple_indicies' ) ; const id = `421f2511-5cd1-44fd-95df-e0df83e354d5` ; const { body : { data , total } , } = await findEventsByIds ( undefined , [ id ] , { } , [ id ] ) ; expect ( data . length ) . to . be ( 6 ) ; expect ( total ) . to . be ( 6 ) ; expect ( data . map ( ( foundEvent ) => foundEvent ?. message ) ) . to . eql ( [ 'test 2020-10-28T15:19:53.825Z' , 'test 2020-10-28T15:19:54.849Z' , 'test 2020-10-28T15:19:54.881Z' , 'test 2020-10-28T15:19:55.913Z' , 'test 2020-10-28T15:19:55.938Z' , 'test 2020-10-28T15:19:55.962Z' , ] ) ; await esArchiver . unload ( 'x-pack/test/functional/es_archives/event_log_multiple_indicies' ) ; } ) ; } ) ; describe ( `Legacy Ids` , ( ) => { before ( async ( ) => { await esArchiver . load ( 'x-pack/test/functional/es_archives/event_log_legacy_ids' ) ; } ) ; after ( async ( ) => { await esArchiver . unload ( 'x-pack/test/functional/es_archives/event_log_legacy_ids' ) ; } ) ; it ( 'should support search event by ids and legacyIds' , async ( ) => { const legacyId = `521f2511-5cd1-44fd-95df-e0df83e354d5` ; const id = `621f2511-5cd1-44fd-95df-e0df83e354d5` ; const { body : { data , total } , } = await findEventsByIds ( undefined , [ id ] , { } , [ legacyId ] ) ; expect ( data . length ) . to . be ( 5 ) ; expect ( total ) . to . be ( 5 ) ; expect ( data . map ( ( foundEvent ) => foundEvent ?. message ) ) . to . eql ( [ 'test 2020-10-28T15:19:55.913Z' , 'test legacy 2020-10-28T15:19:55.913Z' , 'test 2020-10-28T15:19:55.938Z' , 'test legacy 2020-10-28T15:19:55.962Z' , 'test 2020-10-28T15:19:55.962Z' , ] ) ; } ) ; it ( 'should search event only by ids if no legacyIds are provided' , async ( ) => { const id = `621f2511-5cd1-44fd-95df-e0df83e354d5` ; const { body : { data , total } , } = await findEventsByIds ( undefined , [ id ] , { } ) ; expect ( data . length ) . to . be ( 3 ) ; expect ( total ) . to . be ( 3 ) ; expect ( data . map ( ( foundEvent ) => foundEvent ?. message ) ) . to . eql ( [ 'test 2020-10-28T15:19:55.913Z' , 'test 2020-10-28T15:19:55.938Z' , 'test 2020-10-28T15:19:55.962Z' , ] ) ; } ) ; } ) ; } ) ; async function findEvents ( namespace , id , query = { } ) { const urlPrefix = urlPrefixFromNamespace ( namespace ) ; const url = `${ urlPrefix }/api/event_log/event_log_test/${ id }/_find${ isEmpty ( query ) ? '' : `?${ Object . entries ( query ) . map ( ( [ key , val ] ) => `${ key }=${ val }` ) . join ( '&' ) }` }` ; await delay ( 1000 ) ; log . debug ( `Finding Events for Saved Object with ${ url }` ) ; return await supertest . get ( url ) . set ( 'kbn-xsrf' , 'foo' ) . expect ( 200 ) ; } async function findEventsByIds ( namespace , ids , query = { } , legacyIds = [ ] ) { const urlPrefix = urlPrefixFromNamespace ( namespace ) ; const url = `${ urlPrefix }/api/event_log/event_log_test/_find${ isEmpty ( query ) ? '' : `?${ Object . entries ( query ) . map ( ( [ key , val ] ) => `${ key }=${ val }` ) . join ( '&' ) }` }` ; await delay ( 1000 ) ; log . debug ( `Finding Events for Saved Object with ${ url }` ) ; return await supertest . post ( url ) . set ( 'kbn-xsrf' , 'foo' ) . send ( { ids , legacyIds , } ) . expect ( 200 ) ; } function assertEventsFromApiMatchCreatedEvents ( foundEvents , expectedEvents ) { try { foundEvents . forEach ( ( foundEvent , index ) => { expect ( omit ( foundEvent ! . event ?? { } , 'start' , 'end' , 'duration' ) ) . to . eql ( expectedEvents [ index ] ! . event ) ; expect ( omit ( foundEvent ! . kibana ?? { } , 'server_uuid' , 'version' ) ) . to . eql ( expectedEvents [ index ] ! . kibana ) ; expect ( foundEvent ! . message ) . to . eql ( expectedEvents [ index ] ! . message ) ; } ) ; } catch ( ex ) { log . debug ( `failed to match ${ JSON . stringify ( { foundEvents , expectedEvents } ) }` ) ; throw ex ; } } async function logTestEvent ( namespace , id , event ) { const urlPrefix = urlPrefixFromNamespace ( namespace ) ; const url = `${ urlPrefix }/api/log_event_fixture/${ id }/_log` ; log . debug ( `Logging Event for Saved Object with ${ url } - ${ JSON . stringify ( event ) }` ) ; return await supertest . post ( url ) . set ( 'kbn-xsrf' , 'foo' ) . send ( event ) . expect ( 200 ) ; } function fakeEvent ( namespace , id , overrides = { } ) { const savedObject = { rel : 'primary' , type : 'event_log_test' , id , } ; if ( namespace !== undefined ) { savedObject . namespace = namespace ; } return merge ( { event : { provider : 'event_log_fixture' , action : 'test' , } , kibana : { saved_objects : [ savedObject ] , } , message : `test ${ moment ( ) . toISOString ( ) }` , } , overrides ) ; } async function logFakeEvents ( namespace , savedObjectId , eventsToLog ) { const expectedEvents = [ ] ; for ( let index = 0 ; index < eventsToLog ; index ++ ) { const event = fakeEvent ( namespace , savedObjectId ) ; await logTestEvent ( namespace , savedObjectId , event ) ; expectedEvents . push ( event ) ; } return expectedEvents ; } } function urlPrefixFromNamespace ( namespace ) { if ( namespace === undefined ) return '' ; return `/s/${ namespace }` ; }