diff --git a/x-pack/test/functional/es_archives/endpoint/resolver_tree/data.json.gz b/x-pack/test/functional/es_archives/endpoint/resolver_tree/data.json.gz new file mode 100644 index 000000000000..0c3e40ce6eb5 Binary files /dev/null and b/x-pack/test/functional/es_archives/endpoint/resolver_tree/data.json.gz differ diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index ad1980cd7218..c25233a0c82e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -30,5 +30,6 @@ export default function (providerContext: FtrProviderContext) { }); loadTestFile(require.resolve('./endpoint_list')); loadTestFile(require.resolve('./policy_details')); + loadTestFile(require.resolve('./resolver')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts new file mode 100644 index 000000000000..620eab37f9b4 --- /dev/null +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts @@ -0,0 +1,197 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['common', 'timePicker', 'hosts', 'settings']); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const queryBar = getService('queryBar'); + + describe('Endpoint Event Resolver', function () { + before(async () => { + await esArchiver.load('endpoint/resolver_tree', { useCreate: true }); + await pageObjects.hosts.navigateToSecurityHostsPage(); + const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; + const toTime = 'now'; + await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await queryBar.setQuery('event.dataset : endpoint.events.file'); + await queryBar.submitQuery(); + await (await testSubjects.find('draggable-content-host.name')).click(); + await testSubjects.existOrFail('header-page-title'); + await (await testSubjects.find('navigation-events')).click(); + await testSubjects.existOrFail('events-viewer-panel'); + await testSubjects.exists('investigate-in-resolver-button', { timeout: 4000 }); + await (await testSubjects.findAll('investigate-in-resolver-button'))[0].click(); + }); + + after(async () => { + await pageObjects.hosts.deleteDataStreams(); + }); + + it('check that Resolver and Data table is loaded', async () => { + await testSubjects.existOrFail('resolver:graph'); + await testSubjects.existOrFail('tableHeaderCell_name_0'); + await testSubjects.existOrFail('tableHeaderCell_timestamp_1'); + }); + + it('compare resolver Nodes Table data and Data length', async () => { + const nodeData: string[] = []; + const TableData: string[] = []; + + const Table = await testSubjects.findAll('resolver:node-list:node-link:title'); + for (const value of Table) { + const text = await value._webElement.getText(); + TableData.push(text.split('\n')[0]); + } + await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); + const Nodes = await testSubjects.findAll('resolver:node:primary-button'); + for (const value of Nodes) { + nodeData.push(await value._webElement.getText()); + } + for (let i = 0; i < nodeData.length; i++) { + expect(TableData[i]).to.eql(nodeData[i]); + } + expect(nodeData.length).to.eql(TableData.length); + await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); + }); + + it('resolver Nodes navigation Up', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:north-button')).click(); + + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 0; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( + parseFloat(NewNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( + parseFloat(NewNodeDataStyle[i].left) + ); + } + await (await testSubjects.find('resolver:graph-controls:center-button')).click(); + }); + + it('resolver Nodes navigation Down', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:south-button')).click(); + + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.equal( + parseFloat(NewNodeDataStyle[i].left) + ); + } + await (await testSubjects.find('resolver:graph-controls:center-button')).click(); + }); + + it('resolver Nodes navigation Left', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:east-button')).click(); + + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 0; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await (await testSubjects.find('resolver:graph-controls:center-button')).click(); + }); + + it('resolver Nodes navigation Right', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await testSubjects.click('resolver:graph-controls:west-button'); + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( + parseFloat(NewNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await (await testSubjects.find('resolver:graph-controls:center-button')).click(); + }); + + it('resolver Nodes navigation Center', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:east-button')).click(); + await (await testSubjects.find('resolver:graph-controls:south-button')).click(); + + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 0; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + await (await testSubjects.find('resolver:graph-controls:center-button')).click(); + const CenterNodeDataStyle = await pageObjects.hosts.parseStyles(); + + for (let i = 0; i < CenterNodeDataStyle.length; i++) { + expect(parseFloat(CenterNodeDataStyle[i].left)).to.equal( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(CenterNodeDataStyle[i].top)).to.equal( + parseFloat(OriginalNodeDataStyle[i].top) + ); + } + }); + + it('resolver Nodes navigation zoom in', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); + + const NewNodeDataStyle = await pageObjects.hosts.parseStyles(); + for (let i = 1; i < NewNodeDataStyle.length; i++) { + expect(parseFloat(NewNodeDataStyle[i].left)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].left) + ); + expect(parseFloat(NewNodeDataStyle[i].top)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].top) + ); + expect(parseFloat(OriginalNodeDataStyle[i].width)).to.lessThan( + parseFloat(NewNodeDataStyle[i].width) + ); + expect(parseFloat(OriginalNodeDataStyle[i].height)).to.lessThan( + parseFloat(NewNodeDataStyle[i].height) + ); + await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); + } + }); + + it('resolver Nodes navigation zoom out', async () => { + const OriginalNodeDataStyle = await pageObjects.hosts.parseStyles(); + await (await testSubjects.find('resolver:graph-controls:zoom-out')).click(); + const NewNodeDataStyle1 = await pageObjects.hosts.parseStyles(); + for (let i = 1; i < OriginalNodeDataStyle.length; i++) { + expect(parseFloat(OriginalNodeDataStyle[i].left)).to.lessThan( + parseFloat(NewNodeDataStyle1[i].left) + ); + expect(parseFloat(OriginalNodeDataStyle[i].top)).to.lessThan( + parseFloat(NewNodeDataStyle1[i].top) + ); + expect(parseFloat(NewNodeDataStyle1[i].width)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].width) + ); + expect(parseFloat(NewNodeDataStyle1[i].height)).to.lessThan( + parseFloat(OriginalNodeDataStyle[i].height) + ); + } + await (await testSubjects.find('resolver:graph-controls:zoom-in')).click(); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index 5aa5e42ffd4e..840862ab0056 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -30,6 +30,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ['securitySolutionManagement']: { pathname: '/app/security/administration', }, + ...xpackFunctionalConfig.get('apps'), + ['security']: { + pathname: '/app/security', + }, }, kbnTestServer: { ...xpackFunctionalConfig.get('kbnTestServer'), diff --git a/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts b/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts new file mode 100644 index 000000000000..c5f7d5b5fdf3 --- /dev/null +++ b/x-pack/test/security_solution_endpoint/page_objects/hosts_page.ts @@ -0,0 +1,109 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; +import { deleteEventsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; +import { deleteAlertsStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; +import { deleteMetadataStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; +import { deletePolicyStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; +import { deleteTelemetryStream } from '../../security_solution_endpoint_api_int/apis/data_stream_helper'; +export interface DataStyle { + left: string; + top: string; + width: string; + height: string; +} + +export function SecurityHostsPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['common', 'header']); + const testSubjects = getService('testSubjects'); + + /** + * @function parseStyles + * Parses a string of inline styles into a typescript object with casing for react + * @param {string} styles + * @returns {Object} + */ + const parseStyle = ( + styles: string + ): { + left?: string; + top?: string; + width?: string; + height?: string; + } => + styles + .split(';') + .filter((style: string) => style.split(':')[0] && style.split(':')[1]) + .map((style: string) => [ + style + .split(':')[0] + .trim() + .replace(/-./g, (c: string) => c.substr(1).toUpperCase()), + style.split(':').slice(1).join(':').trim(), + ]) + .reduce( + (styleObj: {}, style: string[]) => ({ + ...styleObj, + [style[0]]: style[1], + }), + {} + ); + return { + /** + * Navigate to the Security Hosts page + */ + async navigateToSecurityHostsPage() { + await pageObjects.common.navigateToUrlWithBrowserHistory('security', '/hosts/AllHosts'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }, + /** + * Finds a table and returns the data in a nested array with row 0 is the headers if they exist. + * It uses euiTableCellContent to avoid poluting the array data with the euiTableRowCell__mobileHeader data. + * @param dataTestSubj + * @param element + * @returns Promise + */ + async getEndpointEventResolverNodeData(dataTestSubj: string, element: string) { + await testSubjects.exists(dataTestSubj); + const Elements = await testSubjects.findAll(dataTestSubj); + const $ = []; + for (const value of Elements) { + $.push(await value.getAttribute(element)); + } + return $; + }, + + /** + * Gets a array of not parsed styles and returns the Array of parsed styles. + * @returns Promise + */ + async parseStyles() { + const tableData = await this.getEndpointEventResolverNodeData('resolver:node', 'style'); + const styles: DataStyle[] = []; + for (let i = 1; i < tableData.length; i++) { + const eachStyle = parseStyle(tableData[i]); + styles.push({ + top: eachStyle.top ?? '', + height: eachStyle.height ?? '', + left: eachStyle.left ?? '', + width: eachStyle.width ?? '', + }); + } + return styles; + }, + /** + * Deletes DataStreams from Index Management. + */ + async deleteDataStreams() { + await deleteEventsStream(getService); + await deleteAlertsStream(getService); + await deletePolicyStream(getService); + await deleteMetadataStream(getService); + await deleteTelemetryStream(getService); + }, + }; +} diff --git a/x-pack/test/security_solution_endpoint/page_objects/index.ts b/x-pack/test/security_solution_endpoint/page_objects/index.ts index 39a9e7009c38..3664a2033d8b 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/index.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/index.ts @@ -10,6 +10,7 @@ import { EndpointPolicyPageProvider } from './policy_page'; import { TrustedAppsPageProvider } from './trusted_apps_page'; import { EndpointPageUtils } from './page_utils'; import { IngestManagerCreatePackagePolicy } from './ingest_manager_create_package_policy_page'; +import { SecurityHostsPageProvider } from './hosts_page'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -18,4 +19,5 @@ export const pageObjects = { trustedApps: TrustedAppsPageProvider, endpointPageUtils: EndpointPageUtils, ingestManagerCreatePackagePolicy: IngestManagerCreatePackagePolicy, + hosts: SecurityHostsPageProvider, }; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts index be25f26532d9..f1c05b2fc8f2 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts @@ -11,6 +11,7 @@ import { alertsIndexPattern, policyIndexPattern, metadataCurrentIndexPattern, + telemetryIndexPattern, } from '../../../plugins/security_solution/common/endpoint/constants'; export async function deleteDataStream(getService: (serviceName: 'es') => Client, index: string) { @@ -75,3 +76,7 @@ export async function deleteAlertsStream(getService: (serviceName: 'es') => Clie export async function deletePolicyStream(getService: (serviceName: 'es') => Client) { await deleteDataStream(getService, policyIndexPattern); } + +export async function deleteTelemetryStream(getService: (serviceName: 'es') => Client) { + await deleteDataStream(getService, telemetryIndexPattern); +}