From b1a292230115b5449a88275db9c2581d7995bbcb Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:27:53 -0400 Subject: [PATCH] [Security Solution] Integration tests for `data_source` field diff algorithm (#189744) ## Summary Completes https://github.com/elastic/kibana/issues/187659 Switches `data_source` fields to use the implemented diff algorithm assigned to them in https://github.com/elastic/kibana/pull/188874 Adds integration tests in accordance to https://github.com/elastic/kibana/pull/189669 for the `upgrade/_review` API endpoint for the `data_source` field diff algorithm. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../three_way_diff/three_way_diff_outcome.ts | 12 +- .../data_source_diff_algorithm.test.ts | 141 ++- .../algorithms/data_source_diff_algorithm.ts | 26 +- .../calculation/calculate_rule_fields_diff.ts | 13 +- .../trial_license_complete_tier/index.ts | 1 + ...eview_prebuilt_rules.data_source_fields.ts | 972 ++++++++++++++++++ 6 files changed, 1110 insertions(+), 55 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts index d5d656f3bb385..87ec80d1ac292 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts @@ -83,9 +83,9 @@ export const determineOrderAgnosticDiffOutcome = ( * NOTE: uses order agnostic comparison for nested array fields (e.g. `index`) */ export const determineDiffOutcomeForDataSource = ( - baseVersion: RuleDataSource | MissingVersion, - currentVersion: RuleDataSource, - targetVersion: RuleDataSource + baseVersion: RuleDataSource | undefined | MissingVersion, + currentVersion: RuleDataSource | undefined, + targetVersion: RuleDataSource | undefined ): ThreeWayDiffOutcome => { const isBaseVersionMissing = baseVersion === MissingVersion; @@ -151,7 +151,7 @@ export const determineIfValueCanUpdate = (diffCase: ThreeWayDiffOutcome): boolea ); }; -const isIndexPatternDataSourceType = ( - version: RuleDataSource +export const isIndexPatternDataSourceType = ( + version: RuleDataSource | undefined ): version is Extract => - version.type === DataSourceType.index_patterns; + version !== undefined && version.type === DataSourceType.index_patterns; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts index 6944cd137448a..6020738bd5e66 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts @@ -21,7 +21,7 @@ import { dataSourceDiffAlgorithm } from './data_source_diff_algorithm'; describe('dataSourceDiffAlgorithm', () => { describe('returns current_version as merged output if there is no update - scenario AAA', () => { it('if all versions are index patterns', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'two', 'three'], @@ -49,7 +49,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if all versions are data views', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.data_view, data_view_id: '123' }, target_version: { type: DataSourceType.data_view, data_view_id: '123' }, @@ -70,7 +70,7 @@ describe('dataSourceDiffAlgorithm', () => { describe('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => { it('if current version is different data type than base and target', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'three'], @@ -95,7 +95,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if all versions are same data type', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'three'], @@ -121,11 +121,36 @@ describe('dataSourceDiffAlgorithm', () => { }) ); }); + + it('if current version is undefined', () => { + const mockVersions: ThreeVersionsOf = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: undefined, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); }); describe('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => { it('if target version is different data type than base and current', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.data_view, data_view_id: '123' }, target_version: { @@ -147,7 +172,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if all versions are same data type', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.data_view, data_view_id: '123' }, target_version: { type: DataSourceType.data_view, data_view_id: '456' }, @@ -168,7 +193,7 @@ describe('dataSourceDiffAlgorithm', () => { describe('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => { it('if all versions are index patterns', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'three'], @@ -196,7 +221,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if all versions are data views', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.data_view, data_view_id: '456' }, target_version: { type: DataSourceType.data_view, data_view_id: '456' }, @@ -217,7 +242,7 @@ describe('dataSourceDiffAlgorithm', () => { describe('returns current_version as merged output if all three versions are different - scenario ABC', () => { it('if all versions are index patterns', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'three'], @@ -250,7 +275,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if all versions are data views', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.data_view, data_view_id: '456' }, target_version: { type: DataSourceType.data_view, data_view_id: '789' }, @@ -269,7 +294,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if base version is a data view and others are index patterns ', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.index_patterns, @@ -299,7 +324,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if base version is a index patterns and other are data views', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'three', 'four'], @@ -326,7 +351,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if currrent version is a different data type', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.data_view, data_view_id: '123' }, current_version: { type: DataSourceType.index_patterns, @@ -348,7 +373,7 @@ describe('dataSourceDiffAlgorithm', () => { }); it('if target version is a different data type', () => { - const mockVersions: ThreeVersionsOf = { + const mockVersions: ThreeVersionsOf = { base_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'two', 'three'], @@ -371,16 +396,11 @@ describe('dataSourceDiffAlgorithm', () => { }) ); }); - }); - describe('if base_version is missing', () => { - it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { - const mockVersions: ThreeVersionsOf = { - base_version: MissingVersion, - current_version: { - type: DataSourceType.index_patterns, - index_patterns: ['one', 'three', 'four'], - }, + it('if currrent version is undefined', () => { + const mockVersions: ThreeVersionsOf = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: undefined, target_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'three', 'four'], @@ -391,20 +411,23 @@ describe('dataSourceDiffAlgorithm', () => { expect(result).toEqual( expect.objectContaining({ - has_base_version: false, - base_version: undefined, merged_version: mockVersions.current_version, - diff_outcome: ThreeWayDiffOutcome.MissingBaseNoUpdate, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, merge_outcome: ThreeWayMergeOutcome.Current, - conflict: ThreeWayDiffConflict.NONE, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, }) ); }); + }); - it('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { - const mockVersions: ThreeVersionsOf = { + describe('if base_version is missing', () => { + it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { + const mockVersions: ThreeVersionsOf = { base_version: MissingVersion, - current_version: { type: DataSourceType.data_view, data_view_id: '456' }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, target_version: { type: DataSourceType.index_patterns, index_patterns: ['one', 'three', 'four'], @@ -417,12 +440,62 @@ describe('dataSourceDiffAlgorithm', () => { expect.objectContaining({ has_base_version: false, base_version: undefined, - merged_version: mockVersions.target_version, - diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, - merge_outcome: ThreeWayMergeOutcome.Target, - conflict: ThreeWayDiffConflict.SOLVABLE, + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, }) ); }); + + describe('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { + it('if versions are different types', () => { + const mockVersions: ThreeVersionsOf = { + base_version: MissingVersion, + current_version: { type: DataSourceType.data_view, data_view_id: '456' }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + has_base_version: false, + base_version: undefined, + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + + it('if current version is undefined', () => { + const mockVersions: ThreeVersionsOf = { + base_version: MissingVersion, + current_version: undefined, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + has_base_version: false, + base_version: undefined, + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts index 2f7430ddd4718..86aa886592468 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts @@ -19,12 +19,16 @@ import { DataSourceType, ThreeWayDiffConflict, determineDiffOutcomeForDataSource, + isIndexPatternDataSourceType, } from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; import { getDedupedDataSourceVersion, mergeDedupedArrays } from './helpers'; +/** + * Takes a type of `RuleDataSource | undefined` because the data source can be index patterns, a data view id, or neither in some cases + */ export const dataSourceDiffAlgorithm = ( - versions: ThreeVersionsOf -): ThreeWayDiff => { + versions: ThreeVersionsOf +): ThreeWayDiff => { const { base_version: baseVersion, current_version: currentVersion, @@ -60,14 +64,14 @@ export const dataSourceDiffAlgorithm = ( interface MergeResult { mergeOutcome: ThreeWayMergeOutcome; - mergedVersion: RuleDataSource; + mergedVersion: RuleDataSource | undefined; conflict: ThreeWayDiffConflict; } interface MergeArgs { baseVersion: RuleDataSource | undefined; - currentVersion: RuleDataSource; - targetVersion: RuleDataSource; + currentVersion: RuleDataSource | undefined; + targetVersion: RuleDataSource | undefined; diffOutcome: ThreeWayDiffOutcome; } @@ -78,8 +82,12 @@ const mergeVersions = ({ diffOutcome, }: MergeArgs): MergeResult => { const dedupedBaseVersion = baseVersion ? getDedupedDataSourceVersion(baseVersion) : baseVersion; - const dedupedCurrentVersion = getDedupedDataSourceVersion(currentVersion); - const dedupedTargetVersion = getDedupedDataSourceVersion(targetVersion); + const dedupedCurrentVersion = currentVersion + ? getDedupedDataSourceVersion(currentVersion) + : currentVersion; + const dedupedTargetVersion = targetVersion + ? getDedupedDataSourceVersion(targetVersion) + : targetVersion; switch (diffOutcome) { // Scenario -AA is treated as scenario AAA: @@ -103,8 +111,8 @@ const mergeVersions = ({ case ThreeWayDiffOutcome.CustomizedValueCanUpdate: { if ( - dedupedCurrentVersion.type === DataSourceType.index_patterns && - dedupedTargetVersion.type === DataSourceType.index_patterns + isIndexPatternDataSourceType(dedupedCurrentVersion) && + isIndexPatternDataSourceType(dedupedTargetVersion) ) { const baseVersionToMerge = dedupedBaseVersion && dedupedBaseVersion.type === DataSourceType.index_patterns diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index 0a260bb990d0f..0aeab9cf8ccbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -38,6 +38,7 @@ import type { ThreeVersionsOf } from '../../../../../../../common/api/detection_ import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import { calculateFieldsDiffFor } from './diff_calculation_helpers'; import { + dataSourceDiffAlgorithm, multiLineStringDiffAlgorithm, numberDiffAlgorithm, scalarArrayDiffAlgorithm, @@ -209,7 +210,7 @@ const calculateCustomQueryFieldsDiff = ( const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, }; const calculateSavedQueryFieldsDiff = ( @@ -221,7 +222,7 @@ const calculateSavedQueryFieldsDiff = ( const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, }; const calculateEqlFieldsDiff = ( @@ -233,7 +234,7 @@ const calculateEqlFieldsDiff = ( const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, eql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, event_category_override: singleLineStringDiffAlgorithm, timestamp_field: singleLineStringDiffAlgorithm, tiebreaker_field: singleLineStringDiffAlgorithm, @@ -259,7 +260,7 @@ const calculateThreatMatchFieldsDiff = ( const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, threat_query: simpleDiffAlgorithm, threat_index: scalarArrayDiffAlgorithm, threat_mapping: simpleDiffAlgorithm, @@ -277,7 +278,7 @@ const calculateThresholdFieldsDiff = ( const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, threshold: simpleDiffAlgorithm, }; @@ -303,7 +304,7 @@ const calculateNewTermsFieldsDiff = ( const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, new_terms_fields: scalarArrayDiffAlgorithm, history_window_start: singleLineStringDiffAlgorithm, }; 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 8266fba86a099..4c8efcfa751e0 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 @@ -21,6 +21,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.single_line_string_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.scalar_array_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.multi_line_string_fields')); + loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.data_source_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.stats')); }); }; 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.data_source_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.data_source_fields.ts new file mode 100644 index 0000000000000..f3b009a8ade47 --- /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.data_source_fields.ts @@ -0,0 +1,972 @@ +/* + * 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 { + AllFieldsDiff, + DataSourceType, + RuleUpdateProps, + ThreeWayDiffConflict, + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getPrebuiltRuleMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObject, + installPrebuiltRules, + createPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + createHistoricalPrebuiltRuleAssetSavedObjects, + 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(`data_source fields`, () => { + const getIndexRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 1, + index: ['one', 'two', 'three'], + }), + ]; + + const getDataViewIdRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 1, + data_view_id: 'A', + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + describe('when all versions are index patterns', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'three', 'two'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but data_source field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).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_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when all versions are data view id', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + data_view_id: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but data_source field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).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_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 index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + data_view_id: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source 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.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + 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 current version is data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'two', 'three'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source 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.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + 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 current version is undefined', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'two', 'three'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source 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.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: undefined, + target_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + merged_version: undefined, + 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', () => { + describe('when target version is index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'four'], + }), + ]; + 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.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + target_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + 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 target version is data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'B', + }), + ]; + 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.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + 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', () => { + describe('when current and target version are index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'four'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains data_source field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + 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 current and target version are data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'B', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains data_source field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + 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', () => { + describe('when just current and target versions are index patterns', () => { + it('should show a solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'one', 'two', 'three'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'three', 'five'], + type: DataSourceType.index_patterns, + }, + 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); + }); + }); + + describe('when all versions are index patterns', () => { + it('should show a solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a multi line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + }); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four', 'five'], + type: DataSourceType.index_patterns, + }, + 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); + }); + }); + + describe('when all versions are data view id types', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + 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 current and target versions are different data types', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + 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 current version is undefined', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: undefined, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: undefined, + 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 () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getIndexRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Increment the version of the installed rule, but keep data_source field unchanged + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['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 data_source field (tags is not present, since scenario -AA is not included in response) + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered 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); + }); + }); + + 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, getIndexRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update does not have a conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + 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(2); // version + 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); + }); + }); + }); + }); + }); +};