mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint] Unit tests for Policy settings form (#161814)
## Summary - Adds unit tests for all components of the Policy settings form ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
a811538ee8
commit
4b38775515
46 changed files with 3494 additions and 279 deletions
|
@ -8,4 +8,4 @@
|
|||
import { createLicenseServiceMock } from '../../../../common/license/mocks';
|
||||
|
||||
export const licenseService = createLicenseServiceMock();
|
||||
export const useLicense = () => licenseService;
|
||||
export const useLicense = jest.fn(() => licenseService);
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useQuery as _useQuery } from '@tanstack/react-query';
|
||||
import type { AppContextTestRender, ReactQueryHookRenderer } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import { allFleetHttpMocks } from '../../mocks';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import { useFetchEndpointPolicy } from './use_fetch_endpoint_policy';
|
||||
import type { PolicyData } from '../../../../common/endpoint/types';
|
||||
import {
|
||||
DefaultPolicyNotificationMessage,
|
||||
DefaultPolicyRuleNotificationMessage,
|
||||
} from '../../../../common/endpoint/models/policy_config';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const useQueryMock = _useQuery as jest.Mock;
|
||||
|
||||
jest.mock('@tanstack/react-query', () => {
|
||||
const actualReactQueryModule = jest.requireActual('@tanstack/react-query');
|
||||
|
||||
return {
|
||||
...actualReactQueryModule,
|
||||
useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)),
|
||||
};
|
||||
});
|
||||
|
||||
describe('When using the `useGetFileInfo()` hook', () => {
|
||||
type HookRenderer = ReactQueryHookRenderer<
|
||||
Parameters<typeof useFetchEndpointPolicy>,
|
||||
ReturnType<typeof useFetchEndpointPolicy>
|
||||
>;
|
||||
|
||||
let policy: PolicyData;
|
||||
let queryOptions: NonNullable<Parameters<typeof useFetchEndpointPolicy>[1]>;
|
||||
let http: AppContextTestRender['coreStart']['http'];
|
||||
let apiMocks: ReturnType<typeof allFleetHttpMocks>;
|
||||
let renderHook: () => ReturnType<HookRenderer>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testContext = createAppRootMockRenderer();
|
||||
|
||||
queryOptions = {};
|
||||
http = testContext.coreStart.http;
|
||||
apiMocks = allFleetHttpMocks(http);
|
||||
policy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy();
|
||||
renderHook = () => {
|
||||
return (testContext.renderReactQueryHook as HookRenderer)(() =>
|
||||
useFetchEndpointPolicy(policy.id, queryOptions)
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
it('should call the correct api with expected value', async () => {
|
||||
await renderHook();
|
||||
|
||||
expect(apiMocks.responseProvider.endpointPackagePolicy).toHaveBeenCalledWith({
|
||||
path: `/api/fleet/package_policies/${policy.id}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the expected output', async () => {
|
||||
apiMocks.responseProvider.endpointPackagePolicy.mockReturnValueOnce({
|
||||
item: policy,
|
||||
});
|
||||
const { data } = await renderHook();
|
||||
|
||||
expect(data).toEqual({
|
||||
item: policy,
|
||||
settings: policy.inputs[0].config.policy.value,
|
||||
artifactManifest: policy.inputs[0].config.artifact_manifest.value,
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply defaults to the policy data if necessary', async () => {
|
||||
policy.updated_at = expect.any(String);
|
||||
policy.created_at = expect.any(String);
|
||||
// Expected updates by the hook
|
||||
const policySettings = policy.inputs[0].config.policy.value;
|
||||
[
|
||||
'windows.popup.malware.message',
|
||||
'mac.popup.malware.message',
|
||||
'linux.popup.malware.message',
|
||||
'windows.popup.ransomware.message',
|
||||
].forEach((keyPath) => {
|
||||
set(policySettings, keyPath, DefaultPolicyNotificationMessage);
|
||||
});
|
||||
[
|
||||
'windows.popup.memory_protection.message',
|
||||
'windows.popup.behavior_protection.message',
|
||||
'mac.popup.behavior_protection.message',
|
||||
'linux.popup.behavior_protection.message',
|
||||
].forEach((keyPath) => {
|
||||
set(policySettings, keyPath, DefaultPolicyRuleNotificationMessage);
|
||||
});
|
||||
// These should not be updated by the hook since the API response has them already defined
|
||||
set(policySettings, 'mac.popup.memory_protection.message', 'hello world for mac');
|
||||
set(policySettings, 'linux.popup.memory_protection.message', 'hello world for linux');
|
||||
|
||||
// Setup API response with two of the messages having a value defined.
|
||||
const apiResponsePolicy = new FleetPackagePolicyGenerator(
|
||||
'seed'
|
||||
).generateEndpointPackagePolicy();
|
||||
set(
|
||||
apiResponsePolicy.inputs[0].config.policy.value,
|
||||
'mac.popup.memory_protection.message',
|
||||
'hello world for mac'
|
||||
);
|
||||
set(
|
||||
apiResponsePolicy.inputs[0].config.policy.value,
|
||||
'linux.popup.memory_protection.message',
|
||||
'hello world for linux'
|
||||
);
|
||||
apiMocks.responseProvider.endpointPackagePolicy.mockReturnValueOnce({
|
||||
item: apiResponsePolicy,
|
||||
});
|
||||
|
||||
const { data } = await renderHook();
|
||||
|
||||
expect(data).toEqual({
|
||||
item: policy,
|
||||
settings: policy.inputs[0].config.policy.value,
|
||||
artifactManifest: policy.inputs[0].config.artifact_manifest.value,
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply default values to api returned data', async () => {
|
||||
queryOptions.queryKey = ['a', 'b'];
|
||||
queryOptions.retry = false;
|
||||
queryOptions.refetchInterval = 5;
|
||||
await renderHook();
|
||||
|
||||
expect(useQueryMock).toHaveBeenCalledWith(expect.objectContaining(queryOptions));
|
||||
});
|
||||
});
|
|
@ -27,7 +27,7 @@ interface ApiDataResponse {
|
|||
artifactManifest: ManifestSchema;
|
||||
}
|
||||
|
||||
type UseFetchEndpointPolicyResponse = UseQueryResult<ApiDataResponse, IHttpFetchError>;
|
||||
export type UseFetchEndpointPolicyResponse = UseQueryResult<ApiDataResponse, IHttpFetchError>;
|
||||
|
||||
/**
|
||||
* Retrieve a single endpoint integration policy (details)
|
||||
|
@ -36,7 +36,7 @@ type UseFetchEndpointPolicyResponse = UseQueryResult<ApiDataResponse, IHttpFetch
|
|||
*/
|
||||
export const useFetchEndpointPolicy = (
|
||||
policyId: string,
|
||||
options: UseQueryOptions<ApiDataResponse, IHttpFetchError> = {}
|
||||
options: Omit<UseQueryOptions<ApiDataResponse, IHttpFetchError>, 'queryFn'> = {}
|
||||
): UseFetchEndpointPolicyResponse => {
|
||||
const http = useHttp();
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useQuery as _useQuery } from '@tanstack/react-query';
|
||||
import type { AppContextTestRender, ReactQueryHookRenderer } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import type { PolicyData } from '../../../../common/endpoint/types';
|
||||
import { allFleetHttpMocks } from '../../mocks';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import { useFetchAgentByAgentPolicySummary } from './use_fetch_endpoint_policy_agent_summary';
|
||||
import { agentRouteService } from '@kbn/fleet-plugin/common';
|
||||
|
||||
const useQueryMock = _useQuery as jest.Mock;
|
||||
|
||||
jest.mock('@tanstack/react-query', () => {
|
||||
const actualReactQueryModule = jest.requireActual('@tanstack/react-query');
|
||||
|
||||
return {
|
||||
...actualReactQueryModule,
|
||||
useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)),
|
||||
};
|
||||
});
|
||||
|
||||
describe('When using the `useFetchEndpointPolicyAgentSummary()` hook', () => {
|
||||
type HookRenderer = ReactQueryHookRenderer<
|
||||
Parameters<typeof useFetchAgentByAgentPolicySummary>,
|
||||
ReturnType<typeof useFetchAgentByAgentPolicySummary>
|
||||
>;
|
||||
|
||||
let policy: PolicyData;
|
||||
let queryOptions: NonNullable<Parameters<typeof useFetchAgentByAgentPolicySummary>[1]>;
|
||||
let http: AppContextTestRender['coreStart']['http'];
|
||||
let apiMocks: ReturnType<typeof allFleetHttpMocks>;
|
||||
let renderHook: () => ReturnType<HookRenderer>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testContext = createAppRootMockRenderer();
|
||||
|
||||
queryOptions = {};
|
||||
http = testContext.coreStart.http;
|
||||
apiMocks = allFleetHttpMocks(http);
|
||||
policy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy();
|
||||
renderHook = () => {
|
||||
return (testContext.renderReactQueryHook as HookRenderer)(() =>
|
||||
useFetchAgentByAgentPolicySummary(policy.policy_id, queryOptions)
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
it('should call the correct api with expected value', async () => {
|
||||
const { data } = await renderHook();
|
||||
|
||||
expect(apiMocks.responseProvider.agentStatus).toHaveBeenCalledWith({
|
||||
path: agentRouteService.getStatusPath(),
|
||||
query: { policyId: policy.policy_id },
|
||||
});
|
||||
expect(data).toEqual({
|
||||
total: 50,
|
||||
inactive: 5,
|
||||
online: 40,
|
||||
error: 0,
|
||||
offline: 5,
|
||||
updating: 0,
|
||||
other: 0,
|
||||
events: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply default values to api returned data', async () => {
|
||||
queryOptions.queryKey = ['a', 'b'];
|
||||
queryOptions.retry = false;
|
||||
queryOptions.refetchInterval = 5;
|
||||
await renderHook();
|
||||
|
||||
expect(useQueryMock).toHaveBeenCalledWith(expect.objectContaining(queryOptions));
|
||||
});
|
||||
});
|
|
@ -19,7 +19,7 @@ export const useFetchAgentByAgentPolicySummary = (
|
|||
* The Fleet Agent Policy ID (NOT the endpoint policy id)
|
||||
*/
|
||||
agentPolicyId: string,
|
||||
options: UseQueryOptions<EndpointPolicyAgentSummary, IHttpFetchError> = {}
|
||||
options: Omit<UseQueryOptions<EndpointPolicyAgentSummary, IHttpFetchError>, 'queryFn'> = {}
|
||||
): UseQueryResult<EndpointPolicyAgentSummary, IHttpFetchError> => {
|
||||
const http = useHttp();
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMutation as _useMutation } from '@tanstack/react-query';
|
||||
import type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
import { allFleetHttpMocks } from '../../mocks';
|
||||
import type {
|
||||
UseUpdateEndpointPolicyOptions,
|
||||
UseUpdateEndpointPolicyResult,
|
||||
} from './use_update_endpoint_policy';
|
||||
import type { RenderHookResult } from '@testing-library/react-hooks/src/types';
|
||||
import { useUpdateEndpointPolicy } from './use_update_endpoint_policy';
|
||||
import type { PolicyData } from '../../../../common/endpoint/types';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import { packagePolicyRouteService } from '@kbn/fleet-plugin/common';
|
||||
import { getPolicyDataForUpdate } from '../../../../common/endpoint/service/policy';
|
||||
|
||||
const useMutationMock = _useMutation as jest.Mock;
|
||||
|
||||
jest.mock('@tanstack/react-query', () => {
|
||||
const actualReactQueryModule = jest.requireActual('@tanstack/react-query');
|
||||
|
||||
return {
|
||||
...actualReactQueryModule,
|
||||
useMutation: jest.fn((...args) => actualReactQueryModule.useMutation(...args)),
|
||||
};
|
||||
});
|
||||
|
||||
describe('When using the `useFetchEndpointPolicyAgentSummary()` hook', () => {
|
||||
let customOptions: UseUpdateEndpointPolicyOptions;
|
||||
let http: AppContextTestRender['coreStart']['http'];
|
||||
let apiMocks: ReturnType<typeof allFleetHttpMocks>;
|
||||
let policy: PolicyData;
|
||||
let renderHook: () => RenderHookResult<
|
||||
UseUpdateEndpointPolicyOptions,
|
||||
UseUpdateEndpointPolicyResult
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testContext = createAppRootMockRenderer();
|
||||
|
||||
http = testContext.coreStart.http;
|
||||
apiMocks = allFleetHttpMocks(http);
|
||||
policy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy();
|
||||
customOptions = {};
|
||||
renderHook = () => {
|
||||
return testContext.renderHook(() => useUpdateEndpointPolicy(customOptions));
|
||||
};
|
||||
});
|
||||
|
||||
it('should send expected update payload', async () => {
|
||||
const {
|
||||
result: {
|
||||
current: { mutateAsync },
|
||||
},
|
||||
} = renderHook();
|
||||
const result = await mutateAsync({ policy });
|
||||
|
||||
expect(apiMocks.responseProvider.updateEndpointPolicy).toHaveBeenCalledWith({
|
||||
path: packagePolicyRouteService.getUpdatePath(policy.id),
|
||||
body: JSON.stringify(getPolicyDataForUpdate(policy)),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ item: expect.any(Object) });
|
||||
});
|
||||
|
||||
it('should allow custom options to be passed to ReactQuery', async () => {
|
||||
customOptions.mutationKey = ['abc-123'];
|
||||
customOptions.cacheTime = 10;
|
||||
renderHook();
|
||||
|
||||
expect(useMutationMock).toHaveBeenCalledWith(expect.any(Function), customOptions);
|
||||
});
|
||||
});
|
|
@ -18,13 +18,12 @@ interface UpdateParams {
|
|||
policy: PolicyData;
|
||||
}
|
||||
|
||||
type UseUpdateEndpointPolicyOptions = UseMutationOptions<
|
||||
UpdatePolicyResponse,
|
||||
IHttpFetchError,
|
||||
UpdateParams
|
||||
export type UseUpdateEndpointPolicyOptions = Omit<
|
||||
UseMutationOptions<UpdatePolicyResponse, IHttpFetchError, UpdateParams>,
|
||||
'mutationFn'
|
||||
>;
|
||||
|
||||
type UseUpdateEndpointPolicyResult = UseMutationResult<
|
||||
export type UseUpdateEndpointPolicyResult = UseMutationResult<
|
||||
UpdatePolicyResponse,
|
||||
IHttpFetchError,
|
||||
UpdateParams
|
||||
|
|
|
@ -25,9 +25,16 @@ import {
|
|||
PACKAGE_POLICY_API_ROUTES,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import type { ResponseProvidersInterface } from '../../common/mock/endpoint/http_handler_mock_factory';
|
||||
import { httpHandlerMockFactory } from '../../common/mock/endpoint/http_handler_mock_factory';
|
||||
import {
|
||||
composeHttpHandlerMocks,
|
||||
httpHandlerMockFactory,
|
||||
} from '../../common/mock/endpoint/http_handler_mock_factory';
|
||||
import { EndpointDocGenerator } from '../../../common/endpoint/generate_data';
|
||||
import type { GetPolicyListResponse, GetPolicyResponse } from '../pages/policy/types';
|
||||
import type {
|
||||
GetPolicyListResponse,
|
||||
GetPolicyResponse,
|
||||
UpdatePolicyResponse,
|
||||
} from '../pages/policy/types';
|
||||
import { FleetAgentPolicyGenerator } from '../../../common/endpoint/data_generators/fleet_agent_policy_generator';
|
||||
import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
|
||||
|
@ -169,7 +176,7 @@ export const fleetGetEndpointPackagePolicyHttpMock =
|
|||
method: 'get',
|
||||
handler: () => {
|
||||
const response: GetPolicyResponse = {
|
||||
item: new EndpointDocGenerator('seed').generatePolicyPackagePolicy(),
|
||||
item: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy(),
|
||||
};
|
||||
return response;
|
||||
},
|
||||
|
@ -250,12 +257,12 @@ export const fleetGetAgentPolicyListHttpMock =
|
|||
]);
|
||||
|
||||
export type FleetBulkGetAgentPolicyListHttpMockInterface = ResponseProvidersInterface<{
|
||||
agentPolicy: () => BulkGetAgentPoliciesResponse;
|
||||
bulkAgentPolicy: () => BulkGetAgentPoliciesResponse;
|
||||
}>;
|
||||
export const fleetBulkGetAgentPolicyListHttpMock =
|
||||
httpHandlerMockFactory<FleetGetAgentPolicyListHttpMockInterface>([
|
||||
httpHandlerMockFactory<FleetBulkGetAgentPolicyListHttpMockInterface>([
|
||||
{
|
||||
id: 'agentPolicy',
|
||||
id: 'bulkAgentPolicy',
|
||||
path: AGENT_POLICY_API_ROUTES.BULK_GET_PATTERN,
|
||||
method: 'post',
|
||||
handler: ({ body }) => {
|
||||
|
@ -288,12 +295,12 @@ export const fleetBulkGetAgentPolicyListHttpMock =
|
|||
]);
|
||||
|
||||
export type FleetBulkGetPackagePoliciesListHttpMockInterface = ResponseProvidersInterface<{
|
||||
packagePolicies: () => BulkGetPackagePoliciesResponse;
|
||||
bulkPackagePolicies: () => BulkGetPackagePoliciesResponse;
|
||||
}>;
|
||||
export const fleetBulkGetPackagePoliciesListHttpMock =
|
||||
httpHandlerMockFactory<FleetBulkGetPackagePoliciesListHttpMockInterface>([
|
||||
{
|
||||
id: 'packagePolicies',
|
||||
id: 'bulkPackagePolicies',
|
||||
path: PACKAGE_POLICY_API_ROUTES.BULK_GET_PATTERN,
|
||||
method: 'post',
|
||||
handler: ({ body }) => {
|
||||
|
@ -427,3 +434,49 @@ export const fleetGetAgentStatusHttpMock =
|
|||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type FleetPutEndpointPackagePolicyHttpMockInterface = ResponseProvidersInterface<{
|
||||
updateEndpointPolicy: () => UpdatePolicyResponse;
|
||||
}>;
|
||||
export const fleetPutEndpointPackagePolicyHttpMock =
|
||||
httpHandlerMockFactory<FleetPutEndpointPackagePolicyHttpMockInterface>([
|
||||
{
|
||||
id: 'updateEndpointPolicy',
|
||||
path: PACKAGE_POLICY_API_ROUTES.UPDATE_PATTERN,
|
||||
method: 'put',
|
||||
handler: ({ body }) => {
|
||||
const updatedPolicy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy(
|
||||
JSON.parse(body as string)
|
||||
);
|
||||
|
||||
return {
|
||||
item: updatedPolicy,
|
||||
};
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export type AllFleetHttpMocksInterface = FleetGetAgentStatusHttpMockInterface &
|
||||
FleetGetEndpointPackagePolicyHttpMockInterface &
|
||||
FleetGetEndpointPackagePolicyListHttpMockInterface &
|
||||
FleetGetPackageHttpMockInterface &
|
||||
FleetBulkGetAgentPolicyListHttpMockInterface &
|
||||
FleetGetAgentPolicyListHttpMockInterface &
|
||||
FleetGetPackageListHttpMockInterface &
|
||||
FleetGetPackagePoliciesListHttpMockInterface &
|
||||
FleetBulkGetPackagePoliciesListHttpMockInterface &
|
||||
FleetPutEndpointPackagePolicyHttpMockInterface;
|
||||
|
||||
export const allFleetHttpMocks = composeHttpHandlerMocks<AllFleetHttpMocksInterface>([
|
||||
fleetGetAgentStatusHttpMock,
|
||||
fleetGetEndpointPackagePolicyHttpMock,
|
||||
fleetGetEndpointPackagePolicyListHttpMock,
|
||||
fleetGetPackageHttpMock,
|
||||
fleetBulkGetAgentPolicyListHttpMock,
|
||||
fleetGetAgentPolicyListHttpMock,
|
||||
fleetGetPackageListHttpMock,
|
||||
fleetGetPackageListHttpMock,
|
||||
fleetGetPackagePoliciesListHttpMock,
|
||||
fleetBulkGetPackagePoliciesListHttpMock,
|
||||
fleetPutEndpointPackagePolicyHttpMock,
|
||||
]);
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { useLicense as _useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import { createLicenseServiceMock } from '../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../common/hooks/__mocks__/use_license';
|
||||
import type { AdvancedSectionProps } from './advanced_section';
|
||||
import { AdvancedSection } from './advanced_section';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AdvancedPolicySchema } from '../../../models/advanced_policy_schema';
|
||||
import { within } from '@testing-library/dom';
|
||||
import { set } from 'lodash';
|
||||
|
||||
jest.mock('../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy Advanced Settings section', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').advancedSection;
|
||||
|
||||
let formProps: AdvancedSectionProps;
|
||||
let render: (expanded?: boolean) => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
const clickShowHideButton = () => {
|
||||
userEvent.click(renderResult.getByTestId(testSubj.showHideButton));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.container,
|
||||
};
|
||||
|
||||
render = (expanded = true) => {
|
||||
renderResult = mockedContext.render(<AdvancedSection {...formProps} />);
|
||||
|
||||
if (expanded) {
|
||||
clickShowHideButton();
|
||||
expect(renderResult.getByTestId(testSubj.settingsContainer));
|
||||
}
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render initially collapsed', () => {
|
||||
render(false);
|
||||
|
||||
expect(renderResult.queryByTestId(testSubj.settingsContainer)).toBeNull();
|
||||
});
|
||||
|
||||
it('should expand and collapse section when button is clicked', () => {
|
||||
render(false);
|
||||
|
||||
expect(renderResult.queryByTestId(testSubj.settingsContainer)).toBeNull();
|
||||
|
||||
clickShowHideButton();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.settingsContainer));
|
||||
});
|
||||
|
||||
it('should show warning callout', () => {
|
||||
const { getByTestId } = render(true);
|
||||
|
||||
expect(getByTestId(testSubj.warningCallout));
|
||||
});
|
||||
|
||||
it('should render all advanced options', () => {
|
||||
const fieldsWithDefaultValues = [
|
||||
'mac.advanced.capture_env_vars',
|
||||
'linux.advanced.capture_env_vars',
|
||||
];
|
||||
|
||||
render(true);
|
||||
|
||||
for (const advancedOption of AdvancedPolicySchema) {
|
||||
const optionTestSubj = testSubj.settingRowTestSubjects(advancedOption.key);
|
||||
const renderedRow = within(renderResult.getByTestId(optionTestSubj.container));
|
||||
|
||||
expect(renderedRow.getByTestId(optionTestSubj.textField));
|
||||
expect(renderedRow.getByTestId(optionTestSubj.label)).toHaveTextContent(
|
||||
exactMatchText(advancedOption.key)
|
||||
);
|
||||
expect(renderedRow.getByTestId(optionTestSubj.versionInfo)).toHaveTextContent(
|
||||
advancedOption.first_supported_version
|
||||
);
|
||||
|
||||
if (advancedOption.last_supported_version) {
|
||||
expect(renderedRow.getByTestId(optionTestSubj.versionInfo)).toHaveTextContent(
|
||||
advancedOption.last_supported_version
|
||||
);
|
||||
}
|
||||
|
||||
if (fieldsWithDefaultValues.includes(advancedOption.key)) {
|
||||
expect(renderedRow.getByTestId<HTMLInputElement>(optionTestSubj.textField).value).not.toBe(
|
||||
''
|
||||
);
|
||||
} else {
|
||||
expect(renderedRow.getByTestId<HTMLInputElement>(optionTestSubj.textField).value).toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('and when license is lower than Platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should not render options that require platinum license', () => {
|
||||
render(true);
|
||||
|
||||
for (const advancedOption of AdvancedPolicySchema) {
|
||||
if (advancedOption.license) {
|
||||
if (advancedOption.license === 'platinum') {
|
||||
expect(
|
||||
renderResult.queryByTestId(
|
||||
testSubj.settingRowTestSubjects(advancedOption.key).container
|
||||
)
|
||||
).toBeNull();
|
||||
} else {
|
||||
throw new Error(
|
||||
`${advancedOption.key}: Unknown license value: ${advancedOption.license}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('and when rendered in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render with no form fields', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(testSubj.settingsContainer));
|
||||
});
|
||||
|
||||
it('should render options in expected content', () => {
|
||||
const option1 = AdvancedPolicySchema[0];
|
||||
const option2 = AdvancedPolicySchema[4];
|
||||
|
||||
set(formProps.policy, option1.key, 'foo');
|
||||
set(formProps.policy, option2.key, ''); // test empty value
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(testSubj.settingsContainer));
|
||||
expect(getByTestId(testSubj.settingRowTestSubjects(option1.key).container)).toHaveTextContent(
|
||||
exactMatchText('linux.advanced.agent.connection_delayInfo 7.9+foo')
|
||||
);
|
||||
expect(getByTestId(testSubj.settingRowTestSubjects(option2.key).container)).toHaveTextContent(
|
||||
exactMatchText('linux.advanced.artifacts.global.intervalInfo 7.9+—')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -197,18 +197,25 @@ export const AdvancedSection = memo<AdvancedSectionProps>(
|
|||
<EuiFormRow
|
||||
key={key}
|
||||
fullWidth
|
||||
data-test-subj={getTestId(`${key}-container`)}
|
||||
label={
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem grow={true}>{key}</EuiFlexItem>
|
||||
<EuiFlexItem grow={true} data-test-subj={getTestId(`${key}-label`)}>
|
||||
{key}
|
||||
</EuiFlexItem>
|
||||
{documentation && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip content={documentation} position="right" />
|
||||
<EuiIconTip
|
||||
content={documentation}
|
||||
position="right"
|
||||
anchorProps={{ 'data-test-subj': getTestId(`${key}-tooltipIcon`) }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs">
|
||||
<EuiText size="xs" data-test-subj={getTestId(`${key}-versionInfo`)}>
|
||||
{lastVersion ? `${firstVersion}-${lastVersion}` : `${firstVersion}+`}
|
||||
</EuiText>
|
||||
}
|
||||
|
@ -220,10 +227,11 @@ export const AdvancedSection = memo<AdvancedSectionProps>(
|
|||
name={key}
|
||||
value={value as string}
|
||||
onChange={handleAdvancedSettingUpdate}
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
) : (
|
||||
<EuiText size="xs">{value || getEmptyValue()}</EuiText>
|
||||
<EuiText size="xs" data-test-subj={getTestId(`${key}-viewValue`)}>
|
||||
{value || getEmptyValue()}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects } from '../../mocks';
|
||||
import type { AntivirusRegistrationCardProps } from './antivirus_registration_card';
|
||||
import {
|
||||
NOT_REGISTERED_LABEL,
|
||||
REGISTERED_LABEL,
|
||||
AntivirusRegistrationCard,
|
||||
} from './antivirus_registration_card';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
|
||||
describe('Policy Form Antivirus Registration Card', () => {
|
||||
const antivirusTestSubj = getPolicySettingsFormTestSubjects('test').antivirusRegistration;
|
||||
|
||||
let formProps: AntivirusRegistrationCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': antivirusTestSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<AntivirusRegistrationCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render in edit mode', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display for windows OS with restriction', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.osValueContainer)).toHaveTextContent(
|
||||
'Windows RestrictionsInfo'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to enable the option', () => {
|
||||
const expectedUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedUpdate, 'windows.antivirus_registration.enabled', true);
|
||||
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
|
||||
userEvent.click(renderResult.getByTestId(antivirusTestSubj.enableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to disable the option', async () => {
|
||||
set(formProps.policy, 'windows.antivirus_registration.enabled', true);
|
||||
|
||||
const expectedUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedUpdate, 'windows.antivirus_registration.enabled', false);
|
||||
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
|
||||
userEvent.click(renderResult.getByTestId(antivirusTestSubj.enableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
describe('And rendered in View only mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render in view mode (option disabled)', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(antivirusTestSubj.card));
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.viewOnlyValue)).toHaveTextContent(
|
||||
NOT_REGISTERED_LABEL
|
||||
);
|
||||
});
|
||||
|
||||
it('should render in view mode (option enabled)', () => {
|
||||
formProps.policy.windows.antivirus_registration.enabled = true;
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(antivirusTestSubj.card));
|
||||
expect(renderResult.getByTestId(antivirusTestSubj.viewOnlyValue)).toHaveTextContent(
|
||||
REGISTERED_LABEL
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -21,7 +21,7 @@ const CARD_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const DESCRIPTON = i18n.translate(
|
||||
const DESCRIPTION = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation',
|
||||
{
|
||||
defaultMessage:
|
||||
|
@ -30,21 +30,21 @@ const DESCRIPTON = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const REGISTERED_LABEL = i18n.translate(
|
||||
export const REGISTERED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type',
|
||||
{
|
||||
defaultMessage: 'Register as antivirus',
|
||||
}
|
||||
);
|
||||
|
||||
const NOT_REGISTERED_LABEL = i18n.translate(
|
||||
export const NOT_REGISTERED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.notRegisteredLabel',
|
||||
{
|
||||
defaultMessage: 'Do not register as antivirus',
|
||||
}
|
||||
);
|
||||
|
||||
type AntivirusRegistrationCardProps = PolicyFormComponentCommonProps;
|
||||
export type AntivirusRegistrationCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const AntivirusRegistrationCard = memo<AntivirusRegistrationCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
|
@ -76,14 +76,19 @@ export const AntivirusRegistrationCard = memo<AntivirusRegistrationCardProps>(
|
|||
}
|
||||
)}
|
||||
>
|
||||
{isEditMode && <EuiText size="s">{DESCRIPTON}</EuiText>}
|
||||
{isEditMode && <EuiText size="s">{DESCRIPTION}</EuiText>}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{isEditMode ? (
|
||||
<EuiSwitch label={label} checked={isChecked} onChange={handleSwitchChange} />
|
||||
<EuiSwitch
|
||||
label={label}
|
||||
checked={isChecked}
|
||||
onChange={handleSwitchChange}
|
||||
data-test-subj={getTestId('switch')}
|
||||
/>
|
||||
) : (
|
||||
<div>{label}</div>
|
||||
<div data-test-subj={getTestId('value')}>{label}</div>
|
||||
)}
|
||||
</SettingCard>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type { AttackSurfaceReductionCardProps } from './attack_surface_reduction_card';
|
||||
import {
|
||||
AttackSurfaceReductionCard,
|
||||
LOCKED_CARD_ATTACK_SURFACE_REDUCTION,
|
||||
SWITCH_DISABLED_LABEL,
|
||||
SWITCH_ENABLED_LABEL,
|
||||
} from './attack_surface_reduction_card';
|
||||
import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license';
|
||||
|
||||
jest.mock('../../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy Attack Surface Reduction Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').attackSurface;
|
||||
|
||||
let formProps: AttackSurfaceReductionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<AttackSurfaceReductionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should show card in edit mode', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.enableDisableSwitch));
|
||||
});
|
||||
|
||||
it('should show correct OS support', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.osValues)).toHaveTextContent('Windows');
|
||||
});
|
||||
|
||||
it('should show option enabled', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show option disabled', () => {
|
||||
set(formProps.policy, 'windows.attack_surface_reduction.credential_hardening.enabled', false);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to toggle to disabled', () => {
|
||||
const expectedUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedUpdate, 'windows.attack_surface_reduction.credential_hardening.enabled', false);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
|
||||
userEvent.click(renderResult.getByTestId(testSubj.enableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
it('should should be able to toggle to enabled', () => {
|
||||
set(formProps.policy, 'windows.attack_surface_reduction.credential_hardening.enabled', false);
|
||||
|
||||
const expectedUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedUpdate, 'windows.attack_surface_reduction.credential_hardening.enabled', true);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.enableDisableSwitch)).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
|
||||
userEvent.click(renderResult.getByTestId(testSubj.enableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and license is lower than Platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should show locked card if license not platinum+', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.lockedCardTitle)).toHaveTextContent(
|
||||
LOCKED_CARD_ATTACK_SURFACE_REDUCTION
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and displayed in View Mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render in view mode', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(testSubj.card));
|
||||
});
|
||||
|
||||
it('should show correct value when disabled', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.viewModeValue)).toHaveTextContent(
|
||||
SWITCH_ENABLED_LABEL
|
||||
);
|
||||
});
|
||||
|
||||
it('should show correct value when enabled', () => {
|
||||
set(formProps.policy, 'windows.attack_surface_reduction.credential_hardening.enabled', false);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.viewModeValue)).toHaveTextContent(
|
||||
SWITCH_DISABLED_LABEL
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,7 +18,7 @@ import { SettingCard } from '../setting_card';
|
|||
|
||||
const ATTACK_SURFACE_OS_LIST = [OperatingSystem.WINDOWS];
|
||||
|
||||
const LOCKED_CARD_ATTACK_SURFACE_REDUCTION = i18n.translate(
|
||||
export const LOCKED_CARD_ATTACK_SURFACE_REDUCTION = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.attack_surface_reduction',
|
||||
{
|
||||
defaultMessage: 'Attack Surface Reduction',
|
||||
|
@ -32,21 +32,21 @@ const CARD_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const SWITCH_ENABLED_LABEL = i18n.translate(
|
||||
export const SWITCH_ENABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.credentialHardening.toggleEnabled',
|
||||
{
|
||||
defaultMessage: 'Credential hardening enabled',
|
||||
}
|
||||
);
|
||||
|
||||
const SWITCH_DISABLED_LABEL = i18n.translate(
|
||||
export const SWITCH_DISABLED_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.credentialHardening.toggleDisabled',
|
||||
{
|
||||
defaultMessage: 'Credential hardening disabled',
|
||||
}
|
||||
);
|
||||
|
||||
type AttackSurfaceReductionCardProps = PolicyFormComponentCommonProps;
|
||||
export type AttackSurfaceReductionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
|
@ -69,7 +69,12 @@ export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
|||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_ATTACK_SURFACE_REDUCTION} />;
|
||||
return (
|
||||
<SettingLockedCard
|
||||
title={LOCKED_CARD_ATTACK_SURFACE_REDUCTION}
|
||||
data-test-subj={getTestId('locked')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -86,7 +91,7 @@ export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
|||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
) : (
|
||||
<>{label}</>
|
||||
<span data-test-subj={getTestId('valueLabel')}>{label}</span>
|
||||
)}
|
||||
</SettingCard>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type { BehaviourProtectionCardProps } from './behaviour_protection_card';
|
||||
import { BehaviourProtectionCard, LOCKED_CARD_BEHAVIOR_TITLE } from './behaviour_protection_card';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license';
|
||||
import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks';
|
||||
import { set } from 'lodash';
|
||||
import { ProtectionModes } from '../../../../../../../../common/endpoint/types';
|
||||
|
||||
jest.mock('../../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy Behaviour Protection Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').behaviour;
|
||||
|
||||
let formProps: BehaviourProtectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<BehaviourProtectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render the card with expected components', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId(testSubj.enableDisableSwitch));
|
||||
expect(getByTestId(testSubj.protectionPreventRadio));
|
||||
expect(getByTestId(testSubj.notifyUserCheckbox));
|
||||
expect(getByTestId(testSubj.rulesCallout));
|
||||
});
|
||||
|
||||
it('should show supported OS values', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId(testSubj.osValuesContainer)).toHaveTextContent('Windows, Mac, Linux');
|
||||
});
|
||||
|
||||
describe('and license is lower than Platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should show locked card if license not platinum+', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.lockedCardTitle)).toHaveTextContent(
|
||||
LOCKED_CARD_BEHAVIOR_TITLE
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is enabled', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
'Type' +
|
||||
'Malicious behavior' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malicious behavior protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is disabled', () => {
|
||||
set(formProps.policy, 'windows.behavior_protection.mode', ProtectionModes.off);
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Malicious behavior' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malicious behavior protections disabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display user notification disabled', () => {
|
||||
set(formProps.policy, 'windows.popup.behavior_protection.enabled', false);
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Malicious behavior' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malicious behavior protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
"Don't notify user" +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,7 @@
|
|||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
|
@ -18,13 +17,12 @@ import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
|||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { BehaviorProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
|
||||
const LOCKED_CARD_BEHAVIOR_TITLE = i18n.translate(
|
||||
export const LOCKED_CARD_BEHAVIOR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.behavior',
|
||||
{
|
||||
defaultMessage: 'Malicious Behavior',
|
||||
|
@ -37,7 +35,7 @@ const BEHAVIOUR_OS_VALUES: Immutable<BehaviorProtectionOSes[]> = [
|
|||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
|
||||
type BehaviourProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type BehaviourProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
|
@ -52,7 +50,12 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_BEHAVIOR_TITLE} />;
|
||||
return (
|
||||
<SettingLockedCard
|
||||
title={LOCKED_CARD_BEHAVIOR_TITLE}
|
||||
data-test-subj={getTestId('locked')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -70,7 +73,7 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
protection={protection}
|
||||
protectionLabel={protectionLabel}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
data-test-subj={getTestId()}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
@ -80,6 +83,7 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
mode={mode}
|
||||
protection={protection}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
data-test-subj={getTestId('protectionLevel')}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
|
@ -88,25 +92,11 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
mode={mode}
|
||||
protection={protection}
|
||||
osList={BEHAVIOUR_OS_VALUES}
|
||||
data-test-subj={getTestId('notifyUser')}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<RelatedDetectionRulesCallout data-test-subj={getTestId('rulesCallout')} />
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type { LinuxEventCollectionCardProps } from './linux_event_collection_card';
|
||||
import { LinuxEventCollectionCard } from './linux_event_collection_card';
|
||||
import { set } from 'lodash';
|
||||
|
||||
describe('Policy Linux Event Collection Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').linuxEvents;
|
||||
|
||||
let formProps: LinuxEventCollectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<LinuxEventCollectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render card with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(
|
||||
getByTestId(testSubj.optionsContainer).querySelectorAll('input[type="checkbox"]')
|
||||
).toHaveLength(3);
|
||||
expect(getByTestId(testSubj.fileCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.networkCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.processCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.osValueContainer)).toHaveTextContent(exactMatchText('Linux'));
|
||||
expect(getByTestId(testSubj.sessionDataCheckbox)).not.toBeChecked();
|
||||
expect(getByTestId(testSubj.captureTerminalCheckbox)).toBeDisabled();
|
||||
});
|
||||
|
||||
describe('and is displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render card with expected content when session data collection is disabled', () => {
|
||||
render();
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Linux ' +
|
||||
'3 / 3 event collections enabled' +
|
||||
'Events' +
|
||||
'File' +
|
||||
'Network' +
|
||||
'Process'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should render card with expected content when session data collection is enabled', () => {
|
||||
set(formProps.policy, 'linux.events.session_data', true);
|
||||
set(formProps.policy, 'linux.events.tty_io', true);
|
||||
set(formProps.policy, 'linux.events.file', false);
|
||||
render();
|
||||
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Linux ' +
|
||||
'2 / 3 event collections enabled' +
|
||||
'Events' +
|
||||
'Network' +
|
||||
'Process' +
|
||||
'Session data' +
|
||||
'Collect session data' +
|
||||
'Capture terminal output' +
|
||||
'Info' +
|
||||
'beta'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,8 +10,8 @@ import { OperatingSystem } from '@kbn/securitysolution-utils';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import type { UIPolicyConfig } from '../../../../../../../../common/endpoint/types';
|
||||
import type { EventFormOption, SupplementalEventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import type { EventFormOption, SupplementalEventFormOption } from '../event_collection_card';
|
||||
import { EventCollectionCard } from '../event_collection_card';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.LINUX>> = [
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingS
|
|||
},
|
||||
];
|
||||
|
||||
type LinuxEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type LinuxEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const LinuxEventCollectionCard = memo<LinuxEventCollectionCardProps>((props) => {
|
||||
const supplementalOptions = useMemo(() => {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { set } from 'lodash';
|
||||
import type { MacEventCollectionCardProps } from './mac_event_collection_card';
|
||||
import { MacEventCollectionCard } from './mac_event_collection_card';
|
||||
|
||||
describe('Policy Mac Event Collection Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').macEvents;
|
||||
|
||||
let formProps: MacEventCollectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () => (renderResult = mockedContext.render(<MacEventCollectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render card with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(
|
||||
getByTestId(testSubj.optionsContainer).querySelectorAll('input[type="checkbox"]')
|
||||
).toHaveLength(3);
|
||||
expect(getByTestId(testSubj.fileCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.networkCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.processCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.osValueContainer)).toHaveTextContent(exactMatchText('Mac'));
|
||||
});
|
||||
|
||||
describe('and is displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render card with expected content when session data collection is disabled', () => {
|
||||
render();
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Mac ' +
|
||||
'3 / 3 event collections enabled' +
|
||||
'Events' +
|
||||
'File' +
|
||||
'Process' +
|
||||
'Network'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should render card with expected content when certain events are un-checked', () => {
|
||||
set(formProps.policy, 'mac.events.file', false);
|
||||
render();
|
||||
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Mac ' +
|
||||
'2 / 3 event collections enabled' +
|
||||
'Events' +
|
||||
'Process' +
|
||||
'Network'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,8 @@
|
|||
import React, { memo } from 'react';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { EventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import type { EventFormOption } from '../event_collection_card';
|
||||
import { EventCollectionCard } from '../event_collection_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [
|
||||
|
@ -33,7 +33,7 @@ const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [
|
|||
},
|
||||
];
|
||||
|
||||
type MacEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type MacEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const MacEventCollectionCard = memo<MacEventCollectionCardProps>((props) => {
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
expectIsViewOnly,
|
||||
getPolicySettingsFormTestSubjects,
|
||||
exactMatchText,
|
||||
setMalwareMode,
|
||||
} from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type { MalwareProtectionsProps } from './malware_protections_card';
|
||||
import { MalwareProtectionsCard } from './malware_protections_card';
|
||||
import { ProtectionModes } from '../../../../../../../../common/endpoint/types';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('../../../../../../../common/hooks/use_license');
|
||||
|
||||
describe('Policy Malware Protections Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').malware;
|
||||
|
||||
let formProps: MalwareProtectionsProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () => (renderResult = mockedContext.render(<MalwareProtectionsCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render the card with expected components', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId(testSubj.enableDisableSwitch));
|
||||
expect(getByTestId(testSubj.protectionPreventRadio));
|
||||
expect(getByTestId(testSubj.notifyUserCheckbox));
|
||||
expect(getByTestId(testSubj.rulesCallout));
|
||||
});
|
||||
|
||||
it('should show supported OS values', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.osValuesContainer)).toHaveTextContent(
|
||||
'Windows, Mac, Linux'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set Blocklist to disabled if malware is turned off', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
setMalwareMode(expectedUpdatedPolicy, true);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId(testSubj.enableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow blocklist to be disabled', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.malware.blocklist', false);
|
||||
set(expectedUpdatedPolicy, 'mac.malware.blocklist', false);
|
||||
set(expectedUpdatedPolicy, 'linux.malware.blocklist', false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId(testSubj.blocklistEnableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow blocklist to be enabled', () => {
|
||||
set(formProps.policy, 'windows.malware.blocklist', false);
|
||||
set(formProps.policy, 'mac.malware.blocklist', false);
|
||||
set(formProps.policy, 'linux.malware.blocklist', false);
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.malware.blocklist', true);
|
||||
set(expectedUpdatedPolicy, 'mac.malware.blocklist', true);
|
||||
set(expectedUpdatedPolicy, 'linux.malware.blocklist', true);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId(testSubj.blocklistEnableDisableSwitch));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is enabled', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Malware' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malware protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'Blocklist enabled' +
|
||||
'Info' +
|
||||
'User notification' +
|
||||
'Agent version 7.11+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is disabled', () => {
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.off);
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Malware' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malware protections disabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'Blocklist enabled' +
|
||||
'Info' +
|
||||
'User notification' +
|
||||
'Agent version 7.11+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display user notification disabled', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Malware' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Malware protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'Blocklist enabled' +
|
||||
'Info' +
|
||||
'User notification' +
|
||||
'Agent version 7.11+' +
|
||||
"Don't notify user" +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,25 +8,16 @@
|
|||
import React, { memo, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { APP_UI_ID } from '../../../../../../../../common';
|
||||
import { SecurityPageName } from '../../../../../../../app/types';
|
||||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { MalwareProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import type { ProtectionSettingCardSwitchProps } from '../protection_setting_card_switch';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
|
@ -112,7 +103,7 @@ export const MalwareProtectionsCard = React.memo<MalwareProtectionsProps>(
|
|||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId('enableDisableBlocklist')}
|
||||
data-test-subj={getTestId('blocklist')}
|
||||
/>
|
||||
|
||||
<NotifyUserOption
|
||||
|
@ -125,22 +116,7 @@ export const MalwareProtectionsCard = React.memo<MalwareProtectionsProps>(
|
|||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<RelatedDetectionRulesCallout data-test-subj={getTestId('rulesCallout')} />
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
|
@ -150,56 +126,60 @@ MalwareProtectionsCard.displayName = 'MalwareProtectionsCard';
|
|||
|
||||
type EnableDisableBlocklistProps = PolicyFormComponentCommonProps;
|
||||
|
||||
const EnableDisableBlocklist = memo<EnableDisableBlocklistProps>(({ policy, onChange, mode }) => {
|
||||
const checked = policy.windows.malware.blocklist;
|
||||
const isDisabled = policy.windows.malware.mode === 'off';
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = checked ? BLOCKLIST_ENABLED_LABEL : BLOCKLIST_DISABLED_LABEL;
|
||||
const EnableDisableBlocklist = memo<EnableDisableBlocklistProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const checked = policy.windows.malware.blocklist;
|
||||
const isDisabled = policy.windows.malware.mode === 'off';
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = checked ? BLOCKLIST_ENABLED_LABEL : BLOCKLIST_DISABLED_LABEL;
|
||||
|
||||
const handleBlocklistSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const value = event.target.checked;
|
||||
const newPayload = cloneDeep(policy);
|
||||
const handleBlocklistSwitchChange = useCallback(
|
||||
(event) => {
|
||||
const value = event.target.checked;
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
adjustBlocklistSettingsOnProtectionSwitch({
|
||||
value,
|
||||
policyConfigData: newPayload,
|
||||
protectionOsList: MALWARE_OS_VALUES,
|
||||
});
|
||||
adjustBlocklistSettingsOnProtectionSwitch({
|
||||
value,
|
||||
policyConfigData: newPayload,
|
||||
protectionOsList: MALWARE_OS_VALUES,
|
||||
});
|
||||
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
onChange({ isValid: true, updatedPolicy: newPayload });
|
||||
},
|
||||
[onChange, policy]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
{isEditMode ? (
|
||||
<EuiSwitch
|
||||
label={label}
|
||||
checked={checked}
|
||||
onChange={handleBlocklistSwitchChange}
|
||||
disabled={isDisabled}
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" data-test-subj={getTestId()}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{isEditMode ? (
|
||||
<EuiSwitch
|
||||
label={label}
|
||||
checked={checked}
|
||||
onChange={handleBlocklistSwitchChange}
|
||||
disabled={isDisabled}
|
||||
data-test-subj={getTestId('enableDisableSwitch')}
|
||||
/>
|
||||
) : (
|
||||
<>{label}</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip"
|
||||
defaultMessage="Enables or disables the blocklist associated with this policy. The blocklist is a collection hashes, paths, or signers which extends the list of processes the endpoint considers malicious. See the blocklist tab for entry details."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>{label}</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.blocklistTooltip"
|
||||
defaultMessage="Enables or disables the blocklist associated with this policy. The blocklist is a collection hashes, paths, or signers which extends the list of processes the endpoint considers malicious. See the blocklist tab for entry details."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
EnableDisableBlocklist.displayName = 'EnableDisableBlocklist';
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { ProtectionModes } from '../../../../../../../../common/endpoint/types';
|
||||
import { set } from 'lodash';
|
||||
import type { MemoryProtectionCardProps } from './memory_protection_card';
|
||||
import { LOCKED_CARD_MEMORY_TITLE, MemoryProtectionCard } from './memory_protection_card';
|
||||
import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license';
|
||||
import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
|
||||
jest.mock('../../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy Memory Protections Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').memory;
|
||||
|
||||
let formProps: MemoryProtectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () => (renderResult = mockedContext.render(<MemoryProtectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render the card with expected components', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId(testSubj.enableDisableSwitch));
|
||||
expect(getByTestId(testSubj.protectionPreventRadio));
|
||||
expect(getByTestId(testSubj.notifyUserCheckbox));
|
||||
expect(getByTestId(testSubj.rulesCallout));
|
||||
});
|
||||
|
||||
it('should show supported OS values', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.osValuesContainer)).toHaveTextContent(
|
||||
'Windows, Mac, Linux'
|
||||
);
|
||||
});
|
||||
|
||||
describe('and license is lower than Platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should show locked card if license not platinum+', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.lockedCardTitle)).toHaveTextContent(
|
||||
exactMatchText(LOCKED_CARD_MEMORY_TITLE)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is enabled', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Memory threat' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Memory threat protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is disabled', () => {
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.off);
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Memory threat' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Memory threat protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display user notification disabled', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Memory threat' +
|
||||
'Operating system' +
|
||||
'Windows, Mac, Linux ' +
|
||||
'Memory threat protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.15+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,7 @@
|
|||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
|
@ -18,13 +17,12 @@ import { SettingLockedCard } from '../setting_locked_card';
|
|||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { MemoryProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
|
||||
const LOCKED_CARD_MEMORY_TITLE = i18n.translate(
|
||||
export const LOCKED_CARD_MEMORY_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.memory',
|
||||
{
|
||||
defaultMessage: 'Memory Threat',
|
||||
|
@ -37,7 +35,7 @@ const MEMORY_PROTECTION_OS_VALUES: Immutable<MemoryProtectionOSes[]> = [
|
|||
PolicyOperatingSystem.linux,
|
||||
];
|
||||
|
||||
type MemoryProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type MemoryProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
|
@ -52,7 +50,9 @@ export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
|||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_MEMORY_TITLE} />;
|
||||
return (
|
||||
<SettingLockedCard title={LOCKED_CARD_MEMORY_TITLE} data-test-subj={getTestId('locked')} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -93,22 +93,7 @@ export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
|||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<RelatedDetectionRulesCallout data-test-subj={getTestId('rulesCallout')} />
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { ProtectionModes } from '../../../../../../../../common/endpoint/types';
|
||||
import { set } from 'lodash';
|
||||
import { createLicenseServiceMock } from '../../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../../common/hooks/__mocks__/use_license';
|
||||
import { useLicense as _useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import type { RansomwareProtectionCardProps } from './ransomware_protection_card';
|
||||
import {
|
||||
LOCKED_CARD_RAMSOMWARE_TITLE,
|
||||
RansomwareProtectionCard,
|
||||
} from './ransomware_protection_card';
|
||||
|
||||
jest.mock('../../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy Ransomware Protections Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').ransomware;
|
||||
|
||||
let formProps: RansomwareProtectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<RansomwareProtectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render the card with expected components', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId(testSubj.enableDisableSwitch));
|
||||
expect(getByTestId(testSubj.protectionPreventRadio));
|
||||
expect(getByTestId(testSubj.notifyUserCheckbox));
|
||||
expect(getByTestId(testSubj.rulesCallout));
|
||||
});
|
||||
|
||||
it('should show supported OS values', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.osValuesContainer)).toHaveTextContent(
|
||||
exactMatchText('Windows')
|
||||
);
|
||||
});
|
||||
|
||||
describe('and license is lower than Platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should show locked card if license not platinum+', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubj.lockedCardTitle)).toHaveTextContent(
|
||||
exactMatchText(LOCKED_CARD_RAMSOMWARE_TITLE)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is enabled', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Ransomware' +
|
||||
'Operating system' +
|
||||
'Windows ' +
|
||||
'Ransomware protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.12+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display correctly when overall card is disabled', () => {
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.off);
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Ransomware' +
|
||||
'Operating system' +
|
||||
'Windows ' +
|
||||
'Ransomware protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.12+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display user notification disabled', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expectIsViewOnly(getByTestId(testSubj.card));
|
||||
|
||||
expect(getByTestId(testSubj.card)).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Ransomware' +
|
||||
'Operating system' +
|
||||
'Windows ' +
|
||||
'Ransomware protections enabled' +
|
||||
'Protection level' +
|
||||
'Prevent' +
|
||||
'User notification' +
|
||||
'Agent version 7.12+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'—' +
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { ProtectionSettingCardSwitch } from '../protection_setting_card_switch';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
|
@ -19,23 +18,22 @@ import type { PolicyFormComponentCommonProps } from '../../types';
|
|||
import type { Immutable } from '../../../../../../../../common/endpoint/types';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { RansomwareProtectionOSes } from '../../../../types';
|
||||
import { LinkToApp } from '../../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../../common';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
|
||||
const RANSOMEWARE_OS_VALUES: Immutable<RansomwareProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
];
|
||||
|
||||
const LOCKED_CARD_RAMSOMWARE_TITLE = i18n.translate(
|
||||
export const LOCKED_CARD_RAMSOMWARE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.ransomware',
|
||||
{
|
||||
defaultMessage: 'Ransomware',
|
||||
}
|
||||
);
|
||||
|
||||
type RansomwareProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type RansomwareProtectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
|
@ -50,7 +48,12 @@ export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps
|
|||
);
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return <SettingLockedCard title={LOCKED_CARD_RAMSOMWARE_TITLE} />;
|
||||
return (
|
||||
<SettingLockedCard
|
||||
title={LOCKED_CARD_RAMSOMWARE_TITLE}
|
||||
data-test-subj={getTestId('locked')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -91,22 +94,7 @@ export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps
|
|||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCallOut iconType="iInCircle">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp appId={APP_UI_ID} deepLinkId={SecurityPageName.rules}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<RelatedDetectionRulesCallout data-test-subj={getTestId('rulesCallout')} />
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects, exactMatchText } from '../../mocks';
|
||||
import type { AppContextTestRender } from '../../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { set } from 'lodash';
|
||||
import type { WindowsEventCollectionCardProps } from './windows_event_collection_card';
|
||||
import { WindowsEventCollectionCard } from './windows_event_collection_card';
|
||||
|
||||
describe('Policy Windows Event Collection Card', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test').windowsEvents;
|
||||
|
||||
let formProps: WindowsEventCollectionCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': testSubj.card,
|
||||
};
|
||||
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<WindowsEventCollectionCard {...formProps} />));
|
||||
});
|
||||
|
||||
it('should render card with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(
|
||||
getByTestId(testSubj.optionsContainer).querySelectorAll('input[type="checkbox"]')
|
||||
).toHaveLength(8);
|
||||
expect(getByTestId(testSubj.credentialsCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.dllCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.dnsCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.fileCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.networkCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.processCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.registryCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.securityCheckbox)).toBeChecked();
|
||||
expect(getByTestId(testSubj.osValueContainer)).toHaveTextContent(exactMatchText('Windows'));
|
||||
});
|
||||
|
||||
describe('and is displayed in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render card with expected content when session data collection is disabled', () => {
|
||||
render();
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Windows 8 / 8 event collections enabled' +
|
||||
'Events' +
|
||||
'Credential Access' +
|
||||
'DLL and Driver Load' +
|
||||
'DNS' +
|
||||
'File' +
|
||||
'Network' +
|
||||
'Process' +
|
||||
'Registry' +
|
||||
'Security'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should render card with expected content when some events are un-checked', () => {
|
||||
set(formProps.policy, 'windows.events.file', false);
|
||||
set(formProps.policy, 'windows.events.dns', false);
|
||||
render();
|
||||
|
||||
const card = renderResult.getByTestId(testSubj.card);
|
||||
|
||||
expectIsViewOnly(card);
|
||||
expect(card).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Type' +
|
||||
'Event collection' +
|
||||
'Operating system' +
|
||||
'Windows ' +
|
||||
'6 / 8 event collections enabled' +
|
||||
'Events' +
|
||||
'Credential Access' +
|
||||
'DLL and Driver Load' +
|
||||
'Network' +
|
||||
'Process' +
|
||||
'Registry' +
|
||||
'Security'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,8 @@
|
|||
import React, { memo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import type { EventFormOption } from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import type { EventFormOption } from '../event_collection_card';
|
||||
import { EventCollectionCard } from '../event_collection_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
||||
const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [
|
||||
|
@ -84,7 +84,7 @@ const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [
|
|||
},
|
||||
];
|
||||
|
||||
type WindowsEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
export type WindowsEventCollectionCardProps = PolicyFormComponentCommonProps;
|
||||
|
||||
export const WindowsEventCollectionCard = memo<WindowsEventCollectionCardProps>((props) => {
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type { DetectPreventProtectionLevelProps } from './detect_prevent_protection_level';
|
||||
import { DetectPreventProtectionLevel } from './detect_prevent_protection_level';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import { expectIsViewOnly, exactMatchText } from '../mocks';
|
||||
import { createLicenseServiceMock } from '../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../common/hooks/__mocks__/use_license';
|
||||
import { useLicense as _useLicense } from '../../../../../../common/hooks/use_license';
|
||||
|
||||
jest.mock('../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy form Detect Prevent Protection level component', () => {
|
||||
let formProps: DetectPreventProtectionLevelProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
const clickProtection = (level: 'detect' | 'prevent') => {
|
||||
userEvent.click(renderResult.getByTestId(`test-${level}Radio`).querySelector('label')!);
|
||||
};
|
||||
|
||||
const isProtectionChecked = (level: 'detect' | 'prevent'): boolean => {
|
||||
return renderResult.getByTestId(`test-${level}Radio`)!.querySelector('input')!.checked ?? false;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': 'test',
|
||||
protection: 'malware',
|
||||
osList: ['windows', 'mac', 'linux'],
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<DetectPreventProtectionLevel {...formProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render expected options', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-detectRadio'));
|
||||
expect(getByTestId('test-preventRadio'));
|
||||
});
|
||||
|
||||
it('should allow detect mode to be selected', () => {
|
||||
const expectedPolicyUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedPolicyUpdate, 'windows.malware.mode', ProtectionModes.detect);
|
||||
set(expectedPolicyUpdate, 'mac.malware.mode', ProtectionModes.detect);
|
||||
set(expectedPolicyUpdate, 'linux.malware.mode', ProtectionModes.detect);
|
||||
set(expectedPolicyUpdate, 'windows.popup.malware.enabled', false);
|
||||
set(expectedPolicyUpdate, 'mac.popup.malware.enabled', false);
|
||||
set(expectedPolicyUpdate, 'linux.popup.malware.enabled', false);
|
||||
render();
|
||||
|
||||
expect(isProtectionChecked('prevent')).toBe(true);
|
||||
|
||||
clickProtection('detect');
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedPolicyUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow prevent mode to be selected', () => {
|
||||
formProps.osList = ['windows'];
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.detect);
|
||||
const expectedPolicyUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedPolicyUpdate, 'windows.malware.mode', ProtectionModes.prevent);
|
||||
render();
|
||||
|
||||
expect(isProtectionChecked('detect')).toBe(true);
|
||||
|
||||
clickProtection('prevent');
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedPolicyUpdate,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and license is lower than platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should NOT update user notification options', () => {
|
||||
const expectedPolicyUpdate = cloneDeep(formProps.policy);
|
||||
set(expectedPolicyUpdate, 'windows.malware.mode', ProtectionModes.detect);
|
||||
set(expectedPolicyUpdate, 'mac.malware.mode', ProtectionModes.detect);
|
||||
set(expectedPolicyUpdate, 'linux.malware.mode', ProtectionModes.detect);
|
||||
render();
|
||||
|
||||
expect(isProtectionChecked('prevent')).toBe(true);
|
||||
|
||||
clickProtection('detect');
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedPolicyUpdate,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and rendered in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should display prevent', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText('Protection levelPrevent')
|
||||
);
|
||||
});
|
||||
|
||||
it('should display detect', () => {
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.detect);
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText('Protection levelDetect')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,7 +8,7 @@
|
|||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { EuiFlexItemProps } from '@elastic/eui';
|
||||
import { EuiRadio, EuiSpacer, EuiFlexGroup, EuiFlexItem, useGeneratedHtmlId } from '@elastic/eui';
|
||||
import { EuiRadio, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
|
@ -31,12 +31,12 @@ const PREVENT_LABEL = i18n.translate('xpack.securitySolution.endpoint.policy.det
|
|||
defaultMessage: 'Prevent',
|
||||
});
|
||||
|
||||
export type DetectPreventProtectionLavelProps = PolicyFormComponentCommonProps & {
|
||||
export type DetectPreventProtectionLevelProps = PolicyFormComponentCommonProps & {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
};
|
||||
|
||||
export const DetectPreventProtectionLevel = memo<DetectPreventProtectionLavelProps>(
|
||||
export const DetectPreventProtectionLevel = memo<DetectPreventProtectionLevelProps>(
|
||||
({ policy, protection, osList, mode, onChange, 'data-test-subj': dataTestSubj }) => {
|
||||
const isEditMode = mode === 'edit';
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
@ -127,11 +127,14 @@ const ProtectionRadio = React.memo(
|
|||
mode,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: ProtectionRadioProps) => {
|
||||
const radioButtonId = useGeneratedHtmlId();
|
||||
const selected = policy.windows[protection].mode;
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const showEditableFormFields = mode === 'edit';
|
||||
|
||||
const radioId = useMemo(() => {
|
||||
return `${osList.join('-')}-${protection}-${protectionMode}`;
|
||||
}, [osList, protection, protectionMode]);
|
||||
|
||||
const handleRadioChange = useCallback(() => {
|
||||
const newPayload = cloneDeep(policy);
|
||||
|
||||
|
@ -172,7 +175,7 @@ const ProtectionRadio = React.memo(
|
|||
return (
|
||||
<EuiRadio
|
||||
label={label}
|
||||
id={radioButtonId}
|
||||
id={radioId}
|
||||
checked={selected === protectionMode}
|
||||
onChange={handleRadioChange}
|
||||
disabled={!showEditableFormFields || selected === ProtectionModes.off}
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import type {
|
||||
EventCollectionCardProps,
|
||||
SupplementalEventFormOption,
|
||||
} from './event_collection_card';
|
||||
import { EventCollectionCard } from './event_collection_card';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { expectIsViewOnly, exactMatchText } from '../mocks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import { within } from '@testing-library/dom';
|
||||
|
||||
describe('Policy Event Collection Card common component', () => {
|
||||
let formProps: EventCollectionCardProps<OperatingSystem.WINDOWS>;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
const isChecked = (selector: string): boolean => {
|
||||
return (renderResult.getByTestId(selector) as HTMLInputElement).checked;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
const policy = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value;
|
||||
|
||||
formProps = {
|
||||
policy,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': 'test',
|
||||
os: OperatingSystem.WINDOWS,
|
||||
selection: {
|
||||
file: policy.windows.events.file,
|
||||
network: policy.windows.events.network,
|
||||
// For testing purposes, limit the number of events to only 2
|
||||
} as typeof policy.windows.events,
|
||||
options: [
|
||||
{
|
||||
name: 'File',
|
||||
protectionField: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
protectionField: 'network',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(
|
||||
<EventCollectionCard<OperatingSystem.WINDOWS> {...formProps} />
|
||||
);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render card with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('2 / 2 event collections enabled')
|
||||
);
|
||||
expect(getByTestId('test-osValues')).toHaveTextContent(exactMatchText('Windows'));
|
||||
expect(isChecked('test-file')).toBe(true);
|
||||
expect(isChecked('test-network')).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow items to be unchecked', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.events.file', false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test-file'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow items to be checked', () => {
|
||||
set(formProps.policy, 'windows.events.file', false);
|
||||
formProps.selection.file = false;
|
||||
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.events.file', true);
|
||||
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('1 / 2 event collections enabled')
|
||||
);
|
||||
expect(isChecked('test-file')).toBe(false);
|
||||
|
||||
userEvent.click(getByTestId('test-file'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and supplementalOptions are used', () => {
|
||||
let supplementalEntry: SupplementalEventFormOption<OperatingSystem.WINDOWS>;
|
||||
|
||||
beforeEach(() => {
|
||||
formProps.selection.dns = true;
|
||||
supplementalEntry = {
|
||||
protectionField: 'dns',
|
||||
name: 'Collect DNS',
|
||||
id: 'dns',
|
||||
title: 'DNS collection',
|
||||
uncheckedName: 'Do not collect DNS',
|
||||
description: 'This collects info about DNS',
|
||||
tooltipText: 'This is a tooltip',
|
||||
};
|
||||
formProps.supplementalOptions = [supplementalEntry];
|
||||
});
|
||||
|
||||
it('should render supplemental option', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('2 / 2 event collections enabled')
|
||||
);
|
||||
expect(isChecked('test-dns')).toBe(true);
|
||||
|
||||
const optionContainer = within(getByTestId('test-dnsContainer'));
|
||||
|
||||
expect(optionContainer.getByTestId('test-dnsTitle')).toHaveTextContent(
|
||||
exactMatchText(supplementalEntry.title!)
|
||||
);
|
||||
expect(optionContainer.getByTestId('test-dnsDescription')).toHaveTextContent(
|
||||
exactMatchText(supplementalEntry.description!)
|
||||
);
|
||||
expect(optionContainer.getAllByLabelText(supplementalEntry.name));
|
||||
expect(optionContainer.getByTestId('test-dnsTooltipIcon'));
|
||||
});
|
||||
|
||||
it('should render with minimum set of options defined', () => {
|
||||
supplementalEntry = {
|
||||
name: supplementalEntry.name,
|
||||
protectionField: supplementalEntry.protectionField,
|
||||
};
|
||||
formProps.supplementalOptions = [supplementalEntry];
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-dnsContainer')).toHaveTextContent(
|
||||
exactMatchText(supplementalEntry.name)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include BETA badge', () => {
|
||||
supplementalEntry.beta = true;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-dnsBadge')).toHaveTextContent(exactMatchText('beta'));
|
||||
});
|
||||
|
||||
it('should indent entry', () => {
|
||||
supplementalEntry.indented = true;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-dnsContainer').getAttribute('style')).toMatch(
|
||||
/padding-left/
|
||||
);
|
||||
});
|
||||
|
||||
it('should should render it disabled', () => {
|
||||
supplementalEntry.isDisabled = () => true;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-dns')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and when rendered in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should render with expected content', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
expect(renderResult.getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('2 / 2 event collections enabled')
|
||||
);
|
||||
expect(renderResult.getByTestId('test-options')).toHaveTextContent(
|
||||
exactMatchText('FileNetwork')
|
||||
);
|
||||
});
|
||||
|
||||
it('should only display events that were checked', () => {
|
||||
set(formProps.policy, 'windows.events.file', false);
|
||||
formProps.selection.file = false;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('1 / 2 event collections enabled')
|
||||
);
|
||||
expect(renderResult.getByTestId('test-options')).toHaveTextContent(exactMatchText('Network'));
|
||||
});
|
||||
|
||||
it('should show empty value if no events are selected', () => {
|
||||
set(formProps.policy, 'windows.events.file', false);
|
||||
set(formProps.policy, 'windows.events.network', false);
|
||||
formProps.selection.file = false;
|
||||
formProps.selection.network = false;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-selectedCount')).toHaveTextContent(
|
||||
exactMatchText('0 / 2 event collections enabled')
|
||||
);
|
||||
expect(renderResult.getByTestId('test-options')).toHaveTextContent(exactMatchText('—'));
|
||||
});
|
||||
|
||||
describe('and supplemental options are used', () => {
|
||||
let supplementalEntry: SupplementalEventFormOption<OperatingSystem.WINDOWS>;
|
||||
|
||||
beforeEach(() => {
|
||||
formProps.selection.dns = true;
|
||||
supplementalEntry = {
|
||||
protectionField: 'dns',
|
||||
name: 'Collect DNS',
|
||||
id: 'dns',
|
||||
title: 'DNS collection',
|
||||
uncheckedName: 'Do not collect DNS',
|
||||
description: 'This collects info about DNS',
|
||||
tooltipText: 'This is a tooltip',
|
||||
};
|
||||
formProps.supplementalOptions = [supplementalEntry];
|
||||
});
|
||||
|
||||
it('should render expected content when option is checked', () => {
|
||||
render();
|
||||
const dnsOption = renderResult.getByTestId('test-dnsContainer');
|
||||
|
||||
expectIsViewOnly(dnsOption);
|
||||
expect(dnsOption).toHaveTextContent(
|
||||
exactMatchText('DNS collectionThis collects info about DNSCollect DNSInfo')
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render option if un-checked', () => {
|
||||
formProps.policy.windows.events.dns = false;
|
||||
formProps.selection.dns = false;
|
||||
render();
|
||||
|
||||
expect(renderResult.queryByTestId('test-dnsContainer')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,16 +18,15 @@ import {
|
|||
EuiIconTip,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { cloneDeep, get, set } from 'lodash';
|
||||
import type { EuiCheckboxProps } from '@elastic/eui/src/components/form/checkbox/checkbox';
|
||||
import { getEmptyValue } from '../../../../../../../common/components/empty_value';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
import { SettingCard, SettingCardHeader } from '../setting_card';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../../common/endpoint/types';
|
||||
import type { UIPolicyConfig } from '../../../../../../../../common/endpoint/types';
|
||||
import { getEmptyValue } from '../../../../../../common/components/empty_value';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import { SettingCard, SettingCardHeader } from './setting_card';
|
||||
import { PolicyOperatingSystem } from '../../../../../../../common/endpoint/types';
|
||||
import type { UIPolicyConfig } from '../../../../../../../common/endpoint/types';
|
||||
|
||||
const mapOperatingSystemToPolicyOsKey = {
|
||||
[OperatingSystem.WINDOWS]: PolicyOperatingSystem.windows,
|
||||
|
@ -48,15 +47,15 @@ export interface EventFormOption<T extends OperatingSystem> {
|
|||
}
|
||||
|
||||
export interface SupplementalEventFormOption<T extends OperatingSystem> {
|
||||
name: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
indented?: boolean;
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
name: string;
|
||||
uncheckedName?: string;
|
||||
protectionField: ProtectionField<T>;
|
||||
tooltipText?: string;
|
||||
beta?: boolean;
|
||||
indented?: boolean;
|
||||
isDisabled?(policyConfig: UIPolicyConfig): boolean;
|
||||
}
|
||||
|
||||
|
@ -112,7 +111,7 @@ export const EventCollectionCard = memo(
|
|||
})}
|
||||
supportedOss={[os]}
|
||||
rightCorner={
|
||||
<EuiText size="s" color="subdued">
|
||||
<EuiText size="s" color="subdued" data-test-subj={getTestId('selectedCount')}>
|
||||
{i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled',
|
||||
{
|
||||
|
@ -134,23 +133,25 @@ export const EventCollectionCard = memo(
|
|||
</SettingCardHeader>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{options.map(({ name, protectionField }) => {
|
||||
const keyPath = `${policyOs}.events.${protectionField}`;
|
||||
<div data-test-subj={getTestId('options')}>
|
||||
{options.map(({ name, protectionField }) => {
|
||||
const keyPath = `${policyOs}.events.${protectionField}`;
|
||||
|
||||
return (
|
||||
<EventCheckbox
|
||||
label={name}
|
||||
key={keyPath}
|
||||
keyPath={keyPath}
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId(protectionField as string)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<EventCheckbox
|
||||
label={name}
|
||||
key={keyPath}
|
||||
keyPath={keyPath}
|
||||
policy={policy}
|
||||
onChange={onChange}
|
||||
mode={mode}
|
||||
data-test-subj={getTestId(protectionField as string)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{selectedCount === 0 && !isEditMode && <div>{getEmptyValue()}</div>}
|
||||
{selectedCount === 0 && !isEditMode && <div>{getEmptyValue()}</div>}
|
||||
</div>
|
||||
|
||||
{supplementalOptions &&
|
||||
supplementalOptions.map(
|
||||
|
@ -167,6 +168,7 @@ export const EventCollectionCard = memo(
|
|||
}) => {
|
||||
const keyPath = `${policyOs}.events.${protectionField}`;
|
||||
const isChecked = get(policy, keyPath);
|
||||
const fieldString = protectionField as string;
|
||||
|
||||
if (!isEditMode && !isChecked) {
|
||||
return null;
|
||||
|
@ -176,18 +178,25 @@ export const EventCollectionCard = memo(
|
|||
<div
|
||||
key={String(protectionField)}
|
||||
style={indented ? { paddingLeft: theme.eui.euiSizeL } : {}}
|
||||
data-test-subj={getTestId(`${fieldString}Container`)}
|
||||
>
|
||||
{title && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<SettingCardHeader>{title}</SettingCardHeader>
|
||||
<SettingCardHeader data-test-subj={getTestId(`${fieldString}Title`)}>
|
||||
{title}
|
||||
</SettingCardHeader>
|
||||
</>
|
||||
)}
|
||||
|
||||
{description && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<EuiText
|
||||
size="xs"
|
||||
color="subdued"
|
||||
data-test-subj={getTestId(`${fieldString}Description`)}
|
||||
>
|
||||
{description}
|
||||
</EuiText>
|
||||
</>
|
||||
|
@ -206,19 +215,27 @@ export const EventCollectionCard = memo(
|
|||
onChange={onChange}
|
||||
mode={mode}
|
||||
disabled={isDisabled ? isDisabled(policy) : false}
|
||||
data-test-subj={getTestId(protectionField as string)}
|
||||
data-test-subj={getTestId(fieldString)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{tooltipText && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip position="right" content={tooltipText} />
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={tooltipText}
|
||||
anchorProps={{ 'data-test-subj': getTestId(`${fieldString}TooltipIcon`) }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{beta && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge label="beta" size="s" />
|
||||
<EuiBetaBadge
|
||||
label="beta"
|
||||
size="s"
|
||||
data-test-subj={getTestId(`${fieldString}Badge`)}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
@ -250,7 +267,6 @@ const EventCheckbox = memo<EventCheckboxProps>(
|
|||
disabled,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const checkboxId = useGeneratedHtmlId();
|
||||
const isChecked: boolean = get(policy, keyPath);
|
||||
const isEditMode = mode === 'edit';
|
||||
const displayLabel = isChecked ? label : unCheckedLabel ? unCheckedLabel : label;
|
||||
|
@ -268,7 +284,7 @@ const EventCheckbox = memo<EventCheckboxProps>(
|
|||
return isEditMode ? (
|
||||
<EuiCheckbox
|
||||
key={keyPath}
|
||||
id={checkboxId}
|
||||
id={keyPath}
|
||||
label={displayLabel}
|
||||
data-test-subj={dataTestSubj}
|
||||
checked={isChecked}
|
||||
|
@ -276,7 +292,7 @@ const EventCheckbox = memo<EventCheckboxProps>(
|
|||
disabled={disabled}
|
||||
/>
|
||||
) : isChecked ? (
|
||||
<div>{displayLabel}</div>
|
||||
<div data-test-subj={dataTestSubj}>{displayLabel}</div>
|
||||
) : null;
|
||||
}
|
||||
);
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useLicense as _useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { createLicenseServiceMock } from '../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../common/hooks/__mocks__/use_license';
|
||||
import type { NotifyUserOptionProps } from './notify_user_option';
|
||||
import {
|
||||
CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL,
|
||||
NOTIFY_USER_CHECKBOX_LABEL,
|
||||
NOTIFY_USER_SECTION_TITLE,
|
||||
NotifyUserOption,
|
||||
} from './notify_user_option';
|
||||
import { expectIsViewOnly, exactMatchText } from '../mocks';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy form Detect Prevent Protection level component', () => {
|
||||
let formProps: NotifyUserOptionProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
const isChecked = (selector: string): boolean => {
|
||||
return (renderResult.getByTestId(selector) as HTMLInputElement).checked;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': 'test',
|
||||
protection: 'malware',
|
||||
osList: ['windows', 'mac', 'linux'],
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<NotifyUserOption {...formProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render with expected content', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.message', 'hello world');
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-title')).toHaveTextContent(exactMatchText(NOTIFY_USER_SECTION_TITLE));
|
||||
expect(getByTestId('test-supportedVersion')).toHaveTextContent(
|
||||
exactMatchText('Agent version 7.11+')
|
||||
);
|
||||
expect(isChecked('test-checkbox')).toBe(true);
|
||||
expect(renderResult.getByLabelText(NOTIFY_USER_CHECKBOX_LABEL));
|
||||
expect(getByTestId('test-customMessageTitle')).toHaveTextContent(
|
||||
exactMatchText(CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL)
|
||||
);
|
||||
expect(getByTestId('test-customMessage')).toHaveValue('hello world');
|
||||
});
|
||||
|
||||
it('should render with options un-checked', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
render();
|
||||
|
||||
expect(isChecked('test-checkbox')).toBe(false);
|
||||
expect(renderResult.queryByTestId('test-customMessage')).toBeNull();
|
||||
});
|
||||
|
||||
it('should render checked disabled if protection mode is OFF', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.off);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-checkbox')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be able to un-check the option', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.popup.malware.enabled', false);
|
||||
set(expectedUpdatedPolicy, 'mac.popup.malware.enabled', false);
|
||||
set(expectedUpdatedPolicy, 'linux.popup.malware.enabled', false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test-checkbox'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to check the option', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.popup.malware.enabled', true);
|
||||
set(expectedUpdatedPolicy, 'mac.popup.malware.enabled', true);
|
||||
set(expectedUpdatedPolicy, 'linux.popup.malware.enabled', true);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test-checkbox'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to change the notification message', () => {
|
||||
const msg = 'a';
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
set(expectedUpdatedPolicy, 'windows.popup.malware.message', msg);
|
||||
set(expectedUpdatedPolicy, 'mac.popup.malware.message', msg);
|
||||
set(expectedUpdatedPolicy, 'linux.popup.malware.message', msg);
|
||||
render();
|
||||
userEvent.type(renderResult.getByTestId('test-customMessage'), msg);
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and license is lower than platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should NOT render the component', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.queryByTestId('test')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and rendered in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
set(formProps.policy, 'windows.popup.malware.message', 'you got owned');
|
||||
});
|
||||
|
||||
it('should render with no form elements', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
});
|
||||
|
||||
it('should render with expected output when checked', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'User notification' +
|
||||
'Agent version 7.11+' +
|
||||
'Notify user' +
|
||||
'Notification message' +
|
||||
'you got owned'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with expected output when checked with empty message', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.message', '');
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText('User notificationAgent version 7.11+Notify userNotification message—')
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with expected output when un-checked', () => {
|
||||
set(formProps.policy, 'windows.popup.malware.enabled', false);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText("User notificationAgent version 7.11+Don't notify user")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -28,14 +28,19 @@ import type { ImmutableArray, UIPolicyConfig } from '../../../../../../../common
|
|||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
|
||||
const NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
export const NOTIFY_USER_SECTION_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.userNotification',
|
||||
{ defaultMessage: 'User notification' }
|
||||
);
|
||||
|
||||
export const NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.notifyUser',
|
||||
{
|
||||
defaultMessage: 'Notify user',
|
||||
}
|
||||
);
|
||||
|
||||
const DO_NOT_NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
export const DO_NOT_NOTIFY_USER_CHECKBOX_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.doNotNotifyUser',
|
||||
{
|
||||
defaultMessage: "Don't notify user",
|
||||
|
@ -49,14 +54,14 @@ const NOTIFICATION_MESSAGE_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
const CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL = i18n.translate(
|
||||
export const CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification',
|
||||
{
|
||||
defaultMessage: 'Customize notification message',
|
||||
}
|
||||
);
|
||||
|
||||
interface NotifyUserOptionProps extends PolicyFormComponentCommonProps {
|
||||
export interface NotifyUserOptionProps extends PolicyFormComponentCommonProps {
|
||||
protection: PolicyProtection;
|
||||
osList: ImmutableArray<Partial<keyof UIPolicyConfig>>;
|
||||
}
|
||||
|
@ -161,11 +166,8 @@ export const NotifyUserOption = React.memo(
|
|||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<EuiSpacer size="m" />
|
||||
<SettingCardHeader>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.userNotification"
|
||||
defaultMessage="User notification"
|
||||
/>
|
||||
<SettingCardHeader data-test-subj={getTestId('title')}>
|
||||
{NOTIFY_USER_SECTION_TITLE}
|
||||
</SettingCardHeader>
|
||||
|
||||
<SupportedVersionForProtectionNotice
|
||||
|
@ -194,7 +196,7 @@ export const NotifyUserOption = React.memo(
|
|||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<EuiText size="s" data-test-subj={getTestId('customMessageTitle')}>
|
||||
<h4>{CUSTOMIZE_NOTIFICATION_MESSAGE_LABEL}</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useLicense as _useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import React from 'react';
|
||||
import { createLicenseServiceMock } from '../../../../../../../common/license/mocks';
|
||||
import { licenseService as licenseServiceMocked } from '../../../../../../common/hooks/__mocks__/use_license';
|
||||
import type { ProtectionSettingCardSwitchProps } from './protection_setting_card_switch';
|
||||
import { ProtectionSettingCardSwitch } from './protection_setting_card_switch';
|
||||
import { exactMatchText, expectIsViewOnly, setMalwareMode } from '../mocks';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('../../../../../../common/hooks/use_license');
|
||||
|
||||
const useLicenseMock = _useLicense as jest.Mock;
|
||||
|
||||
describe('Policy form ProtectionSettingCardSwitch component', () => {
|
||||
let formProps: ProtectionSettingCardSwitchProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': 'test',
|
||||
protection: 'malware',
|
||||
protectionLabel: 'Malware',
|
||||
osList: ['windows', 'mac', 'linux'],
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<ProtectionSettingCardSwitch {...formProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render expected output when enabled', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test')).toHaveAttribute('aria-checked', 'true');
|
||||
expect(getByTestId('test-label')).toHaveTextContent(exactMatchText('Malware enabled'));
|
||||
});
|
||||
|
||||
it('should render expected output when disabled', () => {
|
||||
set(formProps.policy, 'windows.malware.mode', ProtectionModes.off);
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(getByTestId('test-label')).toHaveTextContent(exactMatchText('Malware disabled'));
|
||||
});
|
||||
|
||||
it('should be able to disable it', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
setMalwareMode(expectedUpdatedPolicy, true, true, false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to enable it', () => {
|
||||
setMalwareMode(formProps.policy, true, true, false);
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
setMalwareMode(expectedUpdatedPolicy, false, true, false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should invoke `additionalOnSwitchChange` callback if one was defined', () => {
|
||||
formProps.additionalOnSwitchChange = jest.fn(({ policyConfigData }) => {
|
||||
const updated = cloneDeep(policyConfigData);
|
||||
updated.windows.popup.malware.message = 'foo';
|
||||
return updated;
|
||||
});
|
||||
|
||||
const expectedPolicyDataBeforeAdditionalCallback = cloneDeep(formProps.policy);
|
||||
setMalwareMode(expectedPolicyDataBeforeAdditionalCallback, true, true, false);
|
||||
|
||||
const expectedUpdatedPolicy = cloneDeep(expectedPolicyDataBeforeAdditionalCallback);
|
||||
expectedUpdatedPolicy.windows.popup.malware.message = 'foo';
|
||||
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test'));
|
||||
|
||||
expect(formProps.additionalOnSwitchChange).toHaveBeenCalledWith({
|
||||
value: false,
|
||||
policyConfigData: expectedPolicyDataBeforeAdditionalCallback,
|
||||
protectionOsList: formProps.osList,
|
||||
});
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
describe('and license is lower than platinum', () => {
|
||||
beforeEach(() => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
||||
|
||||
useLicenseMock.mockReturnValue(licenseServiceMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLicenseMock.mockReturnValue(licenseServiceMocked);
|
||||
});
|
||||
|
||||
it('should NOT update notification settings when disabling', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
setMalwareMode(expectedUpdatedPolicy, true, false, false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT update notification settings when enabling', () => {
|
||||
const expectedUpdatedPolicy = cloneDeep(formProps.policy);
|
||||
setMalwareMode(formProps.policy, true, false, false);
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test'));
|
||||
|
||||
expect(formProps.onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: expectedUpdatedPolicy,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and rendered in View mode', () => {
|
||||
beforeEach(() => {
|
||||
formProps.mode = 'view';
|
||||
});
|
||||
|
||||
it('should not include any form elements', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
});
|
||||
|
||||
it('should show option enabled', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(exactMatchText('Malware enabled'));
|
||||
});
|
||||
|
||||
it('should show option disabled', () => {
|
||||
setMalwareMode(formProps.policy, true, true, false);
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText('Malware disabled')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,19 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import type { PolicyFormComponentCommonProps } from '../types';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
import type {
|
||||
ImmutableArray,
|
||||
UIPolicyConfig,
|
||||
PolicyConfig,
|
||||
UIPolicyConfig,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../../common/endpoint/types';
|
||||
import type { PolicyProtection, MacPolicyProtection, LinuxPolicyProtection } from '../../../types';
|
||||
import type { LinuxPolicyProtection, MacPolicyProtection, PolicyProtection } from '../../../types';
|
||||
|
||||
export interface ProtectionSettingCardSwitchProps extends PolicyFormComponentCommonProps {
|
||||
protection: PolicyProtection;
|
||||
|
@ -45,19 +46,20 @@ export const ProtectionSettingCardSwitch = React.memo(
|
|||
mode,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: ProtectionSettingCardSwitchProps) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const isEditMode = mode === 'edit';
|
||||
const selected = policy && policy.windows[protection].mode;
|
||||
const switchLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.protectionsEnabled',
|
||||
{
|
||||
const selected = (policy && policy.windows[protection].mode) !== ProtectionModes.off;
|
||||
|
||||
const switchLabel = useMemo(() => {
|
||||
return i18n.translate('xpack.securitySolution.endpoint.policy.details.protectionsEnabled', {
|
||||
defaultMessage: '{protectionLabel} {mode, select, true {enabled} false {disabled}}',
|
||||
values: {
|
||||
protectionLabel,
|
||||
mode: selected !== ProtectionModes.off,
|
||||
mode: selected,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [protectionLabel, selected]);
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(event) => {
|
||||
|
@ -122,16 +124,16 @@ export const ProtectionSettingCardSwitch = React.memo(
|
|||
);
|
||||
|
||||
if (!isEditMode) {
|
||||
return <>{switchLabel}</>;
|
||||
return <span data-test-subj={getTestId()}>{switchLabel}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={switchLabel}
|
||||
checked={selected !== ProtectionModes.off}
|
||||
labelProps={{ 'data-test-subj': getTestId('label') }}
|
||||
checked={selected}
|
||||
onChange={handleSwitchChange}
|
||||
disabled={!isEditMode}
|
||||
data-test-subj={dataTestSubj}
|
||||
data-test-subj={getTestId()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import React from 'react';
|
||||
import { RelatedDetectionRulesCallout } from './related_detection_rules_callout';
|
||||
import { exactMatchText } from '../mocks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('Policy form RelatedDetectionRulesCallout component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let history: AppContextTestRender['history'];
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
history = mockedContext.history;
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<RelatedDetectionRulesCallout data-test-subj="test" />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render with expected content', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'View related detection rules. Prebuilt rules are tagged “Elastic” on the Detection Rules page.'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should navigate to Detection Rules when link is clicked', () => {
|
||||
render();
|
||||
userEvent.click(renderResult.getByTestId('test-link'));
|
||||
|
||||
expect(history.location.pathname).toEqual('/rules');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { APP_UI_ID, SecurityPageName } from '../../../../../../../common';
|
||||
|
||||
export const RelatedDetectionRulesCallout = memo<{ 'data-test-subj'?: string }>(
|
||||
({ 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
<EuiCallOut iconType="iInCircle" data-test-subj={getTestId()}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesMessage"
|
||||
defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page."
|
||||
values={{
|
||||
detectionRulesLink: (
|
||||
<LinkToApp
|
||||
appId={APP_UI_ID}
|
||||
deepLinkId={SecurityPageName.rules}
|
||||
data-test-subj={getTestId('link')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink"
|
||||
defaultMessage="related detection rules"
|
||||
/>
|
||||
</LinkToApp>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
);
|
||||
RelatedDetectionRulesCallout.displayName = 'RelatedDetectionRulesCallout';
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import React from 'react';
|
||||
import type { SettingCardProps } from './setting_card';
|
||||
import { SettingCard } from './setting_card';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { exactMatchText } from '../mocks';
|
||||
|
||||
describe('Policy form SettingCard component', () => {
|
||||
let formProps: SettingCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
type: 'Malware',
|
||||
supportedOss: [OperatingSystem.WINDOWS, OperatingSystem.MAC, OperatingSystem.LINUX],
|
||||
osRestriction: undefined,
|
||||
rightCorner: undefined,
|
||||
dataTestSubj: 'test',
|
||||
children: <div data-test-subj="test-bodyContent">{'body content here'}</div>,
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<SettingCard {...formProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test-osValues')).toHaveTextContent(exactMatchText('Windows, Mac, Linux'));
|
||||
expect(getByTestId('test-type')).toHaveTextContent(exactMatchText('Malware'));
|
||||
expect(getByTestId('test-rightCornerContainer')).toBeEmptyDOMElement();
|
||||
expect(getByTestId('test-bodyContent'));
|
||||
});
|
||||
|
||||
it('should show OS restriction info', () => {
|
||||
formProps.osRestriction = <>{'some content here'}</>;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-osRestriction')).toHaveTextContent(
|
||||
exactMatchText('RestrictionsInfo')
|
||||
);
|
||||
});
|
||||
|
||||
it('should show right corner content', () => {
|
||||
formProps.rightCorner = <div data-test-subj="test-rightContent">{'foo'}</div>;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-rightCornerContainer')).not.toBeEmptyDOMElement();
|
||||
expect(renderResult.getByTestId('test-rightContent'));
|
||||
});
|
||||
});
|
|
@ -35,7 +35,16 @@ const TITLES = {
|
|||
}),
|
||||
};
|
||||
|
||||
interface SettingCardProps {
|
||||
export const SettingCardHeader = memo<{ children: React.ReactNode; 'data-test-subj'?: string }>(
|
||||
({ children, 'data-test-subj': dataTestSubj }) => (
|
||||
<EuiTitle size="xxs" data-test-subj={dataTestSubj}>
|
||||
<h5>{children}</h5>
|
||||
</EuiTitle>
|
||||
)
|
||||
);
|
||||
SettingCardHeader.displayName = 'SettingCardHeader';
|
||||
|
||||
export type SettingCardProps = React.PropsWithChildren<{
|
||||
/**
|
||||
* A subtitle for this component.
|
||||
**/
|
||||
|
@ -48,16 +57,7 @@ interface SettingCardProps {
|
|||
dataTestSubj?: string;
|
||||
/** React Node to be put on the right corner of the card */
|
||||
rightCorner?: ReactNode;
|
||||
}
|
||||
|
||||
export const SettingCardHeader = memo<{ children: React.ReactNode; 'data-test-subj'?: string }>(
|
||||
({ children, 'data-test-subj': dataTestSubj }) => (
|
||||
<EuiTitle size="xxs" data-test-subj={dataTestSubj}>
|
||||
<h5>{children}</h5>
|
||||
</EuiTitle>
|
||||
)
|
||||
);
|
||||
SettingCardHeader.displayName = 'SettingCardHeader';
|
||||
}>;
|
||||
|
||||
export const SettingCard: FC<SettingCardProps> = memo(
|
||||
({ type, supportedOss, osRestriction, dataTestSubj, rightCorner, children }) => {
|
||||
|
@ -73,19 +73,26 @@ export const SettingCard: FC<SettingCardProps> = memo(
|
|||
style={{ padding: `${paddingSize} ${paddingSize} 0 ${paddingSize}` }}
|
||||
>
|
||||
<EuiFlexItem grow={1}>
|
||||
<SettingCardHeader data-test-subj={getTestId('title')}>{TITLES.type}</SettingCardHeader>
|
||||
<EuiText size="s">{type}</EuiText>
|
||||
<SettingCardHeader>{TITLES.type}</SettingCardHeader>
|
||||
<EuiText size="s" data-test-subj={getTestId('type')}>
|
||||
{type}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<SettingCardHeader data-test-subj={getTestId('osTitle')}>{TITLES.os}</SettingCardHeader>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<SettingCardHeader>{TITLES.os}</SettingCardHeader>
|
||||
<EuiFlexGroup
|
||||
direction="row"
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
data-test-subj={getTestId('osValueContainer')}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" data-test-subj={getTestId('osValues')}>
|
||||
{supportedOss.map((os) => OS_TITLES[os]).join(', ')}{' '}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{osRestriction && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} data-test-subj={getTestId('osRestriction')}>
|
||||
<EuiFlexGroup direction="row" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTextColor color="subdued">
|
||||
|
@ -96,7 +103,12 @@ export const SettingCard: FC<SettingCardProps> = memo(
|
|||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip type="warning" color="warning" content={osRestriction} />
|
||||
<EuiIconTip
|
||||
type="warning"
|
||||
color="warning"
|
||||
content={osRestriction}
|
||||
anchorProps={{ 'data-test-subj': getTestId('osRestrictionTooltipIcon') }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
@ -106,12 +118,16 @@ export const SettingCard: FC<SettingCardProps> = memo(
|
|||
<EuiShowFor sizes={['m', 'l', 'xl']}>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup direction="row" gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>{rightCorner}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} data-test-subj={getTestId('rightCornerContainer')}>
|
||||
{rightCorner}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiShowFor>
|
||||
<EuiShowFor sizes={rightCorner ? ['s', 'xs'] : []}>
|
||||
<EuiFlexItem>{rightCorner}</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={getTestId('rightCornerContainer')}>
|
||||
{rightCorner}
|
||||
</EuiFlexItem>
|
||||
</EuiShowFor>
|
||||
</EuiFlexGroup>
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
|
||||
import React from 'react';
|
||||
import { exactMatchText } from '../mocks';
|
||||
import type { SettingLockedCardProps } from './setting_locked_card';
|
||||
import { SettingLockedCard } from './setting_locked_card';
|
||||
|
||||
describe('Policy form SettingLockedCard component', () => {
|
||||
let formProps: SettingLockedCardProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
title: 'Malware locked',
|
||||
'data-test-subj': 'test',
|
||||
};
|
||||
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<SettingLockedCard {...formProps} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should render with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('test')).toHaveTextContent(
|
||||
exactMatchText(
|
||||
'Malware locked' +
|
||||
'Upgrade to Elastic Platinum' +
|
||||
'To turn on this protection, you must upgrade your license to Platinum, start a free 30-day ' +
|
||||
'trial, or spin up a ' +
|
||||
'cloud deployment' +
|
||||
'External link(opens in a new tab or window) ' +
|
||||
'on AWS, GCP, or Azure.Platinum'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -31,8 +31,13 @@ const LockedPolicyDiv = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
export interface SettingLockedCardProps {
|
||||
title: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const SettingLockedCard = memo(
|
||||
({ title, 'data-test-subj': dataTestSubj }: { title: string; 'data-test-subj'?: string }) => {
|
||||
({ title, 'data-test-subj': dataTestSubj }: SettingLockedCardProps) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
return (
|
||||
|
@ -40,6 +45,7 @@ export const SettingLockedCard = memo(
|
|||
<EuiCard
|
||||
data-test-subj={getTestId()}
|
||||
betaBadgeProps={{
|
||||
'data-test-subj': getTestId('badge'),
|
||||
label: i18n.translate('xpack.securitySolution.endpoint.policy.details.platinum', {
|
||||
defaultMessage: 'Platinum',
|
||||
}),
|
||||
|
@ -47,7 +53,7 @@ export const SettingLockedCard = memo(
|
|||
isDisabled={true}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
title={
|
||||
<h3>
|
||||
<h3 data-test-subj={getTestId('title')}>
|
||||
<strong>{title}</strong>
|
||||
</h3>
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { set } from 'lodash';
|
||||
import type { PolicyConfig } from '../../../../../../common/endpoint/types';
|
||||
import { ProtectionModes } from '../../../../../../common/endpoint/types';
|
||||
|
||||
interface TestSubjGenerator {
|
||||
(suffix?: string): string;
|
||||
withPrefix: (prefix: string) => TestSubjGenerator;
|
||||
|
@ -37,6 +41,8 @@ export const getPolicySettingsFormTestSubjects = (
|
|||
const windowsEventsTestSubj = genTestSubj.withPrefix('windowsEvents');
|
||||
const macEventsTestSubj = genTestSubj.withPrefix('macEvents');
|
||||
const linuxEventsTestSubj = genTestSubj.withPrefix('linuxEvents');
|
||||
const antivirusTestSubj = genTestSubj.withPrefix('antivirusRegistration');
|
||||
const attackSurfaceTestSubj = genTestSubj.withPrefix('attackSurface');
|
||||
|
||||
const testSubj = {
|
||||
form: genTestSubj(),
|
||||
|
@ -52,9 +58,14 @@ export const getPolicySettingsFormTestSubjects = (
|
|||
notifyCustomMessageTooltipIcon: malwareTestSubj('notifyUser-tooltipIcon'),
|
||||
notifyCustomMessageTooltipInfo: malwareTestSubj('notifyUser-tooltipInfo'),
|
||||
osValuesContainer: malwareTestSubj('osValues'),
|
||||
rulesCallout: malwareTestSubj('rulesCallout'),
|
||||
blocklistContainer: malwareTestSubj('blocklist'),
|
||||
blocklistEnableDisableSwitch: malwareTestSubj('blocklist-enableDisableSwitch'),
|
||||
},
|
||||
ransomware: {
|
||||
card: ransomwareTestSubj(),
|
||||
lockedCard: ransomwareTestSubj('locked'),
|
||||
lockedCardTitle: ransomwareTestSubj('locked-title'),
|
||||
enableDisableSwitch: ransomwareTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: ransomwareTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: ransomwareTestSubj('protectionLevel-detectRadio'),
|
||||
|
@ -64,53 +75,147 @@ export const getPolicySettingsFormTestSubjects = (
|
|||
notifyCustomMessageTooltipIcon: ransomwareTestSubj('notifyUser-tooltipIcon'),
|
||||
notifyCustomMessageTooltipInfo: ransomwareTestSubj('notifyUser-tooltipInfo'),
|
||||
osValuesContainer: ransomwareTestSubj('osValues'),
|
||||
rulesCallout: ransomwareTestSubj('rulesCallout'),
|
||||
},
|
||||
memory: {
|
||||
card: memoryTestSubj(),
|
||||
lockedCard: memoryTestSubj('locked'),
|
||||
lockedCardTitle: memoryTestSubj('locked-title'),
|
||||
enableDisableSwitch: memoryTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: memoryTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: memoryTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: memoryTestSubj('notifyUser-checkbox'),
|
||||
osValuesContainer: memoryTestSubj('osValues'),
|
||||
rulesCallout: memoryTestSubj('rulesCallout'),
|
||||
},
|
||||
behaviour: {
|
||||
card: behaviourTestSubj(),
|
||||
lockedCard: behaviourTestSubj('locked'),
|
||||
lockedCardTitle: behaviourTestSubj('locked-title'),
|
||||
enableDisableSwitch: behaviourTestSubj('enableDisableSwitch'),
|
||||
protectionPreventRadio: behaviourTestSubj('protectionLevel-preventRadio'),
|
||||
protectionDetectRadio: behaviourTestSubj('protectionLevel-detectRadio'),
|
||||
notifyUserCheckbox: behaviourTestSubj('notifyUser-checkbox'),
|
||||
osValuesContainer: behaviourTestSubj('osValues'),
|
||||
rulesCallout: behaviourTestSubj('rulesCallout'),
|
||||
},
|
||||
attachSurface: {
|
||||
card: genTestSubj('attackSurface'),
|
||||
enableDisableSwitch: genTestSubj('attachSurface-enableDisableSwitch'),
|
||||
osValuesContainer: genTestSubj('attackSurface-osValues'),
|
||||
attackSurface: {
|
||||
card: attackSurfaceTestSubj(),
|
||||
lockedCard: attackSurfaceTestSubj('locked'),
|
||||
lockedCardTitle: attackSurfaceTestSubj('locked-title'),
|
||||
enableDisableSwitch: attackSurfaceTestSubj('enableDisableSwitch'),
|
||||
osValues: attackSurfaceTestSubj('osValues'),
|
||||
viewModeValue: attackSurfaceTestSubj('valueLabel'),
|
||||
},
|
||||
|
||||
windowsEvents: {
|
||||
card: windowsEventsTestSubj(),
|
||||
osValueContainer: windowsEventsTestSubj('osValueContainer'),
|
||||
optionsContainer: windowsEventsTestSubj('options'),
|
||||
credentialsCheckbox: windowsEventsTestSubj('credential_access'),
|
||||
dllCheckbox: windowsEventsTestSubj('dll_and_driver_load'),
|
||||
dnsCheckbox: windowsEventsTestSubj('dns'),
|
||||
processCheckbox: windowsEventsTestSubj('process'),
|
||||
fileCheckbox: windowsEventsTestSubj('file'),
|
||||
networkCheckbox: windowsEventsTestSubj('network'),
|
||||
processCheckbox: windowsEventsTestSubj('process'),
|
||||
registryCheckbox: windowsEventsTestSubj('registry'),
|
||||
securityCheckbox: windowsEventsTestSubj('security'),
|
||||
},
|
||||
macEvents: {
|
||||
card: macEventsTestSubj(),
|
||||
osValueContainer: macEventsTestSubj('osValueContainer'),
|
||||
optionsContainer: macEventsTestSubj('options'),
|
||||
fileCheckbox: macEventsTestSubj('file'),
|
||||
networkCheckbox: macEventsTestSubj('network'),
|
||||
processCheckbox: macEventsTestSubj('process'),
|
||||
},
|
||||
linuxEvents: {
|
||||
card: linuxEventsTestSubj(),
|
||||
osValueContainer: linuxEventsTestSubj('osValueContainer'),
|
||||
optionsContainer: linuxEventsTestSubj('options'),
|
||||
fileCheckbox: linuxEventsTestSubj('file'),
|
||||
networkCheckbox: linuxEventsTestSubj('network'),
|
||||
processCheckbox: linuxEventsTestSubj('process'),
|
||||
sessionDataCheckbox: linuxEventsTestSubj('session_data'),
|
||||
captureTerminalCheckbox: linuxEventsTestSubj('tty_io'),
|
||||
},
|
||||
antivirusRegistration: {
|
||||
card: genTestSubj('antivirusRegistration'),
|
||||
card: antivirusTestSubj(),
|
||||
enableDisableSwitch: antivirusTestSubj('switch'),
|
||||
osValueContainer: antivirusTestSubj('osValueContainer'),
|
||||
viewOnlyValue: antivirusTestSubj('value'),
|
||||
},
|
||||
advancedSection: {
|
||||
container: advancedSectionTestSubj(''),
|
||||
showHideButton: advancedSectionTestSubj('showButton'),
|
||||
settingsContainer: advancedSectionTestSubj('settings'),
|
||||
warningCallout: advancedSectionTestSubj('warning'),
|
||||
settingRowTestSubjects: (settingKeyPath: string) => {
|
||||
const testSubjForSetting = advancedSectionTestSubj.withPrefix(settingKeyPath);
|
||||
|
||||
return {
|
||||
container: testSubjForSetting('container'),
|
||||
label: testSubjForSetting('label'),
|
||||
tooltipIcon: testSubjForSetting('tooltipIcon'),
|
||||
versionInfo: testSubjForSetting('versionInfo'),
|
||||
textField: settingKeyPath,
|
||||
viewValue: testSubjForSetting('viewValue'),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return testSubj;
|
||||
};
|
||||
|
||||
export const expectIsViewOnly = (ele: HTMLElement): void => {
|
||||
expect(
|
||||
ele.querySelectorAll(
|
||||
'button:not(.euiLink, [data-test-subj*="advancedSection-showButton"]),input,select,textarea'
|
||||
)
|
||||
).toHaveLength(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a regular expression with the provided text that ensure it matches the entire string.
|
||||
* @param text
|
||||
*/
|
||||
export const exactMatchText = (text: string): RegExp => {
|
||||
// RegExp below taken from: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
|
||||
return new RegExp(`^${text.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')}$`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets malware off or on (to prevent protection level) in the given policy settings
|
||||
*
|
||||
* NOTE: this utiliy MUTATES `policy` provided on input
|
||||
*
|
||||
* @param policy
|
||||
* @param turnOff
|
||||
* @param includePopup
|
||||
*/
|
||||
export const setMalwareMode = (
|
||||
policy: PolicyConfig,
|
||||
turnOff: boolean = false,
|
||||
includePopup: boolean = true,
|
||||
includeBlocklist: boolean = true
|
||||
) => {
|
||||
const mode = turnOff ? ProtectionModes.off : ProtectionModes.prevent;
|
||||
const enableValue = mode !== ProtectionModes.off;
|
||||
|
||||
set(policy, 'windows.malware.mode', mode);
|
||||
set(policy, 'mac.malware.mode', mode);
|
||||
set(policy, 'linux.malware.mode', mode);
|
||||
|
||||
if (includeBlocklist) {
|
||||
set(policy, 'windows.malware.blocklist', enableValue);
|
||||
set(policy, 'mac.malware.blocklist', enableValue);
|
||||
set(policy, 'linux.malware.blocklist', enableValue);
|
||||
}
|
||||
|
||||
if (includePopup) {
|
||||
set(policy, 'windows.popup.malware.enabled', enableValue);
|
||||
set(policy, 'mac.popup.malware.enabled', enableValue);
|
||||
set(policy, 'linux.popup.malware.enabled', enableValue);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects } from './mocks';
|
||||
import type { AppContextTestRender } from '../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
|
||||
import type { PolicySettingsFormProps } from './policy_settings_form';
|
||||
import { PolicySettingsForm } from './policy_settings_form';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_license');
|
||||
|
||||
describe('Endpoint Policy Settings Form', () => {
|
||||
const testSubj = getPolicySettingsFormTestSubjects('test');
|
||||
|
||||
let formProps: PolicySettingsFormProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
onChange: jest.fn(),
|
||||
mode: 'edit',
|
||||
'data-test-subj': 'test',
|
||||
};
|
||||
|
||||
render = () => (renderResult = mockedContext.render(<PolicySettingsForm {...formProps} />));
|
||||
});
|
||||
|
||||
it.each([
|
||||
['malware', testSubj.malware.card],
|
||||
['ransomware', testSubj.ransomware.card],
|
||||
['memory', testSubj.memory.card],
|
||||
['behaviour', testSubj.behaviour.card],
|
||||
['attack surface', testSubj.attackSurface.card],
|
||||
['windows events', testSubj.windowsEvents.card],
|
||||
['mac events', testSubj.macEvents.card],
|
||||
['linux events', testSubj.linuxEvents.card],
|
||||
['antivirus registration', testSubj.antivirusRegistration.card],
|
||||
['advanced settings', testSubj.advancedSection.container],
|
||||
])('should include %s card', (_, testSubjSelector) => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId(testSubjSelector));
|
||||
});
|
||||
|
||||
it('should render in View mode', () => {
|
||||
formProps.mode = 'view';
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import type { PolicyData } from '../../../../../../common/endpoint/types';
|
||||
import type { AppContextTestRender } from '../../../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
|
||||
import { PolicySettingsLayout } from './policy_settings_layout';
|
||||
import { useUserPrivileges as _useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../../../../common/components/user_privileges/__mocks__';
|
||||
import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks';
|
||||
import { allFleetHttpMocks } from '../../../../mocks';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { expectIsViewOnly, getPolicySettingsFormTestSubjects } from '../policy_settings_form/mocks';
|
||||
import { cloneDeep, set } from 'lodash';
|
||||
import { ProtectionModes } from '../../../../../../common/endpoint/types';
|
||||
import { waitFor, cleanup } from '@testing-library/react';
|
||||
import { packagePolicyRouteService } from '@kbn/fleet-plugin/common';
|
||||
import { getPolicyDataForUpdate } from '../../../../../../common/endpoint/service/policy';
|
||||
import { getDeferred } from '../../../../mocks/utils';
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_license');
|
||||
jest.mock('../../../../../common/components/user_privileges');
|
||||
|
||||
const useUserPrivilegesMock = _useUserPrivileges as jest.Mock;
|
||||
|
||||
describe('When rendering PolicySettingsLayout', () => {
|
||||
jest.setTimeout(15000);
|
||||
|
||||
const testSubj = getPolicySettingsFormTestSubjects();
|
||||
|
||||
let policyData: PolicyData;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let apiMocks: ReturnType<typeof allFleetHttpMocks>;
|
||||
let toasts: AppContextTestRender['coreStart']['notifications']['toasts'];
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
toasts = mockedContext.coreStart.notifications.toasts;
|
||||
apiMocks = allFleetHttpMocks(mockedContext.coreStart.http);
|
||||
policyData = new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy();
|
||||
render = () => {
|
||||
renderResult = mockedContext.render(<PolicySettingsLayout policy={policyData} />);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('and user has Edit permissions', () => {
|
||||
const clickSave = async (andConfirm: boolean = true, ensureApiIsCalled: boolean = true) => {
|
||||
const { getByTestId } = renderResult;
|
||||
|
||||
userEvent.click(getByTestId('policyDetailsSaveButton'));
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('confirmModalConfirmButton'));
|
||||
});
|
||||
|
||||
if (andConfirm) {
|
||||
userEvent.click(getByTestId('confirmModalConfirmButton'));
|
||||
|
||||
if (ensureApiIsCalled) {
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.responseProvider.updateEndpointPolicy).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes updates to the policy form on the UI and return back a new (cloned) `PolicyData`
|
||||
* with the updates reflected in it
|
||||
*/
|
||||
const makeUpdates = () => {
|
||||
const { getByTestId } = renderResult;
|
||||
const expectedUpdates = cloneDeep(policyData);
|
||||
const policySettings = expectedUpdates.inputs[0].config.policy.value;
|
||||
|
||||
// Turn off malware
|
||||
userEvent.click(getByTestId(testSubj.malware.enableDisableSwitch));
|
||||
set(policySettings, 'windows.malware.mode', ProtectionModes.off);
|
||||
set(policySettings, 'mac.malware.mode', ProtectionModes.off);
|
||||
set(policySettings, 'linux.malware.mode', ProtectionModes.off);
|
||||
set(policySettings, 'windows.malware.blocklist', false);
|
||||
set(policySettings, 'mac.malware.blocklist', false);
|
||||
set(policySettings, 'linux.malware.blocklist', false);
|
||||
set(policySettings, 'windows.popup.malware.enabled', false);
|
||||
set(policySettings, 'mac.popup.malware.enabled', false);
|
||||
set(policySettings, 'linux.popup.malware.enabled', false);
|
||||
|
||||
// Turn off Behaviour Protection
|
||||
userEvent.click(getByTestId(testSubj.behaviour.enableDisableSwitch));
|
||||
set(policySettings, 'windows.behavior_protection.mode', ProtectionModes.off);
|
||||
set(policySettings, 'mac.behavior_protection.mode', ProtectionModes.off);
|
||||
set(policySettings, 'linux.behavior_protection.mode', ProtectionModes.off);
|
||||
set(policySettings, 'windows.popup.behavior_protection.enabled', false);
|
||||
set(policySettings, 'mac.popup.behavior_protection.enabled', false);
|
||||
set(policySettings, 'linux.popup.behavior_protection.enabled', false);
|
||||
|
||||
// Set Ransomware User Notification message
|
||||
userEvent.type(getByTestId(testSubj.ransomware.notifyCustomMessage), 'foo message');
|
||||
set(policySettings, 'windows.popup.ransomware.message', 'foo message');
|
||||
|
||||
userEvent.click(getByTestId(testSubj.advancedSection.showHideButton));
|
||||
userEvent.type(getByTestId('linux.advanced.agent.connection_delay'), '1000');
|
||||
set(policySettings, 'linux.advanced.agent.connection_delay', '1000');
|
||||
|
||||
return expectedUpdates;
|
||||
};
|
||||
|
||||
it('should render layout with expected content', () => {
|
||||
const { getByTestId } = render();
|
||||
|
||||
expect(getByTestId('endpointPolicyForm'));
|
||||
expect(getByTestId('policyDetailsCancelButton')).not.toBeDisabled();
|
||||
expect(getByTestId('policyDetailsSaveButton')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should allow updates to be made', async () => {
|
||||
render();
|
||||
const expectedUpdatedPolicy = makeUpdates();
|
||||
await clickSave();
|
||||
|
||||
expect(apiMocks.responseProvider.updateEndpointPolicy).toHaveBeenCalledWith({
|
||||
path: packagePolicyRouteService.getUpdatePath(policyData.id),
|
||||
body: JSON.stringify(getPolicyDataForUpdate(expectedUpdatedPolicy)),
|
||||
});
|
||||
});
|
||||
|
||||
it('should show buttons disabled while update is in flight', async () => {
|
||||
const deferred = getDeferred();
|
||||
apiMocks.responseProvider.updateEndpointPolicy.mockDelay.mockReturnValue(deferred.promise);
|
||||
const { getByTestId } = render();
|
||||
await clickSave(true, false);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByTestId('policyDetailsCancelButton')).toBeDisabled();
|
||||
});
|
||||
|
||||
expect(getByTestId('policyDetailsSaveButton')).toBeDisabled();
|
||||
expect(
|
||||
getByTestId('policyDetailsSaveButton').querySelector('.euiLoadingSpinner')
|
||||
).not.toBeNull();
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
it('should show success toast on update success', async () => {
|
||||
render();
|
||||
await clickSave();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('policyDetailsSaveButton')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
expect(toasts.addSuccess).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'policyDetailsSuccessMessage',
|
||||
text: 'Integration Endpoint Policy {ku5j) has been updated.',
|
||||
title: 'Success!',
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Danger toast on update failure', async () => {
|
||||
apiMocks.responseProvider.updateEndpointPolicy.mockImplementation(() => {
|
||||
throw new Error('oh oh!');
|
||||
});
|
||||
render();
|
||||
await clickSave();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('policyDetailsSaveButton')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
expect(toasts.addDanger).toHaveBeenCalledWith({
|
||||
'data-test-subj': 'policyDetailsFailureMessage',
|
||||
text: 'oh oh!',
|
||||
title: 'Failed!',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and user has View Only permissions', () => {
|
||||
beforeEach(() => {
|
||||
const privileges = getUserPrivilegesMockDefaultValue();
|
||||
privileges.endpointPrivileges = getEndpointPrivilegesInitialStateMock({
|
||||
canWritePolicyManagement: false,
|
||||
});
|
||||
useUserPrivilegesMock.mockReturnValue(privileges);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useUserPrivilegesMock.mockReset();
|
||||
useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue);
|
||||
});
|
||||
|
||||
it('should render form in view mode', () => {
|
||||
render();
|
||||
|
||||
expectIsViewOnly(renderResult.getByTestId(testSubj.form));
|
||||
});
|
||||
|
||||
it('should not include the Save button', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.queryByTestId('policyDetailsSaveButton')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -165,6 +165,7 @@ export const PolicySettingsLayout = memo<PolicySettingsLayoutProps>(({ policy: _
|
|||
color="text"
|
||||
onClick={handleCancelOnClick}
|
||||
data-test-subj="policyDetailsCancelButton"
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.details.cancel"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue