diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts index 3794f33278c7e..dd0df11b753f0 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts @@ -47,10 +47,11 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const ctx = await context.resolve(['core', 'alerting']); + const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); const savedObjectsClient = ctx.core.savedObjects.client; const rulesClient = await ctx.alerting.getRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); + const mlAuthz = ctx.securitySolution.getMlAuthz(); try { const latestPrebuiltRules = await ruleAssetsClient.fetchLatestAssets(); @@ -69,8 +70,16 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = ( await getExistingPrepackagedRules({ rulesClient, logger }) ); - const rulesToInstall = getRulesToInstall(latestPrebuiltRules, installedPrebuiltRules); - const rulesToUpdate = getRulesToUpdate(latestPrebuiltRules, installedPrebuiltRules); + const rulesToInstall = await getRulesToInstall( + latestPrebuiltRules, + installedPrebuiltRules, + mlAuthz + ); + const rulesToUpdate = await getRulesToUpdate( + latestPrebuiltRules, + installedPrebuiltRules, + mlAuthz + ); const frameworkRequest = await buildFrameworkRequest(context, request); const prebuiltTimelineStatus = await checkTimelinesStatus(frameworkRequest); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts index b9eaac82f3971..b638c55854d8f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/legacy_create_prepackaged_rules.ts @@ -44,6 +44,7 @@ export const legacyCreatePrepackagedRules = async ( const exceptionsListClient = context.getExceptionListClient() ?? exceptionsClient; const detectionRulesClient = context.getDetectionRulesClient(); const ruleAssetsClient = createPrebuiltRuleAssetsClient(savedObjectsClient); + const mlAuthz = context.getMlAuthz(); if (!siemClient || !rulesClient) { throw new PrepackagedRulesError('', 404); @@ -63,8 +64,16 @@ export const legacyCreatePrepackagedRules = async ( const installedPrebuiltRules = rulesToMap( await getExistingPrepackagedRules({ rulesClient, logger }) ); - const rulesToInstall = getRulesToInstall(latestPrebuiltRules, installedPrebuiltRules); - const rulesToUpdate = getRulesToUpdate(latestPrebuiltRules, installedPrebuiltRules); + const rulesToInstall = await getRulesToInstall( + latestPrebuiltRules, + installedPrebuiltRules, + mlAuthz + ); + const rulesToUpdate = await getRulesToUpdate( + latestPrebuiltRules, + installedPrebuiltRules, + mlAuthz + ); const ruleCreationResult = await createPrebuiltRules( detectionRulesClient, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts index fc14ab92bbcbb..3db75e52514d6 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts @@ -7,37 +7,40 @@ import { getRulesToInstall } from './get_rules_to_install'; import { getRuleMock } from '../../routes/__mocks__/request_responses'; -import { getPrebuiltRuleMock } from '../mocks'; +import { getPrebuiltRuleMock, getPrebuiltRuleMockOfType } from '../mocks'; import { getQueryRuleParams } from '../../rule_schema/mocks'; import { rulesToMap } from './utils'; +import { buildMlAuthz, buildRestrictedMlAuthz } from '../../../machine_learning/__mocks__/authz'; describe('get_rules_to_install', () => { - test('should return empty array if both rule sets are empty', () => { - const update = getRulesToInstall([], rulesToMap([])); + const mockMlAuthz = buildMlAuthz(); + + test('should return empty array if both rule sets are empty', async () => { + const update = await getRulesToInstall([], rulesToMap([]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return empty array if the two rule ids match', () => { + test('should return empty array if the two rule ids match', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; - const update = getRulesToInstall([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToInstall([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return the rule to install if the id of the two rules do not match', () => { + test('should return the rule to install if the id of the two rules do not match', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; - const update = getRulesToInstall([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToInstall([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([ruleAsset]); }); - test('should return two rules to install if both the ids of the two rules do not match', () => { + test('should return two rules to install if both the ids of the two rules do not match', async () => { const ruleAsset1 = getPrebuiltRuleMock(); ruleAsset1.rule_id = 'rule-1'; @@ -46,11 +49,15 @@ describe('get_rules_to_install', () => { const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; - const update = getRulesToInstall([ruleAsset1, ruleAsset2], rulesToMap([installedRule])); + const update = await getRulesToInstall( + [ruleAsset1, ruleAsset2], + rulesToMap([installedRule]), + mockMlAuthz + ); expect(update).toEqual([ruleAsset1, ruleAsset2]); }); - test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { + test('should return two rules of three to install if both the ids of the two rules do not match but the third does', async () => { const ruleAsset1 = getPrebuiltRuleMock(); ruleAsset1.rule_id = 'rule-1'; @@ -62,10 +69,27 @@ describe('get_rules_to_install', () => { const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; - const update = getRulesToInstall( + const update = await getRulesToInstall( [ruleAsset1, ruleAsset2, ruleAsset3], - rulesToMap([installedRule]) + rulesToMap([installedRule]), + mockMlAuthz ); expect(update).toEqual([ruleAsset1, ruleAsset2]); }); + + test('should exclude license-restricted rules from rules to install', async () => { + const queryRuleAsset = getPrebuiltRuleMock(); + queryRuleAsset.rule_id = 'rule-query'; + + const mlRuleAsset = getPrebuiltRuleMockOfType('machine_learning'); + + const mlAuthzRestrictingMlRules = buildRestrictedMlAuthz(); + + const update = await getRulesToInstall( + [queryRuleAsset, mlRuleAsset], + rulesToMap([]), + mlAuthzRestrictingMlRules + ); + expect(update).toEqual([queryRuleAsset]); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts index d2e6bc4acf7b4..a43587ed816a5 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts @@ -5,12 +5,16 @@ * 2.0. */ +import type { MlAuthz } from '../../../machine_learning/authz'; import type { RuleAlertType } from '../../rule_schema'; import type { PrebuiltRuleAsset } from '../model/rule_assets/prebuilt_rule_asset'; +import { excludeLicenseRestrictedRules } from './utils'; -export const getRulesToInstall = ( +export const getRulesToInstall = async ( latestPrebuiltRules: PrebuiltRuleAsset[], - installedRules: Map + installedRules: Map, + mlAuthz: MlAuthz ) => { - return latestPrebuiltRules.filter((rule) => !installedRules.has(rule.rule_id)); + const uninstalledRules = latestPrebuiltRules.filter((rule) => !installedRules.has(rule.rule_id)); + return excludeLicenseRestrictedRules(uninstalledRules, mlAuthz); }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts index 9cedfc101a932..d3e4d25b84c4e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts @@ -7,17 +7,20 @@ import { filterInstalledRules, getRulesToUpdate } from './get_rules_to_update'; import { getRuleMock } from '../../routes/__mocks__/request_responses'; -import { getPrebuiltRuleMock } from '../mocks'; +import { getPrebuiltRuleMock, getPrebuiltRuleMockOfType } from '../mocks'; import { getQueryRuleParams } from '../../rule_schema/mocks'; import { rulesToMap } from './utils'; +import { buildRestrictedMlAuthz, buildMlAuthz } from '../../../machine_learning/__mocks__/authz'; describe('get_rules_to_update', () => { - test('should return empty array if both rule sets are empty', () => { - const update = getRulesToUpdate([], rulesToMap([])); + const mockMlAuthz = buildMlAuthz(); + + test('should return empty array if both rule sets are empty', async () => { + const update = await getRulesToUpdate([], rulesToMap([]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return empty array if the rule_id of the two rules do not match', () => { + test('should return empty array if the rule_id of the two rules do not match', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; ruleAsset.version = 2; @@ -25,11 +28,11 @@ describe('get_rules_to_update', () => { const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; installedRule.params.version = 1; - const update = getRulesToUpdate([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToUpdate([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return empty array if the version of file system rule is less than the installed version', () => { + test('should return empty array if the version of file system rule is less than the installed version', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; ruleAsset.version = 1; @@ -37,11 +40,11 @@ describe('get_rules_to_update', () => { const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 2; - const update = getRulesToUpdate([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToUpdate([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return empty array if the version of file system rule is the same as the installed version', () => { + test('should return empty array if the version of file system rule is the same as the installed version', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; ruleAsset.version = 1; @@ -49,11 +52,11 @@ describe('get_rules_to_update', () => { const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; - const update = getRulesToUpdate([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToUpdate([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([]); }); - test('should return the rule to update if the version of file system rule is greater than the installed version', () => { + test('should return the rule to update if the version of file system rule is greater than the installed version', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; ruleAsset.version = 2; @@ -63,11 +66,11 @@ describe('get_rules_to_update', () => { installedRule.params.version = 1; installedRule.params.exceptionsList = []; - const update = getRulesToUpdate([ruleAsset], rulesToMap([installedRule])); + const update = await getRulesToUpdate([ruleAsset], rulesToMap([installedRule]), mockMlAuthz); expect(update).toEqual([ruleAsset]); }); - test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', () => { + test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', async () => { const ruleAsset = getPrebuiltRuleMock(); ruleAsset.rule_id = 'rule-1'; ruleAsset.version = 2; @@ -82,11 +85,15 @@ describe('get_rules_to_update', () => { installedRule2.params.version = 1; installedRule2.params.exceptionsList = []; - const update = getRulesToUpdate([ruleAsset], rulesToMap([installedRule1, installedRule2])); + const update = await getRulesToUpdate( + [ruleAsset], + rulesToMap([installedRule1, installedRule2]), + mockMlAuthz + ); expect(update).toEqual([ruleAsset]); }); - test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', () => { + test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', async () => { const ruleAsset1 = getPrebuiltRuleMock(); ruleAsset1.rule_id = 'rule-1'; ruleAsset1.version = 2; @@ -105,12 +112,41 @@ describe('get_rules_to_update', () => { installedRule2.params.version = 1; installedRule2.params.exceptionsList = []; - const update = getRulesToUpdate( + const update = await getRulesToUpdate( [ruleAsset1, ruleAsset2], - rulesToMap([installedRule1, installedRule2]) + rulesToMap([installedRule1, installedRule2]), + mockMlAuthz ); expect(update).toEqual([ruleAsset1, ruleAsset2]); }); + + test('should exclude license-restricted rules from rules to update', async () => { + const queryRuleAsset = getPrebuiltRuleMock(); + queryRuleAsset.rule_id = 'rule-query'; + queryRuleAsset.version = 2; + + const mlRuleAsset = getPrebuiltRuleMockOfType('machine_learning'); + mlRuleAsset.version = 2; + + const installedQueryRule = getRuleMock(getQueryRuleParams()); + installedQueryRule.params.ruleId = 'rule-query'; + installedQueryRule.params.version = 1; + installedQueryRule.params.exceptionsList = []; + + const installedMlRule = getRuleMock(getQueryRuleParams()); + installedMlRule.params.ruleId = mlRuleAsset.rule_id; + installedMlRule.params.version = 1; + installedMlRule.params.exceptionsList = []; + + const mlAuthzRestrictingMlRules = buildRestrictedMlAuthz(); + + const update = await getRulesToUpdate( + [queryRuleAsset, mlRuleAsset], + rulesToMap([installedQueryRule, installedMlRule]), + mlAuthzRestrictingMlRules + ); + expect(update).toEqual([queryRuleAsset]); + }); }); describe('filterInstalledRules', () => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts index dac43534e7420..f2c66d527ec5d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts @@ -5,8 +5,10 @@ * 2.0. */ +import type { MlAuthz } from '../../../machine_learning/authz'; import type { RuleAlertType } from '../../rule_schema'; import type { PrebuiltRuleAsset } from '../model/rule_assets/prebuilt_rule_asset'; +import { excludeLicenseRestrictedRules } from './utils'; /** * Returns the rules to update by doing a compare to the rules from the file system against @@ -15,13 +17,15 @@ import type { PrebuiltRuleAsset } from '../model/rule_assets/prebuilt_rule_asset * @param latestPrebuiltRules The latest rules to check against installed * @param installedRules The installed rules */ -export const getRulesToUpdate = ( +export const getRulesToUpdate = async ( latestPrebuiltRules: PrebuiltRuleAsset[], - installedRules: Map + installedRules: Map, + mlAuthz: MlAuthz ) => { - return latestPrebuiltRules.filter((latestRule) => + const rulesToUpdate = latestPrebuiltRules.filter((latestRule) => filterInstalledRules(latestRule, installedRules) ); + return excludeLicenseRestrictedRules(rulesToUpdate, mlAuthz); }; /** diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts index 33ab06ba67a8f..5631a792f71a7 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts @@ -11,4 +11,13 @@ export const mlServicesMock = mlPluginServerMock; const mockValidateRuleType = jest.fn().mockResolvedValue({ valid: true, message: undefined }); +const mockValidateInvalidRuleType = jest.fn().mockImplementation(async (type: string) => ({ + valid: type !== 'machine_learning', + message: type === 'machine_learning' ? 'ML rules require a platinum license' : undefined, +})); + export const buildMlAuthz = jest.fn().mockReturnValue({ validateRuleType: mockValidateRuleType }); + +export const buildRestrictedMlAuthz = jest + .fn() + .mockReturnValue({ validateRuleType: mockValidateInvalidRuleType }); diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts index c19c9c5d8488d..cca668e78d49e 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/perform_installation/perform_installation.ts @@ -13,7 +13,9 @@ import { createRuleAssetSavedObjectOfType, deleteAllPrebuiltRuleAssets, installPrebuiltRules, + installPrebuiltRulesAndTimelines, } from '../../../../utils'; +import { createMlRuleThroughAlertingEndpoint } from '../utils'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); @@ -75,5 +77,44 @@ export default ({ getService }: FtrProviderContext): void => { ], }); }); + + describe('legacy (PUT /api/detection_engine/rules/prepackaged)', () => { + it('ML rules are silently excluded from installation', async () => { + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning'); + const nonMlRuleAsset = createRuleAssetSavedObjectOfType('query'); + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset, nonMlRuleAsset]); + + const response = await installPrebuiltRulesAndTimelines(es, supertest); + + expect(response.rules_installed).toBe(1); + expect(response.rules_updated).toBe(0); + }); + + it('ML rules are silently excluded from updates', async () => { + const queryRuleAsset = createRuleAssetSavedObjectOfType('query', { + rule_id: 'query-rule', + version: 1, + }); + await createPrebuiltRuleAssetSavedObjects(es, [queryRuleAsset]); + await installPrebuiltRulesAndTimelines(es, supertest); + + await createMlRuleThroughAlertingEndpoint(supertest, { ruleId: 'ml-rule', version: 1 }); + + const targetQueryRuleAsset = createRuleAssetSavedObjectOfType('query', { + rule_id: 'query-rule', + version: 2, + }); + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + rule_id: 'ml-rule', + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetQueryRuleAsset, targetMlRuleAsset]); + + const response = await installPrebuiltRulesAndTimelines(es, supertest); + + expect(response.rules_installed).toBe(0); + expect(response.rules_updated).toBe(1); + }); + }); }); }; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts index eaddf656931d1..aec417cf3a5d9 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/ml_disabled/status/status.ts @@ -12,6 +12,7 @@ import { createPrebuiltRuleAssetSavedObjects, createRuleAssetSavedObjectOfType, deleteAllPrebuiltRuleAssets, + getPrebuiltRulesAndTimelinesStatus, getPrebuiltRulesStatus, } from '../../../../utils'; import { createMlRuleThroughAlertingEndpoint } from '../utils'; @@ -65,5 +66,34 @@ export default ({ getService }: FtrProviderContext): void => { }, }); }); + + describe('legacy (GET /api/detection_engine/rules/prepackaged/_status)', () => { + it('ML rules are not counted towards installable rules', async () => { + const mlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { version: 1 }); + await createPrebuiltRuleAssetSavedObjects(es, [mlRuleAsset]); + + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(es, supertest); + + expect(statusResponse.rules_not_installed).toBe(0); + }); + + it('ML rules are not counted towards upgradable rules', async () => { + await createMlRuleThroughAlertingEndpoint(supertest, { + ruleId: 'ml-rule', + version: 1, + }); + + const targetMlRuleAsset = createRuleAssetSavedObjectOfType('machine_learning', { + rule_id: 'ml-rule', + version: 2, + }); + await createPrebuiltRuleAssetSavedObjects(es, [targetMlRuleAsset]); + + const statusResponse = await getPrebuiltRulesAndTimelinesStatus(es, supertest); + + expect(statusResponse.rules_not_updated).toBe(0); + expect(statusResponse.rules_installed).toBe(1); + }); + }); }); };