diff --git a/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade.md b/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade.md index 4beb517f9598a..ed93f04d4c075 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade.md +++ b/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade.md @@ -116,6 +116,47 @@ Status: `in progress`. The current test plan matches [Rule Immutability/Customiz - User should be able to install prebuilt rules with and without previewing what exactly they would install (rule properties). - User should be able to upgrade prebuilt rules with and without previewing what updates they would apply (rule properties of target rule versions). +- User should be able to review and perform upgrades for all diffable fields across rule types: + +| Field Name | Rule Type | Field Type | +|------------------------|-------------------|---------------------| +| name | Common | Single Line String | +| severity | Common | Single Line String | +| description | Common | Multi Line String | +| note | Common | Multi Line String | +| setup | Common | Multi Line String | +| risk_score | Common | Number | +| max_signals | Common | Number | +| tags | Common | Scalar Array | +| references | Common | Scalar Array | +| severity_mapping | Common | Simple | +| risk_score_mapping | Common | Simple | +| false_positives | Common | Simple | +| threat | Common | Simple | +| related_integrations | Common | Simple | +| required_fields | Common | Simple | +| rule_schedule | Common | Simple | +| rule_name_override | Common | Simple | +| timestamp_override | Common | Simple | +| timeline_template | Common | Simple | +| building_block | Common | Simple | +| investigation_fields | Common | Simple | +| alert_suppression | Common | Simple | +| data_source | Common | Data Source | +| type | Common | Rule Type | +| version | Common | Force Target Version| +| kql_query | query | KQL Query | +| threat_indicator_path | threat_match | Single Line String | +| threat_query | threat_match | KQL Query | +| threat_index | threat_match | Scalar Array | +| threshold | threshold | Simple | +| anomaly_threshold | machine_learning | Number | +| machine_learning_job_id| machine_learning | Simple | +| history_window_start | new_terms | Single Line String | +| new_terms_fields | new_terms | Scalar Array | +| eql_query | eql | EQL Query | +| esql_query | esql | ESQL Query | + - If user chooses to preview a prebuilt rule to be installed/upgraded, we currently show this preview in a flyout. - In the prebuilt rule preview a tab that doesn't have any sections should not be displayed and a section that doesn't have any properties also should not be displayed. diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index b861a8432797b..42f27db0c8982 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -337,3 +337,44 @@ const allFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { ...newTermsFieldsDiffAlgorithms, type: ruleTypeDiffAlgorithm, }; + +export type SINGLE_LINE_STRING_FIELDS = + | 'name' + | 'severity' + | 'threat_indicator_path' + | 'history_window_start'; + +export type MULTI_LINE_STRING_FIELDS = 'description' | 'note' | 'setup'; + +export type NUMBER_FIELDS = 'risk_score' | 'max_signals' | 'anomaly_threshold'; + +export type SCALAR_ARRAY_FIELDS = 'tags' | 'references' | 'threat_index' | 'new_terms_fields'; + +export type SIMPLE_FIELDS = + | 'severity_mapping' + | 'risk_score_mapping' + | 'false_positives' + | 'threat' + | 'related_integrations' + | 'required_fields' + | 'rule_schedule' + | 'rule_name_override' + | 'timestamp_override' + | 'timeline_template' + | 'building_block' + | 'investigation_fields' + | 'alert_suppression' + | 'threshold' + | 'machine_learning_job_id'; + +export type KQL_QUERY_FIELDS = 'kql_query' | 'threat_query'; + +export type EQL_QUERY_FIELDS = 'eql_query'; + +export type ESQL_QUERY_FIELDS = 'esql_query'; + +export type DATA_SOURCE_FIELDS = 'data_source'; + +export type RULE_TYPE_FIELDS = 'type'; + +export type FORCE_TARGET_VERSION_FIELDS = 'version'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts index 6442582c1b573..392f2e2308913 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.mock.ts @@ -81,6 +81,7 @@ export const getPrebuiltThreatMatchRuleSpecificFieldsMock = (): ThreatMatchRuleC ], }, ], + threat_indicator_path: 'threat.indicator.mock', concurrent_searches: 2, items_per_search: 10, }); @@ -109,7 +110,7 @@ export const getPrebuiltNewTermsRuleSpecificFieldsMock = (): NewTermsRuleCreateF query: 'user.name: *', language: 'kuery', new_terms_fields: ['user.name'], - history_window_start: '1h', + history_window_start: 'now-1h', }); export const getPrebuiltEsqlRuleSpecificFieldsMock = (): EsqlRuleCreateFields => ({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts index 46db3e2602702..77f5f46dea2ca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { - describe('Rules Management - Prebuilt Rules - Prebuilt Rules Management', function () { + describe('Rules Management - Prebuilt Rules - Prebuilt Rules Management - ', function () { loadTestFile(require.resolve('./bootstrap_prebuilt_rules')); loadTestFile(require.resolve('./get_prebuilt_rules_status')); loadTestFile(require.resolve('./get_prebuilt_timelines_status')); @@ -20,6 +20,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./upgrade_perform_prebuilt_rules.all_rules_mode')); loadTestFile(require.resolve('./upgrade_perform_prebuilt_rules.specific_rules_mode')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.rule_type_fields')); + loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.simple_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.number_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.single_line_string_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.scalar_array_fields')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts index 9ee99df7a977a..8a9d48b9a1dfa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.all_rules_mode.ts @@ -6,500 +6,657 @@ */ import expect from 'expect'; -import type SuperTest from 'supertest'; import { cloneDeep } from 'lodash'; +import type SuperTest from 'supertest'; import { - QueryRuleCreateFields, - EqlRuleCreateFields, - EsqlRuleCreateFields, - RuleResponse, + ModeEnum, + AllFieldsDiff, ThreatMatchRuleCreateFields, ThreatMatchRule, FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, - ModeEnum, - AllFieldsDiff, DataSourceIndexPatterns, QueryRule, + QueryRuleCreateFields, } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; +import { + NUMBER_FIELDS, + SINGLE_LINE_STRING_FIELDS, + SIMPLE_FIELDS, + MULTI_LINE_STRING_FIELDS, + SCALAR_ARRAY_FIELDS, + KQL_QUERY_FIELDS, +} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObjectOfType, installPrebuiltRules, performUpgradePrebuiltRules, - patchRule, createHistoricalPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, getInstalledRules, + createRuleAssetSavedObjectOfType, + updateRule, + fetchRule, createRuleAssetSavedObject, + patchRule, getWebHookAction, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - const securitySolutionApi = getService('securitySolutionApi'); - - describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - mode: ALL_RULES', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - const CURRENT_NAME = 'My current name'; - const CURRENT_TAGS = ['current', 'tags']; - const TARGET_NAME = 'My target name'; - const TARGET_TAGS = ['target', 'tags']; - - describe(`successful updates`, () => { - const queryRule = createRuleAssetSavedObjectOfType('query'); - const eqlRule = createRuleAssetSavedObjectOfType('eql'); - const esqlRule = createRuleAssetSavedObjectOfType('esql'); - - const basePrebuiltAssets = [queryRule, eqlRule, esqlRule]; - const basePrebuiltAssetsMap = createIdToRuleMap( - basePrebuiltAssets.map((r) => r['security-rule']) - ); - - const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { - const targetObject = cloneDeep(ruleAssetSavedObject); - targetObject['security-rule'].version += 1; - targetObject['security-rule'].name = TARGET_NAME; - targetObject['security-rule'].tags = TARGET_TAGS; - - return targetObject; - }); - +import { + SINGLE_LINE_STRING_FIELDS_MOCK_VALUES, + SINGLE_LINE_FIELD_RULE_TYPE_MAPPING, + SingleLineStringFieldTestValues, + NUMBER_FIELD_RULE_TYPE_MAPPING, + NUMBER_FIELDS_MOCK_VALUES, + NumberFieldTestValues, + SIMPLE_FIELD_RULE_TYPE_MAPPING, + SIMPLE_FIELDS_MOCK_VALUES, + SimpleFieldTestValues, + MULTI_LINE_STRING_FIELDS_MOCK_VALUES, + MULTI_LINE_FIELD_RULE_TYPE_MAPPING, + MultiLineStringFieldTestValues, + KQL_QUERY_FIELD_RULE_TYPE_MAPPING, + KQL_QUERY_FIELDS_MOCK_VALUES, + KqlQueryFieldTestValues, + mapKQLQueryDiffableFieldToRuleFields, + mapDiffableFieldToRuleFields, + SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING, + SCALAR_ARRAY_FIELDS_MOCK_VALUES, + ScalarArrayFieldTestValues, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = + | typeof SINGLE_LINE_FIELD_RULE_TYPE_MAPPING + | typeof MULTI_LINE_FIELD_RULE_TYPE_MAPPING + | typeof NUMBER_FIELD_RULE_TYPE_MAPPING + | typeof SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING + | typeof KQL_QUERY_FIELD_RULE_TYPE_MAPPING + | typeof SIMPLE_FIELD_RULE_TYPE_MAPPING; + +type FieldType = + | SINGLE_LINE_STRING_FIELDS + | MULTI_LINE_STRING_FIELDS + | NUMBER_FIELDS + | SCALAR_ARRAY_FIELDS + | KQL_QUERY_FIELDS + | SIMPLE_FIELDS; + +type TestValues = + | SingleLineStringFieldTestValues + | MultiLineStringFieldTestValues + | NumberFieldTestValues + | ScalarArrayFieldTestValues + | KqlQueryFieldTestValues + | SimpleFieldTestValues; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: T, + testValues: TestValues, + mapperFunction: ((field: T, value: any) => Record) | null, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + // Map DiffableRuleFields to their respective fields from rule schema, where appropriate + const getMappedFields = (version: TestValues) => + mapperFunction ? mapperFunction(field, version) : { [field]: version }; + + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + ...getMappedFields(baseValue), + }), + ]; + + describe('successful updates', () => { it('upgrades all upgreadeable rules fields to their BASE versions', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Perform the upgrade, all rules' fields to their BASE versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.ALL_RULES, pick_version: 'BASE', }); - expect(performUpgradeResponse.summary.succeeded).toEqual(3); - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); - if (!matchingBaseAsset) { - throw new Error(`Could not find matching base asset for rule ${updatedRule.rule_id}`); - } - - // Rule Version should be incremented by 1 - // Rule Name and Tags should match the base asset's values, not the Target asset's values - expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); - expect(updatedRule.name).toEqual(matchingBaseAsset.name); - expect(updatedRule.tags).toEqual(matchingBaseAsset.tags); - }); + expect(performResponse.summary.succeeded).toBe(1); + expect(performResponse.summary.failed).toBe(0); - // Get installed rules const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - for (const [ruleId, installedRule] of installedRulesMap) { - const matchingBaseAsset = basePrebuiltAssetsMap.get(ruleId); - expect(installedRule.name).toEqual(matchingBaseAsset?.name); - expect(installedRule.tags).toEqual(matchingBaseAsset?.tags); - } + Object.entries(getMappedFields(baseValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + }); }); it('upgrades all upgreadeable rules fields to their CURRENT versions', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Patch all 3 installed rules to create a current version for each - for (const baseRule of basePrebuiltAssets) { - await patchRule(supertest, log, { - rule_id: baseRule['security-rule'].rule_id, - name: CURRENT_NAME, - tags: CURRENT_TAGS, - }); - } + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), + }); + + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Perform the upgrade, all rules' fields to their CURRENT versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.ALL_RULES, pick_version: 'CURRENT', }); - expect(performUpgradeResponse.summary.succeeded).toEqual(3); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); - // Rule Version should be incremented by 1 - // Rule Query should match the current's version query - if (matchingBaseAsset) { - expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); - expect(updatedRule.name).toEqual(CURRENT_NAME); - expect(updatedRule.tags).toEqual(CURRENT_TAGS); - } else { - throw new Error(`Matching base asset not found for rule_id: ${updatedRule.rule_id}`); - } - }); + expect(performResponse.summary.succeeded).toBe(1); + expect(performResponse.summary.failed).toBe(0); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - for (const [_, installedRule] of installedRulesMap) { - expect(installedRule.name).toEqual(CURRENT_NAME); - expect(installedRule.tags).toEqual(CURRENT_TAGS); - } + Object.entries(getMappedFields(customValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + }); }); it('upgrades all upgreadeable rules fields to their TARGET versions', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Patch all 3 installed rules to create a current version for each - for (const baseRule of basePrebuiltAssets) { - await patchRule(supertest, log, { - rule_id: baseRule['security-rule'].rule_id, - query: CURRENT_NAME, - tags: CURRENT_TAGS, - }); - } + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Perform the upgrade, all rules' fields to their CURRENT versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.ALL_RULES, pick_version: 'TARGET', }); - expect(performUpgradeResponse.summary.succeeded).toEqual(3); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const matchingBaseAsset = basePrebuiltAssetsMap.get(updatedRule.rule_id); - - // Rule Version should be incremented by 1 - // Rule Query should match the current's version query - if (matchingBaseAsset) { - expect(updatedRule.version).toEqual(matchingBaseAsset.version + 1); - expect(updatedRule.name).toEqual(TARGET_NAME); - expect(updatedRule.tags).toEqual(TARGET_TAGS); - } else { - throw new Error(`Matching base asset not found for rule_id: ${updatedRule.rule_id}`); - } - }); + expect(performResponse.summary.succeeded).toBe(1); + expect(performResponse.summary.failed).toBe(0); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - for (const [_, installedRule] of installedRulesMap) { - expect(installedRule.name).toEqual(TARGET_NAME); - expect(installedRule.tags).toEqual(TARGET_TAGS); - } + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + }); }); it('upgrades all upgreadeable rules fields to their MERGED versions', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Call the /upgrade/_review endpoint to save the calculated merged_versions const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const reviewRuleResponseMap = new Map( - reviewResponse.rules.map((upgradeInfo) => [ - upgradeInfo.rule_id, - { - tags: upgradeInfo.diff.fields.tags?.merged_version, - name: upgradeInfo.diff.fields.name?.merged_version, - }, - ]) - ); + const mergedVersion = (reviewResponse.rules[0].diff.fields as AllFieldsDiff)[field] + ?.merged_version; - // Perform the upgrade, all rules' fields to their MERGED versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.ALL_RULES, pick_version: 'MERGED', }); - const updatedRulesMap = createIdToRuleMap(performUpgradeResponse.results.updated); - // All upgrades should succeed: neither query nor tags should have a merge conflict - expect(performUpgradeResponse.summary.succeeded).toEqual(3); + expect(performResponse.summary.succeeded).toBe(1); + expect(performResponse.summary.failed).toBe(0); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - for (const [ruleId, installedRule] of installedRulesMap) { - expect(installedRule.name).toEqual(updatedRulesMap.get(ruleId)?.name); - expect(installedRule.name).toEqual(reviewRuleResponseMap.get(ruleId)?.name); - expect(installedRule.tags).toEqual(updatedRulesMap.get(ruleId)?.tags); - expect(installedRule.tags).toEqual(reviewRuleResponseMap.get(ruleId)?.tags); - } + Object.entries(getMappedFields(mergedVersion as unknown as TestValues)).forEach( + ([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + } + ); }); + }); - it('correctly upgrades rules with DataSource diffs to their MERGED versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [queryRule]); + describe('edge cases and unhappy paths', () => { + it('rejects updates of rules with a pick_version of MERGED which have fields which result in conflicts', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - const targetObject = cloneDeep(queryRule); - targetObject['security-rule'].version += 1; - targetObject['security-rule'].name = TARGET_NAME; - targetObject['security-rule'].tags = TARGET_TAGS; - targetObject['security-rule'].index = ['auditbeat-*']; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetObject]); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), + }); - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const ruleDiffFields = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.ALL_RULES, pick_version: 'MERGED', }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); + expect(performResponse.summary.succeeded).toBe(0); + expect(performResponse.summary.failed).toBe(1); + expect(performResponse.errors[0].message).toContain('Merge conflicts found'); + }); + }); + }); +}; - const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data[0] as QueryRule; +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const securitySolutionApi = getService('securitySolutionApi'); - expect(installedRule.name).toEqual(ruleDiffFields.name.merged_version); - expect(installedRule.tags).toEqual(ruleDiffFields.tags.merged_version); + describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - ALL Rules mode - ', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - // Check that the updated rules has an `index` field which equals the output of the diff algorithm - // for the DataSource diffable field, and that the data_view_id is correspondingly set to undefined. - expect(installedRule.index).toEqual( - (ruleDiffFields.data_source.merged_version as DataSourceIndexPatterns).index_patterns - ); - expect(installedRule.data_view_id).toBe(undefined); + describe('Single Line String Fields - ', () => { + Object.entries(SINGLE_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = + SINGLE_LINE_STRING_FIELDS_MOCK_VALUES[field as SINGLE_LINE_STRING_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SINGLE_LINE_STRING_FIELDS, + testValues, + null, + { es, supertest, log } + ); + }); + }); }); }); - describe('edge cases and unhappy paths', () => { - const firstQueryRule = createRuleAssetSavedObject({ - type: 'query', - language: 'kuery', - rule_id: 'query-rule-1', + describe('Multi Line String Fields - ', () => { + Object.entries(MULTI_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = + MULTI_LINE_STRING_FIELDS_MOCK_VALUES[field as MULTI_LINE_STRING_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as MULTI_LINE_STRING_FIELDS, + testValues, + null, + { es, supertest, log } + ); + }); + }); }); - const secondQueryRule = createRuleAssetSavedObject({ - type: 'query', - language: 'kuery', - rule_id: 'query-rule-2', + }); + + describe('Number Fields - ', () => { + Object.entries(NUMBER_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = NUMBER_FIELDS_MOCK_VALUES[field as NUMBER_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as NUMBER_FIELDS, + testValues, + null, + { + es, + supertest, + log, + } + ); + }); + }); }); - const eqlRule = createRuleAssetSavedObject({ - type: 'eql', - language: 'eql', - rule_id: 'eql-rule', + }); + + describe('Scalar Array Fields - ', () => { + Object.entries(SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = SCALAR_ARRAY_FIELDS_MOCK_VALUES[field as SCALAR_ARRAY_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SCALAR_ARRAY_FIELDS, + testValues, + null, + { + es, + supertest, + log, + } + ); + }); + }); }); + }); - const basePrebuiltAssets = [firstQueryRule, eqlRule, secondQueryRule]; + describe('KQL Query Fields - ', () => { + Object.entries(KQL_QUERY_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = KQL_QUERY_FIELDS_MOCK_VALUES[field as KQL_QUERY_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as KQL_QUERY_FIELDS, + testValues, + mapKQLQueryDiffableFieldToRuleFields, + { + es, + supertest, + log, + } + ); + }); + }); + }); + }); - it('rejects all updates of rules which have a rule type change if the pick_version is not TARGET', async () => { - // Install base prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); - await installPrebuiltRules(es, supertest); + describe('Simple Diff Algorithm Fields - ', () => { + Object.entries(SIMPLE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = SIMPLE_FIELDS_MOCK_VALUES[field]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SIMPLE_FIELDS, + testValues, + mapDiffableFieldToRuleFields, + { + es, + supertest, + log, + } + ); + }); + }); + }); + }); - // Mock a rule type change to 'ml' to the first two rules of the basePrebuiltAssets array - const targetMLPrebuiltAssets = basePrebuiltAssets - .slice(0, 2) - .map((ruleAssetSavedObject) => { - const targetObject = cloneDeep(ruleAssetSavedObject); + // TODO: Maybe move these scenarios to a different file? + describe('Scenarios for all rule types', () => { + const CURRENT_NAME = 'My current name'; + const CURRENT_TAGS = ['current', 'tags']; + const TARGET_NAME = 'My target name'; + const TARGET_TAGS = ['target', 'tags']; - return { - ...targetObject, - ...createRuleAssetSavedObject({ - rule_id: targetObject['security-rule'].rule_id, - version: targetObject['security-rule'].version + 1, - type: 'machine_learning', - machine_learning_job_id: 'job_id', - anomaly_threshold: 1, - }), - }; - }); + describe(`successful updates`, () => { + it('correctly upgrades rules with DataSource diffs to their MERGED versions', async () => { + const queryRule = createRuleAssetSavedObjectOfType('query'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [queryRule]); + await installPrebuiltRules(es, supertest); - // Mock an normal update of the rule 'query-rule-2', with NO rule type change - const targetAssetSameTypeUpdate = createRuleAssetSavedObject({ - type: 'query', - language: 'kuery', - rule_id: 'query-rule-2', - version: 2, - }); + const targetObject = cloneDeep(queryRule); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + targetObject['security-rule'].index = ['auditbeat-*']; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetObject]); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - ...targetMLPrebuiltAssets, - targetAssetSameTypeUpdate, - ]); + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const ruleDiffFields = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - // Perform the upgrade, all rules' fields to their BASE versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.ALL_RULES, - pick_version: 'BASE', - }); + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'MERGED', + }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); // update of same type - expect(performUpgradeResponse.summary.failed).toEqual(2); // updates with rule type change + expect(performUpgradeResponse.summary.succeeded).toEqual(1); - expect(performUpgradeResponse.errors).toHaveLength(2); - performUpgradeResponse.errors.forEach((error) => { - const ruleId = error.rules[0].rule_id; - expect(error.message).toContain( - `Rule update for rule ${ruleId} has a rule type change. All 'pick_version' values for rule must match 'TARGET'` + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data[0] as QueryRule; + + expect(installedRule.name).toEqual(ruleDiffFields.name.merged_version); + expect(installedRule.tags).toEqual(ruleDiffFields.tags.merged_version); + + // Check that the updated rules has an `index` field which equals the output of the diff algorithm + // for the DataSource diffable field, and that the data_view_id is correspondingly set to undefined. + expect(installedRule.index).toEqual( + (ruleDiffFields.data_source.merged_version as DataSourceIndexPatterns).index_patterns ); + expect(installedRule.data_view_id).toBe(undefined); }); }); - it('rejects updates of rules with a pick_version of MERGED which have fields which result in conflicts in the three way diff calculations', async () => { - // Install base prebuilt detection rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); - await installPrebuiltRules(es, supertest); + describe('edge cases and unhappy paths', () => { + const firstQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-1', + }); + const secondQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + }); + const eqlRule = createRuleAssetSavedObject({ + type: 'eql', + language: 'eql', + rule_id: 'eql-rule', + }); - // Patch all 3 installed rules to create a current version for each - for (const baseRule of basePrebuiltAssets) { - await patchRule(supertest, log, { - rule_id: baseRule['security-rule'].rule_id, - name: CURRENT_NAME, - tags: CURRENT_TAGS, + const basePrebuiltAssets = [firstQueryRule, eqlRule, secondQueryRule]; + + it('rejects all updates of rules which have a rule type change if the pick_version is not TARGET', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Mock a rule type change to 'ml' to the first two rules of the basePrebuiltAssets array + const targetMLPrebuiltAssets = basePrebuiltAssets + .slice(0, 2) + .map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + + return { + ...targetObject, + ...createRuleAssetSavedObject({ + rule_id: targetObject['security-rule'].rule_id, + version: targetObject['security-rule'].version + 1, + type: 'machine_learning', + machine_learning_job_id: 'job_id', + anomaly_threshold: 1, + }), + }; + }); + + // Mock an normal update of the rule 'query-rule-2', with NO rule type change + const targetAssetSameTypeUpdate = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + version: 2, }); - } - const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { - const targetObject = cloneDeep(ruleAssetSavedObject); - targetObject['security-rule'].version += 1; - targetObject['security-rule'].name = TARGET_NAME; - targetObject['security-rule'].tags = TARGET_TAGS; + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + ...targetMLPrebuiltAssets, + targetAssetSameTypeUpdate, + ]); - return targetObject; - }); + // Perform the upgrade, all rules' fields to their BASE versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'BASE', + }); - // Create new versions of the assets of the installed rules - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + expect(performUpgradeResponse.summary.succeeded).toEqual(1); // update of same type + expect(performUpgradeResponse.summary.failed).toEqual(2); // updates with rule type change - // Perform the upgrade, all rules' fields to their MERGED versions - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.ALL_RULES, - pick_version: 'MERGED', + expect(performUpgradeResponse.errors).toHaveLength(2); + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Rule update for rule ${ruleId} has a rule type change. All 'pick_version' values for rule must match 'TARGET'` + ); + }); }); - expect(performUpgradeResponse.summary.succeeded).toEqual(0); // all rules have conflicts - expect(performUpgradeResponse.summary.failed).toEqual(3); // all rules have conflicts + it('rejects updates of rules with a pick_version of MERGED which have fields which result in conflicts in the three way diff calculations', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Patch all 3 installed rules to create a current version for each + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } - performUpgradeResponse.errors.forEach((error) => { - const ruleId = error.rules[0].rule_id; - expect(error.message).toContain( - `Merge conflicts found in rule '${ruleId}' for fields: name, tags. Please resolve the conflict manually or choose another value for 'pick_version'` - ); + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + + return targetObject; + }); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their MERGED versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'MERGED', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(0); // all rules have conflicts + expect(performUpgradeResponse.summary.failed).toEqual(3); // all rules have conflicts + + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Merge conflicts found in rule '${ruleId}' for fields: name, tags. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + }); }); - }); - it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { - const baseRule = - createRuleAssetSavedObjectOfType('threat_match'); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); - await installPrebuiltRules(es, supertest); + it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { + const baseRule = + createRuleAssetSavedObjectOfType('threat_match'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); + await installPrebuiltRules(es, supertest); - const ruleId = baseRule['security-rule'].rule_id; + const ruleId = baseRule['security-rule'].rule_id; - const installedBaseRule = ( - await securitySolutionApi.readRule({ - query: { - rule_id: ruleId, + const installedBaseRule = ( + await securitySolutionApi.readRule({ + query: { + rule_id: ruleId, + }, + }) + ).body as ThreatMatchRule; + + // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value + const currentValues: { [key: string]: unknown } = { + enabled: true, + exceptions_list: [ + { + id: 'test-list', + list_id: 'test-list', + type: 'detection', + namespace_type: 'single', + } as const, + ], + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' as const }, }, - }) - ).body as ThreatMatchRule; - - // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value - const currentValues: { [key: string]: unknown } = { - enabled: true, - exceptions_list: [ - { - id: 'test-list', - list_id: 'test-list', - type: 'detection', - namespace_type: 'single', - } as const, - ], - alert_suppression: { - group_by: ['host.name'], - duration: { value: 5, unit: 'm' as const }, - }, - actions: [await createAction(supertest)], - response_actions: [ - { - params: { - command: 'isolate' as const, - comment: 'comment', + actions: [await createAction(supertest)], + response_actions: [ + { + params: { + command: 'isolate' as const, + comment: 'comment', + }, + action_type_id: '.endpoint' as const, }, - action_type_id: '.endpoint' as const, + ], + meta: { some_key: 'some_value' }, + output_index: '.siem-signals-default', + namespace: 'default', + concurrent_searches: 5, + items_per_search: 100, + }; + + await securitySolutionApi.updateRule({ + body: { + ...installedBaseRule, + ...currentValues, + id: undefined, }, - ], - meta: { some_key: 'some_value' }, - output_index: '.siem-signals-default', - namespace: 'default', - concurrent_searches: 5, - items_per_search: 100, - }; - - await securitySolutionApi.updateRule({ - body: { - ...installedBaseRule, - ...currentValues, - id: undefined, - }, - }); + }); - // Create a target version with undefined values for these fields - const targetRule = cloneDeep(baseRule); - targetRule['security-rule'].version += 1; - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - // @ts-expect-error - targetRule['security-rule'][field] = undefined; - }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + // Create a target version with undefined values for these fields + const targetRule = cloneDeep(baseRule); + targetRule['security-rule'].version += 1; + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + // @ts-expect-error + targetRule['security-rule'][field] = undefined; + }); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Perform the upgrade - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.ALL_RULES, - pick_version: 'TARGET', - }); + // Perform the upgrade + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'TARGET', + }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); - const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; - // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(upgradedRule[field]).toEqual(currentValues[field]); - }); + // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(upgradedRule[field]).toEqual(currentValues[field]); + }); - // Verify the installed rule - const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data.find( - (rule) => rule.rule_id === baseRule['security-rule'].rule_id - ) as ThreatMatchRule; + // Verify the installed rule + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseRule['security-rule'].rule_id + ) as ThreatMatchRule; - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(installedRule[field]).toEqual(currentValues[field]); + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(installedRule[field]).toEqual(currentValues[field]); + }); }); }); }); }); }; -function createIdToRuleMap(rules: Array) { - return new Map(rules.map((rule) => [rule.rule_id, rule])); -} - async function createAction(supertest: SuperTest.Agent) { const createConnector = async (payload: Record) => ( diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts index d3884b12808c5..6e5901049020d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_perform_prebuilt_rules.specific_rules_mode.ts @@ -6,822 +6,864 @@ */ import expect from 'expect'; -import type SuperTest from 'supertest'; import { cloneDeep } from 'lodash'; +import type SuperTest from 'supertest'; import { - QueryRuleCreateFields, + ModeEnum, + AllFieldsDiff, + PickVersionValues, EqlRuleCreateFields, EsqlRuleCreateFields, + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, + QueryRuleCreateFields, + ThreatMatchRule, ThreatMatchRuleCreateFields, RuleResponse, - ModeEnum, - PickVersionValues, - RuleEqlQuery, - EqlRule, - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { + KQL_QUERY_FIELDS, + MULTI_LINE_STRING_FIELDS, + NUMBER_FIELDS, + SIMPLE_FIELDS, + SINGLE_LINE_STRING_FIELDS, +} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; -import { ThreatMatchRule } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/rule_schemas.gen'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObjectOfType, installPrebuiltRules, performUpgradePrebuiltRules, - patchRule, - getInstalledRules, createHistoricalPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, + getInstalledRules, + createRuleAssetSavedObjectOfType, + updateRule, + fetchRule, createRuleAssetSavedObject, + patchRule, getWebHookAction, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - const securitySolutionApi = getService('securitySolutionApi'); - - describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - mode: SPECIFIC_RULES', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - const CURRENT_NAME = 'My current name'; - const CURRENT_TAGS = ['current', 'tags']; - const TARGET_NAME = 'My target name'; - const TARGET_TAGS = ['target', 'tags']; +import { + KQL_QUERY_FIELDS_MOCK_VALUES, + KQL_QUERY_FIELD_RULE_TYPE_MAPPING, + KqlQueryFieldTestValues, + MULTI_LINE_FIELD_RULE_TYPE_MAPPING, + MULTI_LINE_STRING_FIELDS_MOCK_VALUES, + MultiLineStringFieldTestValues, + NUMBER_FIELDS_MOCK_VALUES, + NUMBER_FIELD_RULE_TYPE_MAPPING, + NumberFieldTestValues, + SIMPLE_FIELDS_MOCK_VALUES, + SIMPLE_FIELD_RULE_TYPE_MAPPING, + SINGLE_LINE_FIELD_RULE_TYPE_MAPPING, + SINGLE_LINE_STRING_FIELDS_MOCK_VALUES, + SimpleFieldTestValues, + SingleLineStringFieldTestValues, + mapKQLQueryDiffableFieldToRuleFields, + mapDiffableFieldToRuleFields, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = + | typeof SINGLE_LINE_FIELD_RULE_TYPE_MAPPING + | typeof MULTI_LINE_FIELD_RULE_TYPE_MAPPING + | typeof NUMBER_FIELD_RULE_TYPE_MAPPING + | typeof KQL_QUERY_FIELD_RULE_TYPE_MAPPING + | typeof SIMPLE_FIELD_RULE_TYPE_MAPPING; + +type FieldType = + | SINGLE_LINE_STRING_FIELDS + | MULTI_LINE_STRING_FIELDS + | NUMBER_FIELDS + | KQL_QUERY_FIELDS + | SIMPLE_FIELDS; + +type TestValues = + | SingleLineStringFieldTestValues + | MultiLineStringFieldTestValues + | NumberFieldTestValues + | KqlQueryFieldTestValues + | SimpleFieldTestValues; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: T, + testValues: TestValues, + mapperFunction: ((field: T, value: any) => Record) | null, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest } = services; + const { baseValue, customValue, updatedValue, resolvedValue } = testValues; + + describe(`testing field: ${field}`, () => { + // Map DiffableRuleFields to their respective fields from rule schema, where appropriate + const getMappedFields = (version: TestValues) => + mapperFunction ? mapperFunction(field, version) : { [field]: version }; + + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + ...getMappedFields(baseValue), + }), + ]; describe('successful updates', () => { - const queryRule = createRuleAssetSavedObjectOfType('query'); - const eqlRule = createRuleAssetSavedObjectOfType('eql'); - const esqlRule = createRuleAssetSavedObjectOfType('esql'); - - const basePrebuiltAssets = [queryRule, eqlRule, esqlRule]; - - const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { - const targetObject = cloneDeep(ruleAssetSavedObject); - targetObject['security-rule'].version += 1; - targetObject['security-rule'].name = TARGET_NAME; - targetObject['security-rule'].tags = TARGET_TAGS; - return targetObject; - }); - it('upgrades specific rules to their BASE versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); - const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ - rule_id: rule['security-rule'].rule_id, - revision: 0, - version: rule['security-rule'].version + 1, - })); - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'BASE', - rules: rulesToUpgrade, + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; }); - const expectedResults = basePrebuiltAssets.map((asset) => ({ - rule_id: asset['security-rule'].rule_id, - version: asset['security-rule'].version + 1, - name: asset['security-rule'].name, - tags: asset['security-rule'].tags, - })); - - expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); - expect(updatedRule.version).toEqual(expected?.version); - expect(updatedRule.name).toEqual(expected?.name); - expect(updatedRule.tags).toEqual(expected?.tags); + const performResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'BASE', + rules: [ + { + rule_id: 'rule-1', + revision: 0, + version: 2, + }, + ], }); + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - expectedResults.forEach((expected) => { - const installedRule = installedRulesMap.get(expected.rule_id); - expect(installedRule?.name).toEqual(expected.name); - expect(installedRule?.tags).toEqual(expected.tags); + Object.entries(getMappedFields(baseValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); }); }); it('upgrades specific rules to their CURRENT versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - for (const baseRule of basePrebuiltAssets) { - await patchRule(supertest, log, { - rule_id: baseRule['security-rule'].rule_id, - name: CURRENT_NAME, - tags: CURRENT_TAGS, - }); - } + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), + }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ - rule_id: rule['security-rule'].rule_id, - revision: 1, - version: rule['security-rule'].version + 1, - })); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'CURRENT', - rules: rulesToUpgrade, - }); - - const expectedResults = basePrebuiltAssets.map((asset) => ({ - rule_id: asset['security-rule'].rule_id, - name: CURRENT_NAME, - tags: CURRENT_TAGS, - })); - - expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); - expect(updatedRule.name).toEqual(expected?.name); - expect(updatedRule.tags).toEqual(expected?.tags); + rules: [ + { + rule_id: 'rule-1', + revision: 1, + version: 2, + }, + ], }); + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - expectedResults.forEach((expected) => { - const installedRule = installedRulesMap.get(expected.rule_id); - expect(installedRule?.name).toEqual(expected.name); - expect(installedRule?.tags).toEqual(expected.tags); + Object.entries(getMappedFields(customValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); }); }); it('upgrades specific rules to their TARGET versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); - const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ - rule_id: rule['security-rule'].rule_id, - revision: 0, - version: rule['security-rule'].version + 1, - })); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - const expectedResults = basePrebuiltAssets.map((asset) => ({ - rule_id: asset['security-rule'].rule_id, - name: TARGET_NAME, - tags: TARGET_TAGS, - })); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'TARGET', - rules: rulesToUpgrade, - }); - - expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); - expect(updatedRule.name).toEqual(expected?.name); - expect(updatedRule.tags).toEqual(expected?.tags); + rules: [ + { + rule_id: 'rule-1', + revision: 0, + version: 2, + }, + ], }); + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - expectedResults.forEach((expected) => { - const installedRule = installedRulesMap.get(expected.rule_id); - expect(installedRule?.name).toEqual(expected.name); - expect(installedRule?.tags).toEqual(expected.tags); + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); }); }); it('upgrades specific rules to their MERGED versions', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const expectedResults = reviewResponse.rules.map((upgradeInfo) => ({ - rule_id: upgradeInfo.rule_id, - name: upgradeInfo.diff.fields.name?.merged_version, - tags: upgradeInfo.diff.fields.tags?.merged_version, - })); - - const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ - rule_id: rule['security-rule'].rule_id, - revision: 0, - version: rule['security-rule'].version + 1, - })); - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const mergedVersion = (reviewResponse.rules[0].diff.fields as AllFieldsDiff)[field] + ?.merged_version; + + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'MERGED', - rules: rulesToUpgrade, - }); - - expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); - expect(updatedRule.name).toEqual(expected?.name); - expect(updatedRule.tags).toEqual(expected?.tags); + rules: [ + { + rule_id: 'rule-1', + revision: 0, + version: 2, + }, + ], }); + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - expectedResults.forEach((expected) => { - const installedRule = installedRulesMap.get(expected.rule_id); - expect(installedRule?.name).toEqual(expected.name); - expect(installedRule?.tags).toEqual(expected.tags); - }); + Object.entries(getMappedFields(mergedVersion as unknown as TestValues)).forEach( + ([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + } + ); }); it('upgrades specific rules to their TARGET versions but overrides some fields with `fields` in the request payload', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); - - const rulesToUpgrade = basePrebuiltAssets.map((rule) => ({ - rule_id: rule['security-rule'].rule_id, - revision: 0, - version: rule['security-rule'].version + 1, - fields: { - name: { pick_version: 'BASE' as PickVersionValues }, - }, - })); - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'TARGET', - rules: rulesToUpgrade, - }); - const expectedResults = basePrebuiltAssets.map((asset) => ({ - rule_id: asset['security-rule'].rule_id, - name: asset['security-rule'].name, - tags: TARGET_TAGS, - })); - - expect(performUpgradeResponse.summary.succeeded).toEqual(basePrebuiltAssets.length); - - performUpgradeResponse.results.updated.forEach((updatedRule) => { - const expected = expectedResults.find((r) => r.rule_id === updatedRule.rule_id); - expect(updatedRule.name).toEqual(expected?.name); - expect(updatedRule.tags).toEqual(expected?.tags); - }); - - const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - expectedResults.forEach((expected) => { - const installedRule = installedRulesMap.get(expected.rule_id); - expect(installedRule?.name).toEqual(expected.name); - expect(installedRule?.tags).toEqual(expected.tags); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; }); - }); - it('upgrades specific rules with different pick_version at global, rule, and field levels', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); - await installPrebuiltRules(es, supertest); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - for (const baseRule of basePrebuiltAssets) { - await patchRule(supertest, log, { - rule_id: baseRule['security-rule'].rule_id, - name: CURRENT_NAME, - tags: CURRENT_TAGS, - }); - } - - await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); - - const rulesToUpgrade = [ - { - rule_id: basePrebuiltAssets[0]['security-rule'].rule_id, - revision: 1, - version: basePrebuiltAssets[0]['security-rule'].version + 1, - pick_version: 'CURRENT' as PickVersionValues, - }, - { - rule_id: basePrebuiltAssets[1]['security-rule'].rule_id, - revision: 1, - version: basePrebuiltAssets[1]['security-rule'].version + 1, - fields: { - name: { pick_version: 'TARGET' as PickVersionValues }, - tags: { pick_version: 'BASE' as PickVersionValues }, - }, - }, - { - rule_id: basePrebuiltAssets[2]['security-rule'].rule_id, - revision: 1, - version: basePrebuiltAssets[2]['security-rule'].version + 1, - }, - ]; - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'BASE', - rules: rulesToUpgrade, - }); - - expect(performUpgradeResponse.summary.succeeded).toEqual(3); - const updatedRulesMap = createIdToRuleMap(performUpgradeResponse.results.updated); - - const expectedResults = [ - { name: CURRENT_NAME, tags: CURRENT_TAGS }, - { name: TARGET_NAME, tags: basePrebuiltAssets[1]['security-rule'].tags }, - { - name: basePrebuiltAssets[2]['security-rule'].name, - tags: basePrebuiltAssets[2]['security-rule'].tags, - }, - ]; - - basePrebuiltAssets.forEach((asset, index) => { - const ruleId = asset['security-rule'].rule_id; - const updatedRule = updatedRulesMap.get(ruleId); - expect(updatedRule?.name).toEqual(expectedResults[index].name); - expect(updatedRule?.tags).toEqual(expectedResults[index].tags); + pick_version: 'TARGET', + rules: [ + { + rule_id: 'rule-1', + revision: 0, + version: 2, + fields: { + [field]: { pick_version: 'BASE' as PickVersionValues }, + }, + }, + ], }); + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRulesMap = createIdToRuleMap(installedRules.data); - - basePrebuiltAssets.forEach((asset, index) => { - const ruleId = asset['security-rule'].rule_id; - const installedRule = installedRulesMap.get(ruleId); - expect(installedRule?.name).toEqual(expectedResults[index].name); - expect(installedRule?.tags).toEqual(expectedResults[index].tags); + Object.entries(getMappedFields(baseValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); }); }); it('successfully resolves a non-resolvable conflict by using pick_version:RESOLVED for that field', async () => { - const baseEqlRule = createRuleAssetSavedObjectOfType('eql'); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseEqlRule]); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Patch the installed rule to edit its query - const patchedQuery = 'sequence by process.name [MY CURRENT QUERY]'; - await patchRule(supertest, log, { - rule_id: baseEqlRule['security-rule'].rule_id, - query: patchedQuery, + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), }); - // Create a new version of the prebuilt rule asset with a different query and generate the conflict - const targetEqlRule = cloneDeep(baseEqlRule); - targetEqlRule['security-rule'].version += 1; - targetEqlRule['security-rule'].query = 'sequence by process.name [MY TARGET QUERY]'; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetEqlRule]); + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - const resolvedValue = { - query: 'sequence by process.name [MY RESOLVED QUERY]', - language: 'eql', - filters: [], - }; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - // Perform the upgrade with manual conflict resolution - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'MERGED', rules: [ { - rule_id: baseEqlRule['security-rule'].rule_id, + rule_id: 'rule-1', revision: 1, - version: baseEqlRule['security-rule'].version + 1, + version: 2, fields: { - eql_query: { + [field]: { pick_version: 'RESOLVED', - resolved_value: resolvedValue as RuleEqlQuery, + resolved_value: resolvedValue, }, }, }, ], }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); - const updatedRule = performUpgradeResponse.results.updated[0] as EqlRule; - expect(updatedRule.rule_id).toEqual(baseEqlRule['security-rule'].rule_id); - expect(updatedRule.query).toEqual(resolvedValue.query); - expect(updatedRule.filters).toEqual(resolvedValue.filters); - expect(updatedRule.language).toEqual(resolvedValue.language); - + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data.find( - (rule) => rule.rule_id === baseEqlRule['security-rule'].rule_id - ) as EqlRule; - expect(installedRule?.query).toEqual(resolvedValue.query); - expect(installedRule?.filters).toEqual(resolvedValue.filters); - expect(installedRule?.language).toEqual(resolvedValue.language); + Object.entries(getMappedFields(resolvedValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + }); }); }); describe('edge cases and unhappy paths', () => { - const queryRule = createRuleAssetSavedObject({ - type: 'query', - language: 'kuery', - rule_id: 'query-rule', - }); - const eqlRule = createRuleAssetSavedObject({ - type: 'eql', - language: 'eql', - rule_id: 'eql-rule', - }); - - const basePrebuiltAssets = [queryRule, eqlRule]; - - it('rejects updates when rule type changes and pick_version is not TARGET at all levels', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); - await installPrebuiltRules(es, supertest); - - const targetMLRule = createRuleAssetSavedObject({ - rule_id: queryRule['security-rule'].rule_id, - version: queryRule['security-rule'].version + 1, - type: 'machine_learning', - machine_learning_job_id: 'job_id', - anomaly_threshold: 1, - }); - - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetMLRule]); - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'BASE', - rules: [ - { - rule_id: queryRule['security-rule'].rule_id, - revision: 0, - version: queryRule['security-rule'].version + 1, - }, - ], - }); - - expect(performUpgradeResponse.summary.failed).toEqual(1); - expect(performUpgradeResponse.errors[0].message).toContain( - 'Rule update for rule query-rule has a rule type change' - ); - }); - - it('rejects updates when incompatible fields are provided for a rule type', async () => { - const baseEqlRule = createRuleAssetSavedObjectOfType('eql'); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseEqlRule]); - await installPrebuiltRules(es, supertest); - - // Create a new version of the prebuilt rule asset with a different query and generate the conflict - const targetEqlRule = cloneDeep(baseEqlRule); - targetEqlRule['security-rule'].version += 1; - targetEqlRule['security-rule'].query = 'sequence by process.name [MY TARGET QUERY]'; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetEqlRule]); - - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'TARGET', - rules: [ - { - rule_id: baseEqlRule['security-rule'].rule_id, - revision: 0, - version: baseEqlRule['security-rule'].version + 1, - fields: { - machine_learning_job_id: { pick_version: 'TARGET' }, - }, - }, - ], - }); - - expect(performUpgradeResponse.summary.failed).toEqual(1); - expect(performUpgradeResponse.errors[0].message).toContain( - "machine_learning_job_id is not a valid upgradeable field for type 'eql'" - ); - }); - it('rejects updates with NON_SOLVABLE conflicts when using MERGED pick_version', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await patchRule(supertest, log, { - rule_id: queryRule['security-rule'].rule_id, - name: CURRENT_NAME, + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), }); - const targetQueryRule = cloneDeep(queryRule); - targetQueryRule['security-rule'].version += 1; - targetQueryRule['security-rule'].name = TARGET_NAME; + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'MERGED', rules: [ { - rule_id: queryRule['security-rule'].rule_id, + rule_id: 'rule-1', revision: 1, - version: queryRule['security-rule'].version + 1, + version: 2, }, ], }); - expect(performUpgradeResponse.summary.failed).toEqual(1); - expect(performUpgradeResponse.errors[0].message).toContain( - `Automatic merge calculation for field 'name' in rule of rule_id ${performUpgradeResponse.errors[0].rules[0].rule_id} resulted in a conflict. Please resolve the conflict manually or choose another value for 'pick_version'` + expect(performResponse.summary.failed).toBe(1); + expect(performResponse.errors[0].message).toContain( + `Automatic merge calculation for field '${field}'` ); }); it('allows updates with NON_SOLVABLE conflicts when specific fields have non-MERGED pick_version', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await patchRule(supertest, log, { - rule_id: queryRule['security-rule'].rule_id, - name: CURRENT_NAME, + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), }); - const targetQueryRule = cloneDeep(queryRule); - targetQueryRule['security-rule'].version += 1; - targetQueryRule['security-rule'].name = TARGET_NAME; + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'MERGED', rules: [ { - rule_id: queryRule['security-rule'].rule_id, + rule_id: 'rule-1', revision: 1, - version: queryRule['security-rule'].version + 1, + version: 2, fields: { - name: { pick_version: 'TARGET' }, + [field]: { pick_version: 'TARGET' }, }, }, ], }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); - expect(performUpgradeResponse.results.updated[0].name).toEqual(TARGET_NAME); - + expect(performResponse.summary.succeeded).toBe(1); const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data.find( - (rule) => rule.rule_id === queryRule['security-rule'].rule_id - ); - expect(installedRule?.name).toEqual(TARGET_NAME); + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + expect((installedRules.data[0] as Record)[key]).toStrictEqual(value); + }); }); it('rejects updates for specific fields with MERGED pick_version and NON_SOLVABLE conflicts', async () => { - await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - await patchRule(supertest, log, { - rule_id: queryRule['security-rule'].rule_id, - name: CURRENT_NAME, + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...getMappedFields(customValue), }); - const targetQueryRule = cloneDeep(queryRule); - targetQueryRule['security-rule'].version += 1; - targetQueryRule['security-rule'].name = TARGET_NAME; + const targetRule = cloneDeep(getRuleAssetSavedObjects()[0]); + targetRule['security-rule'].version += 1; + Object.entries(getMappedFields(updatedValue)).forEach(([key, value]) => { + (targetRule['security-rule'] as Record)[key] = value; + }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetQueryRule]); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + const performResponse = await performUpgradePrebuiltRules(es, supertest, { mode: ModeEnum.SPECIFIC_RULES, pick_version: 'TARGET', rules: [ { - rule_id: queryRule['security-rule'].rule_id, + rule_id: 'rule-1', revision: 1, - version: queryRule['security-rule'].version + 1, + version: 2, fields: { - name: { pick_version: 'MERGED' }, + [field]: { pick_version: 'MERGED' }, }, }, ], }); - expect(performUpgradeResponse.summary.failed).toEqual(1); - expect(performUpgradeResponse.errors[0].message).toContain( - `Automatic merge calculation for field 'name' in rule of rule_id ${performUpgradeResponse.errors[0].rules[0].rule_id} resulted in a conflict. Please resolve the conflict manually or choose another value for 'pick_version'.` + expect(performResponse.summary.failed).toBe(1); + expect(performResponse.errors[0].message).toContain( + `Automatic merge calculation for field '${field}'` ); }); + }); + }); +}; - it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { - const baseRule = - createRuleAssetSavedObjectOfType('threat_match'); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); - await installPrebuiltRules(es, supertest); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + const securitySolutionApi = getService('securitySolutionApi'); - const ruleId = baseRule['security-rule'].rule_id; + describe('@ess @serverless @skipInServerlessMKI Perform Prebuilt Rules Upgrades - SPECIFIC_RULES mode', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - const installedBaseRule = ( - await securitySolutionApi.readRule({ - query: { - rule_id: ruleId, - }, - }) - ).body as ThreatMatchRule; + describe('Single Line String Fields - ', () => { + Object.entries(SINGLE_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = + SINGLE_LINE_STRING_FIELDS_MOCK_VALUES[field as SINGLE_LINE_STRING_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SINGLE_LINE_STRING_FIELDS, + testValues, + null, + { es, supertest, log } + ); + }); + }); + }); + }); - // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value - const currentValues: { [key: string]: unknown } = { - enabled: true, - exceptions_list: [ - { - id: 'test-list', - list_id: 'test-list', - type: 'detection', - namespace_type: 'single', - } as const, - ], - alert_suppression: { - group_by: ['host.name'], - duration: { value: 5, unit: 'm' as const }, - }, - actions: [await createAction(supertest)], - response_actions: [ - { - params: { - command: 'isolate' as const, - comment: 'comment', - }, - action_type_id: '.endpoint' as const, - }, - ], - meta: { some_key: 'some_value' }, - output_index: '.siem-signals-default', - namespace: 'default', - concurrent_searches: 5, - items_per_search: 100, - }; - - await securitySolutionApi.updateRule({ - body: { - ...installedBaseRule, - ...currentValues, - id: undefined, - }, - }); - - // Create a target version with undefined values for these fields - const targetRule = cloneDeep(baseRule); - targetRule['security-rule'].version += 1; - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - // @ts-expect-error - targetRule['security-rule'][field] = undefined; + describe('Multi Line String Fields - ', () => { + Object.entries(MULTI_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = + MULTI_LINE_STRING_FIELDS_MOCK_VALUES[field as MULTI_LINE_STRING_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as MULTI_LINE_STRING_FIELDS, + testValues, + null, + { es, supertest, log } + ); + }); }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + }); + }); - // Perform the upgrade - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'TARGET', - rules: [ - { - rule_id: baseRule['security-rule'].rule_id, - revision: 1, - version: baseRule['security-rule'].version + 1, - }, - ], + describe('Number Fields - ', () => { + Object.entries(NUMBER_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = NUMBER_FIELDS_MOCK_VALUES[field as NUMBER_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as NUMBER_FIELDS, + testValues, + null, + { + es, + supertest, + log, + } + ); + }); }); + }); + }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); - const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + describe('KQL Query Fields - ', () => { + Object.entries(KQL_QUERY_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = KQL_QUERY_FIELDS_MOCK_VALUES[field as KQL_QUERY_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as KQL_QUERY_FIELDS, + testValues, + mapKQLQueryDiffableFieldToRuleFields, + { + es, + supertest, + log, + } + ); + }); + }); + }); + }); - // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(upgradedRule[field]).toEqual(currentValues[field]); + describe('Simple Diff Algorithm Fields - ', () => { + Object.entries(SIMPLE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule`, () => { + fields.forEach((field) => { + const testValues = SIMPLE_FIELDS_MOCK_VALUES[field]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SIMPLE_FIELDS, + testValues, + mapDiffableFieldToRuleFields, + { + es, + supertest, + log, + } + ); + }); }); + }); + }); - // Verify the installed rule - const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data.find( - (rule) => rule.rule_id === baseRule['security-rule'].rule_id - ) as ThreatMatchRule; + // TODO: Maybe move these scenarios to a different file? + describe('Scenarios for all rule types', () => { + const CURRENT_NAME = 'My current name'; + const CURRENT_TAGS = ['current', 'tags']; + const TARGET_NAME = 'My target name'; + const TARGET_TAGS = ['target', 'tags']; + + describe(`successful updates`, () => { + const queryRule = createRuleAssetSavedObjectOfType('query'); + const eqlRule = createRuleAssetSavedObjectOfType('eql'); + const esqlRule = createRuleAssetSavedObjectOfType('esql'); - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(installedRule[field]).toEqual(currentValues[field]); + const basePrebuiltAssets = [queryRule, eqlRule, esqlRule]; + + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + return targetObject; }); - }); - it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when fields are attempted to be updated via resolved values', async () => { - const baseRule = - createRuleAssetSavedObjectOfType('threat_match'); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); - await installPrebuiltRules(es, supertest); + it('upgrades specific rules with different pick_version at global, rule, and field levels', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); - const ruleId = baseRule['security-rule'].rule_id; + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } - const installedBaseRule = ( - await securitySolutionApi.readRule({ - query: { - rule_id: ruleId, - }, - }) - ).body as ThreatMatchRule; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); - // Set current values for FIELDS_TO_UPGRADE_TO_CURRENT_VERSION - const currentValues: { [key: string]: unknown } = { - enabled: true, - exceptions_list: [ + const rulesToUpgrade = [ { - id: 'test-list', - list_id: 'test-list', - type: 'detection', - namespace_type: 'single', - } as const, - ], - alert_suppression: { - group_by: ['host.name'], - duration: { value: 5, unit: 'm' as const }, - }, - actions: [await createAction(supertest)], - response_actions: [ + rule_id: basePrebuiltAssets[0]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[0]['security-rule'].version + 1, + pick_version: 'CURRENT' as PickVersionValues, + }, { - params: { - command: 'isolate' as const, - comment: 'comment', + rule_id: basePrebuiltAssets[1]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[1]['security-rule'].version + 1, + fields: { + name: { pick_version: 'TARGET' as PickVersionValues }, + tags: { pick_version: 'BASE' as PickVersionValues }, }, - action_type_id: '.endpoint' as const, }, - ], - meta: { some_key: 'some_value' }, - output_index: '.siem-signals-default', - namespace: 'default', - concurrent_searches: 5, - items_per_search: 100, - }; - - await securitySolutionApi.updateRule({ - body: { - ...installedBaseRule, - ...currentValues, - id: undefined, - }, - }); - - // Create a target version with undefined values for these fields - const targetRule = cloneDeep(baseRule); - targetRule['security-rule'].version += 1; - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - // @ts-expect-error - targetRule['security-rule'][field] = undefined; - }); - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); - - // Create resolved values different from current values - const resolvedValues: { [key: string]: unknown } = { - alert_suppression: { - group_by: ['test'], - duration: { value: 10, unit: 'm' as const }, - }, - }; - - const fields = Object.fromEntries( - Object.keys(resolvedValues).map((field) => [ - field, { - pick_version: 'RESOLVED' as PickVersionValues, - resolved_value: resolvedValues[field], + rule_id: basePrebuiltAssets[2]['security-rule'].rule_id, + revision: 1, + version: basePrebuiltAssets[2]['security-rule'].version + 1, }, - ]) - ); + ]; - // Perform the upgrade with resolved values - const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { - mode: ModeEnum.SPECIFIC_RULES, - pick_version: 'TARGET', - rules: [ + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.SPECIFIC_RULES, + pick_version: 'BASE', + rules: rulesToUpgrade, + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(3); + const updatedRulesMap = createIdToRuleMap(performUpgradeResponse.results.updated); + + const expectedResults = [ + { name: CURRENT_NAME, tags: CURRENT_TAGS }, + { name: TARGET_NAME, tags: basePrebuiltAssets[1]['security-rule'].tags }, { - rule_id: baseRule['security-rule'].rule_id, - revision: 1, - version: baseRule['security-rule'].version + 1, - fields, + name: basePrebuiltAssets[2]['security-rule'].name, + tags: basePrebuiltAssets[2]['security-rule'].tags, }, - ], + ]; + + basePrebuiltAssets.forEach((asset, index) => { + const ruleId = asset['security-rule'].rule_id; + const updatedRule = updatedRulesMap.get(ruleId); + expect(updatedRule?.name).toEqual(expectedResults[index].name); + expect(updatedRule?.tags).toEqual(expectedResults[index].tags); + }); + + const installedRules = await getInstalledRules(supertest); + const installedRulesMap = createIdToRuleMap(installedRules.data); + + basePrebuiltAssets.forEach((asset, index) => { + const ruleId = asset['security-rule'].rule_id; + const installedRule = installedRulesMap.get(ruleId); + expect(installedRule?.name).toEqual(expectedResults[index].name); + expect(installedRule?.tags).toEqual(expectedResults[index].tags); + }); }); + }); - expect(performUpgradeResponse.summary.succeeded).toEqual(1); - const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + describe('edge cases and unhappy paths', () => { + const firstQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-1', + }); + const secondQueryRule = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + }); + const eqlRule = createRuleAssetSavedObject({ + type: 'eql', + language: 'eql', + rule_id: 'eql-rule', + }); + + const basePrebuiltAssets = [firstQueryRule, eqlRule, secondQueryRule]; + + it('rejects all updates of rules which have a rule type change if the pick_version is not TARGET', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Mock a rule type change to 'ml' to the first two rules of the basePrebuiltAssets array + const targetMLPrebuiltAssets = basePrebuiltAssets + .slice(0, 2) + .map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + + return { + ...targetObject, + ...createRuleAssetSavedObject({ + rule_id: targetObject['security-rule'].rule_id, + version: targetObject['security-rule'].version + 1, + type: 'machine_learning', + machine_learning_job_id: 'job_id', + anomaly_threshold: 1, + }), + }; + }); + + // Mock an normal update of the rule 'query-rule-2', with NO rule type change + const targetAssetSameTypeUpdate = createRuleAssetSavedObject({ + type: 'query', + language: 'kuery', + rule_id: 'query-rule-2', + version: 2, + }); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + ...targetMLPrebuiltAssets, + targetAssetSameTypeUpdate, + ]); + + // Perform the upgrade, all rules' fields to their BASE versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'BASE', + }); - // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(upgradedRule[field]).toEqual(currentValues[field]); + expect(performUpgradeResponse.summary.succeeded).toEqual(1); // update of same type + expect(performUpgradeResponse.summary.failed).toEqual(2); // updates with rule type change + + expect(performUpgradeResponse.errors).toHaveLength(2); + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Rule update for rule ${ruleId} has a rule type change. All 'pick_version' values for rule must match 'TARGET'` + ); + }); }); - // Verify the installed rule - const installedRules = await getInstalledRules(supertest); - const installedRule = installedRules.data.find( - (rule) => rule.rule_id === baseRule['security-rule'].rule_id - ) as ThreatMatchRule; + it('rejects updates of rules with a pick_version of MERGED which have fields which result in conflicts in the three way diff calculations', async () => { + // Install base prebuilt detection rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, basePrebuiltAssets); + await installPrebuiltRules(es, supertest); + + // Patch all 3 installed rules to create a current version for each + for (const baseRule of basePrebuiltAssets) { + await patchRule(supertest, log, { + rule_id: baseRule['security-rule'].rule_id, + name: CURRENT_NAME, + tags: CURRENT_TAGS, + }); + } + + const targetPrebuiltAssets = basePrebuiltAssets.map((ruleAssetSavedObject) => { + const targetObject = cloneDeep(ruleAssetSavedObject); + targetObject['security-rule'].version += 1; + targetObject['security-rule'].name = TARGET_NAME; + targetObject['security-rule'].tags = TARGET_TAGS; + + return targetObject; + }); + + // Create new versions of the assets of the installed rules + await createHistoricalPrebuiltRuleAssetSavedObjects(es, targetPrebuiltAssets); + + // Perform the upgrade, all rules' fields to their MERGED versions + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'MERGED', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(0); // all rules have conflicts + expect(performUpgradeResponse.summary.failed).toEqual(3); // all rules have conflicts + + performUpgradeResponse.errors.forEach((error) => { + const ruleId = error.rules[0].rule_id; + expect(error.message).toContain( + `Merge conflicts found in rule '${ruleId}' for fields: name, tags. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + }); + }); - FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { - expect(installedRule[field]).toEqual(currentValues[field]); + it('preserves FIELDS_TO_UPGRADE_TO_CURRENT_VERSION when upgrading to TARGET version with undefined fields', async () => { + const baseRule = + createRuleAssetSavedObjectOfType('threat_match'); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [baseRule]); + await installPrebuiltRules(es, supertest); + + const ruleId = baseRule['security-rule'].rule_id; + + const installedBaseRule = ( + await securitySolutionApi.readRule({ + query: { + rule_id: ruleId, + }, + }) + ).body as ThreatMatchRule; + + // Patch the installed rule to set all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION to some defined value + const currentValues: { [key: string]: unknown } = { + enabled: true, + exceptions_list: [ + { + id: 'test-list', + list_id: 'test-list', + type: 'detection', + namespace_type: 'single', + } as const, + ], + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' as const }, + }, + actions: [await createAction(supertest)], + response_actions: [ + { + params: { + command: 'isolate' as const, + comment: 'comment', + }, + action_type_id: '.endpoint' as const, + }, + ], + meta: { some_key: 'some_value' }, + output_index: '.siem-signals-default', + namespace: 'default', + concurrent_searches: 5, + items_per_search: 100, + }; + + await securitySolutionApi.updateRule({ + body: { + ...installedBaseRule, + ...currentValues, + id: undefined, + }, + }); + + // Create a target version with undefined values for these fields + const targetRule = cloneDeep(baseRule); + targetRule['security-rule'].version += 1; + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + // @ts-expect-error + targetRule['security-rule'][field] = undefined; + }); + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [targetRule]); + + // Perform the upgrade + const performUpgradeResponse = await performUpgradePrebuiltRules(es, supertest, { + mode: ModeEnum.ALL_RULES, + pick_version: 'TARGET', + }); + + expect(performUpgradeResponse.summary.succeeded).toEqual(1); + const upgradedRule = performUpgradeResponse.results.updated[0] as ThreatMatchRule; + + // Check that all FIELDS_TO_UPGRADE_TO_CURRENT_VERSION still have their "current" values + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(upgradedRule[field]).toEqual(currentValues[field]); + }); + + // Verify the installed rule + const installedRules = await getInstalledRules(supertest); + const installedRule = installedRules.data.find( + (rule) => rule.rule_id === baseRule['security-rule'].rule_id + ) as ThreatMatchRule; + + FIELDS_TO_UPGRADE_TO_CURRENT_VERSION.forEach((field) => { + expect(installedRule[field]).toEqual(currentValues[field]); + }); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.mock_data.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.mock_data.ts new file mode 100644 index 0000000000000..c5a9e3e7b95c4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_prebuilt_rules.mock_data.ts @@ -0,0 +1,556 @@ +/* + * 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 { + TEXT_XL_A, + TEXT_XL_B, + TEXT_XL_C, + TEXT_XL_MERGED, +} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.mock'; +import { + KQL_QUERY_FIELDS, + MULTI_LINE_STRING_FIELDS, + NUMBER_FIELDS, + SCALAR_ARRAY_FIELDS, + SIMPLE_FIELDS, + SINGLE_LINE_STRING_FIELDS, +} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { calculateFromValue } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; + +// ---------------------- Number fields --------------------------- // +export const NUMBER_FIELD_RULE_TYPE_MAPPING = { + query: ['risk_score', 'max_signals'], + machine_learning: ['anomaly_threshold'], +} as const; + +export interface NumberFieldTestValues { + baseValue: number; + customValue: number; + updatedValue: number; + resolvedValue: number; +} + +export const NUMBER_FIELDS_MOCK_VALUES: Record = { + risk_score: { + baseValue: 25, + customValue: 50, + updatedValue: 75, + resolvedValue: 100, + }, + max_signals: { + baseValue: 100, + customValue: 200, + updatedValue: 300, + resolvedValue: 400, + }, + anomaly_threshold: { + baseValue: 50, + customValue: 75, + updatedValue: 90, + resolvedValue: 100, + }, +}; + +// ---------------------- Scalar array fields --------------------------- // +export const SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING = { + query: ['tags', 'references'], + threat_match: ['threat_index'], + new_terms: ['new_terms_fields'], +} as const; + +export interface ScalarArrayFieldTestValues { + baseValue: string[]; + customValue: string[]; + updatedValue: string[]; + resolvedValue: string[]; +} + +export const SCALAR_ARRAY_FIELDS_MOCK_VALUES: Record< + SCALAR_ARRAY_FIELDS, + ScalarArrayFieldTestValues +> = { + tags: { + baseValue: ['one', 'two', 'three'], + customValue: ['one', 'two', 'four'], + updatedValue: ['one', 'two', 'five'], + resolvedValue: ['one', 'two', 'resolved'], + }, + references: { + baseValue: ['ref1', 'ref2', 'ref3'], + customValue: ['ref1', 'ref2', 'ref4'], + updatedValue: ['ref1', 'ref2', 'ref5'], + resolvedValue: ['ref1', 'ref2', 'resolved'], + }, + threat_index: { + baseValue: ['index1', 'index2', 'index3'], + customValue: ['index1', 'index2', 'index4'], + updatedValue: ['index1', 'index2', 'index5'], + resolvedValue: ['index1', 'index2', 'resolved'], + }, + new_terms_fields: { + baseValue: ['field1', 'field2', 'field3'], + customValue: ['field1', 'field2', 'field4'], + updatedValue: ['field1', 'field2', 'field5'], + resolvedValue: ['field1', 'field2', 'resolved'], + }, +}; + +// ---------------------- Single line string fields ---------------------- // +export const SINGLE_LINE_FIELD_RULE_TYPE_MAPPING = { + query: ['name', 'severity'], + threat_match: ['threat_indicator_path'], + new_terms: ['history_window_start'], +} as const; + +export interface SingleLineStringFieldTestValues { + baseValue: string; + customValue: string; + updatedValue: string; + resolvedValue: string; +} + +export const SINGLE_LINE_STRING_FIELDS_MOCK_VALUES: Record< + SINGLE_LINE_STRING_FIELDS, + SingleLineStringFieldTestValues +> = { + name: { + baseValue: 'Base Rule Name', + customValue: 'Custom Rule Name', + updatedValue: 'Updated Rule Name', + resolvedValue: 'Resolved Rule Name', + }, + severity: { + baseValue: 'low', + customValue: 'medium', + updatedValue: 'high', + resolvedValue: 'low', + }, + threat_indicator_path: { + baseValue: 'indicator.file.hash.base', + customValue: 'indicator.ip.current', + updatedValue: 'indicator.domain.target', + resolvedValue: 'resolved.file.hash.base', + }, + history_window_start: { + baseValue: 'now-10000m', + customValue: 'now-20000m', + updatedValue: 'now-30000m', + resolvedValue: 'now-50000m', + }, +}; + +// ---------------------- Multi line string fields ---------------------- // +export const MULTI_LINE_FIELD_RULE_TYPE_MAPPING = { + query: ['description', 'note', 'setup'], +} as const; + +export interface MultiLineStringFieldTestValues { + baseValue: string; + customValue: string; + updatedValue: string; + mergedValue: string; + resolvedValue: string; + longBaseValue?: string; + longCustomValue?: string; + longUpdatedValue?: string; + longMergedValue?: string; + longResolvedValue?: string; +} +export const MULTI_LINE_STRING_FIELDS_MOCK_VALUES: Record< + MULTI_LINE_STRING_FIELDS, + MultiLineStringFieldTestValues +> = { + description: { + baseValue: 'My description.\nThis is a second line.', + customValue: 'My GREAT description.\nThis is a second line.', + updatedValue: 'My description.\nThis is a second line, now longer.', + mergedValue: 'My GREAT description.\nThis is a second line, now longer.', + resolvedValue: 'My RESOLVED value\nThis is a second line, now longer.', + longBaseValue: TEXT_XL_A, + longCustomValue: TEXT_XL_B, + longUpdatedValue: TEXT_XL_C, + longMergedValue: TEXT_XL_MERGED, + }, + note: { + baseValue: 'My note.\nThis is a second line.', + customValue: 'My GREAT note.\nThis is a second line.', + updatedValue: 'My note.\nThis is a second line, now longer.', + mergedValue: 'My GREAT note.\nThis is a second line, now longer.', + resolvedValue: 'My RESOLVED note.\nThis is a second line, now longer.', + }, + setup: { + baseValue: 'My setup.\nThis is a second line.', + customValue: 'My GREAT setup.\nThis is a second line.', + updatedValue: 'My setup.\nThis is a second line, now longer.', + mergedValue: 'My GREAT setup.\nThis is a second line, now longer.', + resolvedValue: 'My RESOLVED setup.\nThis is a second line, now longer.', + }, +}; + +// ---------------------- KQL query fields ------------------------ // +export const KQL_QUERY_FIELD_RULE_TYPE_MAPPING = { + query: ['kql_query'], + threat_match: ['threat_query'], +} as const; + +interface Value { + query: string; + language: string; + type?: string; + filters: string[]; +} + +export interface KqlQueryFieldTestValues { + baseValue: Value; + customValue: Value; + updatedValue: Value; + resolvedValue: Value; +} + +export const mapKQLQueryDiffableFieldToRuleFields = (diffableField: string, value: Value) => { + const updatePayload: Record = {}; + + switch (diffableField) { + case 'kql_query': + updatePayload.query = value.query; + updatePayload.language = value.language; + updatePayload.filters = value.filters; + break; + case 'threat_query': + updatePayload.threat_query = value.query; + updatePayload.threat_language = value.language; + updatePayload.threat_filters = value.filters; + break; + } + + return updatePayload; +}; + +export const KQL_QUERY_FIELDS_MOCK_VALUES: Record = { + kql_query: { + baseValue: { + query: 'process.name:*.exe', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + customValue: { + query: 'process.name:*.dll', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + updatedValue: { + query: 'process.name:*.sys', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + resolvedValue: { + query: 'resolved.name:*.sys', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + }, + threat_query: { + baseValue: { + query: 'source.ip:10.0.0.*', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + customValue: { + query: 'source.ip:192.168.*', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + updatedValue: { + query: 'source.ip:172.16.*', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + resolvedValue: { + query: 'resolved.ip:172.16.*', + language: 'kuery', + filters: [], + type: 'inline_query', + }, + }, +}; + +// ---------------------- Simple diff algorithm fields ------------------------ // +export const SIMPLE_FIELD_RULE_TYPE_MAPPING = { + query: [ + 'severity_mapping', + 'risk_score_mapping', + 'false_positives', + 'threat', + 'related_integrations', + 'required_fields', + 'rule_schedule', + 'rule_name_override', + 'timestamp_override', + 'timeline_template', + 'building_block', + 'investigation_fields', + 'alert_suppression', + ], + threshold: ['threshold'], + machine_learning: ['machine_learning_job_id'], +} as const; + +export interface SimpleFieldTestValues { + baseValue: any; + customValue: any; + updatedValue: any; + resolvedValue: any; +} + +export const mapDiffableFieldToRuleFields = (diffableField: string, value: any) => { + const updatePayload: Record = {}; + + switch (diffableField) { + case 'rule_schedule': + updatePayload.interval = value.interval; + updatePayload.from = calculateFromValue(value.interval, value.lookback); + updatePayload.to = 'now'; + break; + case 'timestamp_override': + updatePayload.timestamp_override = value.field_name; + break; + case 'timestamp_override_fallback_disabled': + updatePayload.timestamp_override_fallback_disabled = value.field_name; + case 'rule_name_override': + updatePayload.rule_name_override = value.field_name; + case 'timeline_template': + updatePayload.timeline_id = value.timeline_id; + updatePayload.timeline_title = value.timeline_title; + + break; + case 'building_block': + updatePayload.building_block_type = value.type; + break; + default: + updatePayload[diffableField] = value; + } + + return updatePayload; +}; + +export const SIMPLE_FIELDS_MOCK_VALUES: Record = { + severity_mapping: { + baseValue: [ + { + field: 'base.field', + value: 'base-value', + operator: 'equals', + severity: 'low', + }, + ], + customValue: [ + { + field: 'custom.field', + value: 'custom-value', + operator: 'equals', + severity: 'medium', + }, + ], + updatedValue: [ + { + field: 'updated.field', + value: 'updated-value', + operator: 'equals', + severity: 'high', + }, + ], + resolvedValue: [ + { + field: 'resolved.field', + value: 'resolved-value', + operator: 'equals', + severity: 'low', + }, + ], + }, + risk_score_mapping: { + baseValue: [ + { + field: 'base.field', + value: 'base-value', + operator: 'equals', + risk_score: 10, + }, + ], + customValue: [ + { + field: 'custom.field', + value: 'custom-value', + operator: 'equals', + risk_score: 20, + }, + ], + updatedValue: [ + { + field: 'updated.field', + value: 'updated-value', + operator: 'equals', + risk_score: 30, + }, + ], + resolvedValue: [ + { + field: 'resolved.field', + value: 'resolved-value', + operator: 'equals', + risk_score: 30, + }, + ], + }, + false_positives: { + baseValue: ['base-false-positive'], + customValue: ['custom-false-positive'], + updatedValue: ['updated-false-positive'], + resolvedValue: ['resolved-false-positive'], + }, + threat: { + baseValue: [{ framework: 'MITRE', tactic: { id: 'base', name: 'base', reference: 'base' } }], + customValue: [ + { framework: 'MITRE', tactic: { id: 'custom', name: 'custom', reference: 'custom' } }, + ], + updatedValue: [ + { framework: 'MITRE', tactic: { id: 'updated', name: 'updated', reference: 'updated' } }, + ], + resolvedValue: [ + { framework: 'MITRE', tactic: { id: 'resolved', name: 'resolved', reference: 'resolved' } }, + ], + }, + related_integrations: { + baseValue: [ + { + package: 'base-package', + version: '1.0.0', + integration: 'base-integration', + }, + ], + customValue: [ + { + package: 'custom-package', + version: '1.0.0', + integration: 'custom-integration', + }, + ], + updatedValue: [ + { + package: 'updated-package', + version: '1.0.0', + integration: 'updated-integration', + }, + ], + resolvedValue: [ + { + package: 'resolved-package', + version: '1.0.0', + integration: 'resolved-integration', + }, + ], + }, + required_fields: { + baseValue: [ + { + name: 'base.field', + type: 'keyword', + ecs: false, + }, + ], + customValue: [ + { + name: 'custom.field', + type: 'keyword', + ecs: false, + }, + ], + updatedValue: [ + { + name: 'updated.field', + type: 'keyword', + ecs: false, + }, + ], + resolvedValue: [ + { + name: 'resolved.field', + type: 'keyword', + ecs: false, + }, + ], + }, + rule_schedule: { + baseValue: { interval: '5m', lookback: '60s' }, + customValue: { interval: '10m', lookback: '0s' }, + updatedValue: { interval: '15m', lookback: '60s' }, + resolvedValue: { interval: '20m', lookback: '60s' }, + }, + rule_name_override: { + baseValue: { field_name: 'base-override' }, + customValue: { field_name: 'custom-override' }, + updatedValue: { field_name: 'updated-override' }, + resolvedValue: { field_name: 'resolved-override' }, + }, + timestamp_override: { + baseValue: { field_name: 'base-timestamp', fallback_disabled: false }, + customValue: { field_name: 'custom-timestamp', fallback_disabled: false }, + updatedValue: { field_name: 'updated-timestamp', fallback_disabled: false }, + resolvedValue: { field_name: 'resolved-timestamp', fallback_disabled: false }, + }, + timeline_template: { + baseValue: { timeline_id: 'base-template', timeline_title: 'base-template' }, + customValue: { timeline_id: 'custom-template', timeline_title: 'base-template' }, + updatedValue: { timeline_id: 'updated-template', timeline_title: 'base-template' }, + resolvedValue: { timeline_id: 'resolved-template', timeline_title: 'base-template' }, + }, + building_block: { + baseValue: { type: 'a' }, + customValue: { type: 'b' }, + updatedValue: { type: 'c' }, + resolvedValue: { type: 'c' }, + }, + investigation_fields: { + baseValue: { + field_names: ['base.field'], + }, + customValue: { + field_names: ['custom.field'], + }, + updatedValue: { + field_names: ['updated.field'], + }, + resolvedValue: { + field_names: ['resolved.field'], + }, + }, + alert_suppression: { + baseValue: { group_by: ['base-field'] }, + customValue: { group_by: ['custom-field'] }, + updatedValue: { group_by: ['updated-field'] }, + resolvedValue: { group_by: ['resolved-field'] }, + }, + threshold: { + baseValue: { field: ['base-field'], value: 100 }, + customValue: { field: ['custom-field'], value: 200 }, + updatedValue: { field: ['updated-field'], value: 300 }, + resolvedValue: { field: ['resolved-field'], value: 300 }, + }, + machine_learning_job_id: { + baseValue: ['base-job-id'], + customValue: ['custom-job-id'], + updatedValue: ['updated-job-id'], + resolvedValue: ['resolved-job-id'], + }, +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts index d00d2d842f2ba..683c3fedaf918 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts @@ -6,1167 +6,356 @@ */ import expect from 'expect'; import { - AllFieldsDiff, - KqlQueryType, - RuleUpdateProps, ThreeWayDiffConflict, ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { - getPrebuiltRuleMock, - getPrebuiltThreatMatchRuleMock, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; -import { PrebuiltRuleAsset } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules'; +import { KQL_QUERY_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, installPrebuiltRules, createPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + fetchRule, updateRule, - patchRule, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - - describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); +import { + KQL_QUERY_FIELD_RULE_TYPE_MAPPING, + KQL_QUERY_FIELDS_MOCK_VALUES, + KqlQueryFieldTestValues, + mapKQLQueryDiffableFieldToRuleFields, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof KQL_QUERY_FIELD_RULE_TYPE_MAPPING; + +type FieldDiffs = Record; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: KQL_QUERY_FIELDS, + testValues: KqlQueryFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + ...mapKQLQueryDiffableFieldToRuleFields(field, baseValue), + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + ...mapKQLQueryDiffableFieldToRuleFields(field, baseValue), + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); }); - describe(`kql_query fields`, () => { - const getQueryRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [], - }), - ]; - - const getSavedQueryRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - type: 'saved_query', - saved_id: 'saved-query-id', - }), - ]; - - describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { - describe('when all versions are inline query types', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but kql_query field is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toBeUndefined(); + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...mapKQLQueryDiffableFieldToRuleFields(field, customValue), }); - describe('when all versions are saved query types', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getSavedQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'saved_query', - saved_id: 'saved-query-id', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but kql_query field is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toBeUndefined(); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + ...mapKQLQueryDiffableFieldToRuleFields(field, baseValue), + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, }); - describe('when all query versions have different surrounding whitespace', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'query', - query: '\nquery string = true', - language: 'kuery', - filters: [], - saved_id: undefined, - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = true\n', - language: 'kuery', - filters: [], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update but kql_query field is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toBeUndefined(); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { - describe('when current version is inline query type', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getSavedQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [], - saved_id: undefined, - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'saved_query', - saved_id: 'saved-query-id', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that kql_query diff field is returned but field does not have an update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // `type` is considered to be a conflict - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapKQLQueryDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, }); - describe('when current version is saved query type', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'saved_query', - query: undefined, - language: undefined, - filters: undefined, - saved_id: 'saved-query-id', - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, do NOT update the related kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that kql_query diff field is returned but field does not have an update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // `type` is considered to be a conflict - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe('when rule field has an update but does not have a custom value - scenario AAB', () => { - describe('when all versions are inline query type', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = false', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); - - describe('when all versions are saved query type', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getSavedQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'saved_query', - saved_id: 'new-saved-query-id', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - current_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - target_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - merged_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...mapKQLQueryDiffableFieldToRuleFields(field, customValue), }); - }); - - describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { - describe('when all versions are inline query type', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - query: 'query string = false', - }); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = false', - language: 'kuery', - filters: [], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains kql_query field - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapKQLQueryDiffableFieldToRuleFields(field, customValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, }); - describe('when all versions are saved query types', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getSavedQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'saved_query', - saved_id: 'new-saved-query-id', - } as RuleUpdateProps); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'saved_query', - saved_id: 'new-saved-query-id', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains kql_query field - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - current_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - target_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - merged_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe('when rule field has an update and a custom value that are different - scenario ABC', () => { - describe('when current version is different type than base and target', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'saved_query', - query: undefined, - language: undefined, - filters: undefined, - saved_id: 'saved-query-id', - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = false', - language: 'kuery', - filters: [], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is also considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // `type` is also considered to be a conflict - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(2); + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...mapKQLQueryDiffableFieldToRuleFields(field, customValue), }); - describe('when all versions are inline query type', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - query: 'query string = false', - }); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [{ field: 'query' }], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [{ field: 'query' }], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapKQLQueryDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, }); - describe('when all versions are saved query type', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects( - es, - getSavedQueryRuleAssetSavedObjects() - ); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'saved_query', - saved_id: 'new-saved-query-id', - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'saved_query', - saved_id: 'even-newer-saved-query-id', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'saved-query-id', - }, - current_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - target_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'even-newer-saved-query-id', - }, - merged_version: { - type: KqlQueryType.saved_query, - saved_query_id: 'new-saved-query-id', - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + }); + }); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); - }); + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - describe('when rule type is threat match', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - ...getPrebuiltThreatMatchRuleMock(), - threat_filters: [], - } as PrebuiltRuleAsset), - ]); - await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); - // Customize a threat_query on the installed rule - await updateRule(supertest, { - ...getPrebuiltThreatMatchRuleMock(), + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - threat_query: '*', - threat_filters: [], - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a threat_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - ...getPrebuiltThreatMatchRuleMock(), - threat_query: `*:'new query'`, - threat_filters: [], - version: 2, - } as PrebuiltRuleAsset), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and threat_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.threat_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: '*:*', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: '*', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: `*:'new query'`, - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: '*', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); + version: 2, + ...mapKQLQueryDiffableFieldToRuleFields(field, baseValue), + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe('when rule type is threshold', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - type: 'threshold', - query: 'query string = true', - threshold: { - field: 'some.field', - value: 4, - }, - }), - ]); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'threshold', - query: 'query string = false', - threshold: { - field: 'some.field', - value: 4, - }, - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'threshold', - query: 'new query string = true', - threshold: { - field: 'some.field', - value: 4, - }, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'new query string = true', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { + it('should show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + await deleteAllPrebuiltRuleAssets(es, log); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...mapKQLQueryDiffableFieldToRuleFields(field, customValue), }); - }); - describe('when rule type is new_terms', () => { - it('should show a non-solvable conflict in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - type: 'new_terms', - query: 'query string = true', - new_terms_fields: ['user.name'], - history_window_start: 'now-7d', - }), - ]); - await installPrebuiltRules(es, supertest); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - type: 'new_terms', - query: 'query string = false', - new_terms_fields: ['user.name'], - history_window_start: 'now-7d', - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'new_terms', - query: 'new query string = true', - new_terms_fields: ['user.name'], - history_window_start: 'now-7d', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - base_version: { - type: KqlQueryType.inline_query, - query: 'query string = true', - language: 'kuery', - filters: [], - }, - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'new query string = true', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + version: 2, + ...mapKQLQueryDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + current_version: customValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: false, }); - }); - }); - - describe('when rule base version does not exist', () => { - describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getQueryRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); - - // Add a v2 rule asset to make the upgrade possible, but keep kql_query field unchanged - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'query string = true', - language: 'kuery', - filters: [], - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // but does NOT contain kql_query field - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toBeUndefined(); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); + }); + }); +}; - describe('when rule field has an update and a custom value that are different - scenario -AB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getQueryRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); - - // Customize a kql_query field on the installed rule - await updateRule(supertest, { - ...getPrebuiltRuleMock(), - rule_id: 'rule-1', - type: 'query', - query: 'query string = false', - language: 'kuery', - filters: [], - } as RuleUpdateProps); - - // Add a v2 rule asset to make the upgrade possible, update a kql_query field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - type: 'query', - query: 'new query string = true', - language: 'kuery', - filters: [], - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and kql_query field update does not have a conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; - expect(fieldDiffObject.kql_query).toEqual({ - current_version: { - type: KqlQueryType.inline_query, - query: 'query string = false', - language: 'kuery', - filters: [], - }, - target_version: { - type: KqlQueryType.inline_query, - query: 'new query string = true', - language: 'kuery', - filters: [], - }, - merged_version: { - type: KqlQueryType.inline_query, - query: 'new query string = true', - language: 'kuery', - filters: [], - }, - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: false, - }); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // query - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + Object.entries(KQL_QUERY_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule kql query fields`, () => { + fields.forEach((field) => { + const testValues = KQL_QUERY_FIELDS_MOCK_VALUES[field as KQL_QUERY_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as KQL_QUERY_FIELDS, + testValues, + { + es, + supertest, + log, + } + ); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts index 23bfd08f5b520..6e8338a0c316f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts @@ -10,196 +10,369 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { - TEXT_XL_A, - TEXT_XL_B, - TEXT_XL_C, - TEXT_XL_MERGED, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.mock'; +import { MULTI_LINE_STRING_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, installPrebuiltRules, createPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, - patchRule, + updateRule, + fetchRule, createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { + MultiLineStringFieldTestValues, + MULTI_LINE_STRING_FIELDS_MOCK_VALUES, + MULTI_LINE_FIELD_RULE_TYPE_MAPPING, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof MULTI_LINE_FIELD_RULE_TYPE_MAPPING; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: MULTI_LINE_STRING_FIELDS, + testValues: MultiLineStringFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue, mergedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + version: 1, + [field]: baseValue, + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect( + (reviewResponse.rules[0].diff.fields as Record)[field] + ).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); }); - describe(`multi line string fields`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - description: 'My description.\nThis is a second line.', - }), - ]; + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); - // Increment the version of the installed rule, do NOT update the related multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - description: 'My description.\nThis is a second line.', - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible - // for update but multi-line string field (description) is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toBeUndefined(); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // version - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + version: 2, + [field]: customValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response with a solvable conflict', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { rule_id: 'rule-1', - description: 'My GREAT description.\nThis is a second line.', + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: mergedValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + + if (field === 'description' && testValues.longBaseValue) { + it('should handle long multi-line strings without timing out', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ + createRuleAssetSavedObjectOfType('query', { + rule_id: 'rule-1', + version: 1, + [field]: testValues.longBaseValue, + }), + ]); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: testValues.longCustomValue as string, }); - // Increment the version of the installed rule, do NOT update the related multi line string field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType('query', { rule_id: 'rule-1', - description: 'My description.\nThis is a second line.', version: 2, + [field]: testValues.longUpdatedValue, }), ]; await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that multi line string diff field is returned but field does not have an update const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: 'My description.\nThis is a second line.', - current_version: 'My GREAT description.\nThis is a second line.', - target_version: 'My description.\nThis is a second line.', - merged_version: 'My GREAT description.\nThis is a second line.', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: testValues.longBaseValue, + current_version: testValues.longCustomValue, + target_version: testValues.longUpdatedValue, + merged_version: testValues.longMergedValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, has_base_version: true, }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); - }); + } - describe('when rule field has an update but does not have a custom value - scenario AAB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule + describe('when all versions are not mergeable - scenario ABC with non-solvable conflict', () => { + it('should show in the upgrade/_review API response with a non-solvable conflict', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: `${customValue}\nThis is a third line.`, + }); + const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType('query', { rule_id: 'rule-1', version: 2, - description: 'My GREAT description.\nThis is a second line.', + [field]: `My EXCELLENT ${field}.\nThis is a fourth line.`, }), ]; await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: 'My description.\nThis is a second line.', - current_version: 'My description.\nThis is a second line.', - target_version: 'My GREAT description.\nThis is a second line.', - merged_version: 'My GREAT description.\nThis is a second line.', - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: `${customValue}\nThis is a third line.`, + target_version: `My EXCELLENT ${field}.\nThis is a fourth line.`, + merged_version: `${customValue}\nThis is a third line.`, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, has_update: true, has_base_version: true, }); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); }); }); + }); - describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - description: 'My GREAT description.\nThis is a second line.', + await deleteAllPrebuiltRuleAssets(es, log); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: baseValue, }); - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType('query', { rule_id: 'rule-1', version: 2, - description: 'My GREAT description.\nThis is a second line.', + [field]: baseValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: 'My description.\nThis is a second line.', - current_version: 'My GREAT description.\nThis is a second line.', - target_version: 'My GREAT description.\nThis is a second line.', - merged_version: 'My GREAT description.\nThis is a second line.', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); + expect( + (reviewResponse.rules[0].diff.fields as Record)[field] + ).toBeUndefined(); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); @@ -210,233 +383,76 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - describe('when rule field has an update and a custom value that are different - scenario ABC', () => { - describe('when all versions are mergable', () => { - it('should show in the upgrade/_review API response with a solvable conflict', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { + it('should show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - description: 'My GREAT description.\nThis is a second line.', - }); - - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - description: 'My description.\nThis is a second line, now longer.', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and multi line string field update has no conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: 'My description.\nThis is a second line.', - current_version: 'My GREAT description.\nThis is a second line.', - target_version: 'My description.\nThis is a second line, now longer.', - merged_version: 'My GREAT description.\nThis is a second line, now longer.', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + await deleteAllPrebuiltRuleAssets(es, log); - it('should handle long multi-line strings without timing out', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - description: TEXT_XL_A, - }), - ]); - await installPrebuiltRules(es, supertest); - - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - description: TEXT_XL_B, - }); - - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - description: TEXT_XL_C, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and multi line string field update has no conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: TEXT_XL_A, - current_version: TEXT_XL_B, - target_version: TEXT_XL_C, - merged_version: TEXT_XL_MERGED, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: baseValue, }); - }); - describe('when all versions are not mergable', () => { - it('should show in the upgrade/_review API response with a non-solvable conflict', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType('query', { rule_id: 'rule-1', - description: 'My GREAT description.\nThis is a third line.', - }); - - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - description: 'My EXCELLENT description.\nThis is a fourth.', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and multi line string field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - base_version: 'My description.\nThis is a second line.', - current_version: 'My GREAT description.\nThis is a third line.', - target_version: 'My EXCELLENT description.\nThis is a fourth.', - merged_version: 'My GREAT description.\nThis is a third line.', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, - has_update: true, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); - }); - }); - }); + version: 2, + [field]: updatedValue, + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - describe('when rule base version does not exist', () => { - describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: false, + }); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - description: 'My description.\nThis is a second line.', - }); - - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - description: 'My description.\nThis is a second line.', - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // but does NOT contain multi line string field, since -AA is treated as AAA - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toBeUndefined(); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); + }); + }); +}; - describe('when rule field has an update and a custom value that are different - scenario -AB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - // Customize a multi line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - description: 'My description.\nThis is a second line.', - }); - - // Increment the version of the installed rule, update a multi line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - description: 'My GREAT description.\nThis is a second line.', - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and multi line string field update does not have a conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.description).toEqual({ - current_version: 'My description.\nThis is a second line.', - target_version: 'My GREAT description.\nThis is a second line.', - merged_version: 'My GREAT description.\nThis is a second line.', - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: false, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + Object.entries(MULTI_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule multi line string fields`, () => { + fields.forEach((field) => { + const testValues = MULTI_LINE_STRING_FIELDS_MOCK_VALUES[field]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as MULTI_LINE_STRING_FIELDS, + testValues, + { es, supertest, log } + ); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts index bd059ec137a96..36a988ca204aa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts @@ -10,187 +10,267 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { NUMBER_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, installPrebuiltRules, createPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, - patchRule, createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + fetchRule, + updateRule, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - - describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); +import { + NUMBER_FIELDS_MOCK_VALUES, + NUMBER_FIELD_RULE_TYPE_MAPPING, + NumberFieldTestValues, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof NUMBER_FIELD_RULE_TYPE_MAPPING; + +type FieldDiffs = Record; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: NUMBER_FIELDS, + testValues: NumberFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + [field]: baseValue, + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); }); - describe(`number fields`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1, risk_score: 1 }), - ]; + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Increment the version of the installed rule, do NOT update the related number field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - risk_score: 1, - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible - // for update but number field (risk_score) is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toBeUndefined(); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // version - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Customize a number field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - risk_score: 2, - }); + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); - // Increment the version of the installed rule, do NOT update the related number field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - risk_score: 1, - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that number diff field is returned but field does not have an update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toEqual({ - base_version: 1, - current_version: 2, - target_version: 1, - merged_version: 2, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - }); - describe('when rule field has an update but does not have a custom value - scenario AAB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: customValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - // Increment the version of the installed rule, update a number field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - risk_score: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toEqual({ - base_version: 1, - current_version: 1, - target_version: 2, - merged_version: 2, - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, - has_update: true, - has_base_version: true, - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); }); + }); - describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a number field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - risk_score: 2, - }); + await deleteAllPrebuiltRuleAssets(es, log); - // Increment the version of the installed rule, update a number field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - risk_score: 2, + [field]: baseValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains number field const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toEqual({ - base_version: 1, - current_version: 2, - target_version: 2, - merged_version: 2, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); @@ -201,133 +281,74 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a number field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - risk_score: 2, + await deleteAllPrebuiltRuleAssets(es, log); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - // Increment the version of the installed rule, update a number field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - risk_score: 3, + [field]: updatedValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and number field update has conflict const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toEqual({ - base_version: 1, - current_version: 2, - target_version: 3, - merged_version: 2, - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + current_version: customValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, has_update: true, - has_base_version: true, + has_base_version: false, }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); + }); + }); +}; - describe('when rule base version does not exist', () => { - describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); - - // Increment the version of the installed rule with the number field maintained - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - risk_score: 1, - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // but does NOT contain the risk_score number field, since -AA is treated as AAA - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toBeUndefined(); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); - - describe('when rule field has an update and a custom value that are different - scenario -AB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - // Customize a number field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - risk_score: 2, - }); - - // Increment the version of the installed rule, update a number field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - risk_score: 3, - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and number field update does not have a conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.risk_score).toEqual({ - current_version: 2, - target_version: 3, - merged_version: 3, - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: false, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + Object.entries(NUMBER_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule number fields`, () => { + fields.forEach((field) => { + const testValues = NUMBER_FIELDS_MOCK_VALUES[field]; + createTestSuite(ruleType as keyof RuleTypeToFields, field as NUMBER_FIELDS, testValues, { + es, + supertest, + log, }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts index 881e8e6122175..1162a6d781cdf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts @@ -10,381 +10,420 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SCALAR_ARRAY_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, installPrebuiltRules, createPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, - patchRule, createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, + fetchRule, + updateRule, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - - describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); +import { + SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING, + ScalarArrayFieldTestValues, + SCALAR_ARRAY_FIELDS_MOCK_VALUES, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: SCALAR_ARRAY_FIELDS, + testValues: ScalarArrayFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + [field]: baseValue, + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect( + (reviewResponse.rules[0].diff.fields as Record)[field] + ).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); }); - describe(`scalar array fields`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - tags: ['one', 'two', 'three'], - }), - ]; + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); - // Increment the version of the installed rule, do NOT update the related scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - tags: ['one', 'three', 'two'], - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but scalar array field is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toBeUndefined(); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - tags: ['one', 'two', 'four'], - }); + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); - // Increment the version of the installed rule, do NOT update the related scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - tags: ['one', 'two', 'three'], - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that scalar array diff field is returned but field does not have an update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'three'], - current_version: ['one', 'two', 'four'], - target_version: ['one', 'two', 'three'], - merged_version: ['one', 'two', 'four'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - }); - describe('when rule field has an update but does not have a custom value - scenario AAB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: customValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['one', 'two', 'four'], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'three'], - current_version: ['one', 'two', 'three'], - target_version: ['one', 'two', 'four'], - merged_version: ['one', 'two', 'four'], - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, - has_update: true, - has_base_version: true, - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - }); - - describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - tags: ['one', 'two', 'four'], - }); - - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['one', 'two', 'four'], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: [...customValue, ...updatedValue.filter((v) => !customValue.includes(v))], + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, + }); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains scalar array field - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'three'], - current_version: ['one', 'two', 'four'], - target_version: ['one', 'two', 'four'], - merged_version: ['one', 'two', 'four'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); - describe('when rule field has an update and a custom value that are different - scenario ABC', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - tags: ['one', 'two', 'four'], - }); + it('should compare values after deduplication', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['one', 'two', 'five'], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: [baseValue[0], baseValue[0], customValue[2]], + }); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and scalar array field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'three'], - current_version: ['one', 'two', 'four'], - target_version: ['one', 'two', 'five'], - merged_version: ['one', 'two', 'four', 'five'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: true, - }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: [customValue[2], customValue[2], baseValue[0]], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: [baseValue[0], baseValue[0], customValue[2]], + target_version: [customValue[2], customValue[2], baseValue[0]], + merged_version: [baseValue[0], customValue[2]], + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + it('should compare values sensitive of case', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: [baseValue[0].toUpperCase(), customValue[2]], }); - it('should compare values after deduplication', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - tags: ['one', 'two', 'two'], - }), - ]); - await installPrebuiltRules(es, supertest); - - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - tags: ['two', 'one', 'three'], - }); - - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['three', 'three', 'one'], - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and scalar array field update has conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'two'], - current_version: ['two', 'one', 'three'], - target_version: ['three', 'three', 'one'], - merged_version: ['one', 'three'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: true, - }); + version: 2, + [field]: [baseValue[0].toUpperCase(), updatedValue[2]], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: [baseValue[0].toUpperCase(), customValue[2]], + target_version: [baseValue[0].toUpperCase(), updatedValue[2]], + merged_version: [baseValue[0].toUpperCase(), customValue[2], updatedValue[2]], + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + it('should handle empty arrays', async () => { + // Skip test for new_terms_fields since field cannot be updated to empty array, + // i.e. the array must contain at least one element + if (field === 'new_terms_fields') { + return; + } + + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: [], + }); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: [baseValue[0], baseValue[1], updatedValue[2]], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + base_version: baseValue, + current_version: [], + target_version: [baseValue[0], baseValue[1], updatedValue[2]], + merged_version: [updatedValue[2]], + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, }); + }); + }); - it('should compare values sensitive of case', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 1, - tags: ['ONE', 'TWO'], - }), - ]); + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - tags: ['one', 'ONE'], - }); + await deleteAllPrebuiltRuleAssets(es, log); - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - tags: ['ONE', 'THREE'], + [field]: baseValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and scalar array field update has conflict const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['ONE', 'TWO'], - current_version: ['one', 'ONE'], - target_version: ['ONE', 'THREE'], - merged_version: ['ONE', 'one', 'THREE'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: true, - }); + expect( + (reviewResponse.rules[0].diff.fields as Record)[field] + ).toBeUndefined(); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - it('should handle empty arrays', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { + it('should show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - tags: [], + await deleteAllPrebuiltRuleAssets(es, log); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - tags: ['one', 'two', 'five'], + [field]: updatedValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and scalar array field update has conflict const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - base_version: ['one', 'two', 'three'], - current_version: [], - target_version: ['one', 'two', 'five'], - merged_version: ['five'], - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Merged, + expect((reviewResponse.rules[0].diff.fields as Record)[field]).toEqual({ + current_version: customValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, conflict: ThreeWayDiffConflict.SOLVABLE, has_update: true, - has_base_version: true, + has_base_version: false, }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); @@ -396,89 +435,32 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); + }); + }); +}; - describe('when rule base version does not exist', () => { - describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); - - // Increment the version of the installed rule, but keep scalar array field unchanged - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['one', 'two', 'three'], // unchanged - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // but does NOT contain scalar array field (tags is not present, since scenario -AA is not included in response) - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toBeUndefined(); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); - - describe('when rule field has an update and a custom value that are different - scenario -AB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - // Customize a scalar array field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - tags: ['one', 'two', 'four'], - }); - - // Increment the version of the installed rule, update a scalar array field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - tags: ['one', 'two', 'five'], - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and scalar array field update does not have a conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.tags).toEqual({ - current_version: ['one', 'two', 'four'], - target_version: ['one', 'two', 'five'], - merged_version: ['one', 'two', 'five'], - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: false, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // tags - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + Object.entries(SCALAR_ARRAY_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule scalar array fields`, () => { + fields.forEach((field) => { + const testValues = SCALAR_ARRAY_FIELDS_MOCK_VALUES[field as SCALAR_ARRAY_FIELDS]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SCALAR_ARRAY_FIELDS, + testValues, + { es, supertest, log } + ); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.simple_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.simple_fields.ts new file mode 100644 index 0000000000000..190d7a5aee2e0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.simple_fields.ts @@ -0,0 +1,364 @@ +/* + * 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 expect from 'expect'; +import { + ThreeWayDiffConflict, + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SIMPLE_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; + +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + installPrebuiltRules, + createPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + updateRule, + fetchRule, + createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { + SIMPLE_FIELD_RULE_TYPE_MAPPING, + mapDiffableFieldToRuleFields, + SIMPLE_FIELDS_MOCK_VALUES, + SimpleFieldTestValues, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof SIMPLE_FIELD_RULE_TYPE_MAPPING; + +type FieldDiffs = Record; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: SIMPLE_FIELDS, + testValues: SimpleFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + ...mapDiffableFieldToRuleFields(field, baseValue), + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + ...mapDiffableFieldToRuleFields(field, baseValue), + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + const mappedFields = mapDiffableFieldToRuleFields(field, customValue); + + await updateRule(supertest, { + ...rule, + id: undefined, + ...mappedFields, + }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + ...mapDiffableFieldToRuleFields(field, baseValue), + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + const mappedFields = mapDiffableFieldToRuleFields(field, customValue); + + await updateRule(supertest, { + ...rule, + id: undefined, + ...mappedFields, + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapDiffableFieldToRuleFields(field, customValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + const mappedFields = mapDiffableFieldToRuleFields(field, customValue); + + await updateRule(supertest, { + ...rule, + id: undefined, + ...mappedFields, + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + }); + }); + + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + await deleteAllPrebuiltRuleAssets(es, log); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapDiffableFieldToRuleFields(field, baseValue), + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { + it('should show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + await deleteAllPrebuiltRuleAssets(es, log); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + ...mapDiffableFieldToRuleFields(field, customValue), + }); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + ...mapDiffableFieldToRuleFields(field, updatedValue), + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + current_version: customValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: false, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + }); +}; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + Object.entries(SIMPLE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule simple fields`, () => { + fields.forEach((field) => { + const testValues = SIMPLE_FIELDS_MOCK_VALUES[field]; + createTestSuite(ruleType as keyof RuleTypeToFields, field as SIMPLE_FIELDS, testValues, { + es, + supertest, + log, + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts index 6d32d8df7bc72..476d9f3879a09 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts @@ -10,224 +10,265 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SINGLE_LINE_STRING_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; +import { Client } from '@elastic/elasticsearch'; +import TestAgent from 'supertest/lib/agent'; +import { ToolingLog } from '@kbn/tooling-log'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, installPrebuiltRules, createPrebuiltRuleAssetSavedObjects, reviewPrebuiltRulesToUpgrade, - patchRule, + updateRule, + fetchRule, createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObjectOfType, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const log = getService('log'); - - describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllTimelines(es, log); - await deleteAllPrebuiltRuleAssets(es, log); +import { + SINGLE_LINE_FIELD_RULE_TYPE_MAPPING, + SingleLineStringFieldTestValues, + SINGLE_LINE_STRING_FIELDS_MOCK_VALUES, +} from './upgrade_prebuilt_rules.mock_data'; + +type RuleTypeToFields = typeof SINGLE_LINE_FIELD_RULE_TYPE_MAPPING; + +type FieldDiffs = Record; + +const createTestSuite = ( + ruleType: keyof RuleTypeToFields, + field: SINGLE_LINE_STRING_FIELDS, + testValues: SingleLineStringFieldTestValues, + services: { es: Client; supertest: TestAgent; log: ToolingLog } +) => { + const { es, supertest, log } = services; + const { baseValue, customValue, updatedValue } = testValues; + + describe(`testing field: ${field}`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 1, + [field]: baseValue, + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + it('should not show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); }); - describe(`single line string fields`, () => { - const getRuleAssetSavedObjects = () => [ - createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1, name: 'A' }), - ]; - - describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Increment the version of the installed rule, do NOT update the related single line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - name: 'A', - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update - // but single line string field (name) is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + [field]: baseValue, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: baseValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - it('should trim all whitespace before version comparison', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Customize a single line string field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - name: 'A\n', - }); + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: baseValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); - // Increment the version of the installed rule, do NOT update the related single line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - name: '\nA', - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update - // but single line string field (name) is NOT returned - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - }); - - describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - // Customize a single line string field on the installed rule - await patchRule(supertest, log, { + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', - name: 'B', - }); - - // Increment the version of the installed rule, do NOT update the related single line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - name: 'A', - version: 2, - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that single line string diff field - // is returned but field does not have an update, and the merge outcome is "Current" - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ - base_version: 'A', - current_version: 'B', - target_version: 'A', - merged_version: 'B', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); + version: 2, + [field]: customValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: customValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); + }); - describe('when rule field has an update but does not have a custom value - scenario AAB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + it('should show in the upgrade/_review API response', async () => { + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); - // Increment the version of the installed rule, update a single line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - name: 'B', - }), - ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, + }); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ - base_version: 'A', - current_version: 'A', - target_version: 'B', - merged_version: 'B', - diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.NONE, - has_update: true, - has_base_version: true, - }); + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObjectOfType(ruleType, { + rule_id: 'rule-1', + version: 2, + [field]: updatedValue, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + base_version: baseValue, + current_version: customValue, + target_version: updatedValue, + merged_version: customValue, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); }); + }); - describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a single line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - name: 'B', - }); + await deleteAllPrebuiltRuleAssets(es, log); - // Increment the version of the installed rule, update a single line string field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - name: 'B', + [field]: baseValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ - base_version: 'A', - current_version: 'B', - target_version: 'B', - merged_version: 'B', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, - has_update: false, - has_base_version: true, - }); + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); @@ -239,135 +280,76 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); await installPrebuiltRules(es, supertest); - // Customize a single line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - name: 'B', + await deleteAllPrebuiltRuleAssets(es, log); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + await updateRule(supertest, { + ...rule, + id: undefined, + [field]: customValue, }); - // Increment the version of the installed rule, update a single line string field, and create the new rule assets const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ + createRuleAssetSavedObjectOfType(ruleType, { rule_id: 'rule-1', version: 2, - name: 'C', + [field]: updatedValue, }), ]; - await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and single line string field update has NON_SOLVABLE conflict, and merged version is CURRENT const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ - base_version: 'A', - current_version: 'B', - target_version: 'C', - merged_version: 'B', - diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NON_SOLVABLE, + expect((reviewResponse.rules[0].diff.fields as FieldDiffs)[field]).toEqual({ + current_version: customValue, + target_version: updatedValue, + merged_version: updatedValue, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, has_update: true, - has_base_version: true, + has_base_version: false, }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); + }); + }); +}; - describe('when rule base version does not exist', () => { - describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { - it('should not show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); - - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); - - // Increment the version of the installed rule, but keep single line string field unchanged - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - name: 'A', // unchanged - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // but does NOT contain single line string field - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); - }); - - describe('when rule field has an update and a custom value that are different - scenario -AB', () => { - it('should show in the upgrade/_review API response', async () => { - // Install base prebuilt detection rule - await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(es, supertest); +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); - // Clear previous rule assets - await deleteAllPrebuiltRuleAssets(es, log); + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); - // Customize a single line string field on the installed rule - await patchRule(supertest, log, { - rule_id: 'rule-1', - name: 'B', - }); - - // Increment the version of the installed rule, update a single line string field, and create the new rule assets - const updatedRuleAssetSavedObjects = [ - createRuleAssetSavedObject({ - rule_id: 'rule-1', - version: 2, - name: 'C', - }), - ]; - await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); - - // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update - // and single line string field update does not have a conflict - const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ - current_version: 'B', - target_version: 'C', - merged_version: 'C', - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, - has_update: true, - has_base_version: false, - }); - - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // name is considered as a conflict - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); - - expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); - expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); - }); + Object.entries(SINGLE_LINE_FIELD_RULE_TYPE_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule single line string fields`, () => { + fields.forEach((field) => { + const testValues = SINGLE_LINE_STRING_FIELDS_MOCK_VALUES[field]; + createTestSuite( + ruleType as keyof RuleTypeToFields, + field as SINGLE_LINE_STRING_FIELDS, + testValues, + { es, supertest, log } + ); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts index 3ebd928123cc4..9a8897891d812 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/create_prebuilt_rule_saved_objects.ts @@ -42,9 +42,10 @@ export const createRuleAssetSavedObject = (overrideParams: Partial( - type: T['type'] + type: T['type'], + rewrites?: Partial ) => ({ - 'security-rule': getPrebuiltRuleMockOfType(type), + 'security-rule': { ...getPrebuiltRuleMockOfType(type), ...rewrites }, ...ruleAssetSavedObjectESFields, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts index cee439311d2a1..c36f0b4f584f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts @@ -23,12 +23,13 @@ import { export const updateRule = async ( supertest: SuperTest.Agent, updatedRule: RuleUpdateProps -): Promise => - ( - await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule) - .expect(200) - ).body; +): Promise => { + const res = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule) + .expect(200); + + return res.body; +};