diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 9a92270fc9c14..aa3f0bf287fca 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -8,16 +8,26 @@ import seedrandom from 'seedrandom'; import { AlertEvent, EndpointEvent, + EndpointStatus, Host, HostMetadata, - OSFields, HostPolicyResponse, HostPolicyResponseActionStatus, + OSFields, PolicyData, - EndpointStatus, } from './types'; import { factory as policyFactory } from './models/policy_config'; import { parentEntityId } from './models/event'; +import { + GetAgentConfigsResponseItem, + GetPackagesResponse, +} from '../../../ingest_manager/common/types/rest_spec'; +import { + AgentConfigStatus, + EsAssetReference, + InstallationStatus, + KibanaAssetReference, +} from '../../../ingest_manager/common/types/models'; export type Event = AlertEvent | EndpointEvent; /** @@ -1062,6 +1072,97 @@ export class EndpointDocGenerator { }; } + /** + * Generate an Agent Configuration (ingest) + */ + public generateAgentConfig(): GetAgentConfigsResponseItem { + return { + id: this.seededUUIDv4(), + name: 'Agent Config', + status: AgentConfigStatus.Active, + description: 'Some description', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + revision: 2, + updated_at: '2020-07-22T16:36:49.196Z', + updated_by: 'elastic', + package_configs: ['852491f0-cc39-11ea-bac2-cdbf95b4b41a'], + agents: 0, + }; + } + + /** + * Generate an EPM Package for Endpoint + */ + public generateEpmPackage(): GetPackagesResponse['response'][0] { + return { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.5.0', + description: 'This is the Elastic Endpoint package.', + type: 'solution', + download: '/epr/endpoint/endpoint-0.5.0.tar.gz', + path: '/package/endpoint/0.5.0', + icons: [ + { + src: '/package/endpoint/0.5.0/img/logo-endpoint-64-color.svg', + size: '16x16', + type: 'image/svg+xml', + }, + ], + status: 'installed' as InstallationStatus, + savedObject: { + type: 'epm-packages', + id: 'endpoint', + attributes: { + installed_kibana: [ + { id: '826759f0-7074-11ea-9bc8-6b38f4d29a16', type: 'dashboard' }, + { id: '1cfceda0-728b-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, + { id: '1e525190-7074-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, + { id: '55387750-729c-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, + { id: '92b1edc0-706a-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, + { id: 'a3a3bd10-706b-11ea-9bc8-6b38f4d29a16', type: 'map' }, + ] as KibanaAssetReference[], + installed_es: [ + { id: 'logs-endpoint.alerts', type: 'index_template' }, + { id: 'events-endpoint', type: 'index_template' }, + { id: 'logs-endpoint.events.file', type: 'index_template' }, + { id: 'logs-endpoint.events.library', type: 'index_template' }, + { id: 'metrics-endpoint.metadata', type: 'index_template' }, + { id: 'metrics-endpoint.metadata_mirror', type: 'index_template' }, + { id: 'logs-endpoint.events.network', type: 'index_template' }, + { id: 'metrics-endpoint.policy', type: 'index_template' }, + { id: 'logs-endpoint.events.process', type: 'index_template' }, + { id: 'logs-endpoint.events.registry', type: 'index_template' }, + { id: 'logs-endpoint.events.security', type: 'index_template' }, + { id: 'metrics-endpoint.telemetry', type: 'index_template' }, + ] as EsAssetReference[], + es_index_patterns: { + alerts: 'logs-endpoint.alerts-*', + events: 'events-endpoint-*', + file: 'logs-endpoint.events.file-*', + library: 'logs-endpoint.events.library-*', + metadata: 'metrics-endpoint.metadata-*', + metadata_mirror: 'metrics-endpoint.metadata_mirror-*', + network: 'logs-endpoint.events.network-*', + policy: 'metrics-endpoint.policy-*', + process: 'logs-endpoint.events.process-*', + registry: 'logs-endpoint.events.registry-*', + security: 'logs-endpoint.events.security-*', + telemetry: 'metrics-endpoint.telemetry-*', + }, + name: 'endpoint', + version: '0.5.0', + internal: false, + removable: false, + }, + references: [], + updated_at: '2020-06-24T14:41:23.098Z', + version: 'Wzc0LDFd', + }, + }; + } + /** * Generates a Host Policy response message */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 621fab2e4ee11..4a4326d5b2919 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -81,6 +81,11 @@ interface ServerReturnedHostNonExistingPolicies { payload: HostState['nonExistingPolicies']; } +interface ServerReturnedHostExistValue { + type: 'serverReturnedHostExistValue'; + payload: boolean; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList @@ -92,6 +97,7 @@ export type HostAction = | ServerFailedToReturnPoliciesForOnboarding | UserSelectedEndpointPolicy | ServerCancelledHostListLoading + | ServerReturnedHostExistValue | ServerCancelledPolicyItemsLoading | ServerReturnedEndpointPackageInfo | ServerReturnedHostNonExistingPolicies; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index b6e18506b6111..8ff4ad5a043b5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -51,6 +51,7 @@ describe('HostList store concerns', () => { policyItemsLoading: false, endpointPackageInfo: undefined, nonExistingPolicies: {}, + hostsExist: true, }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index edeca5659ee38..74bebf211258a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpSetup } from 'kibana/public'; +import { HttpStart } from 'kibana/public'; import { HostInfo, HostResultList } from '../../../../../common/endpoint/types'; import { GetPolicyListResponse } from '../../policy/types'; import { ImmutableMiddlewareFactory } from '../../../../common/store'; @@ -28,6 +28,8 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor return ({ getState, dispatch }) => (next) => async (action) => { next(action); const state = getState(); + + // Host list if ( action.type === 'userChangedUrl' && isOnHostPage(state) && @@ -89,6 +91,19 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor // No hosts, so we should check to see if there are policies for onboarding if (hostResponse && hostResponse.hosts.length === 0) { const http = coreStart.http; + + // The original query to the list could have had an invalid param (ex. invalid page_size), + // so we check first if hosts actually do exist before pulling in data for the onboarding + // messages. + if (await doHostsExist(http)) { + return; + } + + dispatch({ + type: 'serverReturnedHostExistValue', + payload: false, + }); + try { const policyDataResponse: GetPolicyListResponse = await sendGetEndpointSpecificPackageConfigs( http, @@ -119,6 +134,8 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }); } } + + // Host Details if (action.type === 'userChangedUrl' && hasSelectedHost(state) === true) { dispatch({ type: 'serverCancelledPolicyItemsLoading', @@ -160,7 +177,6 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor type: 'serverFailedToReturnHostList', payload: error, }); - return; } } else { dispatch({ @@ -217,7 +233,7 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = (cor }; const getNonExistingPoliciesForHostsList = async ( - http: HttpSetup, + http: HttpStart, hosts: HostResultList['hosts'], currentNonExistingPolicies: HostState['nonExistingPolicies'] ): Promise => { @@ -274,3 +290,23 @@ const getNonExistingPoliciesForHostsList = async ( return nonExisting; }; + +const doHostsExist = async (http: HttpStart): Promise => { + try { + return ( + ( + await http.post('/api/endpoint/metadata', { + body: JSON.stringify({ + paging_properties: [{ page_index: 0 }, { page_size: 1 }], + }), + }) + ).hosts.length !== 0 + ); + } catch (error) { + // eslint-disable-next-line no-console + console.error(`error while trying to check if hosts exist`); + // eslint-disable-next-line no-console + console.error(error); + } + return false; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_host_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_host_result_list.ts index 05af1ee062de6..355c2bb5c19fc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_host_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_host_result_list.ts @@ -4,8 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostInfo, HostResultList, HostStatus } from '../../../../../common/endpoint/types'; +import { HttpStart } from 'kibana/public'; +import { + GetHostPolicyResponse, + HostInfo, + HostPolicyResponse, + HostResultList, + HostStatus, +} from '../../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; +import { + INGEST_API_AGENT_CONFIGS, + INGEST_API_EPM_PACKAGES, + INGEST_API_PACKAGE_CONFIGS, +} from '../../policy/store/policy_list/services/ingest'; +import { + GetAgentConfigsResponse, + GetPackagesResponse, +} from '../../../../../../ingest_manager/common/types/rest_spec'; +import { GetPolicyListResponse } from '../../policy/types'; + +const generator = new EndpointDocGenerator('seed'); export const mockHostResultList: (options?: { total?: number; @@ -26,7 +45,6 @@ export const mockHostResultList: (options?: { const hosts = []; for (let index = 0; index < actualCountToReturn; index++) { - const generator = new EndpointDocGenerator('seed'); hosts.push({ metadata: generator.generateHostMetadata(), host_status: HostStatus.ERROR, @@ -45,9 +63,133 @@ export const mockHostResultList: (options?: { * returns a mocked API response for retrieving a single host metadata */ export const mockHostDetailsApiResult = (): HostInfo => { - const generator = new EndpointDocGenerator('seed'); return { metadata: generator.generateHostMetadata(), host_status: HostStatus.ERROR, }; }; + +/** + * Mock API handlers used by the Endpoint Host list. It also sets up a list of + * API handlers for Host details based on a list of Host results. + */ +const hostListApiPathHandlerMocks = ({ + hostsResults = mockHostResultList({ total: 3 }).hosts, + epmPackages = [generator.generateEpmPackage()], + endpointPackageConfigs = [], + policyResponse = generator.generatePolicyResponse(), +}: { + /** route handlers will be setup for each individual host in this array */ + hostsResults?: HostResultList['hosts']; + epmPackages?: GetPackagesResponse['response']; + endpointPackageConfigs?: GetPolicyListResponse['items']; + policyResponse?: HostPolicyResponse; +} = {}) => { + const apiHandlers = { + // endpoint package info + [INGEST_API_EPM_PACKAGES]: (): GetPackagesResponse => { + return { + response: epmPackages, + success: true, + }; + }, + + // host list + '/api/endpoint/metadata': (): HostResultList => { + return { + hosts: hostsResults, + request_page_size: 10, + request_page_index: 0, + total: hostsResults?.length || 0, + }; + }, + + // Do policies referenced in host list exist + // just returns 1 single agent config that includes all of the packageConfig IDs provided + [INGEST_API_AGENT_CONFIGS]: (): GetAgentConfigsResponse => { + const agentConfig = generator.generateAgentConfig(); + (agentConfig.package_configs as string[]).push( + ...endpointPackageConfigs.map((packageConfig) => packageConfig.id) + ); + return { + items: [agentConfig], + total: 10, + success: true, + perPage: 10, + page: 1, + }; + }, + + // Policy Response + '/api/endpoint/policy_response': (): GetHostPolicyResponse => { + return { policy_response: policyResponse }; + }, + + // List of Policies (package configs) for onboarding + [INGEST_API_PACKAGE_CONFIGS]: (): GetPolicyListResponse => { + return { + items: endpointPackageConfigs, + page: 1, + perPage: 10, + total: endpointPackageConfigs?.length, + success: true, + }; + }, + }; + + // Build a GET route handler for each host details based on the list of Hosts passed on input + if (hostsResults) { + hostsResults.forEach((host) => { + // @ts-ignore + apiHandlers[`/api/endpoint/metadata/${host.metadata.host.id}`] = () => host; + }); + } + + return apiHandlers; +}; + +/** + * Sets up mock impelementations in support of the Hosts list view + * + * @param mockedHttpService + * @param hostsResults + * @param pathHandlersOptions + */ +export const setHostListApiMockImplementation: ( + mockedHttpService: jest.Mocked, + apiResponses?: Parameters[0] +) => void = ( + mockedHttpService, + { hostsResults = mockHostResultList({ total: 3 }).hosts, ...pathHandlersOptions } = {} +) => { + const apiHandlers = hostListApiPathHandlerMocks({ ...pathHandlersOptions, hostsResults }); + + mockedHttpService.post + .mockImplementation(async (...args) => { + throw new Error(`un-expected call to http.post: ${args}`); + }) + // First time called, return list of hosts + .mockImplementationOnce(async () => { + return apiHandlers['/api/endpoint/metadata'](); + }); + + // If the hosts list results is zero, then mock the second call to `/metadata` to return + // empty list - indicating there are no hosts currently present on the system + if (!hostsResults.length) { + mockedHttpService.post.mockImplementationOnce(async () => { + return apiHandlers['/api/endpoint/metadata'](); + }); + } + + // Setup handling of GET requests + mockedHttpService.get.mockImplementation(async (...args) => { + const [path] = args; + if (typeof path === 'string') { + if (apiHandlers[path]) { + return apiHandlers[path](); + } + } + + throw new Error(`MOCK: api request does not have a mocked handler: ${path}`); + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 7f68baa4b85bd..e54f7df4d4f75 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -29,6 +29,7 @@ export const initialHostListState: Immutable = { policyItemsLoading: false, endpointPackageInfo: undefined, nonExistingPolicies: {}, + hostsExist: true, }; /* eslint-disable-next-line complexity */ @@ -125,6 +126,11 @@ export const hostListReducer: ImmutableReducer = ( ...state, endpointPackageInfo: action.payload, }; + } else if (action.type === 'serverReturnedHostExistValue') { + return { + ...state, + hostsExist: action.payload, + }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { ...state, @@ -181,6 +187,7 @@ export const hostListReducer: ImmutableReducer = ( error: undefined, detailsError: undefined, policyResponseError: undefined, + hostsExist: true, }; } return state; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 6e0823a920413..ca006f21c29ac 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -203,3 +203,9 @@ export const policyResponseStatus: (state: Immutable) => string = cre export const nonExistingPolicies: ( state: Immutable ) => Immutable = (state) => state.nonExistingPolicies; + +/** + * Return boolean that indicates whether hosts exist + * @param state + */ +export const hostsExist: (state: Immutable) => boolean = (state) => state.hostsExist; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 582a59cfd7605..6c949e9700b9a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -52,6 +52,8 @@ export interface HostState { endpointPackageInfo?: GetPackagesResponse['response'][0]; /** tracks the list of policies IDs used in Host metadata that may no longer exist */ nonExistingPolicies: Record; + /** Tracks whether hosts exist and helps control if onboarding should be visible */ + hostsExist: boolean; } /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 9d49c8705affe..3e00a5cc33db1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -8,18 +8,22 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; import { HostList } from './index'; -import { mockHostDetailsApiResult, mockHostResultList } from '../store/mock_host_result_list'; -import { mockPolicyResultList } from '../../policy/store/policy_list/mock_policy_result_list'; +import { + mockHostDetailsApiResult, + mockHostResultList, + setHostListApiMockImplementation, +} from '../store/mock_host_result_list'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; import { HostInfo, + HostPolicyResponse, HostPolicyResponseActionStatus, HostPolicyResponseAppliedAction, HostStatus, } from '../../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; -import { AppAction } from '../../../../common/store/actions'; import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants'; +import { mockPolicyResultList } from '../../policy/store/policy_list/test_mock_utils'; jest.mock('../../../../common/components/link_to'); @@ -35,6 +39,9 @@ describe('when on the hosts page', () => { const mockedContext = createAppRootMockRenderer(); ({ history, store, coreStart, middlewareSpy } = mockedContext); render = () => mockedContext.render(); + reactTestingLibrary.act(() => { + history.push('/hosts'); + }); }); it('should NOT display timeline', async () => { @@ -43,35 +50,29 @@ describe('when on the hosts page', () => { expect(timelineFlyout).toBeNull(); }); - it('should show the empty state when there are no hosts or polices', async () => { - const renderResult = render(); - // Initially, there are no hosts or policies, so we prompt to add policies first. - const table = await renderResult.findByTestId('emptyPolicyTable'); - expect(table).not.toBeNull(); - }); - - describe('when there are policies, but no hosts', () => { + describe('when there are no hosts or polices', () => { beforeEach(() => { - reactTestingLibrary.act(() => { - const hostListData = mockHostResultList({ total: 0 }); - coreStart.http.get.mockReturnValue(Promise.resolve(hostListData)); - const hostAction: AppAction = { - type: 'serverReturnedHostList', - payload: hostListData, - }; - store.dispatch(hostAction); + setHostListApiMockImplementation(coreStart.http, { + hostsResults: [], + }); + }); - jest.clearAllMocks(); + it('should show the empty state when there are no hosts or polices', async () => { + const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding'); + }); + // Initially, there are no hosts or policies, so we prompt to add policies first. + const table = await renderResult.findByTestId('emptyPolicyTable'); + expect(table).not.toBeNull(); + }); + }); - const policyListData = mockPolicyResultList({ total: 3 }); - coreStart.http.get.mockReturnValue(Promise.resolve(policyListData)); - const policyAction: AppAction = { - type: 'serverReturnedPoliciesForOnboarding', - payload: { - policyItems: policyListData.items, - }, - }; - store.dispatch(policyAction); + describe('when there are policies, but no hosts', () => { + beforeEach(async () => { + setHostListApiMockImplementation(coreStart.http, { + hostsResults: [], + endpointPackageConfigs: mockPolicyResultList({ total: 3 }).items, }); }); afterEach(() => { @@ -80,18 +81,27 @@ describe('when on the hosts page', () => { it('should show the no hosts empty state', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding'); + }); const emptyHostsTable = await renderResult.findByTestId('emptyHostsTable'); expect(emptyHostsTable).not.toBeNull(); }); it('should display the onboarding steps', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding'); + }); const onboardingSteps = await renderResult.findByTestId('onboardingSteps'); expect(onboardingSteps).not.toBeNull(); }); it('should show policy selection', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedPoliciesForOnboarding'); + }); const onboardingPolicySelect = await renderResult.findByTestId('onboardingPolicySelect'); expect(onboardingPolicySelect).not.toBeNull(); }); @@ -112,39 +122,54 @@ describe('when on the hosts page', () => { let firstPolicyID: string; beforeEach(() => { reactTestingLibrary.act(() => { - const hostListData = mockHostResultList({ total: 4 }); - firstPolicyID = hostListData.hosts[0].metadata.Endpoint.policy.applied.id; + const hostListData = mockHostResultList({ total: 4 }).hosts; + + firstPolicyID = hostListData[0].metadata.Endpoint.policy.applied.id; + [HostStatus.ERROR, HostStatus.ONLINE, HostStatus.OFFLINE, HostStatus.UNENROLLING].forEach( (status, index) => { - hostListData.hosts[index] = { - metadata: hostListData.hosts[index].metadata, + hostListData[index] = { + metadata: hostListData[index].metadata, host_status: status, }; } ); - hostListData.hosts.forEach((item, index) => { + hostListData.forEach((item, index) => { generatedPolicyStatuses[index] = item.metadata.Endpoint.policy.applied.status; }); - const action: AppAction = { - type: 'serverReturnedHostList', - payload: hostListData, - }; - store.dispatch(action); + + // Make sure that the first policy id in the host result is not set as non-existent + const ingestPackageConfigs = mockPolicyResultList({ total: 1 }).items; + ingestPackageConfigs[0].id = firstPolicyID; + + setHostListApiMockImplementation(coreStart.http, { + hostsResults: hostListData, + endpointPackageConfigs: ingestPackageConfigs, + }); }); }); it('should display rows in the table', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const rows = await renderResult.findAllByRole('row'); expect(rows).toHaveLength(5); }); it('should show total', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const total = await renderResult.findByTestId('hostListTableTotal'); expect(total.textContent).toEqual('4 Hosts'); }); it('should display correct status', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const hostStatuses = await renderResult.findAllByTestId('rowHostStatus'); expect(hostStatuses[0].textContent).toEqual('Error'); @@ -168,6 +193,9 @@ describe('when on the hosts page', () => { it('should display correct policy status', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const policyStatuses = await renderResult.findAllByTestId('rowPolicyStatus'); policyStatuses.forEach((status, index) => { @@ -184,6 +212,9 @@ describe('when on the hosts page', () => { it('should display policy name as a link', async () => { const renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const firstPolicyName = (await renderResult.findAllByTestId('policyNameCellLink'))[0]; expect(firstPolicyName).not.toBeNull(); expect(firstPolicyName.getAttribute('href')).toContain(`policy/${firstPolicyID}`); @@ -192,17 +223,10 @@ describe('when on the hosts page', () => { describe('when the user clicks the first hostname in the table', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { - const hostDetailsApiResponse = mockHostDetailsApiResult(); - - coreStart.http.get.mockReturnValue(Promise.resolve(hostDetailsApiResponse)); - reactTestingLibrary.act(() => { - store.dispatch({ - type: 'serverReturnedHostDetails', - payload: hostDetailsApiResponse, - }); - }); - renderResult = render(); + await reactTestingLibrary.act(async () => { + await middlewareSpy.waitForAction('serverReturnedHostList'); + }); const hostNameLinks = await renderResult.findAllByTestId('hostnameCellLink'); if (hostNameLinks.length) { reactTestingLibrary.fireEvent.click(hostNameLinks[0]); @@ -221,9 +245,11 @@ describe('when on the hosts page', () => { describe('when there is a selected host in the url', () => { let hostDetails: HostInfo; let agentId: string; - const dispatchServerReturnedHostPolicyResponse = ( + let renderAndWaitForData: () => Promise>; + + const createPolicyResponse = ( overallStatus: HostPolicyResponseActionStatus = HostPolicyResponseActionStatus.success - ) => { + ): HostPolicyResponse => { const policyResponse = docGenerator.generatePolicyResponse(); const malwareResponseConfigurations = policyResponse.Endpoint.policy.applied.response.configurations.malware; @@ -269,21 +295,28 @@ describe('when on the hosts page', () => { policyResponse.Endpoint.policy.applied.actions.push(unknownAction); malwareResponseConfigurations.concerned_actions.push(unknownAction.name); + return policyResponse; + }; + + const dispatchServerReturnedHostPolicyResponse = ( + overallStatus: HostPolicyResponseActionStatus = HostPolicyResponseActionStatus.success + ) => { reactTestingLibrary.act(() => { store.dispatch({ type: 'serverReturnedHostPolicyResponse', payload: { - policy_response: policyResponse, + policy_response: createPolicyResponse(overallStatus), }, }); }); }; - beforeEach(() => { + beforeEach(async () => { const { host_status, metadata: { host, ...details }, } = mockHostDetailsApiResult(); + hostDetails = { host_status, metadata: { @@ -297,34 +330,37 @@ describe('when on the hosts page', () => { agentId = hostDetails.metadata.elastic.agent.id; - coreStart.http.get.mockReturnValue(Promise.resolve(hostDetails)); + const policy = docGenerator.generatePolicyPackageConfig(); + policy.id = hostDetails.metadata.Endpoint.policy.applied.id; - reactTestingLibrary.act(() => { - history.push({ - ...history.location, - search: '?selected_host=1', - }); + setHostListApiMockImplementation(coreStart.http, { + hostsResults: [hostDetails], + endpointPackageConfigs: [policy], }); + reactTestingLibrary.act(() => { - store.dispatch({ - type: 'serverReturnedHostDetails', - payload: hostDetails, - }); + history.push('/hosts?selected_host=1'); }); + + renderAndWaitForData = async () => { + const renderResult = render(); + await middlewareSpy.waitForAction('serverReturnedHostDetails'); + return renderResult; + }; }); afterEach(() => { jest.clearAllMocks(); }); - it('should show the flyout', () => { - const renderResult = render(); + it('should show the flyout', async () => { + const renderResult = await renderAndWaitForData(); return renderResult.findByTestId('hostDetailsFlyout').then((flyout) => { expect(flyout).not.toBeNull(); }); }); it('should display policy name value as a link', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); expect(policyDetailsLink).not.toBeNull(); expect(policyDetailsLink.getAttribute('href')).toEqual( @@ -333,10 +369,7 @@ describe('when on the hosts page', () => { }); it('should update the URL when policy name link is clicked', async () => { - const policyItem = mockPolicyResultList({ total: 1 }).items[0]; - coreStart.http.get.mockReturnValue(Promise.resolve({ item: policyItem })); - - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); reactTestingLibrary.act(() => { @@ -349,7 +382,7 @@ describe('when on the hosts page', () => { }); it('should display policy status value as a link', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); expect(policyStatusLink).not.toBeNull(); expect(policyStatusLink.getAttribute('href')).toEqual( @@ -358,7 +391,7 @@ describe('when on the hosts page', () => { }); it('should update the URL when policy status link is clicked', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); reactTestingLibrary.act(() => { @@ -371,7 +404,7 @@ describe('when on the hosts page', () => { }); it('should display Success overall policy status', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.success); }); @@ -385,7 +418,7 @@ describe('when on the hosts page', () => { }); it('should display Warning overall policy status', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning); }); @@ -399,7 +432,7 @@ describe('when on the hosts page', () => { }); it('should display Failed overall policy status', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure); }); @@ -413,7 +446,7 @@ describe('when on the hosts page', () => { }); it('should display Unknown overall policy status', async () => { - const renderResult = render(); + const renderResult = await renderAndWaitForData(); reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse('' as HostPolicyResponseActionStatus); }); @@ -428,7 +461,7 @@ describe('when on the hosts page', () => { it('should include the link to reassignment in Ingest', async () => { coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); expect(linkToReassign).not.toBeNull(); expect(linkToReassign.textContent).toEqual('Reassign Configuration'); @@ -440,7 +473,7 @@ describe('when on the hosts page', () => { describe('when link to reassignment in Ingest is clicked', () => { beforeEach(async () => { coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); - const renderResult = render(); + const renderResult = await renderAndWaitForData(); const linkToReassign = await renderResult.findByTestId('hostDetailsLinkToIngest'); reactTestingLibrary.act(() => { reactTestingLibrary.fireEvent.click(linkToReassign); @@ -461,13 +494,14 @@ describe('when on the hosts page', () => { } throw new Error(`POST to '${requestOptions.path}' does not have a mock response!`); }); - renderResult = render(); + renderResult = await renderAndWaitForData(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl'); reactTestingLibrary.act(() => { reactTestingLibrary.fireEvent.click(policyStatusLink); }); await userChangedUrlChecker; + await middlewareSpy.waitForAction('serverReturnedHostPolicyResponse'); reactTestingLibrary.act(() => { dispatchServerReturnedHostPolicyResponse(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 2692f7791b7c0..58442ab417b60 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -89,6 +89,7 @@ export const HostList = () => { selectedPolicyId, policyItemsLoading, endpointPackageVersion, + hostsExist, } = useHostSelector(selector); const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); @@ -329,7 +330,7 @@ export const HostList = () => { }, [formatUrl, queryParams, search]); const renderTableOrEmptyState = useMemo(() => { - if (!loading && listData && listData.length > 0) { + if (hostsExist) { return ( { error={listError?.message} pagination={paginationSetup} onChange={onTableChange} + loading={loading} /> ); } else if (!policyItemsLoading && policyItems && policyItems.length > 0) { @@ -356,19 +358,20 @@ export const HostList = () => { ); } }, [ - listData, + loading, + hostsExist, + policyItemsLoading, policyItems, + listData, columns, - loading, + listError?.message, paginationSetup, onTableChange, - listError?.message, - handleCreatePolicyClick, handleDeployEndpointsClick, - handleSelectableOnChange, selectedPolicyId, + handleSelectableOnChange, selectionOptions, - policyItemsLoading, + handleCreatePolicyClick, ]); return ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts index 8203aae244f24..6ee6a4232f7cf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts @@ -143,6 +143,7 @@ describe('policy list store concerns', () => { isLoading: false, isDeleting: false, deleteStatus: undefined, + endpointPackageInfo: undefined, pageIndex: 0, pageSize: 10, total: 0, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts index b4e1da4e43da3..6fe555113617d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts @@ -29,6 +29,7 @@ export const policyListMiddlewareFactory: ImmutableMiddlewareFactory GetPolicyListResponse = (options = {}) => { - const { - total = 1, - request_page_size: requestPageSize = 10, - request_page_index: requestPageIndex = 0, - } = options; - - // Skip any that are before the page we're on - const numberToSkip = requestPageSize * requestPageIndex; - - // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 - const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); - - const policies = []; - for (let index = 0; index < actualCountToReturn; index++) { - const generator = new EndpointDocGenerator('seed'); - policies.push(generator.generatePolicyPackageConfig()); - } - const mock: GetPolicyListResponse = { - items: policies, - total, - page: requestPageIndex, - perPage: requestPageSize, - success: true, - }; - return mock; -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts index 7daa500ef1884..83cd8558306a0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.test.ts @@ -12,7 +12,7 @@ import { } from './ingest'; import { httpServiceMock } from '../../../../../../../../../../src/core/public/mocks'; import { PACKAGE_CONFIG_SAVED_OBJECT_TYPE } from '../../../../../../../../ingest_manager/common'; -import { apiPathMockResponseProviders } from '../test_mock_utils'; +import { policyListApiPathHandlers } from '../test_mock_utils'; describe('ingest service', () => { let http: ReturnType; @@ -61,7 +61,9 @@ describe('ingest service', () => { describe('sendGetEndpointSecurityPackage()', () => { it('should query EPM with category=security', async () => { - http.get.mockReturnValue(apiPathMockResponseProviders[INGEST_API_EPM_PACKAGES]()); + http.get.mockReturnValue( + Promise.resolve(policyListApiPathHandlers()[INGEST_API_EPM_PACKAGES]()) + ); await sendGetEndpointSecurityPackage(http); expect(http.get).toHaveBeenCalledWith('/api/ingest_manager/epm/packages', { query: { category: 'security' }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts index c6e6146f4d5e4..266faf9eae32c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts @@ -20,7 +20,7 @@ import { NewPolicyData } from '../../../../../../../common/endpoint/types'; const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_PACKAGE_CONFIGS = `${INGEST_API_ROOT}/package_configs`; -const INGEST_API_AGENT_CONFIGS = `${INGEST_API_ROOT}/agent_configs`; +export const INGEST_API_AGENT_CONFIGS = `${INGEST_API_ROOT}/agent_configs`; const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`; const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; export const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts index b5c67cc2c2014..3c9d5fde9b826 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts @@ -5,122 +5,84 @@ */ import { HttpStart } from 'kibana/public'; -import { INGEST_API_PACKAGE_CONFIGS, INGEST_API_EPM_PACKAGES } from './services/ingest'; +import { INGEST_API_EPM_PACKAGES, INGEST_API_PACKAGE_CONFIGS } from './services/ingest'; import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; import { GetPolicyListResponse } from '../../types'; -import { - KibanaAssetReference, - EsAssetReference, - GetPackagesResponse, - InstallationStatus, -} from '../../../../../../../ingest_manager/common'; +import { GetPackagesResponse } from '../../../../../../../ingest_manager/common'; const generator = new EndpointDocGenerator('policy-list'); -/** - * a list of API paths response mock providers - */ -export const apiPathMockResponseProviders = { - [INGEST_API_EPM_PACKAGES]: () => - Promise.resolve({ - response: [ - { - name: 'endpoint', - title: 'Elastic Endpoint', - version: '0.5.0', - description: 'This is the Elastic Endpoint package.', - type: 'solution', - download: '/epr/endpoint/endpoint-0.5.0.tar.gz', - path: '/package/endpoint/0.5.0', - icons: [ - { - src: '/package/endpoint/0.5.0/img/logo-endpoint-64-color.svg', - size: '16x16', - type: 'image/svg+xml', - }, - ], - status: 'installed' as InstallationStatus, - savedObject: { - type: 'epm-packages', - id: 'endpoint', - attributes: { - installed_kibana: [ - { id: '826759f0-7074-11ea-9bc8-6b38f4d29a16', type: 'dashboard' }, - { id: '1cfceda0-728b-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, - { id: '1e525190-7074-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, - { id: '55387750-729c-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, - { id: '92b1edc0-706a-11ea-9bc8-6b38f4d29a16', type: 'visualization' }, - { id: 'a3a3bd10-706b-11ea-9bc8-6b38f4d29a16', type: 'map' }, - ] as KibanaAssetReference[], - installed_es: [ - { id: 'logs-endpoint.alerts', type: 'index_template' }, - { id: 'events-endpoint', type: 'index_template' }, - { id: 'logs-endpoint.events.file', type: 'index_template' }, - { id: 'logs-endpoint.events.library', type: 'index_template' }, - { id: 'metrics-endpoint.metadata', type: 'index_template' }, - { id: 'metrics-endpoint.metadata_mirror', type: 'index_template' }, - { id: 'logs-endpoint.events.network', type: 'index_template' }, - { id: 'metrics-endpoint.policy', type: 'index_template' }, - { id: 'logs-endpoint.events.process', type: 'index_template' }, - { id: 'logs-endpoint.events.registry', type: 'index_template' }, - { id: 'logs-endpoint.events.security', type: 'index_template' }, - { id: 'metrics-endpoint.telemetry', type: 'index_template' }, - ] as EsAssetReference[], - es_index_patterns: { - alerts: 'logs-endpoint.alerts-*', - events: 'events-endpoint-*', - file: 'logs-endpoint.events.file-*', - library: 'logs-endpoint.events.library-*', - metadata: 'metrics-endpoint.metadata-*', - metadata_mirror: 'metrics-endpoint.metadata_mirror-*', - network: 'logs-endpoint.events.network-*', - policy: 'metrics-endpoint.policy-*', - process: 'logs-endpoint.events.process-*', - registry: 'logs-endpoint.events.registry-*', - security: 'logs-endpoint.events.security-*', - telemetry: 'metrics-endpoint.telemetry-*', - }, - name: 'endpoint', - version: '0.5.0', - internal: false, - removable: false, - }, - references: [], - updated_at: '2020-06-24T14:41:23.098Z', - version: 'Wzc0LDFd', - }, - }, - ], - success: true, - }), -}; - /** * It sets the mock implementation on the necessary http methods to support the policy list view * @param mockedHttpService - * @param responseItems + * @param totalPolicies */ export const setPolicyListApiMockImplementation = ( mockedHttpService: jest.Mocked, - responseItems: GetPolicyListResponse['items'] = [generator.generatePolicyPackageConfig()] + totalPolicies: number = 1 ): void => { - mockedHttpService.get.mockImplementation((...args) => { + const policyApiHandlers = policyListApiPathHandlers(totalPolicies); + + mockedHttpService.get.mockImplementation(async (...args) => { const [path] = args; if (typeof path === 'string') { - if (path === INGEST_API_PACKAGE_CONFIGS) { - return Promise.resolve({ - items: responseItems, - total: 10, - page: 1, - perPage: 10, - success: true, - }); - } - - if (apiPathMockResponseProviders[path]) { - return apiPathMockResponseProviders[path](); + if (policyApiHandlers[path]) { + return policyApiHandlers[path](); } } return Promise.reject(new Error(`MOCK: unknown policy list api: ${path}`)); }); }; + +/** + * Returns the response body for a call to get the list of Policies + * @param options + */ +export const mockPolicyResultList: (options?: { + total?: number; + request_page_size?: number; + request_page_index?: number; +}) => GetPolicyListResponse = (options = {}) => { + const { + total = 1, + request_page_size: requestPageSize = 10, + request_page_index: requestPageIndex = 0, + } = options; + + // Skip any that are before the page we're on + const numberToSkip = requestPageSize * requestPageIndex; + + // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 + const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); + + const policies = []; + for (let index = 0; index < actualCountToReturn; index++) { + policies.push(generator.generatePolicyPackageConfig()); + } + const mock: GetPolicyListResponse = { + items: policies, + total, + page: requestPageIndex, + perPage: requestPageSize, + success: true, + }; + return mock; +}; + +/** + * Returns an object comprised of the API path as the key along with a function that + * returns that API's result value + */ +export const policyListApiPathHandlers = (totalPolicies: number = 1) => { + return { + [INGEST_API_PACKAGE_CONFIGS]: () => { + return mockPolicyResultList({ total: totalPolicies }); + }, + [INGEST_API_EPM_PACKAGES]: (): GetPackagesResponse => { + return { + response: [generator.generateEpmPackage()], + success: true, + }; + }, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 03ab32dcb2b66..c81ffb0060c88 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -11,7 +11,7 @@ import { PolicyDetails } from './policy_details'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; import { getPolicyDetailPath, getHostListPath } from '../../../common/routing'; -import { apiPathMockResponseProviders } from '../store/policy_list/test_mock_utils'; +import { policyListApiPathHandlers } from '../store/policy_list/test_mock_utils'; jest.mock('../../../../common/components/link_to'); @@ -80,6 +80,8 @@ describe('Policy Details', () => { policyPackageConfig = generator.generatePolicyPackageConfig(); policyPackageConfig.id = '1'; + const policyListApiHandlers = policyListApiPathHandlers(); + http.get.mockImplementation((...args) => { const [path] = args; if (typeof path === 'string') { @@ -103,9 +105,9 @@ describe('Policy Details', () => { // Get package data // Used in tests that route back to the list - if (apiPathMockResponseProviders[path]) { + if (policyListApiHandlers[path]) { asyncActions = asyncActions.then(async () => sleep()); - return apiPathMockResponseProviders[path](); + return Promise.resolve(policyListApiHandlers[path]()); } } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx index e35c97698f5cb..97eaceff91e9c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.test.tsx @@ -5,12 +5,9 @@ */ import React from 'react'; -import * as reactTestingLibrary from '@testing-library/react'; - import { PolicyList } from './index'; -import { mockPolicyResultList } from '../store/policy_list/mock_policy_result_list'; import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; -import { AppAction } from '../../../../common/store/actions'; +import { setPolicyListApiMockImplementation } from '../store/policy_list/test_mock_utils'; jest.mock('../../../../common/components/link_to'); @@ -18,11 +15,12 @@ jest.mock('../../../../common/components/link_to'); describe.skip('when on the policies page', () => { let render: () => ReturnType; let history: AppContextTestRender['history']; - let store: AppContextTestRender['store']; + let coreStart: AppContextTestRender['coreStart']; + let middlewareSpy: AppContextTestRender['middlewareSpy']; beforeEach(() => { const mockedContext = createAppRootMockRenderer(); - ({ history, store } = mockedContext); + ({ history, coreStart, middlewareSpy } = mockedContext); render = () => mockedContext.render(); }); @@ -46,34 +44,30 @@ describe.skip('when on the policies page', () => { describe('when list data loads', () => { let firstPolicyID: string; - beforeEach(() => { - reactTestingLibrary.act(() => { - history.push('/policy'); - reactTestingLibrary.act(() => { - const policyListData = mockPolicyResultList({ total: 3 }); - firstPolicyID = policyListData.items[0].id; - const action: AppAction = { - type: 'serverReturnedPolicyListData', - payload: { - policyItems: policyListData.items, - total: policyListData.total, - pageSize: policyListData.perPage, - pageIndex: policyListData.page, - }, - }; - store.dispatch(action); - }); - }); + const renderList = async () => { + const renderResult = render(); + history.push('/policy'); + await Promise.all([ + middlewareSpy + .waitForAction('serverReturnedPolicyListData') + .then((action) => (firstPolicyID = action.payload.policyItems[0].id)), + // middlewareSpy.waitForAction('serverReturnedAgentConfigListData'), + ]); + return renderResult; + }; + + beforeEach(async () => { + setPolicyListApiMockImplementation(coreStart.http, 3); }); it('should display rows in the table', async () => { - const renderResult = render(); + const renderResult = await renderList(); const rows = await renderResult.findAllByRole('row'); expect(rows).toHaveLength(4); }); it('should display policy name value as a link', async () => { - const renderResult = render(); + const renderResult = await renderList(); const policyNameLink = (await renderResult.findAllByTestId('policyNameLink'))[0]; expect(policyNameLink).not.toBeNull(); expect(policyNameLink.getAttribute('href')).toContain(`policy/${firstPolicyID}`);