mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Security Solution] Add rule upgrade preview FE integration tests (Rule Upgrade Flyout) (#210377)
**Partially addresses:** https://github.com/elastic/kibana/pull/205645 ## Summary This PR implements Frontend integration tests from the [upgrading prebuilt rules one-by-one with preview test plan](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_upgrade_with_preview.md). ## Details This PR add Jest integration tests (`@kbn/test/jest_integration` preset) for Rule Upgrade Flyout. Test scenarios are described in [upgrading prebuilt rules one-by-one with preview test plan](https://github.com/elastic/kibana/blob/main/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_upgrade_with_preview.md). Tests cover each `diffable rule` field separately to guarantee visibility on broken functionality. `esql_query` and `threat_mapping` fields were skipped due to mocking difficulties. ### Tests setup - Rules Management page is used as the root component to test functionality in integration - HTTP responses are mocked via mocking return values from `Kibana.http.fetch()` method - Test scenarios are the same for each diffable rule field and moved out to reusable utility functions
This commit is contained in:
parent
4416bc8bf5
commit
ea7cccab08
64 changed files with 4846 additions and 28 deletions
|
@ -284,6 +284,7 @@ module.exports = {
|
|||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_management_ui[\/\\]components[\/\\]rules_table[\/\\]rules_table_filters[\/\\]rules_table_filters.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_management_ui[\/\\]components[\/\\]rules_table[\/\\]upgrade_prebuilt_rules_table[\/\\]upgrade_prebuilt_rules_table_filters.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_management_ui[\/\\]components[\/\\]rules_table[\/\\]upgrade_prebuilt_rules_table[\/\\]use_ml_jobs_upgrade_modal[\/\\]ml_jobs_upgrade_modal.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_management_ui[\/\\]pages[\/\\]rule_management[\/\\]__integration_tests__[\/\\]rules_upgrade[\/\\]test_utils[\/\\]rule_upgrade_test_providers.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_response_actions[\/\\]response_action_type_form.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detection_engine[\/\\]rule_response_actions[\/\\]response_actions_form.test.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]detections[\/\\]components[\/\\]alerts_kpis[\/\\]alerts_by_rule_panel[\/\\]alerts_by_rule.tsx/,
|
||||
|
|
|
@ -35,12 +35,22 @@ const createStartContract = (): Start => {
|
|||
},
|
||||
getDefaultDataView: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
getDefaultId: jest.fn().mockReturnValue(Promise.resolve('')),
|
||||
get: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
get: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
title: '',
|
||||
fields: [],
|
||||
})
|
||||
),
|
||||
clearCache: jest.fn(),
|
||||
getCanSaveSync: jest.fn(),
|
||||
getIdsWithTitle: jest.fn(),
|
||||
getIdsWithTitle: jest.fn().mockResolvedValue([]),
|
||||
getFieldsForIndexPattern: jest.fn(),
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
create: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
title: '',
|
||||
fields: [],
|
||||
})
|
||||
),
|
||||
toDataView: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
toDataViewLazy: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
clearInstanceCache: jest.fn(),
|
||||
|
|
|
@ -118,7 +118,7 @@ const extractDiffableCommonFields = (
|
|||
version: rule.version,
|
||||
|
||||
// Main domain fields
|
||||
name: rule.name.trim(),
|
||||
name: rule.name?.trim(),
|
||||
tags: rule.tags ?? [],
|
||||
description: rule.description,
|
||||
severity: rule.severity,
|
||||
|
|
|
@ -64,7 +64,7 @@ interface ExtractRuleEqlQueryParams {
|
|||
|
||||
export const extractRuleEqlQuery = (params: ExtractRuleEqlQueryParams): RuleEqlQuery => {
|
||||
return {
|
||||
query: params.query.trim(),
|
||||
query: params.query?.trim(),
|
||||
language: params.language,
|
||||
filters: normalizeFilterArray(params.filters),
|
||||
event_category_override: params.eventCategoryOverride,
|
||||
|
@ -78,7 +78,7 @@ export const extractRuleEsqlQuery = (
|
|||
language: EsqlQueryLanguage
|
||||
): RuleEsqlQuery => {
|
||||
return {
|
||||
query: query.trim(),
|
||||
query: query?.trim(),
|
||||
language,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
|||
} from '../../../api/detection_engine/model/rule_schema';
|
||||
|
||||
export const extractThreatArray = (rule: RuleResponse): ThreatArray =>
|
||||
rule.threat.map((threat) => {
|
||||
rule.threat?.map((threat) => {
|
||||
if (threat.technique && threat.technique.length) {
|
||||
return {
|
||||
...threat,
|
||||
|
@ -26,7 +26,7 @@ export const extractThreatArray = (rule: RuleResponse): ThreatArray =>
|
|||
tactic: { ...threat.tactic, reference: normalizeThreatReference(threat.tactic.reference) },
|
||||
technique: undefined,
|
||||
}; // If `technique` is an empty array, remove the field from the `threat` object
|
||||
});
|
||||
}) ?? [];
|
||||
|
||||
const trimTechniqueArray = (techniqueArray: ThreatTechnique[]): ThreatTechnique[] => {
|
||||
return techniqueArray.map((technique) => ({
|
||||
|
|
|
@ -14,6 +14,7 @@ import { coreMock, themeServiceMock } from '@kbn/core/public/mocks';
|
|||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { securityMock } from '@kbn/security-plugin/public/mocks';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
import {
|
||||
DEFAULT_APP_REFRESH_INTERVAL,
|
||||
|
@ -59,6 +60,7 @@ import { calculateBounds } from '@kbn/data-plugin/common';
|
|||
import { alertingPluginMock } from '@kbn/alerting-plugin/public/mocks';
|
||||
import { createTelemetryServiceMock } from '../telemetry/telemetry_service.mock';
|
||||
import { createSiemMigrationsMock } from '../../mock/mock_siem_migrations_service';
|
||||
import { KibanaServices } from './services';
|
||||
|
||||
const mockUiSettings: Record<string, unknown> = {
|
||||
[DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' },
|
||||
|
@ -215,6 +217,13 @@ export const createStartServicesMock = (
|
|||
showQueries: true,
|
||||
saveQuery: true,
|
||||
},
|
||||
maintenanceWindow: {
|
||||
show: true,
|
||||
save: true,
|
||||
},
|
||||
actions: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
security,
|
||||
|
@ -261,6 +270,12 @@ export const createStartServicesMock = (
|
|||
timelineDataService,
|
||||
alerting,
|
||||
siemMigrations,
|
||||
sessionStorage: new Storage({
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
}),
|
||||
} as unknown as StartServices;
|
||||
};
|
||||
|
||||
|
@ -276,6 +291,14 @@ export const createWithKibanaMock = () => {
|
|||
export const createKibanaContextProviderMock = () => {
|
||||
const services = createStartServicesMock();
|
||||
|
||||
KibanaServices.init({
|
||||
...services,
|
||||
kibanaBranch: 'test',
|
||||
kibanaVersion: 'test',
|
||||
buildFlavor: 'test',
|
||||
prebuiltRulesPackageVersion: 'test',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
return ({
|
||||
children,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { act, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { act, fireEvent, waitFor, within } from '@testing-library/react';
|
||||
|
||||
export function showEuiComboBoxOptions(comboBoxToggleButton: HTMLElement): Promise<void> {
|
||||
fireEvent.click(comboBoxToggleButton);
|
||||
|
@ -60,6 +60,28 @@ export function selectEuiComboBoxOption({
|
|||
});
|
||||
}
|
||||
|
||||
interface AddEuiComboBoxOptionParameters {
|
||||
wrapper: HTMLElement;
|
||||
optionText: string;
|
||||
}
|
||||
|
||||
export async function addEuiComboBoxOption({
|
||||
wrapper,
|
||||
optionText,
|
||||
}: AddEuiComboBoxOptionParameters): Promise<void> {
|
||||
const input = within(wrapper).getByRole('combobox');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(input, {
|
||||
target: { value: optionText },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter', charCode: 13 });
|
||||
});
|
||||
}
|
||||
|
||||
export function selectFirstEuiComboBoxOption({
|
||||
comboBoxToggleButton,
|
||||
}: {
|
||||
|
@ -68,12 +90,21 @@ export function selectFirstEuiComboBoxOption({
|
|||
return selectEuiComboBoxOption({ comboBoxToggleButton, optionIndex: 0 });
|
||||
}
|
||||
|
||||
export function clearEuiComboBoxSelection({
|
||||
export async function clearEuiComboBoxSelection({
|
||||
clearButton,
|
||||
}: {
|
||||
clearButton: HTMLElement;
|
||||
}): Promise<void> {
|
||||
return act(async () => {
|
||||
const toggleButton = clearButton.nextElementSibling;
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(clearButton);
|
||||
});
|
||||
|
||||
if (toggleButton) {
|
||||
// Make sure options list gets closed
|
||||
await act(async () => {
|
||||
fireEvent.click(toggleButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { act, fireEvent, waitFor } from '@testing-library/react';
|
||||
|
||||
export function showEuiSuperSelectOptions(toggleButton: HTMLElement): Promise<void> {
|
||||
fireEvent.click(toggleButton);
|
||||
|
||||
return waitFor(() => {
|
||||
const listWithOptionsElement = document.querySelector('[role="listbox"]');
|
||||
const emptyListElement = document.querySelector('.euiComboBoxOptionsList__empty');
|
||||
|
||||
expect(listWithOptionsElement || emptyListElement).toBeInTheDocument();
|
||||
});
|
||||
}
|
||||
|
||||
type SelectEuiSuperSelectOptionParameters =
|
||||
| {
|
||||
toggleButton: HTMLElement;
|
||||
optionIndex: number;
|
||||
optionText?: undefined;
|
||||
}
|
||||
| {
|
||||
toggleButton: HTMLElement;
|
||||
optionText: string;
|
||||
optionIndex?: undefined;
|
||||
};
|
||||
|
||||
export function selectEuiSuperSelectOption({
|
||||
toggleButton,
|
||||
optionIndex,
|
||||
optionText,
|
||||
}: SelectEuiSuperSelectOptionParameters): Promise<void> {
|
||||
return act(async () => {
|
||||
await showEuiSuperSelectOptions(toggleButton);
|
||||
|
||||
const options = Array.from(document.querySelectorAll('[role="listbox"] [role="option"]'));
|
||||
|
||||
if (typeof optionText === 'string') {
|
||||
const lowerCaseOptionText = optionText.toLocaleLowerCase();
|
||||
const optionToSelect = options.find(
|
||||
(option) => option.textContent?.toLowerCase() === lowerCaseOptionText
|
||||
);
|
||||
|
||||
if (optionToSelect) {
|
||||
fireEvent.click(optionToSelect);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Could not find option with text "${optionText}". Available options: ${options
|
||||
.map((option) => option.textContent)
|
||||
.join(', ')}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
fireEvent.click(options[optionIndex]);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,6 +9,7 @@ module.exports = {
|
|||
preset: '@kbn/test',
|
||||
rootDir: '../../../../../../..',
|
||||
roots: ['<rootDir>/x-pack/solutions/security/plugins/security_solution/public/detection_engine'],
|
||||
modulePathIgnorePatterns: ['__integration_tests__'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/security/plugins/security_solution/public/detection_engine',
|
||||
coverageReporters: ['text', 'html'],
|
||||
|
|
|
@ -85,11 +85,14 @@ export function RelatedIntegrationField({
|
|||
);
|
||||
|
||||
const handleVersionChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const version = e.target.value;
|
||||
|
||||
field.setValue((oldValue) => ({
|
||||
...oldValue,
|
||||
version: e.target.value,
|
||||
})),
|
||||
version,
|
||||
}));
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
|
|
|
@ -179,6 +179,7 @@ const RequiredFieldsList = ({
|
|||
}
|
||||
hasChildLabel={false}
|
||||
labelType="legend"
|
||||
data-test-subj="requiredFieldsFormRow"
|
||||
>
|
||||
<>
|
||||
{items.map((item) => (
|
||||
|
|
|
@ -47,6 +47,7 @@ export const AnomalyThresholdSlider = ({
|
|||
tickInterval={25}
|
||||
min={0}
|
||||
max={100}
|
||||
data-test-subj="anomalyThresholdRange"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, snakeCase } from 'lodash';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { KibanaSectionErrorBoundary } from '@kbn/shared-ux-error-boundary';
|
||||
import { VersionsPicker, VersionsPickerOptionEnum } from './versions_picker/versions_picker';
|
||||
|
@ -58,7 +58,7 @@ export function FieldComparisonSide(): JSX.Element {
|
|||
}, [selectedOption, prevResolvedValue, resolvedValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section data-test-subj={`${snakeCase(fieldName)}-comparisonSide`}>
|
||||
<FieldUpgradeSideHeader>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
|
@ -81,6 +81,6 @@ export function FieldComparisonSide(): JSX.Element {
|
|||
<KibanaSectionErrorBoundary sectionName={i18n.TITLE}>
|
||||
<SubfieldChanges fieldName={fieldName} subfieldChanges={subfieldChanges} />
|
||||
</KibanaSectionErrorBoundary>
|
||||
</>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,15 +6,21 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { snakeCase } from 'lodash';
|
||||
import { useFieldUpgradeContext } from '../../rule_upgrade/field_upgrade_context';
|
||||
import { FieldEditFormContextProvider } from '../context/field_edit_form_context';
|
||||
import { FieldFinalSideContent } from './field_final_side_content';
|
||||
import { FieldFinalSideHeader } from './field_final_side_header';
|
||||
|
||||
export function FieldFinalSide(): JSX.Element {
|
||||
const { fieldName } = useFieldUpgradeContext();
|
||||
|
||||
return (
|
||||
<FieldEditFormContextProvider>
|
||||
<FieldFinalSideHeader />
|
||||
<FieldFinalSideContent />
|
||||
</FieldEditFormContextProvider>
|
||||
<section data-test-subj={`${snakeCase(fieldName)}-finalSide`}>
|
||||
<FieldEditFormContextProvider>
|
||||
<FieldFinalSideHeader />
|
||||
<FieldFinalSideContent />
|
||||
</FieldEditFormContextProvider>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export function BuildingBlockEdit(): JSX.Element {
|
|||
|
||||
export function buildingBlockDeserializer(defaultValue: FormData) {
|
||||
return {
|
||||
isBuildingBlock: defaultValue.building_block,
|
||||
isBuildingBlock: Boolean(defaultValue.building_block),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ export function useDataView(indexPatternsOrDataViewId: UseDataViewParams): UseDa
|
|||
setIsLoading(true);
|
||||
|
||||
(async () => {
|
||||
if (dataView !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (indexPatternsOrDataViewId.indexPatterns) {
|
||||
const indexPatternsDataView = await dataViewsService.create({
|
||||
|
@ -51,6 +55,7 @@ export function useDataView(indexPatternsOrDataViewId: UseDataViewParams): UseDa
|
|||
}
|
||||
})();
|
||||
}, [
|
||||
dataView,
|
||||
dataViewsService,
|
||||
indexPatternsOrDataViewId.indexPatterns,
|
||||
indexPatternsOrDataViewId.dataViewId,
|
||||
|
|
|
@ -32,10 +32,12 @@ export function SimpleRuleScheduleAdapter(): JSX.Element {
|
|||
|
||||
const INTERVAL_COMPONENT_PROPS = {
|
||||
minValue: 1,
|
||||
dataTestSubj: 'intervalFormRow',
|
||||
};
|
||||
|
||||
const LOOKBACK_COMPONENT_PROPS = {
|
||||
minValue: 0,
|
||||
dataTestSubj: 'lookbackFormRow',
|
||||
};
|
||||
|
||||
const INTERVAL_FIELD_CONFIG: FieldConfig<string> = {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { snakeCase } from 'lodash';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/css';
|
||||
import { SplitAccordion } from '../../../../../../common/components/split_accordion';
|
||||
|
@ -29,7 +30,7 @@ export function FieldUpgrade(): JSX.Element {
|
|||
/>
|
||||
}
|
||||
initialIsOpen={hasConflict}
|
||||
data-test-subj="ruleUpgradePerFieldDiff"
|
||||
data-test-subj={`${snakeCase(fieldName)}-upgrade`}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="flexStart">
|
||||
<EuiFlexItem grow={1}>
|
||||
|
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* 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 { act, fireEvent, within } from '@testing-library/react';
|
||||
import {
|
||||
ThreeWayDiffConflict,
|
||||
ThreeWayDiffOutcome,
|
||||
} from '../../../../../../../common/api/detection_engine';
|
||||
import { VersionsPickerOptionEnum } from '../../../../../rule_management/components/rule_details/three_way_diff/comparison_side/versions_picker/versions_picker';
|
||||
import {
|
||||
mockRuleUpgradeReviewData,
|
||||
renderRuleUpgradeFlyout,
|
||||
} from './test_utils/rule_upgrade_flyout';
|
||||
import {
|
||||
acceptSuggestedFieldValue,
|
||||
saveAndAcceptFieldValue,
|
||||
saveFieldValue,
|
||||
switchToFieldEdit,
|
||||
toggleFieldAccordion,
|
||||
} from './test_utils/rule_upgrade_helpers';
|
||||
import { inputFieldValue } from './test_utils/set_field_value';
|
||||
|
||||
describe('Rule upgrade preview Diff View options', () => {
|
||||
describe('non-customized field w/ an upgrade (AAB)', () => {
|
||||
it('shows default (incoming upgrade)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/o an upgrade (ABA)', () => {
|
||||
it('shows default (customization)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Initial name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Customized name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Initial name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Initial name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Customized name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ the matching upgrade (ABB)', () => {
|
||||
it('shows default (customization)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Updated name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes and final updates');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows incoming upgrade', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Updated name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
});
|
||||
|
||||
switchDiffViewTo(diffViewSelector, VersionsPickerOptionEnum.UpdateFromElastic);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Updated name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Updated name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ an upgrade resulting in a solvable conflict (ABC)', () => {
|
||||
it('shows default (merged value)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Merged name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent("My changes merged with Elastic's");
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Merged name');
|
||||
});
|
||||
|
||||
it('shows incoming upgrade', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Merged name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
switchDiffViewTo(diffViewSelector, VersionsPickerOptionEnum.UpdateFromElastic);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows original customization', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Merged name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
switchDiffViewTo(diffViewSelector, VersionsPickerOptionEnum.MyOriginalChanges);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes only');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Customized name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Merged name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Merged name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent("My changes merged with Elastic's");
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Merged name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ an upgrade resulting in a non-solvable conflict (ABC)', () => {
|
||||
it('shows default diff view (customization)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Customized name');
|
||||
});
|
||||
|
||||
it('shows incoming upgrade', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
switchDiffViewTo(diffViewSelector, VersionsPickerOptionEnum.UpdateFromElastic);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes');
|
||||
expect(diffViewSection).toHaveTextContent('-Initial name');
|
||||
expect(diffViewSection).toHaveTextContent('+Customized name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('missing base - customized field w/ an upgrade resulting in a solvable conflict (-AB)', () => {
|
||||
it('shows default diff view (incoming update)', async () => {
|
||||
const { diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Customized name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows the same diff after saving unchanged field value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
await acceptSuggestedFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('Changes from Elastic');
|
||||
expect(diffViewSection).toHaveTextContent('-Customized name');
|
||||
expect(diffViewSection).toHaveTextContent('+Updated name');
|
||||
});
|
||||
|
||||
it('shows resolved value', async () => {
|
||||
const { fieldUpgradeWrapper, diffViewSection, diffViewSelector } = await setup({
|
||||
fieldVersions: {
|
||||
current: 'Customized name',
|
||||
target: 'Updated name',
|
||||
merged: 'Customized name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expect(diffViewSelector).toBeVisible();
|
||||
|
||||
const selectedOption = within(diffViewSelector).getByRole('option', { selected: true });
|
||||
expect(selectedOption).toHaveTextContent('My changes and final updates');
|
||||
expect(diffViewSection).toHaveTextContent('-Customized name');
|
||||
expect(diffViewSection).toHaveTextContent('+Resolved name');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
interface SetupParams {
|
||||
fieldVersions: {
|
||||
base?: string;
|
||||
current: string;
|
||||
target: string;
|
||||
merged: string;
|
||||
};
|
||||
diffOutcome: ThreeWayDiffOutcome;
|
||||
conflict?: ThreeWayDiffConflict;
|
||||
}
|
||||
|
||||
interface SetupResult {
|
||||
fieldUpgradeWrapper: HTMLElement;
|
||||
diffViewSection: HTMLElement;
|
||||
diffViewSelector: HTMLElement;
|
||||
}
|
||||
|
||||
async function setup({
|
||||
fieldVersions,
|
||||
diffOutcome,
|
||||
conflict = ThreeWayDiffConflict.NONE,
|
||||
}: SetupParams): Promise<SetupResult> {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions,
|
||||
diffOutcome,
|
||||
conflict,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
// Fields w/o conflicts are shown collapsed
|
||||
if (conflict === ThreeWayDiffConflict.NONE) {
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
}
|
||||
|
||||
const diffViewSection = within(fieldUpgradeWrapper).getByTestId(`name-comparisonSide`);
|
||||
const diffViewSelector = within(diffViewSection).getByRole('combobox');
|
||||
|
||||
return { fieldUpgradeWrapper, diffViewSection, diffViewSelector };
|
||||
}
|
||||
|
||||
function switchDiffViewTo(diffViewSelector: HTMLElement, option: VersionsPickerOptionEnum): void {
|
||||
act(() => {
|
||||
fireEvent.change(diffViewSelector, { target: { value: option } });
|
||||
});
|
||||
|
||||
expect(
|
||||
within(diffViewSelector).getByRole('option', {
|
||||
selected: true,
|
||||
})
|
||||
).toHaveValue(option);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_integration',
|
||||
rootDir: '../../../../../../../../../../../..',
|
||||
modulePathIgnorePatterns: ['upgrade_rule_after_preview'],
|
||||
roots: [
|
||||
'<rootDir>/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management',
|
||||
],
|
||||
testMatch: ['**/*.test.[jt]s?(x)'],
|
||||
openHandlesTimeout: 0,
|
||||
forceExit: true,
|
||||
};
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* 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 { screen, within } from '@testing-library/react';
|
||||
import {
|
||||
ThreeWayDiffConflict,
|
||||
ThreeWayDiffOutcome,
|
||||
} from '../../../../../../../common/api/detection_engine';
|
||||
import {
|
||||
mockRuleUpgradeReviewData,
|
||||
renderRuleUpgradeFlyout,
|
||||
} from './test_utils/rule_upgrade_flyout';
|
||||
import {
|
||||
switchToFieldEdit,
|
||||
toggleFieldAccordion,
|
||||
cancelFieldEdit,
|
||||
saveFieldValue,
|
||||
saveAndAcceptFieldValue,
|
||||
} from './test_utils/rule_upgrade_helpers';
|
||||
import { inputFieldValue } from './test_utils/set_field_value';
|
||||
|
||||
describe('Rule Upgrade button', () => {
|
||||
describe('when there are no fields with conflicts', () => {
|
||||
it('is enabled', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
await renderRuleUpgradeFlyout();
|
||||
|
||||
expectRuleUpgradeButtonToBeEnabled();
|
||||
});
|
||||
|
||||
it('gets disabled after switching a field to edit mode', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
expectRuleUpgradeButtonToBeDisabled();
|
||||
});
|
||||
|
||||
it('gets disabled when field value validation does not pass', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: '' });
|
||||
|
||||
expectRuleUpgradeButtonToBeDisabled();
|
||||
});
|
||||
|
||||
it('gets enabled after switching to readonly mode', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
cancelFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
expectRuleUpgradeButtonToBeEnabled();
|
||||
});
|
||||
|
||||
it('gets enabled after providing a resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expectRuleUpgradeButtonToBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are fields with conflicts', () => {
|
||||
it('is disabled with solvable conflict', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
await renderRuleUpgradeFlyout();
|
||||
|
||||
expectRuleUpgradeButtonToBeDisabled();
|
||||
});
|
||||
|
||||
it('is disabled with non-solvable conflict', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
await renderRuleUpgradeFlyout();
|
||||
|
||||
expectRuleUpgradeButtonToBeDisabled();
|
||||
});
|
||||
|
||||
it('gets enabled after providing a resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType: 'query',
|
||||
fieldName: 'name',
|
||||
fieldVersions: {
|
||||
base: 'Initial name',
|
||||
current: 'Initial name',
|
||||
target: 'Updated name',
|
||||
merged: 'Updated name',
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`name-upgradeWrapper`);
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName: 'name', value: 'Resolved name' });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
expectRuleUpgradeButtonToBeEnabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectRuleUpgradeButtonToBeDisabled(): void {
|
||||
expect(
|
||||
within(screen.getByRole('dialog')).getByRole('button', {
|
||||
name: 'Update rule',
|
||||
})
|
||||
).toBeDisabled();
|
||||
}
|
||||
|
||||
function expectRuleUpgradeButtonToBeEnabled(): void {
|
||||
expect(
|
||||
within(screen.getByRole('dialog')).getByRole('button', {
|
||||
name: 'Update rule',
|
||||
})
|
||||
).toBeEnabled();
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* 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 { act, fireEvent, within, waitFor } from '@testing-library/react';
|
||||
import { isUndefined, omitBy } from 'lodash';
|
||||
import {
|
||||
PERFORM_RULE_UPGRADE_URL,
|
||||
ThreeWayDiffConflict,
|
||||
ThreeWayDiffOutcome,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
import {
|
||||
acceptSuggestedFieldValue,
|
||||
saveFieldValue,
|
||||
saveAndAcceptFieldValue,
|
||||
switchToFieldEdit,
|
||||
toggleFieldAccordion,
|
||||
} from './rule_upgrade_helpers';
|
||||
import { inputFieldValue } from './set_field_value';
|
||||
import {
|
||||
extractSingleKibanaFetchBodyBy,
|
||||
mockRuleUpgradeReviewData,
|
||||
renderRuleUpgradeFlyout,
|
||||
} from './rule_upgrade_flyout';
|
||||
|
||||
interface AssertRuleUpgradeAfterReviewParams {
|
||||
ruleType: string;
|
||||
fieldName: string;
|
||||
fieldVersions: {
|
||||
initial: unknown;
|
||||
customized: unknown;
|
||||
upgrade: unknown;
|
||||
resolvedValue: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export function assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName: rawFieldName,
|
||||
fieldVersions: { initial, customized, upgrade, resolvedValue: rawResolvedValue },
|
||||
}: AssertRuleUpgradeAfterReviewParams) {
|
||||
// TS isn't able to infer the type of the field name for inputFieldValue()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const resolvedValue = rawResolvedValue as any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fieldName = rawFieldName as any;
|
||||
|
||||
describe('non-customized field w/ an upgrade (AAB)', () => {
|
||||
it('upgrades rule to merged value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: initial,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole } = await renderRuleUpgradeFlyout();
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeToMergedValue();
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: initial,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName, value: resolvedValue });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/o an upgrade (ABA)', () => {
|
||||
it('upgrades rule to merged value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole } = await renderRuleUpgradeFlyout();
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeToMergedValue();
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, {
|
||||
fieldName,
|
||||
value: resolvedValue,
|
||||
});
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ the matching upgrade (ABB)', () => {
|
||||
it('upgrades rule to merged value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: upgrade,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole } = await renderRuleUpgradeFlyout();
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeToMergedValue();
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: upgrade,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName, value: resolvedValue });
|
||||
await saveFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ an upgrade resulting in a solvable conflict (ABC)', () => {
|
||||
it('upgrades rule to suggested value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
await acceptSuggestedFieldValue(fieldUpgradeWrapper);
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, customized);
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName, value: resolvedValue });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('customized field w/ an upgrade resulting in a non-solvable conflict (ABC)', () => {
|
||||
it('upgrades rule to suggested value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, customized);
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName, value: resolvedValue });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('missing base version - customized field w/ an upgrade resulted in a solvable conflict (-AB)', () => {
|
||||
it('upgrades rule to suggested value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
await acceptSuggestedFieldValue(fieldUpgradeWrapper);
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, customized);
|
||||
});
|
||||
|
||||
it('upgrades rule to resolved value', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByRole, getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
switchToFieldEdit(fieldUpgradeWrapper);
|
||||
await inputFieldValue(fieldUpgradeWrapper, { fieldName, value: resolvedValue });
|
||||
await saveAndAcceptFieldValue(fieldUpgradeWrapper);
|
||||
|
||||
await clickUpgradeRuleButton(getByRole('dialog'));
|
||||
|
||||
expectRuleUpgradeWithResolvedFieldValue(fieldName, resolvedValue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function clickUpgradeRuleButton(wrapper: HTMLElement): Promise<void> {
|
||||
const upgradeRuleButton = within(wrapper).getByRole('button', {
|
||||
name: 'Update rule',
|
||||
});
|
||||
|
||||
expect(upgradeRuleButton).toBeVisible();
|
||||
|
||||
await waitFor(() => expect(upgradeRuleButton).toBeEnabled(), {
|
||||
timeout: 500,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(upgradeRuleButton);
|
||||
});
|
||||
}
|
||||
|
||||
function expectRuleUpgradeToMergedValue(): void {
|
||||
const body = extractSingleKibanaFetchBodyBy({
|
||||
path: PERFORM_RULE_UPGRADE_URL,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
expect(body).toMatchObject({
|
||||
mode: 'SPECIFIC_RULES',
|
||||
rules: [{ rule_id: 'test-rule', revision: 1, fields: {} }],
|
||||
pick_version: 'MERGED',
|
||||
});
|
||||
}
|
||||
|
||||
function expectRuleUpgradeWithResolvedFieldValue(fieldName: string, value: unknown): void {
|
||||
const body = extractSingleKibanaFetchBodyBy({
|
||||
path: PERFORM_RULE_UPGRADE_URL,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
expect(body).toMatchObject({
|
||||
mode: 'SPECIFIC_RULES',
|
||||
rules: [
|
||||
{
|
||||
rule_id: 'test-rule',
|
||||
revision: 1,
|
||||
fields: {
|
||||
[fieldName]: omitBy({ pick_version: 'RESOLVED', resolved_value: value }, isUndefined),
|
||||
},
|
||||
},
|
||||
],
|
||||
pick_version: 'MERGED',
|
||||
});
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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 { within } from '@testing-library/react';
|
||||
import {
|
||||
ThreeWayDiffConflict,
|
||||
ThreeWayDiffOutcome,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
import { toggleFieldAccordion } from './rule_upgrade_helpers';
|
||||
import { mockRuleUpgradeReviewData, renderRuleUpgradeFlyout } from './rule_upgrade_flyout';
|
||||
|
||||
interface AssertRuleUpgradePreviewParams {
|
||||
ruleType: string;
|
||||
fieldName: string;
|
||||
humanizedFieldName: string;
|
||||
fieldVersions: {
|
||||
initial: unknown;
|
||||
customized: unknown;
|
||||
upgrade: unknown;
|
||||
resolvedValue: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export function assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: { initial, customized, upgrade },
|
||||
}: AssertRuleUpgradePreviewParams) {
|
||||
describe('preview rule upgrade', () => {
|
||||
it('previews non-customized field w/ an upgrade (AAB)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: initial,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.StockValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'No conflicts',
|
||||
upgradeStateBadge: 'Ready for update',
|
||||
isModified: false,
|
||||
});
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('previews customized field w/o an upgrade (ABA)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'No update',
|
||||
isModified: true,
|
||||
});
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('previews customized field w/ the matching upgrade (ABB)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: upgrade,
|
||||
target: upgrade,
|
||||
merged: upgrade,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'Matching update',
|
||||
isModified: true,
|
||||
});
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('previews customized field w/ an upgrade resulting in a solvable conflict (ABC)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'Auto-resolved conflict',
|
||||
upgradeStateBadge: 'Review required',
|
||||
isModified: true,
|
||||
});
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('previews customized field w/ an upgrade resulting in a non-solvable conflict (ABC)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
base: initial,
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NON_SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'Unresolved conflict',
|
||||
upgradeStateBadge: 'Action required',
|
||||
isModified: true,
|
||||
});
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('missing base - previews customized field w/ an upgrade and no conflict (-AB)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.NONE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'No conflict',
|
||||
upgradeStateBadge: 'Ready for update',
|
||||
isModified: false,
|
||||
});
|
||||
|
||||
toggleFieldAccordion(fieldUpgradeWrapper);
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
|
||||
it('missing base - previews customized field w/ an upgrade resulting in a solvable conflict (-AB)', async () => {
|
||||
mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
current: customized,
|
||||
target: upgrade,
|
||||
merged: customized,
|
||||
},
|
||||
diffOutcome: ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict: ThreeWayDiffConflict.SOLVABLE,
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderRuleUpgradeFlyout();
|
||||
|
||||
const fieldUpgradeWrapper = getByTestId(`${fieldName}-upgradeWrapper`);
|
||||
|
||||
expectFieldUpgradeState(fieldUpgradeWrapper, {
|
||||
humanizedFieldName,
|
||||
upgradeStateSummary: 'Auto-resolved conflict',
|
||||
upgradeStateBadge: 'Review required',
|
||||
isModified: false,
|
||||
});
|
||||
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-comparisonSide`)).toBeVisible();
|
||||
expect(within(fieldUpgradeWrapper).getByTestId(`${fieldName}-finalSide`)).toBeVisible();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface ExpectFieldUpgradeStateParams {
|
||||
/**
|
||||
* Human readable name shown in UI
|
||||
*/
|
||||
humanizedFieldName: string;
|
||||
/**
|
||||
* Field upgrade state summary text like "No conflict" or "Solvable conflict"
|
||||
*/
|
||||
upgradeStateSummary: string;
|
||||
/**
|
||||
* Field upgrade state badge text like "Ready to Update" and "Review required"
|
||||
*/
|
||||
upgradeStateBadge?: string;
|
||||
/**
|
||||
* Whether field's "Modified" badge is shown
|
||||
*/
|
||||
isModified: boolean;
|
||||
}
|
||||
|
||||
function expectFieldUpgradeState(
|
||||
wrapper: HTMLElement,
|
||||
params: ExpectFieldUpgradeStateParams
|
||||
): void {
|
||||
expect(wrapper).toHaveTextContent(params.humanizedFieldName);
|
||||
expect(wrapper).toHaveTextContent(params.upgradeStateSummary);
|
||||
|
||||
if (params.upgradeStateBadge) {
|
||||
expect(within(wrapper).getByTitle(params.upgradeStateBadge)).toBeVisible();
|
||||
}
|
||||
|
||||
if (params.isModified) {
|
||||
expect(within(wrapper).getByTitle('Modified')).toBeVisible();
|
||||
} else {
|
||||
expect(within(wrapper).queryByTitle('Modified')).not.toBeInTheDocument();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* 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 { render, act, fireEvent, screen } from '@testing-library/react';
|
||||
import type {
|
||||
DataViewField,
|
||||
DataViewFieldMap,
|
||||
DataViewSpec,
|
||||
FieldSpec,
|
||||
} from '@kbn/data-views-plugin/common';
|
||||
import { invariant } from '../../../../../../../../common/utils/invariant';
|
||||
import { TIMELINES_URL } from '../../../../../../../../common/constants';
|
||||
import { RulesPage } from '../../..';
|
||||
import type { RelatedIntegration } from '../../../../../../../../common/api/detection_engine';
|
||||
import {
|
||||
GET_ALL_INTEGRATIONS_URL,
|
||||
GET_PREBUILT_RULES_STATUS_URL,
|
||||
REVIEW_RULE_UPGRADE_URL,
|
||||
ThreeWayDiffConflict,
|
||||
ThreeWayDiffOutcome,
|
||||
ThreeWayMergeOutcome,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
import { KibanaServices } from '../../../../../../../common/lib/kibana';
|
||||
import { RuleUpgradeTestProviders } from './rule_upgrade_test_providers';
|
||||
|
||||
/** **********************************************/
|
||||
// Mocks necessary to render Rule Upgrade Flyout
|
||||
jest.mock('../../../../../../../detections/components/user_info');
|
||||
jest.mock('../../../../../../../detections/containers/detection_engine/lists/use_lists_config');
|
||||
/** **********************************************/
|
||||
|
||||
/**
|
||||
* Stores KibanaServices.get().http.fetch() mocked responses.
|
||||
*/
|
||||
const mockedResponses = new Map<string, unknown>();
|
||||
|
||||
export async function renderRuleUpgradeFlyout(): Promise<ReturnType<typeof render>> {
|
||||
// KibanaServices.get().http.fetch persists globally
|
||||
// it's important to clear the state for the later assertions
|
||||
(KibanaServices.get().http.fetch as jest.Mock).mockClear();
|
||||
(KibanaServices.get().http.fetch as jest.Mock).mockImplementation((requestedPath) =>
|
||||
mockedResponses.get(requestedPath)
|
||||
);
|
||||
|
||||
mockKibanaFetchResponse(GET_PREBUILT_RULES_STATUS_URL, {
|
||||
stats: {
|
||||
num_prebuilt_rules_installed: 1,
|
||||
num_prebuilt_rules_to_install: 0,
|
||||
num_prebuilt_rules_to_upgrade: 1,
|
||||
num_prebuilt_rules_total_in_package: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const renderResult = render(<RulesPage />, {
|
||||
wrapper: RuleUpgradeTestProviders,
|
||||
});
|
||||
|
||||
await openRuleUpgradeFlyout();
|
||||
|
||||
return renderResult;
|
||||
}
|
||||
|
||||
interface MockRuleUpgradeReviewDataParams {
|
||||
ruleType: string;
|
||||
fieldName: string;
|
||||
fieldVersions: {
|
||||
base?: unknown;
|
||||
current: unknown;
|
||||
target: unknown;
|
||||
merged: unknown;
|
||||
};
|
||||
diffOutcome: ThreeWayDiffOutcome;
|
||||
conflict: ThreeWayDiffConflict;
|
||||
}
|
||||
|
||||
export function mockRuleUpgradeReviewData({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions,
|
||||
diffOutcome,
|
||||
conflict,
|
||||
}: MockRuleUpgradeReviewDataParams): void {
|
||||
mockKibanaFetchResponse(REVIEW_RULE_UPGRADE_URL, {
|
||||
stats: {
|
||||
num_rules_to_upgrade_total: 1,
|
||||
num_rules_with_conflicts:
|
||||
conflict === ThreeWayDiffConflict.SOLVABLE || conflict === ThreeWayDiffConflict.NON_SOLVABLE
|
||||
? 1
|
||||
: 0,
|
||||
num_rules_with_non_solvable_conflicts: conflict === ThreeWayDiffConflict.NON_SOLVABLE ? 1 : 0,
|
||||
tags: [],
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
id: 'test-rule',
|
||||
rule_id: 'test-rule',
|
||||
current_rule: {
|
||||
rule_id: 'test-rule',
|
||||
type: ruleType,
|
||||
rule_source: {
|
||||
type: 'external',
|
||||
is_customized: true,
|
||||
},
|
||||
},
|
||||
target_rule: {
|
||||
rule_id: 'test-rule',
|
||||
type: ruleType,
|
||||
},
|
||||
diff: {
|
||||
num_fields_with_updates: 2, // tested field + version field
|
||||
num_fields_with_conflicts: 1,
|
||||
num_fields_with_non_solvable_conflicts: 1,
|
||||
fields: {
|
||||
[fieldName]: {
|
||||
base_version: fieldVersions.base,
|
||||
current_version: fieldVersions.current,
|
||||
target_version: fieldVersions.target,
|
||||
merged_version: fieldVersions.merged,
|
||||
diff_outcome: diffOutcome,
|
||||
merge_outcome: ThreeWayMergeOutcome.Current,
|
||||
has_base_version: Boolean(fieldVersions.base),
|
||||
has_update:
|
||||
diffOutcome === ThreeWayDiffOutcome.CustomizedValueCanUpdate ||
|
||||
diffOutcome === ThreeWayDiffOutcome.StockValueCanUpdate ||
|
||||
diffOutcome === ThreeWayDiffOutcome.MissingBaseCanUpdate,
|
||||
conflict,
|
||||
},
|
||||
},
|
||||
},
|
||||
revision: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataViews Mocked data views
|
||||
* @param stickyFields Fields added to all data views obtained via `dataViews.create()` or `dataViews.get()`
|
||||
*/
|
||||
export function mockAvailableDataViews(
|
||||
dataViews: DataViewSpec[],
|
||||
stickyFields: DataViewFieldMap
|
||||
): void {
|
||||
(KibanaServices.get().data.dataViews.getIdsWithTitle as jest.Mock).mockResolvedValue(
|
||||
dataViews.map(({ id, title }) => ({ id, title }))
|
||||
);
|
||||
|
||||
(KibanaServices.get().data.dataViews.create as jest.Mock).mockImplementation((dataViewSpec) =>
|
||||
createMockDataView({
|
||||
...dataViewSpec,
|
||||
fields: { ...(dataViewSpec.fields ?? {}), ...stickyFields },
|
||||
})
|
||||
);
|
||||
(KibanaServices.get().data.dataViews.get as jest.Mock).mockImplementation((id: string) => {
|
||||
const dataView = dataViews.find((dv) => dv.id === id);
|
||||
|
||||
invariant(
|
||||
dataView,
|
||||
`It's expected to have data view ${id} mock passed to mockAvailableDataViews() but it was not found`
|
||||
);
|
||||
|
||||
return createMockDataView({
|
||||
...dataView,
|
||||
fields: { ...(dataView.fields ?? {}), ...stickyFields },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function mockRelatedIntegrations(relatedIntegrations: RelatedIntegration[]): void {
|
||||
mockKibanaFetchResponse(GET_ALL_INTEGRATIONS_URL, {
|
||||
integrations: relatedIntegrations.map((ri) => ({
|
||||
package_name: ri.package,
|
||||
package_title: ri.package,
|
||||
is_installed: true,
|
||||
is_enabled: true,
|
||||
latest_package_version: ri.version,
|
||||
installed_package_version: ri.version,
|
||||
integration_name: ri.integration,
|
||||
integration_title: ri.integration,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
export function mockTimelines(timelines: Array<{ id: string; title: string }>): void {
|
||||
mockKibanaFetchResponse(TIMELINES_URL, {
|
||||
timeline: timelines.map((t, index) => ({
|
||||
templateTimelineId: t.id,
|
||||
title: t.title,
|
||||
savedObjectId: `so-id-${index}`,
|
||||
version: '1',
|
||||
})),
|
||||
totalCount: timelines.length,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks KibanaServices.get().http.fetch() responses. Works in combination with renderRuleUpgradeFlyout.
|
||||
*/
|
||||
export function mockKibanaFetchResponse(path: string, mockResponse: unknown): void {
|
||||
mockedResponses.set(path, mockResponse);
|
||||
}
|
||||
|
||||
async function openRuleUpgradeFlyout(): Promise<void> {
|
||||
await act(async () => {
|
||||
fireEvent.click(await screen.findByTestId('ruleName'));
|
||||
});
|
||||
}
|
||||
|
||||
const createMockDataView = ({ id, title, fields }: DataViewSpec) =>
|
||||
Promise.resolve({
|
||||
id,
|
||||
title,
|
||||
fields: Object.values(fields ?? {}).map(createFieldDefinition),
|
||||
getIndexPattern: jest.fn().mockReturnValue(title),
|
||||
toSpec: jest.fn().mockReturnValue({
|
||||
id,
|
||||
title,
|
||||
fields: Object.values(fields ?? {}).map(createFieldDefinition),
|
||||
}),
|
||||
});
|
||||
|
||||
function createFieldDefinition(fieldSpec: FieldSpec): Partial<DataViewField> {
|
||||
return {
|
||||
...fieldSpec,
|
||||
spec: {
|
||||
...fieldSpec,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface ExtractKibanaFetchRequestByParams {
|
||||
path: string;
|
||||
method: string;
|
||||
}
|
||||
|
||||
export function extractSingleKibanaFetchBodyBy({
|
||||
path,
|
||||
method,
|
||||
}: ExtractKibanaFetchRequestByParams): Record<string, unknown> {
|
||||
const kibanaFetchMock = KibanaServices.get().http.fetch as jest.Mock;
|
||||
const ruleUpgradeRequests = kibanaFetchMock.mock.calls.filter(
|
||||
([_path, _options]) => _path === path && _options.method === method
|
||||
);
|
||||
|
||||
expect(ruleUpgradeRequests).toHaveLength(1);
|
||||
|
||||
try {
|
||||
return JSON.parse(ruleUpgradeRequests[0][1].body);
|
||||
} catch {
|
||||
throw new Error('Unable to parse Kibana fetch body');
|
||||
}
|
||||
}
|
|
@ -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 { act, fireEvent, waitFor, within } from '@testing-library/react';
|
||||
|
||||
export function toggleFieldAccordion(fieldWrapper: HTMLElement): void {
|
||||
act(() => {
|
||||
const accordionButton = within(fieldWrapper).getAllByRole('button')[0];
|
||||
|
||||
fireEvent.click(accordionButton);
|
||||
});
|
||||
}
|
||||
|
||||
export function switchToFieldEdit(wrapper: HTMLElement): void {
|
||||
act(() => {
|
||||
fireEvent.click(within(wrapper).getByRole('button', { name: 'Edit' }));
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelFieldEdit(wrapper: HTMLElement): void {
|
||||
act(() => {
|
||||
fireEvent.click(within(wrapper).getByRole('button', { name: 'Cancel' }));
|
||||
});
|
||||
}
|
||||
|
||||
export async function acceptSuggestedFieldValue(wrapper: HTMLElement): Promise<void> {
|
||||
await act(async () => {
|
||||
fireEvent.click(within(wrapper).getByRole('button', { name: 'Accept' }));
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveFieldValue(wrapper: HTMLElement): Promise<void> {
|
||||
await clickFieldSaveButton(wrapper, 'Save');
|
||||
}
|
||||
|
||||
export async function saveAndAcceptFieldValue(wrapper: HTMLElement): Promise<void> {
|
||||
await clickFieldSaveButton(wrapper, 'Save and accept');
|
||||
}
|
||||
|
||||
async function clickFieldSaveButton(wrapper: HTMLElement, buttonName: string): Promise<void> {
|
||||
const saveButton = within(wrapper).getByRole('button', { name: buttonName });
|
||||
|
||||
expect(saveButton).toBeVisible();
|
||||
|
||||
// Wait for async validation to finish
|
||||
await waitFor(() => expect(saveButton).toBeEnabled(), {
|
||||
timeout: 500,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
|
||||
// After saving the form "Save" button should be removed from the DOM
|
||||
await waitFor(() => expect(saveButton).not.toBeInTheDocument(), {
|
||||
timeout: 500,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 type { PropsWithChildren } from 'react';
|
||||
import { EuiProvider } from '@elastic/eui';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { euiDarkVars } from '@kbn/ui-theme';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { SecurityPageName } from '@kbn/deeplinks-security';
|
||||
import { KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockDiscoverInTimelineContext } from '../../../../../../../common/components/discover_in_timeline/mocks/discover_in_timeline_provider';
|
||||
import { createKibanaContextProviderMock } from '../../../../../../../common/lib/kibana/kibana_react.mock';
|
||||
import { createMockStore } from '../../../../../../../common/mock';
|
||||
import { RouterSpyStateContext } from '../../../../../../../common/utils/route/helpers';
|
||||
import { AllRulesTabs } from '../../../../../components/rules_table/rules_table_toolbar';
|
||||
import { useKibana } from '../../../../../../../common/lib/kibana';
|
||||
import { MlCapabilitiesProvider } from '../../../../../../../common/components/ml/permissions/ml_capabilities_provider';
|
||||
import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider';
|
||||
|
||||
const MockKibanaContextProvider = createKibanaContextProviderMock();
|
||||
|
||||
function UpsellingProviderMock({ children }: React.PropsWithChildren<{}>): JSX.Element {
|
||||
return (
|
||||
<UpsellingProvider upsellingService={useKibana().services.upselling}>
|
||||
{children}
|
||||
</UpsellingProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defining custom Test Providers for Rule Upgrade Flyout required to avoid
|
||||
* impact on existing tests. Existing TestProviders doesn't provide necessary
|
||||
* contexts for the Rule Upgrade Flyout like `MlCapabilitiesProvider`. Mocking the
|
||||
* latter in TestProviders requires to refactor multiple Jest tests due to
|
||||
* `useKibana()` custom mocks also used in `MlCapabilitiesProvider`.
|
||||
*/
|
||||
export function RuleUpgradeTestProviders({ children }: PropsWithChildren<{}>): JSX.Element {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
logger: {
|
||||
log: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: () => {},
|
||||
},
|
||||
});
|
||||
const store = createMockStore();
|
||||
|
||||
return (
|
||||
<KibanaErrorBoundaryProvider analytics={undefined}>
|
||||
<RouterSpyStateContext.Provider
|
||||
value={[
|
||||
{
|
||||
pageName: SecurityPageName.rules,
|
||||
detailName: undefined,
|
||||
tabName: AllRulesTabs.updates,
|
||||
search: '',
|
||||
pathName: '/',
|
||||
state: undefined,
|
||||
},
|
||||
jest.fn(),
|
||||
]}
|
||||
>
|
||||
<MemoryRouter>
|
||||
<MockKibanaContextProvider>
|
||||
<MlCapabilitiesProvider>
|
||||
<I18nProvider>
|
||||
<UpsellingProviderMock>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MockDiscoverInTimelineContext>
|
||||
<EuiProvider highContrastMode={false}>{children}</EuiProvider>
|
||||
</MockDiscoverInTimelineContext>
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
</ReduxStoreProvider>
|
||||
</UpsellingProviderMock>
|
||||
</I18nProvider>
|
||||
</MlCapabilitiesProvider>
|
||||
</MockKibanaContextProvider>
|
||||
</MemoryRouter>
|
||||
</RouterSpyStateContext.Provider>
|
||||
</KibanaErrorBoundaryProvider>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,845 @@
|
|||
/*
|
||||
* 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 { act, fireEvent, within, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TimeDuration } from '@kbn/securitysolution-utils/time_duration';
|
||||
import { invariant } from '../../../../../../../../common/utils/invariant';
|
||||
import { toSimpleRuleSchedule } from '../../../../../../../../common/api/detection_engine/model/rule_schema/to_simple_rule_schedule';
|
||||
import {
|
||||
addEuiComboBoxOption,
|
||||
clearEuiComboBoxSelection,
|
||||
selectEuiComboBoxOption,
|
||||
} from '../../../../../../../common/test/eui/combobox';
|
||||
import { selectEuiSuperSelectOption } from '../../../../../../../common/test/eui/super_select';
|
||||
import type {
|
||||
AlertSuppression,
|
||||
AnomalyThreshold,
|
||||
HistoryWindowStart,
|
||||
InlineKqlQuery,
|
||||
MachineLearningJobId,
|
||||
NewTermsFields,
|
||||
RuleEqlQuery,
|
||||
RuleKqlQuery,
|
||||
ThreatIndex,
|
||||
Threshold,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
import {
|
||||
DataSourceType,
|
||||
type BuildingBlockObject,
|
||||
type DiffableAllFields,
|
||||
type InvestigationFields,
|
||||
type RelatedIntegration,
|
||||
type RequiredField,
|
||||
type RiskScoreMapping,
|
||||
type RuleDataSource,
|
||||
type RuleNameOverrideObject,
|
||||
type SeverityMapping,
|
||||
type Threat,
|
||||
type TimelineTemplateReference,
|
||||
type TimestampOverrideObject,
|
||||
KqlQueryType,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
import type { RuleSchedule } from '../../../../../../../../common/api/detection_engine/model/rule_schema/rule_schedule';
|
||||
|
||||
type ToDiscriminatedUnion<T> = {
|
||||
[K in keyof T]-?: { fieldName: K; value: T[K] };
|
||||
}[keyof T];
|
||||
|
||||
export async function inputFieldValue(
|
||||
wrapper: HTMLElement,
|
||||
params: ToDiscriminatedUnion<DiffableAllFields>
|
||||
): Promise<void> {
|
||||
const fieldFinalSide = within(wrapper).getByTestId(`${params.fieldName}-finalSide`);
|
||||
|
||||
switch (params.fieldName) {
|
||||
case 'name':
|
||||
await inputText(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
await inputText(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'severity':
|
||||
await inputSeverity(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'severity_mapping':
|
||||
await inputSeverityMapping(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'risk_score':
|
||||
await inputRiskScore(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'risk_score_mapping':
|
||||
await inputRiskScoreOverride(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'references':
|
||||
await inputStringsArray(fieldFinalSide, {
|
||||
addInputButtonName: 'Add reference URL',
|
||||
items: params.value,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'false_positives':
|
||||
await inputStringsArray(fieldFinalSide, {
|
||||
addInputButtonName: 'Add false positive example',
|
||||
items: params.value,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'threat':
|
||||
await inputThreat(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'note':
|
||||
await inputText(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'setup':
|
||||
await inputText(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'related_integrations':
|
||||
await inputRelatedIntegrations(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'required_fields':
|
||||
await inputRequiredFields(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'rule_schedule':
|
||||
await inputRuleSchedule(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'max_signals':
|
||||
await inputMaxSignals(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'rule_name_override':
|
||||
await inputRuleNameOverride(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'timestamp_override':
|
||||
await inputTimestampOverride(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'timeline_template':
|
||||
await inputTimelineTemplate(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'building_block':
|
||||
await inputBuildingBlock(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'investigation_fields':
|
||||
await inputInvestigationFields(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'data_source':
|
||||
await inputDataSource(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'alert_suppression':
|
||||
await inputAlertSuppression(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'anomaly_threshold':
|
||||
await inputAnomalyThreshold(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'kql_query':
|
||||
await inputKqlQuery(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'eql_query':
|
||||
await inputEqlQuery(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'esql_query':
|
||||
throw new Error('Not implemented');
|
||||
|
||||
case 'history_window_start':
|
||||
await inputHistoryWindowStart(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'machine_learning_job_id':
|
||||
await inputMachineLearningJobId(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'new_terms_fields':
|
||||
await inputNewTermsFields(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'threat_index':
|
||||
await inputThreatIndex(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'threat_indicator_path':
|
||||
await inputText(fieldFinalSide, params.value ?? '');
|
||||
break;
|
||||
|
||||
case 'threat_mapping':
|
||||
throw new Error('Not implemented');
|
||||
|
||||
case 'threat_query':
|
||||
await inputThreatQuery(fieldFinalSide, params.value);
|
||||
break;
|
||||
|
||||
case 'threshold':
|
||||
await inputThreshold(fieldFinalSide, params.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function fireEnterEvent(el: HTMLElement): Promise<void> {
|
||||
await act(async () => {
|
||||
el.focus();
|
||||
await userEvent.keyboard('{Enter}');
|
||||
});
|
||||
}
|
||||
|
||||
async function inputText(fieldFinalSide: HTMLElement, value: string): Promise<void> {
|
||||
await act(async () => {
|
||||
const input = within(fieldFinalSide).getByRole('textbox');
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: { value },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function inputSeverity(fieldFinalSide: HTMLElement, value: string): Promise<void> {
|
||||
const toggleButton = within(fieldFinalSide).getByTestId('select');
|
||||
|
||||
await selectEuiSuperSelectOption({
|
||||
toggleButton,
|
||||
optionText: value,
|
||||
});
|
||||
}
|
||||
|
||||
async function inputSeverityMapping(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: SeverityMapping
|
||||
): Promise<void> {
|
||||
const severityArray = ['low', 'medium', 'high', 'critical'];
|
||||
const severityMappingFormRows = within(fieldFinalSide).getAllByTestId('severityOverrideRow');
|
||||
|
||||
expect(severityMappingFormRows).toHaveLength(severityArray.length);
|
||||
|
||||
for (let i = 0; i < severityArray.length; ++i) {
|
||||
const severityLevel = severityArray[i];
|
||||
const formRow = severityMappingFormRows[i];
|
||||
const [sourceFieldComboboxInput, sourceValueComboboxInput] =
|
||||
within(formRow).getAllByRole('combobox');
|
||||
const mapping = value.find((x) => x.severity.toLowerCase() === severityLevel);
|
||||
|
||||
if (mapping) {
|
||||
await act(async () => {
|
||||
fireEvent.change(sourceFieldComboboxInput, {
|
||||
target: { value: mapping.field },
|
||||
});
|
||||
});
|
||||
await fireEnterEvent(sourceFieldComboboxInput);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(sourceValueComboboxInput, {
|
||||
target: { value: mapping.value },
|
||||
});
|
||||
});
|
||||
await fireEnterEvent(sourceValueComboboxInput);
|
||||
} else {
|
||||
// Clear mapping value for the current severity level
|
||||
await act(async () => {
|
||||
sourceFieldComboboxInput.focus();
|
||||
await userEvent.keyboard('{Backspace}');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function inputRiskScore(fieldFinalSide: HTMLElement, value: number): Promise<void> {
|
||||
await act(async () => {
|
||||
// EuiRange is used for Risk Score
|
||||
const [riskScoreInput] = within(fieldFinalSide).getAllByTestId(
|
||||
'defaultRiskScore-defaultRiskRange'
|
||||
);
|
||||
|
||||
fireEvent.change(riskScoreInput, {
|
||||
target: { value },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function inputRiskScoreOverride(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: RiskScoreMapping
|
||||
): Promise<void> {
|
||||
invariant(value.length === 1, 'setRiskScoreOverride() expects a single entry risk score mapping');
|
||||
|
||||
const sourceFieldComboboxInput = within(fieldFinalSide).getByRole('combobox');
|
||||
|
||||
await waitFor(() => expect(sourceFieldComboboxInput).toBeEnabled(), { timeout: 500 });
|
||||
await act(async () => {
|
||||
fireEvent.change(sourceFieldComboboxInput, {
|
||||
target: { value: value[0].field },
|
||||
});
|
||||
});
|
||||
|
||||
await fireEnterEvent(sourceFieldComboboxInput);
|
||||
}
|
||||
|
||||
async function inputStringsArray(
|
||||
fieldFinalSide: HTMLElement,
|
||||
{
|
||||
addInputButtonName,
|
||||
items,
|
||||
}: {
|
||||
addInputButtonName: string;
|
||||
items: string[];
|
||||
}
|
||||
): Promise<void> {
|
||||
await removeExistingItems(fieldFinalSide);
|
||||
|
||||
const addItem = async () => {
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
within(fieldFinalSide).getByRole('button', {
|
||||
name: addInputButtonName,
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
await addItem();
|
||||
|
||||
const inputs = within(fieldFinalSide).getAllByRole('textbox');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(inputs[i], {
|
||||
target: { value: items[i] },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Limited to tactics
|
||||
async function inputThreat(fieldFinalSide: HTMLElement, value: Threat[]): Promise<void> {
|
||||
await removeExistingItems(fieldFinalSide);
|
||||
|
||||
const addTactic = async () => {
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
within(fieldFinalSide).getByRole('button', {
|
||||
name: 'Add tactic',
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
await addTactic();
|
||||
|
||||
await selectEuiSuperSelectOption({
|
||||
toggleButton: within(fieldFinalSide).getAllByTestId('mitreAttackTactic')[i],
|
||||
optionText: `${value[i].tactic.name} (${value[i].tactic.id})`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires mocking response with integrations from `GET /internal/detection_engine/fleet/integrations/all`
|
||||
*/
|
||||
async function inputRelatedIntegrations(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: RelatedIntegration[]
|
||||
): Promise<void> {
|
||||
await removeExistingItems(fieldFinalSide, { removeButtonName: 'Remove related integration' });
|
||||
|
||||
const addIntegration = async () => {
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
within(fieldFinalSide).getByRole('button', {
|
||||
name: 'Add integration',
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
const { package: integrationPackageName, version } = value[i];
|
||||
|
||||
await addIntegration();
|
||||
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getAllByTestId('comboBoxToggleListButton')[i],
|
||||
// Expect only installed and enabled integrations
|
||||
optionText: `${integrationPackageName}Installed: Enabled`,
|
||||
});
|
||||
|
||||
const packageVersionInput = within(fieldFinalSide).getAllByRole('textbox')[i];
|
||||
|
||||
await waitFor(() => expect(packageVersionInput).toBeEnabled(), { timeout: 500 });
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(packageVersionInput, {
|
||||
target: { value: version },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputRequiredFields(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: RequiredField[]
|
||||
): Promise<void> {
|
||||
await removeExistingItems(fieldFinalSide, { removeButtonName: 'Remove required field' });
|
||||
|
||||
const addRequiredField = async () => {
|
||||
await act(async () => {
|
||||
fireEvent.click(
|
||||
within(fieldFinalSide).getByRole('button', {
|
||||
name: 'Add required field',
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
const { name, type } = value[i];
|
||||
|
||||
await addRequiredField();
|
||||
|
||||
const formRow = within(fieldFinalSide).getAllByTestId('requiredFieldsFormRow')[i];
|
||||
const [nameInput, typeInput] = within(formRow).getAllByRole('combobox');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(nameInput, {
|
||||
target: { value: name },
|
||||
});
|
||||
});
|
||||
await fireEnterEvent(nameInput);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(typeInput, {
|
||||
target: { value: type },
|
||||
});
|
||||
});
|
||||
await fireEnterEvent(typeInput);
|
||||
}
|
||||
}
|
||||
|
||||
async function inputRuleSchedule(
|
||||
fieldFinalSide: HTMLElement,
|
||||
ruleSchedule: RuleSchedule
|
||||
): Promise<void> {
|
||||
const intervalFormRow = within(fieldFinalSide).getByTestId('intervalFormRow');
|
||||
const intervalValueInput = within(intervalFormRow).getByRole('spinbutton');
|
||||
const intervalUnitInput = within(intervalFormRow).getByRole('combobox');
|
||||
const lookBackFormRow = within(fieldFinalSide).getByTestId('lookbackFormRow');
|
||||
const lookBackValueInput = within(lookBackFormRow).getByRole('spinbutton');
|
||||
const lookBackUnitInput = within(lookBackFormRow).getByRole('combobox');
|
||||
|
||||
const simpleRuleSchedule = toSimpleRuleSchedule(ruleSchedule);
|
||||
|
||||
invariant(
|
||||
simpleRuleSchedule,
|
||||
'Provided rule schedule is not convertible to simple rule schedule'
|
||||
);
|
||||
|
||||
const parsedInterval = TimeDuration.parse(simpleRuleSchedule.interval);
|
||||
const parsedLookBack = TimeDuration.parse(simpleRuleSchedule.lookback);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(intervalValueInput, {
|
||||
target: { value: parsedInterval?.value },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(intervalUnitInput, {
|
||||
target: { value: parsedInterval?.unit },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(lookBackValueInput, {
|
||||
target: { value: parsedLookBack?.value },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(lookBackUnitInput, {
|
||||
target: { value: parsedLookBack?.unit },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function inputMaxSignals(fieldFinalSide: HTMLElement, value: number): Promise<void> {
|
||||
const input = within(fieldFinalSide).getByRole('spinbutton');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(input, {
|
||||
target: { value },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function inputRuleNameOverride(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: RuleNameOverrideObject | undefined
|
||||
): Promise<void> {
|
||||
await waitFor(
|
||||
() => expect(within(fieldFinalSide).getByTestId('comboBoxSearchInput')).toBeEnabled(),
|
||||
{ timeout: 500 }
|
||||
);
|
||||
|
||||
if (value) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: value.field_name,
|
||||
});
|
||||
} else {
|
||||
await act(async () => {
|
||||
within(fieldFinalSide).getByTestId('comboBoxSearchInput').focus();
|
||||
await userEvent.keyboard('{Backspace}');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputTimestampOverride(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: TimestampOverrideObject | undefined
|
||||
): Promise<void> {
|
||||
await waitFor(
|
||||
() => expect(within(fieldFinalSide).getByTestId('comboBoxSearchInput')).toBeEnabled(),
|
||||
{ timeout: 500 }
|
||||
);
|
||||
|
||||
if (value) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: value.field_name,
|
||||
});
|
||||
} else {
|
||||
await act(async () => {
|
||||
within(fieldFinalSide).getByTestId('comboBoxSearchInput').focus();
|
||||
await userEvent.keyboard('{Backspace}');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputTimelineTemplate(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: TimelineTemplateReference | undefined
|
||||
): Promise<void> {
|
||||
const timelineSelectToggleButton = within(fieldFinalSide).getByRole('combobox');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(timelineSelectToggleButton);
|
||||
});
|
||||
|
||||
const options = Array.from(document.querySelectorAll('[role="option"]'));
|
||||
|
||||
const lowerCaseOptionText = value?.timeline_title.toLocaleLowerCase() ?? 'None';
|
||||
const optionToSelect = options.find((option) =>
|
||||
option.textContent?.toLowerCase().includes(lowerCaseOptionText)
|
||||
);
|
||||
|
||||
if (optionToSelect) {
|
||||
await act(async () => {
|
||||
fireEvent.click(optionToSelect);
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
`Could not find option with text "${lowerCaseOptionText}". Available options: ${options
|
||||
.map((option) => option.textContent)
|
||||
.join(', ')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function inputBuildingBlock(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: BuildingBlockObject | undefined
|
||||
): Promise<void> {
|
||||
const markGeneratedAlertsAsBuildingBlockAlertsCheckbox = within(fieldFinalSide).getByRole(
|
||||
'checkbox'
|
||||
) as HTMLInputElement;
|
||||
|
||||
// Field is already in the expected state, exit.
|
||||
if (
|
||||
(markGeneratedAlertsAsBuildingBlockAlertsCheckbox.checked && value) ||
|
||||
(!markGeneratedAlertsAsBuildingBlockAlertsCheckbox.checked && !value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(markGeneratedAlertsAsBuildingBlockAlertsCheckbox);
|
||||
});
|
||||
}
|
||||
|
||||
async function inputInvestigationFields(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: InvestigationFields | undefined
|
||||
): Promise<void> {
|
||||
await waitFor(() =>
|
||||
expect(within(fieldFinalSide).queryByTestId('comboBoxClearButton')).toBeVisible()
|
||||
);
|
||||
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(fieldFinalSide).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const fieldName of value?.field_names ?? []) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: fieldName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputDataSource(
|
||||
fieldFinalSide: HTMLElement,
|
||||
dataSource: RuleDataSource | undefined
|
||||
): Promise<void> {
|
||||
if (!dataSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexPatternsEditWrapper = within(fieldFinalSide).getByTestId('indexPatternEdit');
|
||||
const dataViewEditWrapper = within(fieldFinalSide).getByTestId('pick-rule-data-source');
|
||||
|
||||
switch (dataSource.type) {
|
||||
case DataSourceType.index_patterns:
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(indexPatternsEditWrapper).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const indexPattern of dataSource.index_patterns) {
|
||||
await addEuiComboBoxOption({
|
||||
wrapper: indexPatternsEditWrapper,
|
||||
optionText: indexPattern,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DataSourceType.data_view:
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
within(dataViewEditWrapper).queryByTestId('comboBoxToggleListButton')
|
||||
).toBeVisible(),
|
||||
{
|
||||
timeout: 500,
|
||||
}
|
||||
);
|
||||
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(dataViewEditWrapper).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: dataSource.data_view_id,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements only suppression fields
|
||||
*/
|
||||
async function inputAlertSuppression(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: AlertSuppression | undefined
|
||||
): Promise<void> {
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(fieldFinalSide).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const fieldName of value.group_by) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: fieldName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputAnomalyThreshold(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: AnomalyThreshold
|
||||
): Promise<void> {
|
||||
await act(async () => {
|
||||
// EuiRange is used for anomaly threshold
|
||||
const [riskScoreInput] = within(fieldFinalSide).getAllByTestId('anomalyThresholdRange');
|
||||
|
||||
fireEvent.change(riskScoreInput, {
|
||||
target: { value },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't support filters and saved queries
|
||||
*/
|
||||
async function inputKqlQuery(fieldFinalSide: HTMLElement, value: RuleKqlQuery): Promise<void> {
|
||||
if (value.type !== KqlQueryType.inline_query) {
|
||||
return;
|
||||
}
|
||||
|
||||
await waitFor(() => expect(within(fieldFinalSide).getByRole('textbox')).toBeVisible(), {
|
||||
timeout: 500,
|
||||
});
|
||||
|
||||
await inputText(fieldFinalSide, value.query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't support filters and EQL options
|
||||
*/
|
||||
async function inputEqlQuery(fieldFinalSide: HTMLElement, value: RuleEqlQuery): Promise<void> {
|
||||
await waitFor(() => expect(within(fieldFinalSide).getByRole('textbox')).toBeVisible(), {
|
||||
timeout: 500,
|
||||
});
|
||||
|
||||
await inputText(fieldFinalSide, value.query);
|
||||
}
|
||||
|
||||
async function inputHistoryWindowStart(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: HistoryWindowStart
|
||||
): Promise<void> {
|
||||
const valueInput = within(fieldFinalSide).getByTestId('interval');
|
||||
const unitInput = within(fieldFinalSide).getByTestId('timeType');
|
||||
|
||||
invariant(value.startsWith('now-'), 'Unable to parse history window start value');
|
||||
|
||||
const parsed = TimeDuration.parse(value.substring(4));
|
||||
|
||||
invariant(parsed, 'Unable to parse history window start value');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(valueInput, {
|
||||
target: { value: parsed.value },
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(unitInput, {
|
||||
target: { value: parsed.unit },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function inputMachineLearningJobId(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: MachineLearningJobId
|
||||
): Promise<void> {
|
||||
const jobIds = [value].flat();
|
||||
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(fieldFinalSide).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const jobId of jobIds) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: jobId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputNewTermsFields(
|
||||
fieldFinalSide: HTMLElement,
|
||||
value: NewTermsFields
|
||||
): Promise<void> {
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(fieldFinalSide).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const fieldName of value) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(fieldFinalSide).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: fieldName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function inputThreatIndex(fieldFinalSide: HTMLElement, value: ThreatIndex): Promise<void> {
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(fieldFinalSide).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const indexPattern of value) {
|
||||
await addEuiComboBoxOption({
|
||||
wrapper: fieldFinalSide,
|
||||
optionText: indexPattern,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't support filters
|
||||
*/
|
||||
async function inputThreatQuery(fieldFinalSide: HTMLElement, value: InlineKqlQuery): Promise<void> {
|
||||
await waitFor(() => expect(within(fieldFinalSide).getByRole('textbox')).toBeVisible(), {
|
||||
timeout: 500,
|
||||
});
|
||||
|
||||
await inputText(fieldFinalSide, value.query);
|
||||
}
|
||||
|
||||
async function inputThreshold(fieldFinalSide: HTMLElement, value: Threshold): Promise<void> {
|
||||
const groupByFieldsComboBox = within(fieldFinalSide).getByTestId(
|
||||
'detectionEngineStepDefineRuleThresholdField'
|
||||
);
|
||||
const thresholdInput = within(fieldFinalSide).getByTestId(
|
||||
'detectionEngineStepDefineRuleThresholdValue'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const input = within(thresholdInput).getByRole('spinbutton');
|
||||
|
||||
fireEvent.change(input, {
|
||||
target: { value: value.value },
|
||||
});
|
||||
});
|
||||
|
||||
const fields = [value.field].flat();
|
||||
|
||||
await clearEuiComboBoxSelection({
|
||||
clearButton: within(groupByFieldsComboBox).getByTestId('comboBoxClearButton'),
|
||||
});
|
||||
|
||||
for (const field of fields) {
|
||||
await selectEuiComboBoxOption({
|
||||
comboBoxToggleButton: within(groupByFieldsComboBox).getByTestId('comboBoxToggleListButton'),
|
||||
optionText: field,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function removeExistingItems(
|
||||
wrapper: HTMLElement,
|
||||
{ removeButtonName }: { removeButtonName: string } = { removeButtonName: 'Delete' }
|
||||
): Promise<void> {
|
||||
const deleteButtons = within(wrapper).getAllByRole('button', { name: removeButtonName });
|
||||
|
||||
for (let i = deleteButtons.length - 1; i >= 0; --i) {
|
||||
await act(async () => {
|
||||
fireEvent.click(deleteButtons[i]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "alert_suppression" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedString: {
|
||||
name: 'resolvedStringField',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'alert_suppression';
|
||||
const humanizedFieldName = 'Alert suppression';
|
||||
const initial = { group_by: ['fieldA'] };
|
||||
const customized = { group_by: ['fieldB'] };
|
||||
const upgrade = { group_by: ['fieldC'] };
|
||||
const resolvedValue = { group_by: ['resolvedStringField'] };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "building_block" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'building_block';
|
||||
const humanizedFieldName = 'Building Block';
|
||||
const initial = undefined;
|
||||
const customized = { type: 'default' };
|
||||
const upgrade = { type: 'default' };
|
||||
const resolvedValue = undefined;
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { DataSourceType } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "data_source" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews(
|
||||
[
|
||||
{
|
||||
id: 'resolved',
|
||||
title: 'resolved',
|
||||
},
|
||||
{
|
||||
id: 'data_view_B',
|
||||
title: 'Data View B',
|
||||
},
|
||||
{
|
||||
id: 'data_view_C',
|
||||
title: 'Data View C',
|
||||
},
|
||||
],
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'data_source';
|
||||
const humanizedFieldName = 'Data source';
|
||||
|
||||
describe.each([
|
||||
{
|
||||
initial: { type: DataSourceType.index_patterns, index_patterns: ['indexA'] },
|
||||
customized: { type: DataSourceType.index_patterns, index_patterns: ['indexB'] },
|
||||
upgrade: { type: DataSourceType.index_patterns, index_patterns: ['indexC'] },
|
||||
resolvedValue: { type: DataSourceType.index_patterns, index_patterns: ['resolved'] },
|
||||
},
|
||||
{
|
||||
initial: { type: DataSourceType.data_view, data_view_id: 'data_view_A' },
|
||||
customized: { type: DataSourceType.data_view, data_view_id: 'data_view_B' },
|
||||
upgrade: { type: DataSourceType.data_view, data_view_id: 'data_view_C' },
|
||||
resolvedValue: { type: DataSourceType.data_view, data_view_id: 'resolved' },
|
||||
},
|
||||
] as const)('$resolvedValue.type', ({ initial, customized, upgrade, resolvedValue }) => {
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "description" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'description';
|
||||
const humanizedFieldName = 'Description';
|
||||
const initial = 'Initial description';
|
||||
const customized = 'Custom description';
|
||||
const upgrade = 'Updated description';
|
||||
const resolvedValue = 'Resolved description';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "false_positives" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'false_positives';
|
||||
const humanizedFieldName = 'False Positives';
|
||||
const initial = ['exampleA'];
|
||||
const customized = ['exampleB'];
|
||||
const upgrade = ['exampleC'];
|
||||
const resolvedValue = ['resolved'];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "investigation_fields" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedString: {
|
||||
name: 'resolvedStringField',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'investigation_fields';
|
||||
const humanizedFieldName = 'Custom highlighted fields';
|
||||
const initial = { field_names: ['fieldA'] };
|
||||
const customized = { field_names: ['fieldB'] };
|
||||
const upgrade = { field_names: ['fieldC'] };
|
||||
const resolvedValue = { field_names: ['resolvedStringField'] };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_integration',
|
||||
rootDir: '../../../../../../../../../../../../../..',
|
||||
roots: [
|
||||
'<rootDir>/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management',
|
||||
],
|
||||
testMatch: ['**/common_fields/*.test.[jt]s?(x)'],
|
||||
openHandlesTimeout: 0,
|
||||
forceExit: true,
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "max_signals" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'max_signals';
|
||||
const humanizedFieldName = 'Max Signals';
|
||||
const initial = 100;
|
||||
const customized = 150;
|
||||
const upgrade = 200;
|
||||
const resolvedValue = 300;
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "name" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'name';
|
||||
const humanizedFieldName = 'Name';
|
||||
const initial = 'Initial name';
|
||||
const customized = 'Custom name';
|
||||
const upgrade = 'Updated name';
|
||||
const resolvedValue = 'Resolved name';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "note" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'note';
|
||||
const humanizedFieldName = 'Investigation guide';
|
||||
const initial = 'Initial investigation guide';
|
||||
const customized = 'Custom investigation guide';
|
||||
const upgrade = 'Updated investigation guide';
|
||||
const resolvedValue = 'resolved investigation guide';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "references" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'references';
|
||||
const humanizedFieldName = 'Reference URLs';
|
||||
const initial = ['http://url-1'];
|
||||
const customized = ['http://url-2'];
|
||||
const upgrade = ['http://url-3'];
|
||||
const resolvedValue = ['http://resolved'];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { mockRelatedIntegrations } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "related_integrations" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockRelatedIntegrations([
|
||||
{
|
||||
package: 'packageResolved',
|
||||
version: '5.0.0',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'related_integrations';
|
||||
const humanizedFieldName = 'Related Integrations';
|
||||
const initial = [
|
||||
{
|
||||
package: 'packageA',
|
||||
version: '^1.0.0',
|
||||
},
|
||||
];
|
||||
const customized = [
|
||||
{
|
||||
package: 'packageB',
|
||||
version: '^1.0.0',
|
||||
},
|
||||
];
|
||||
const upgrade = [
|
||||
{
|
||||
package: 'packageC',
|
||||
version: '^1.0.0',
|
||||
},
|
||||
];
|
||||
const resolvedValue = [
|
||||
{
|
||||
package: 'packageResolved',
|
||||
version: '^9.0.0',
|
||||
},
|
||||
];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "required_fields" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'required_fields';
|
||||
const humanizedFieldName = 'Required fields';
|
||||
const initial = [
|
||||
{
|
||||
name: 'fieldA',
|
||||
type: 'string',
|
||||
ecs: false,
|
||||
},
|
||||
];
|
||||
const customized = [
|
||||
{
|
||||
name: 'fieldB',
|
||||
type: 'string',
|
||||
ecs: false,
|
||||
},
|
||||
];
|
||||
const upgrade = [
|
||||
{
|
||||
name: 'fieldC',
|
||||
type: 'string',
|
||||
ecs: false,
|
||||
},
|
||||
];
|
||||
const resolvedValue = [
|
||||
{
|
||||
name: 'resolvedStringField',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "risk_score" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'risk_score';
|
||||
const humanizedFieldName = 'Risk Score';
|
||||
const initial = 10;
|
||||
const customized = 20;
|
||||
const upgrade = 30;
|
||||
const resolvedValue = 50;
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "risk_score_mapping" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedNumber: {
|
||||
name: 'resolvedNumberField',
|
||||
type: 'number',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'risk_score_mapping';
|
||||
const humanizedFieldName = 'Risk score override';
|
||||
const initial = [
|
||||
{
|
||||
field: 'fieldA',
|
||||
operator: 'equals',
|
||||
value: '10',
|
||||
risk_score: 10,
|
||||
},
|
||||
];
|
||||
const customized = [
|
||||
{
|
||||
field: 'fieldB',
|
||||
operator: 'equals',
|
||||
value: '30',
|
||||
risk_score: 30,
|
||||
},
|
||||
];
|
||||
const upgrade = [
|
||||
{
|
||||
field: 'fieldC',
|
||||
operator: 'equals',
|
||||
value: '50',
|
||||
risk_score: 50,
|
||||
},
|
||||
];
|
||||
const resolvedValue = [
|
||||
{
|
||||
field: 'resolvedNumberField',
|
||||
operator: 'equals',
|
||||
},
|
||||
];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "rule_name_override" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedString: {
|
||||
name: 'resolvedStringField',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'rule_name_override';
|
||||
const humanizedFieldName = 'Rule name override';
|
||||
const initial = { field_name: 'fieldA' };
|
||||
const customized = { field_name: 'fieldB' };
|
||||
const upgrade = { field_name: 'fieldC' };
|
||||
const resolvedValue = { field_name: 'resolvedStringField' };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "rule_schedule" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'rule_schedule';
|
||||
const humanizedFieldName = 'Rule Schedule';
|
||||
const initial = {
|
||||
interval: '5m',
|
||||
from: 'now-10m',
|
||||
to: 'now',
|
||||
};
|
||||
const customized = {
|
||||
interval: '10m',
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
};
|
||||
const upgrade = {
|
||||
interval: '15m',
|
||||
from: 'now-20m',
|
||||
to: 'now',
|
||||
};
|
||||
const resolvedValue = {
|
||||
interval: '1h',
|
||||
from: 'now-2h',
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "setup" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'setup';
|
||||
const humanizedFieldName = 'Setup';
|
||||
const initial = 'Initial setup';
|
||||
const customized = 'Custom setup';
|
||||
const upgrade = 'Updated setup';
|
||||
const resolvedValue = 'resolved setup';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "severity" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'severity';
|
||||
const humanizedFieldName = 'Severity';
|
||||
const initial = 'low';
|
||||
const customized = 'medium';
|
||||
const upgrade = 'high';
|
||||
const resolvedValue = 'critical';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "severity_mapping" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedString: {
|
||||
name: 'resolvedStringField',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'severity_mapping';
|
||||
const humanizedFieldName = 'Severity override';
|
||||
const initial = [
|
||||
{
|
||||
field: 'fieldA',
|
||||
operator: 'equals',
|
||||
severity: 'low',
|
||||
value: '10',
|
||||
},
|
||||
];
|
||||
const customized = [
|
||||
{
|
||||
field: 'fieldB',
|
||||
operator: 'equals',
|
||||
severity: 'medium',
|
||||
value: '30',
|
||||
},
|
||||
];
|
||||
const upgrade = [
|
||||
{
|
||||
field: 'fieldC',
|
||||
operator: 'equals',
|
||||
severity: 'high',
|
||||
value: '50',
|
||||
},
|
||||
];
|
||||
const resolvedValue = [
|
||||
{
|
||||
field: 'resolvedStringField',
|
||||
value: '70',
|
||||
operator: 'equals',
|
||||
severity: 'critical',
|
||||
},
|
||||
];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "threat" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'threat';
|
||||
const humanizedFieldName = 'MITRE ATT&CK\u2122';
|
||||
const initial = [
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
name: 'tacticA',
|
||||
id: 'tacticA',
|
||||
reference: 'reference',
|
||||
},
|
||||
},
|
||||
];
|
||||
const customized = [
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
name: 'tacticB',
|
||||
id: 'tacticB',
|
||||
reference: 'reference',
|
||||
},
|
||||
},
|
||||
];
|
||||
const upgrade = [
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
name: 'tacticC',
|
||||
id: 'tacticC',
|
||||
reference: 'reference',
|
||||
},
|
||||
},
|
||||
];
|
||||
const resolvedValue = [
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
name: 'Credential Access',
|
||||
id: 'TA0006',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0006/',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { mockTimelines } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "timeline_template" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockTimelines([
|
||||
{
|
||||
id: 'resolved',
|
||||
title: 'timelineResolved',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'timeline_template';
|
||||
const humanizedFieldName = 'Timeline template';
|
||||
const initial = { timeline_id: 'A', timeline_title: 'timelineA' };
|
||||
const customized = { timeline_id: 'B', timeline_title: 'timelineB' };
|
||||
const upgrade = { timeline_id: 'C', timeline_title: 'timelineC' };
|
||||
const resolvedValue = { timeline_id: 'resolved', timeline_title: 'timelineResolved' };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "timestamp_override" (query rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolvedDate: {
|
||||
name: 'resolvedDateField',
|
||||
type: 'date',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'timestamp_override';
|
||||
const humanizedFieldName = 'Timestamp override';
|
||||
const initial = { field_name: 'fieldA', fallback_disabled: false };
|
||||
const customized = { field_name: 'fieldB', fallback_disabled: false };
|
||||
const upgrade = { field_name: 'fieldC', fallback_disabled: false };
|
||||
const resolvedValue = { field_name: 'resolvedDateField', fallback_disabled: false };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "anomaly_threshold" (machine_learning rule type) after preview in flyout', () => {
|
||||
const ruleType = 'machine_learning';
|
||||
const fieldName = 'anomaly_threshold';
|
||||
const humanizedFieldName = 'Anomaly score threshold';
|
||||
const initial = 10;
|
||||
const customized = 20;
|
||||
const upgrade = 30;
|
||||
const resolvedValue = 40;
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { of } from 'rxjs';
|
||||
import { KibanaServices } from '../../../../../../../../common/lib/kibana';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "eql_query" (eql rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
// Mock EQL validation response. It shouldn't contain "errors" field for a valid EQL query.
|
||||
(KibanaServices.get().data.search.search as jest.Mock).mockReturnValue(of({}));
|
||||
});
|
||||
|
||||
const ruleType = 'eql';
|
||||
const fieldName = 'eql_query';
|
||||
const humanizedFieldName = 'EQL query';
|
||||
const initial = {
|
||||
query: 'any where true',
|
||||
language: 'eql',
|
||||
filters: [],
|
||||
};
|
||||
const customized = {
|
||||
query: 'host where host.name == "something"',
|
||||
language: 'eql',
|
||||
filters: [],
|
||||
};
|
||||
const upgrade = {
|
||||
query: 'process where process.name == "regsvr32.exe"',
|
||||
language: 'eql',
|
||||
filters: [],
|
||||
};
|
||||
const resolvedValue = {
|
||||
query: 'process where event.name == "resolved"',
|
||||
language: 'eql',
|
||||
filters: [],
|
||||
};
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "history_window_start" (new_terms rule type) after preview in flyout', () => {
|
||||
const ruleType = 'new_terms';
|
||||
const fieldName = 'history_window_start';
|
||||
const humanizedFieldName = 'History Window Size';
|
||||
const initial = 'now-1h';
|
||||
const customized = 'now-2h';
|
||||
const upgrade = 'now-3h';
|
||||
const resolvedValue = 'now-5h';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_integration',
|
||||
rootDir: '../../../../../../../../../../../../../..',
|
||||
roots: [
|
||||
'<rootDir>/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management',
|
||||
],
|
||||
testMatch: ['**/type_specific_fields/*.test.[jt]s?(x)'],
|
||||
openHandlesTimeout: 0,
|
||||
forceExit: true,
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { KqlQueryType } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "kql_query" (query rule type) after preview in flyout', () => {
|
||||
const ruleType = 'query';
|
||||
const fieldName = 'kql_query';
|
||||
const humanizedFieldName = 'KQL query';
|
||||
const initial = {
|
||||
query: '*:*',
|
||||
language: 'kuery',
|
||||
type: KqlQueryType.inline_query,
|
||||
filters: [],
|
||||
};
|
||||
const customized = {
|
||||
query: '*:*',
|
||||
language: 'kuery',
|
||||
type: KqlQueryType.inline_query,
|
||||
filters: [],
|
||||
};
|
||||
const upgrade = {
|
||||
query: 'process.name:*.sys',
|
||||
language: 'kuery',
|
||||
type: KqlQueryType.inline_query,
|
||||
filters: [],
|
||||
};
|
||||
const resolvedValue = {
|
||||
query: '*:resolved',
|
||||
language: 'kuery',
|
||||
type: KqlQueryType.inline_query,
|
||||
filters: [],
|
||||
};
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
import { mockKibanaFetchResponse } from '../../test_utils/rule_upgrade_flyout';
|
||||
|
||||
describe('Upgrade diffable rule "machine_learning_job_id" (machine_learning rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockKibanaFetchResponse('/internal/ml/ml_capabilities', {
|
||||
capabilities: {
|
||||
isADEnabled: true,
|
||||
isDFAEnabled: true,
|
||||
isNLPEnabled: true,
|
||||
canCreateJob: true,
|
||||
canDeleteJob: true,
|
||||
canOpenJob: true,
|
||||
canCloseJob: true,
|
||||
canResetJob: true,
|
||||
canUpdateJob: true,
|
||||
canForecastJob: true,
|
||||
canDeleteForecast: true,
|
||||
canCreateDatafeed: true,
|
||||
canDeleteDatafeed: true,
|
||||
canStartStopDatafeed: true,
|
||||
canUpdateDatafeed: true,
|
||||
canPreviewDatafeed: true,
|
||||
canGetFilters: true,
|
||||
canCreateCalendar: true,
|
||||
canDeleteCalendar: true,
|
||||
canCreateFilter: true,
|
||||
canDeleteFilter: true,
|
||||
canCreateDataFrameAnalytics: true,
|
||||
canDeleteDataFrameAnalytics: true,
|
||||
canStartStopDataFrameAnalytics: true,
|
||||
canCreateMlAlerts: true,
|
||||
canUseMlAlerts: true,
|
||||
canViewMlNodes: true,
|
||||
canCreateTrainedModels: true,
|
||||
canDeleteTrainedModels: true,
|
||||
canStartStopTrainedModels: true,
|
||||
canCreateInferenceEndpoint: true,
|
||||
canGetJobs: true,
|
||||
canGetDatafeeds: true,
|
||||
canGetCalendars: true,
|
||||
canFindFileStructure: true,
|
||||
canGetDataFrameAnalytics: true,
|
||||
canGetAnnotations: true,
|
||||
canCreateAnnotation: true,
|
||||
canDeleteAnnotation: true,
|
||||
canGetTrainedModels: true,
|
||||
canTestTrainedModels: true,
|
||||
canGetFieldInfo: true,
|
||||
canGetMlInfo: true,
|
||||
canUseAiops: true,
|
||||
},
|
||||
upgradeInProgress: false,
|
||||
isPlatinumOrTrialLicense: true,
|
||||
mlFeatureEnabledInSpace: true,
|
||||
});
|
||||
|
||||
mockKibanaFetchResponse('/internal/ml/jobs/jobs_summary', [
|
||||
{
|
||||
id: 'jobResolved',
|
||||
description: 'jobResolved',
|
||||
groups: [],
|
||||
jobState: 'opened',
|
||||
datafeedIndices: [],
|
||||
hasDatafeed: true,
|
||||
datafeedId: 'jobResolved',
|
||||
datafeedState: '',
|
||||
isSingleMetricViewerJob: true,
|
||||
awaitingNodeAssignment: false,
|
||||
jobTags: {},
|
||||
bucketSpanSeconds: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
mockKibanaFetchResponse('/internal/ml/modules/get_module/', [
|
||||
{
|
||||
id: 'security_network',
|
||||
title: 'test-module',
|
||||
description: 'test-module',
|
||||
type: 'test-module',
|
||||
logoFile: 'test-module',
|
||||
defaultIndexPattern: 'test-module',
|
||||
query: {},
|
||||
jobs: [
|
||||
{
|
||||
id: 'jobResolved',
|
||||
config: {
|
||||
groups: [],
|
||||
description: '',
|
||||
analysis_config: {
|
||||
bucket_span: '1m',
|
||||
detectors: [],
|
||||
influencers: [],
|
||||
},
|
||||
analysis_limits: {
|
||||
model_memory_limit: '1mb',
|
||||
},
|
||||
data_description: {
|
||||
time_field: '@timestamp',
|
||||
},
|
||||
custom_settings: {
|
||||
created_by: 'test',
|
||||
custom_urls: [],
|
||||
},
|
||||
job_type: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
datafeeds: [],
|
||||
kibana: {},
|
||||
},
|
||||
]);
|
||||
|
||||
mockKibanaFetchResponse(
|
||||
'/internal/ml/modules/recognize/apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*',
|
||||
[
|
||||
{
|
||||
id: 'test-module',
|
||||
title: 'test-module',
|
||||
query: {},
|
||||
description: 'test-module',
|
||||
logo: {
|
||||
icon: 'test-module',
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
const ruleType = 'machine_learning';
|
||||
const fieldName = 'machine_learning_job_id';
|
||||
const humanizedFieldName = 'Machine Learning job';
|
||||
const initial = ['jobA'];
|
||||
const customized = ['jobB'];
|
||||
const upgrade = ['jobC'];
|
||||
const resolvedValue = ['jobResolved'];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "new_terms_fields" (new_terms rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolved: {
|
||||
name: 'resolved',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'new_terms';
|
||||
const fieldName = 'new_terms_fields';
|
||||
const humanizedFieldName = 'New Terms Fields';
|
||||
const initial = ['fieldA'];
|
||||
const customized = ['fieldB'];
|
||||
const upgrade = ['fieldA', 'fieldC'];
|
||||
const resolvedValue = ['resolved'];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "threat_index" (threat_match rule type) after preview in flyout', () => {
|
||||
const ruleType = 'threat_match';
|
||||
const fieldName = 'threat_index';
|
||||
const humanizedFieldName = 'Indicator index patterns';
|
||||
const initial = ['indexA'];
|
||||
const customized = ['indexB'];
|
||||
const upgrade = ['indexC'];
|
||||
const resolvedValue = ['resolved'];
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "threat_indicator_path" (threat_match rule type) after preview in flyout', () => {
|
||||
const ruleType = 'threat_match';
|
||||
const fieldName = 'threat_indicator_path';
|
||||
const humanizedFieldName = 'Indicator prefix override';
|
||||
const initial = 'fieldA';
|
||||
const customized = 'fieldB';
|
||||
const upgrade = 'fieldC';
|
||||
const resolvedValue = 'resolvedStringField';
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { KqlQueryType } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "threat_query" (threat_match rule type) after preview in flyout', () => {
|
||||
const ruleType = 'threat_match';
|
||||
const fieldName = 'threat_query';
|
||||
const humanizedFieldName = 'Indicator index query';
|
||||
const initial = {
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'process.name:*.exe',
|
||||
language: 'kuery',
|
||||
filters: [],
|
||||
};
|
||||
const customized = {
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'process.name:*.sys',
|
||||
language: 'kuery',
|
||||
filters: [],
|
||||
};
|
||||
const upgrade = {
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'process.name:*.com',
|
||||
language: 'kuery',
|
||||
filters: [],
|
||||
};
|
||||
const resolvedValue = {
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'process.name:*.sys',
|
||||
language: 'kuery',
|
||||
filters: [],
|
||||
};
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { mockAvailableDataViews } from '../../test_utils/rule_upgrade_flyout';
|
||||
import { assertRuleUpgradePreview } from '../../test_utils/assert_rule_upgrade_preview';
|
||||
import { assertRuleUpgradeAfterReview } from '../../test_utils/assert_rule_upgrade_after_review';
|
||||
|
||||
describe('Upgrade diffable rule "threshold" (threshold rule type) after preview in flyout', () => {
|
||||
beforeAll(() => {
|
||||
mockAvailableDataViews([], {
|
||||
resolved: {
|
||||
name: 'resolved',
|
||||
type: 'string',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const ruleType = 'threshold';
|
||||
const fieldName = 'threshold';
|
||||
const humanizedFieldName = 'Threshold';
|
||||
const initial = { value: 10, field: ['fieldA'] };
|
||||
const customized = { value: 20, field: ['fieldB'] };
|
||||
const upgrade = { value: 30, field: ['fieldC'] };
|
||||
const resolvedValue = { value: 50, field: ['resolved'] };
|
||||
|
||||
assertRuleUpgradePreview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
humanizedFieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
|
||||
assertRuleUpgradeAfterReview({
|
||||
ruleType,
|
||||
fieldName,
|
||||
fieldVersions: {
|
||||
initial,
|
||||
customized,
|
||||
upgrade,
|
||||
resolvedValue,
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const initialState = {
|
||||
loading: false,
|
||||
isSignalIndexExists: true,
|
||||
isAuthenticated: true,
|
||||
hasEncryptionKey: true,
|
||||
canUserCRUD: true,
|
||||
canUserREAD: true,
|
||||
hasIndexManage: true,
|
||||
hasIndexMaintenance: true,
|
||||
hasIndexWrite: true,
|
||||
hasIndexRead: true,
|
||||
hasIndexUpdateDelete: true,
|
||||
signalIndexName: true,
|
||||
signalIndexMappingOutdated: true,
|
||||
};
|
||||
|
||||
export const useUserData = jest.fn().mockReturnValue([initialState]);
|
||||
|
||||
export const useUserInfo = jest.fn().mockReturnValue(initialState);
|
|
@ -19,6 +19,7 @@ import {
|
|||
INSTALL_PREBUILT_RULE_PREVIEW,
|
||||
UPDATE_PREBUILT_RULE_PREVIEW,
|
||||
UPDATE_PREBUILT_RULE_BUTTON,
|
||||
FIELD_UPGRADE_WRAPPER,
|
||||
PER_FIELD_DIFF_WRAPPER,
|
||||
PER_FIELD_DIFF_DEFINITION_SECTION,
|
||||
} from '../../../../screens/alerts_detection_rules';
|
||||
|
@ -1169,14 +1170,15 @@ describe(
|
|||
openRuleUpdatePreview(OUTDATED_RULE_1['security-rule'].name);
|
||||
assertSelectedPreviewTab(PREVIEW_TABS.UPDATES); // Should be open by default
|
||||
|
||||
cy.get(PER_FIELD_DIFF_WRAPPER).should('have.length', 1);
|
||||
cy.get(PER_FIELD_DIFF_WRAPPER).last().contains('Name').should('be.visible');
|
||||
const nameFieldUpgradeWrapper = FIELD_UPGRADE_WRAPPER('name');
|
||||
cy.get(nameFieldUpgradeWrapper).should('have.length', 1);
|
||||
cy.get(nameFieldUpgradeWrapper).last().contains('Name').should('be.visible');
|
||||
|
||||
// expand Name field section
|
||||
cy.get(PER_FIELD_DIFF_WRAPPER).last().contains('Name').click();
|
||||
cy.get(nameFieldUpgradeWrapper).last().contains('Name').click();
|
||||
|
||||
cy.get(PER_FIELD_DIFF_WRAPPER).last().contains('Outdated rule 1').should('be.visible');
|
||||
cy.get(PER_FIELD_DIFF_WRAPPER).last().contains('Updated rule 1').should('be.visible');
|
||||
cy.get(nameFieldUpgradeWrapper).last().contains('Outdated rule 1').should('be.visible');
|
||||
cy.get(nameFieldUpgradeWrapper).last().contains('Updated rule 1').should('be.visible');
|
||||
});
|
||||
|
||||
it('User can see changes when updated rule is a different rule type', () => {
|
||||
|
|
|
@ -359,6 +359,8 @@ export const ESQL_QUERY_TITLE = '[data-test-subj="esqlQueryPropertyTitle"]';
|
|||
export const ESQL_QUERY_VALUE = '[data-test-subj="esqlQueryPropertyValue"]';
|
||||
|
||||
export const PER_FIELD_DIFF_WRAPPER = '[data-test-subj="ruleUpgradePerFieldDiffWrapper"]';
|
||||
export const FIELD_UPGRADE_WRAPPER = (fieldName: string) =>
|
||||
`[data-test-subj="${fieldName}-upgradeWrapper"]`;
|
||||
export const PER_FIELD_DIFF_DEFINITION_SECTION = '[data-test-subj="perFieldDiffDefinitionSection"]';
|
||||
|
||||
export const MODIFIED_RULE_BADGE = '[data-test-subj="upgradeRulesTableModifiedColumnBadge"]';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue