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..0b8880cc8ccf5 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,45 @@ 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 = + | 'rule_id' + | '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..3d5dad259500d 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 @@ -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_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..253b3a43970d6 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,427 @@ */ 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); - }); - - 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', - }), - ]; +interface Value { + query: string; + language: string; + type?: string; + filters: string[]; +} + +interface KqlQueryFieldTestValues { + baseValue: Value; + customValue: Value; + updatedValue: Value; +} + +const mapDiffableFieldToRuleFields = (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; +}; - 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); +const KQL_QUERY_FIELDS_MAP: 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', + }, + }, + threat_query: { + baseValue: { + query: 'source.ip:10.0.0.*', + language: 'kuery', + filters: [], + }, + customValue: { + query: 'source.ip:192.168.*', + language: 'kuery', + filters: [], + }, + updatedValue: { + query: 'source.ip:172.16.*', + language: 'kuery', + filters: [], + }, + }, +}; - // 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); +const RULE_TYPE_FIELD_MAPPING = { + query: ['kql_query'], + // threat_match: ['threat_query'], +} as const; + +type RuleTypeToFields = typeof RULE_TYPE_FIELD_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, + ...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); + }); + }); - // 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, + ...mapDiffableFieldToRuleFields(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', + ...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, }); - 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, + ...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, }); - 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, + ...mapDiffableFieldToRuleFields(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, + ...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, }); - 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, + ...mapDiffableFieldToRuleFields(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, + ...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, }); - 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, + ...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 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, + ...mapDiffableFieldToRuleFields(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, + ...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, }); - }); - }); - - 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(RULE_TYPE_FIELD_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule kql query fields`, () => { + fields.forEach((field) => { + const testValues = KQL_QUERY_FIELDS_MAP[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..5ae04e19c3ee8 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,406 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { MULTI_LINE_STRING_FIELDS } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; 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 { 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'); +interface MultiLineStringFieldTestValues { + baseValue: string; + customValue: string; + updatedValue: string; + mergedValue: string; + longBaseValue?: string; + longCustomValue?: string; + longUpdatedValue?: string; + longMergedValue?: string; +} + +const MULTI_LINE_STRING_FIELDS_MAP: 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.', + 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.', + }, + 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.', + }, +}; - 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 createTestSuite = ( + 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); + }); }); - 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 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 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); + 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, + }); - // 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.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 - 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 () => { + 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, }); + + 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 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 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, + }); - // 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 second line.', + 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 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); + + 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]: 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,235 +420,68 @@ 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_STRING_FIELDS_MAP).forEach(([field, testValues]) => { + createTestSuite(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..b804024adece7 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,291 @@ 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'); +interface NumberFieldTestValues { + baseValue: number; + customValue: number; + updatedValue: number; +} + +const NUMBER_FIELDS_MAP: Record = { + risk_score: { + baseValue: 1, + customValue: 2, + updatedValue: 3, + }, + max_signals: { + baseValue: 100, + customValue: 200, + updatedValue: 300, + }, + anomaly_threshold: { + baseValue: 50, + customValue: 75, + updatedValue: 90, + }, +}; - 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_TYPE_FIELD_MAPPING = { + query: ['risk_score', 'max_signals'], + machine_learning: ['anomaly_threshold'], +} as const; + +type RuleTypeToFields = typeof RULE_TYPE_FIELD_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 +305,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(RULE_TYPE_FIELD_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule number fields`, () => { + fields.forEach((field) => { + const testValues = NUMBER_FIELDS_MAP[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..6b2fc92138aff 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,450 @@ 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'); +interface ScalarArrayFieldTestValues { + baseValue: string[]; + customValue: string[]; + updatedValue: string[]; +} + +const SCALAR_ARRAY_FIELDS_MAP: Record = { + tags: { + baseValue: ['one', 'two', 'three'], + customValue: ['one', 'two', 'four'], + updatedValue: ['one', 'two', 'five'], + }, + references: { + baseValue: ['ref1', 'ref2', 'ref3'], + customValue: ['ref1', 'ref2', 'ref4'], + updatedValue: ['ref1', 'ref2', 'ref5'], + }, + threat_index: { + baseValue: ['index1', 'index2', 'index3'], + customValue: ['index1', 'index2', 'index4'], + updatedValue: ['index1', 'index2', 'index5'], + }, + new_terms_fields: { + baseValue: ['field1', 'field2', 'field3'], + customValue: ['field1', 'field2', 'field4'], + updatedValue: ['field1', 'field2', 'field5'], + }, +}; - 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_TYPE_FIELD_MAPPING = { + query: ['tags', 'references'], + threat_match: ['threat_index'], + new_terms: ['new_terms_fields'], +} as const; + +type RuleTypeToFields = typeof RULE_TYPE_FIELD_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 +465,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(RULE_TYPE_FIELD_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule scalar array fields`, () => { + fields.forEach((field) => { + const testValues = SCALAR_ARRAY_FIELDS_MAP[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..9a35f084b232e --- /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,580 @@ +/* + * 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 { calculateFromValue } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; +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'; + +interface SimpleFieldTestValues { + baseValue: any; + customValue: any; + updatedValue: any; +} + +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 '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; + case 'timestamp_override_fallback_disabled': + updatePayload.fallback_disabled = value; + break; + case 'building_block': + updatePayload.building_block_type = value.type; + break; + default: + updatePayload[diffableField] = value; + } + + return updatePayload; +}; + +const SIMPLE_FIELDS_MAP: Record, SimpleFieldTestValues> = { + 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', + }, + ], + }, + 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, + }, + ], + }, + false_positives: { + baseValue: ['base-false-positive'], + customValue: ['custom-false-positive'], + updatedValue: ['updated-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' } }, + ], + }, + 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', + }, + ], + }, + 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, + }, + ], + }, + rule_schedule: { + baseValue: { interval: '5m', lookback: '60s' }, + customValue: { interval: '10m', lookback: '0s' }, + updatedValue: { interval: '15m', lookback: '60s' }, + }, + rule_name_override: { + baseValue: { field_name: 'base-override' }, + customValue: { field_name: 'custom-override' }, + updatedValue: { field_name: 'updated-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 }, + }, + 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' }, + }, + building_block: { + baseValue: { type: 'a' }, + customValue: { type: 'b' }, + updatedValue: { type: 'c' }, + }, + investigation_fields: { + baseValue: { + field_names: ['base.field'], + }, + customValue: { + field_names: ['custom.field'], + }, + updatedValue: { + field_names: ['updated.field'], + }, + }, + alert_suppression: { + baseValue: { group_by: ['base-field'] }, + customValue: { group_by: ['custom-field'] }, + updatedValue: { group_by: ['updated-field'] }, + }, + threshold: { + baseValue: { field: ['base-field'], value: 100 }, + customValue: { field: ['custom-field'], value: 200 }, + updatedValue: { field: ['updated-field'], value: 300 }, + }, + machine_learning_job_id: { + baseValue: ['base-job-id'], + customValue: ['custom-job-id'], + updatedValue: ['updated-job-id'], + }, +}; + +const RULE_TYPE_FIELD_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; + +type RuleTypeToFields = typeof RULE_TYPE_FIELD_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(RULE_TYPE_FIELD_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule simple fields`, () => { + fields.forEach((field) => { + const testValues = SIMPLE_FIELDS_MAP[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..e16fd945d511c 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,298 @@ 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'); +interface SingleLineStringFieldTestValues { + baseValue: string; + customValue: string; + updatedValue: string; +} + +const SINGLE_LINE_STRING_FIELDS_MAP: Record< + SINGLE_LINE_STRING_FIELDS, + SingleLineStringFieldTestValues +> = { + name: { + baseValue: 'Base Rule Name', + customValue: 'Custom Rule Name', + updatedValue: 'Updated Rule Name', + }, + severity: { + baseValue: 'low', + customValue: 'medium', + updatedValue: 'high', + }, + threat_indicator_path: { + baseValue: 'indicator.file.hash.base', + customValue: 'indicator.ip.current', + updatedValue: 'indicator.domain.target', + }, + history_window_start: { + baseValue: 'now-10000m', + customValue: 'now-20000m', + updatedValue: 'now-30000m', + }, +}; - 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_TYPE_FIELD_MAPPING = { + query: ['name', 'severity'], + threat_match: ['threat_indicator_path'], + new_terms: ['history_window_start'], +} as const; + +type RuleTypeToFields = typeof RULE_TYPE_FIELD_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 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 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 +313,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(RULE_TYPE_FIELD_MAPPING).forEach(([ruleType, fields]) => { + describe(`${ruleType} rule single line string fields`, () => { + fields.forEach((field) => { + const testValues = SINGLE_LINE_STRING_FIELDS_MAP[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; +};