From 8a940a185eff03e1769e7d2fcf579bd8daaa7d9b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:09:27 +1100 Subject: [PATCH] [9.0] [Rules migration][Integration test] Stats APIs (#11232) (#211315) (#211338) # Backport This will backport the following commits from `main` to `9.0`: - [[Rules migration][Integration test] Stats APIs (#11232) (#211315)](https://github.com/elastic/kibana/pull/211315) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Ievgen Sorokopud --- .../public/siem_migrations/rules/api/index.ts | 2 +- .../trial_license_complete_tier/index.ts | 1 + .../trial_license_complete_tier/stats.ts | 145 ++++++++++++++++++ .../siem_migrations/utils/mocks.ts | 56 +++++++ .../siem_migrations/utils/rules.ts | 76 +++++++-- 5 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/stats.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts index e149804630dc9..e4b976a51ab3f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -52,7 +52,7 @@ export interface GetRuleMigrationStatsParams { /** Optional AbortSignal for cancelling request */ signal?: AbortSignal; } -/** Retrieves the stats for all the existing migrations, aggregated by `migration_id`. */ +/** Retrieves the stats for the specific migration. */ export const getRuleMigrationStats = async ({ migrationId, signal, diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts index d36f93afb8656..c5d53b96bafb1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/index.ts @@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('@ess SecuritySolution SIEM Migrations', () => { loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./stats')); loadTestFile(require.resolve('./update')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/stats.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/stats.ts new file mode 100644 index 0000000000000..42c6d4f097150 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/rules/trial_license_complete_tier/stats.ts @@ -0,0 +1,145 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import { + createMigrationRules, + deleteAllMigrationRules, + getMigrationRuleDocuments, + migrationRulesRouteHelpersFactory, + statsOverrideCallbackFactory, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const es = getService('es'); + const supertest = getService('supertest'); + const migrationRulesRoutes = migrationRulesRouteHelpersFactory(supertest); + + describe('@ess @serverless @serverlessQA Stats API', () => { + beforeEach(async () => { + await deleteAllMigrationRules(es); + }); + + it('should return stats for the specific migration', async () => { + const migrationId = uuidv4(); + + const failed = 3; + const pending = 5; + const processing = 7; + const completed = 10; + const total = failed + pending + processing + completed; + const overrideCallback = statsOverrideCallbackFactory({ + migrationId, + failed, + pending, + processing, + completed, // 4 - full, 5 - partial, 1 - untranslated + fullyTranslated: 4, + partiallyTranslated: 5, + }); + const migrationRuleDocuments = getMigrationRuleDocuments(total, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + const response = await migrationRulesRoutes.stats({ migrationId }); + expect(response.body).toEqual( + expect.objectContaining({ + status: 'stopped', + id: migrationId, + rules: { + total, + pending, + processing, + completed, + failed, + }, + }) + ); + }); + + it('should return stats for the existing migrations', async () => { + const migrationId1 = uuidv4(); + const migrationId2 = uuidv4(); + + const overrideCallback1 = statsOverrideCallbackFactory({ + migrationId: migrationId1, + failed: 2, + pending: 4, + processing: 3, + completed: 33, + fullyTranslated: 10, + partiallyTranslated: 10, + }); + const migrationRuleDocuments1 = getMigrationRuleDocuments(42, overrideCallback1); + const overrideCallback2 = statsOverrideCallbackFactory({ + migrationId: migrationId2, + failed: 7, + pending: 2, + processing: 5, + completed: 14, + fullyTranslated: 3, + partiallyTranslated: 5, + }); + const migrationRuleDocuments2 = getMigrationRuleDocuments(28, overrideCallback2); + await createMigrationRules(es, [...migrationRuleDocuments1, ...migrationRuleDocuments2]); + + const response = await migrationRulesRoutes.statsAll({}); + const expectedStats = expect.arrayContaining([ + expect.objectContaining({ + status: 'stopped', + id: migrationId1, + rules: { total: 42, pending: 4, processing: 3, completed: 33, failed: 2 }, + }), + expect.objectContaining({ + status: 'stopped', + id: migrationId2, + rules: { total: 28, pending: 2, processing: 5, completed: 14, failed: 7 }, + }), + ]); + expect(response.body).toEqual(expectedStats); + }); + + it('should return translation stats for the specific migration', async () => { + const migrationId = uuidv4(); + + const failed = 3; + const pending = 5; + const processing = 7; + const completed = 10; + const total = failed + pending + processing + completed; + const overrideCallback = statsOverrideCallbackFactory({ + migrationId, + failed, + pending, + processing, + completed, // 4 - full, 5 - partial, 1 - untranslated + fullyTranslated: 4, + partiallyTranslated: 5, + }); + const migrationRuleDocuments = getMigrationRuleDocuments(total, overrideCallback); + await createMigrationRules(es, migrationRuleDocuments); + + const response = await migrationRulesRoutes.translationStats({ migrationId }); + expect(response.body).toEqual( + expect.objectContaining({ + id: migrationId, + rules: { + total, + success: { + total: completed, + result: { full: 4, partial: 5, untranslatable: 1 }, + installable: 4, + prebuilt: 0, + }, + failed, + }, + }) + ); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts index 63ce51c0a5b2e..ee75f3e4a5dd3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/mocks.ts @@ -6,6 +6,10 @@ */ import type { Client } from '@elastic/elasticsearch'; +import { + RuleTranslationResult, + SiemMigrationStatus, +} from '@kbn/security-solution-plugin/common/siem_migrations/constants'; import { ElasticRule, @@ -95,6 +99,58 @@ export const getMigrationRuleDocuments = ( return docs; }; +export const statsOverrideCallbackFactory = ({ + migrationId, + failed, + pending, + processing, + completed, + fullyTranslated, + partiallyTranslated, +}: { + migrationId: string; + failed: number; + pending: number; + processing: number; + completed: number; + fullyTranslated: number; + partiallyTranslated: number; +}) => { + const overrideCallback = (index: number): Partial => { + let translationResult; + let status = SiemMigrationStatus.PENDING; + + const pendingEndIndex = failed + pending; + const processingEndIndex = failed + pending + processing; + const completedEndIndex = failed + pending + processing + completed; + if (index < failed) { + status = SiemMigrationStatus.FAILED; + } else if (index < pendingEndIndex) { + status = SiemMigrationStatus.PENDING; + } else if (index < processingEndIndex) { + status = SiemMigrationStatus.PROCESSING; + } else if (index < completedEndIndex) { + status = SiemMigrationStatus.COMPLETED; + const fullyTranslatedEndIndex = completedEndIndex - completed + fullyTranslated; + const partiallyTranslatedEndIndex = + completedEndIndex - completed + fullyTranslated + partiallyTranslated; + if (index < fullyTranslatedEndIndex) { + translationResult = RuleTranslationResult.FULL; + } else if (index < partiallyTranslatedEndIndex) { + translationResult = RuleTranslationResult.PARTIAL; + } else { + translationResult = RuleTranslationResult.UNTRANSLATABLE; + } + } + return { + migration_id: migrationId, + translation_result: translationResult, + status, + }; + }; + return overrideCallback; +}; + export const createMigrationRules = async ( es: Client, rules: RuleMigrationDocument[] diff --git a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts index a58cbb53a4310..07e845805cda2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/siem_migrations/utils/rules.ts @@ -13,44 +13,49 @@ import { import { replaceParams } from '@kbn/openapi-common/shared'; import { + SIEM_RULE_MIGRATIONS_ALL_STATS_PATH, SIEM_RULE_MIGRATIONS_PATH, SIEM_RULE_MIGRATION_PATH, + SIEM_RULE_MIGRATION_STATS_PATH, + SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, } from '@kbn/security-solution-plugin/common/siem_migrations/constants'; import { CreateRuleMigrationResponse, + GetAllStatsRuleMigrationResponse, GetRuleMigrationRequestQuery, GetRuleMigrationResponse, + GetRuleMigrationStatsResponse, UpdateRuleMigrationResponse, } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen'; import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants'; import { assertStatusCode } from './asserts'; -export interface GetRuleMigrationParams { +export interface RequestParams { + /** Optional expected status code parameter */ + expectStatusCode?: number; +} + +export interface MigrationRequestParams extends RequestParams { /** `id` of the migration to get rules documents for */ migrationId: string; +} + +export interface GetRuleMigrationParams extends MigrationRequestParams { /** Optional query parameters */ queryParams?: GetRuleMigrationRequestQuery; - /** Optional expected status code parameter */ - expectStatusCode?: number; } -export interface CreateRuleMigrationParams { +export interface CreateRuleMigrationParams extends RequestParams { /** Optional `id` of migration to add the rules to. * The id is necessary only for batching the migration creation in multiple requests */ migrationId?: string; /** Optional payload to send */ payload?: any; - /** Optional expected status code parameter */ - expectStatusCode?: number; } -export interface UpdateRulesParams { - /** `id` of the migration to install rules for */ - migrationId: string; +export interface UpdateRulesParams extends MigrationRequestParams { /** Optional payload to send */ payload?: any; - /** Optional expected status code parameter */ - expectStatusCode?: number; } export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => { @@ -106,5 +111,54 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => return response; }, + + stats: async ({ + migrationId, + expectStatusCode = 200, + }: MigrationRequestParams): Promise<{ body: GetRuleMigrationStatsResponse }> => { + const response = await supertest + .get(replaceParams(SIEM_RULE_MIGRATION_STATS_PATH, { migration_id: migrationId })) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(); + + assertStatusCode(expectStatusCode, response); + + return response; + }, + + statsAll: async ({ + expectStatusCode = 200, + }: RequestParams): Promise<{ body: GetAllStatsRuleMigrationResponse }> => { + const response = await supertest + .get(SIEM_RULE_MIGRATIONS_ALL_STATS_PATH) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(); + + assertStatusCode(expectStatusCode, response); + + return response; + }, + + translationStats: async ({ + migrationId, + expectStatusCode = 200, + }: MigrationRequestParams): Promise<{ body: GetRuleMigrationStatsResponse }> => { + const response = await supertest + .get( + replaceParams(SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, { migration_id: migrationId }) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(); + + assertStatusCode(expectStatusCode, response); + + return response; + }, }; };