diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts index 9a360d211f3fa..fecf13b21ddeb 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts @@ -5,21 +5,25 @@ * 2.0. */ -import { expectedExportedRule, getNewRule } from '../../objects/rule'; +import path from 'path'; +import { expectedExportedRule, getNewRule } from '../../objects/rule'; import { TOASTER_BODY, MODAL_CONFIRMATION_BODY, MODAL_CONFIRMATION_BTN, + TOASTER, } from '../../screens/alerts_detection_rules'; - import { - exportFirstRule, loadPrebuiltDetectionRulesFromHeaderBtn, filterByElasticRules, selectNumberOfRules, bulkExportRules, selectAllRules, + waitForRuleExecution, + exportRule, + importRules, + expectManagementTableRules, } from '../../tasks/alerts_detection_rules'; import { createExceptionList, deleteExceptionList } from '../../tasks/api_calls/exceptions'; import { getExceptionList } from '../../objects/exception'; @@ -30,9 +34,12 @@ import { login, visitWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; import { getAvailablePrebuiltRulesCount } from '../../tasks/api_calls/prebuilt_rules'; +const EXPORTED_RULES_FILENAME = 'rules_export.ndjson'; const exceptionList = getExceptionList(); describe('Export rules', () => { + const downloadsFolder = Cypress.config('downloadsFolder'); + before(() => { cleanKibana(); login(); @@ -45,17 +52,34 @@ describe('Export rules', () => { // Rules get exported via _bulk_action endpoint cy.intercept('POST', '/api/detection_engine/rules/_bulk_action').as('bulk_action'); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - createRule(getNewRule()).as('ruleResponse'); + createRule({ ...getNewRule(), name: 'Rule to export' }).as('ruleResponse'); }); - it('Exports a custom rule', function () { - exportFirstRule(); + it('exports a custom rule', function () { + exportRule('Rule to export'); cy.wait('@bulk_action').then(({ response }) => { cy.wrap(response?.body).should('eql', expectedExportedRule(this.ruleResponse)); cy.get(TOASTER_BODY).should('have.text', 'Successfully exported 1 of 1 rule.'); }); }); + it('creates an importable file from executed rule', () => { + // Rule needs to be enabled to make sure it has been executed so rule's SO contains runtime fields like `execution_summary` + createRule({ ...getNewRule(), name: 'Enabled rule to export', enabled: true }); + waitForRuleExecution('Enabled rule to export'); + + exportRule('Enabled rule to export'); + + cy.get(TOASTER).should('have.text', 'Rules exported'); + cy.get(TOASTER_BODY).should('have.text', 'Successfully exported 1 of 1 rule.'); + + deleteAlertsAndRules(); + importRules(path.join(downloadsFolder, EXPORTED_RULES_FILENAME)); + + cy.get(TOASTER).should('have.text', 'Successfully imported 1 rule'); + expectManagementTableRules(['Enabled rule to export']); + }); + it('shows a modal saying that no rules can be exported if all the selected rules are prebuilt', function () { const expectedElasticRulesCount = 7; diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts index 647edd716bc49..5bb0f44b0e5ec 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts @@ -5,10 +5,9 @@ * 2.0. */ -import { RULES_MANAGEMENT_TABLE, TOASTER } from '../../screens/alerts_detection_rules'; +import { TOASTER } from '../../screens/alerts_detection_rules'; import { - expectNumberOfRules, - expectToContainRule, + expectManagementTableRules, importRules, importRulesWithOverwriteAll, } from '../../tasks/alerts_detection_rules'; @@ -16,6 +15,7 @@ import { cleanKibana, deleteAlertsAndRules, reload } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; +const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; describe('Import rules', () => { before(() => { @@ -29,10 +29,7 @@ describe('Import rules', () => { }); it('Imports a custom rule with exceptions', function () { - const expectedNumberOfRules = 1; - const expectedImportedRuleName = 'Test Custom Rule'; - - importRules('7_16_rules.ndjson'); + importRules(RULES_TO_IMPORT_FILENAME); cy.wait('@import').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); @@ -41,20 +38,19 @@ describe('Import rules', () => { 'Successfully imported 1 ruleSuccessfully imported 1 exception.' ); - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - expectToContainRule(RULES_MANAGEMENT_TABLE, expectedImportedRuleName); + expectManagementTableRules(['Test Custom Rule']); }); }); it('Shows error toaster when trying to import rule and exception list that already exist', function () { - importRules('7_16_rules.ndjson'); + importRules(RULES_TO_IMPORT_FILENAME); cy.wait('@import').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); }); reload(); - importRules('7_16_rules.ndjson'); + importRules(RULES_TO_IMPORT_FILENAME); cy.wait('@import').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); @@ -63,14 +59,14 @@ describe('Import rules', () => { }); it('Does not show error toaster when trying to import rule and exception list that already exist when overwrite is true', function () { - importRules('7_16_rules.ndjson'); + importRules(RULES_TO_IMPORT_FILENAME); cy.wait('@import').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); }); reload(); - importRulesWithOverwriteAll('7_16_rules.ndjson'); + importRulesWithOverwriteAll(RULES_TO_IMPORT_FILENAME); cy.wait('@import').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts index 3d9653ae383ee..7685c2a08c7e4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts @@ -17,8 +17,6 @@ import { } from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { - expectNumberOfRules, - expectToContainRule, filterByCustomRules, filterBySearchTerm, filterByTags, @@ -35,8 +33,8 @@ import { filterByDisabledRules, expectFilterByPrebuiltRules, expectFilterByEnabledRules, + expectManagementTableRules, } from '../../tasks/alerts_detection_rules'; -import { RULES_MANAGEMENT_TABLE } from '../../screens/alerts_detection_rules'; import { createRule } from '../../tasks/api_calls/rules'; import { expectRowsPerPage, @@ -108,14 +106,6 @@ function expectDefaultRulesTableState(): void { expectTablePage(1); } -function expectManagementTableRules(ruleNames: string[]): void { - expectNumberOfRules(RULES_MANAGEMENT_TABLE, ruleNames.length); - - for (const ruleName of ruleNames) { - expectToContainRule(RULES_MANAGEMENT_TABLE, ruleName); - } -} - describe('Persistent rules table state', () => { before(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 47bf62a73c3f8..2af59e2ae67d7 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -71,6 +71,8 @@ export const RULE_CHECKBOX = '.euiTableRow .euiCheckbox__input'; export const RULE_NAME = '[data-test-subj="ruleName"]'; +export const RULE_LAST_RUN = '[data-test-subj="ruleLastRun"]'; + export const RULE_SWITCH = '[data-test-subj="ruleSwitch"]'; export const RULE_SWITCH_LOADER = '[data-test-subj="ruleSwitchLoader"]'; @@ -143,6 +145,8 @@ export const SELECTED_RULES_NUMBER_LABEL = '[data-test-subj="selectedRules"]'; export const REFRESH_SETTINGS_POPOVER = '[data-test-subj="refreshSettings-popover"]'; +export const REFRESH_RULES_TABLE_BUTTON = '[data-test-subj="refreshRulesAction-linkIcon"]'; + export const REFRESH_SETTINGS_SWITCH = '[data-test-subj="refreshSettingsSwitch"]'; export const REFRESH_SETTINGS_SELECTION_NOTE = '[data-test-subj="refreshSettingsSelectionNote"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 12d63a198cb06..f17fbac688f9d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -60,6 +60,8 @@ import { RULES_MONITORING_TAB, ENABLED_RULES_BTN, DISABLED_RULES_BTN, + REFRESH_RULES_TABLE_BUTTON, + RULE_LAST_RUN, } from '../screens/alerts_detection_rules'; import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules'; import { EUI_CHECKBOX } from '../screens/common/controls'; @@ -162,8 +164,10 @@ export const disableSelectedRules = () => { cy.get(DISABLE_RULE_BULK_BTN).click(); }; -export const exportFirstRule = () => { - cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); +export const exportRule = (name: string) => { + cy.log(`Export rule "${name}"`); + + cy.contains(RULE_NAME, name).parents(RULES_ROW).find(COLLAPSED_ACTION_BTN).click(); cy.get(EXPORT_ACTION_BTN).click(); cy.get(EXPORT_ACTION_BTN).should('not.exist'); }; @@ -183,6 +187,19 @@ export const filterByTags = (tags: string[]) => { } }; +export const waitForRuleExecution = (name: string) => { + cy.log(`Wait for rule "${name}" to be executed`); + cy.waitUntil(() => { + cy.get(REFRESH_RULES_TABLE_BUTTON).click(); + + return cy + .contains(RULE_NAME, name) + .parents(RULES_ROW) + .find(RULE_LAST_RUN) + .then(($el) => $el.text().endsWith('ago')); + }); +}; + export const filterByElasticRules = () => { cy.get(ELASTIC_RULES_BTN).click(); waitForRulesTableToBeRefreshed(); @@ -358,7 +375,7 @@ export const checkAutoRefresh = (ms: number, condition: string) => { export const importRules = (rulesFile: string) => { cy.get(RULE_IMPORT_MODAL).click(); cy.get(INPUT_FILE).should('exist'); - cy.get(INPUT_FILE).trigger('click', { force: true }).attachFile(rulesFile).trigger('change'); + cy.get(INPUT_FILE).trigger('click', { force: true }).selectFile(rulesFile).trigger('change'); cy.get(RULE_IMPORT_MODAL_BUTTON).last().click({ force: true }); cy.get(INPUT_FILE).should('not.exist'); }; @@ -455,6 +472,14 @@ const selectOverwriteRulesImport = () => { .should('be.checked'); }; +export const expectManagementTableRules = (ruleNames: string[]): void => { + expectNumberOfRules(RULES_MANAGEMENT_TABLE, ruleNames.length); + + for (const ruleName of ruleNames) { + expectToContainRule(RULES_MANAGEMENT_TABLE, ruleName); + } +}; + const selectOverwriteExceptionsRulesImport = () => { cy.get(RULE_IMPORT_OVERWRITE_EXCEPTIONS_CHECKBOX) .pipe(($el) => $el.trigger('click')) @@ -468,7 +493,7 @@ const selectOverwriteConnectorsRulesImport = () => { export const importRulesWithOverwriteAll = (rulesFile: string) => { cy.get(RULE_IMPORT_MODAL).click(); cy.get(INPUT_FILE).should('exist'); - cy.get(INPUT_FILE).trigger('click', { force: true }).attachFile(rulesFile).trigger('change'); + cy.get(INPUT_FILE).trigger('click', { force: true }).selectFile(rulesFile).trigger('change'); selectOverwriteRulesImport(); selectOverwriteExceptionsRulesImport(); selectOverwriteConnectorsRulesImport(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index fdc9f5f3a5b43..1296e9728d2e6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -278,15 +278,19 @@ export const useRulesColumns = ({ field: 'execution_summary.last_execution.date', name: i18n.COLUMN_LAST_COMPLETE_RUN, render: (value: RuleExecutionSummary['last_execution']['date'] | undefined) => { - return value == null ? ( - getEmptyTagValue() - ) : ( - + return ( + + {value == null ? ( + getEmptyTagValue() + ) : ( + + )} + ); }, sortable: true, @@ -438,15 +442,19 @@ export const useMonitoringColumns = ({ field: 'execution_summary.last_execution.date', name: i18n.COLUMN_LAST_COMPLETE_RUN, render: (value: RuleExecutionSummary['last_execution']['date'] | undefined) => { - return value == null ? ( - getEmptyTagValue() - ) : ( - + return ( + + {value == null ? ( + getEmptyTagValue() + ) : ( + + )} + ); }, sortable: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts index 88f3e9019d493..5da8576c04a58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts @@ -12,7 +12,7 @@ import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server'; import { getNonPackagedRules } from '../search/get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; -import { transformAlertsToRules } from '../../utils/utils'; +import { transformAlertsToRules, transformRuleToExportableFormat } from '../../utils/utils'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; import { getRuleActionConnectorsForExport } from './get_export_rule_action_connectors'; @@ -42,6 +42,7 @@ export const getExportAll = async ( logger, }); const rules = transformAlertsToRules(ruleAlertTypes, legacyActions); + const exportRules = rules.map((r) => transformRuleToExportableFormat(r)); // Gather exceptions const exceptions = rules.flatMap((rule) => rule.exceptions_list ?? []); @@ -55,7 +56,7 @@ export const getExportAll = async ( request ); - const rulesNdjson = transformDataToNdjson(rules); + const rulesNdjson = transformDataToNdjson(exportRules); const exportDetails = getExportDetailsNdjson(rules, [], exceptionDetails, actionConnectorDetails); return { rulesNdjson, exportDetails, exceptionLists, actionConnectors }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts index 90938420e64b5..5221c4f26761b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts @@ -17,6 +17,7 @@ import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../../../rule_schema'; import { findRules } from '../search/find_rules'; +import { transformRuleToExportableFormat } from '../../utils/utils'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; import { getRuleActionConnectorsForExport } from './get_export_rule_action_connectors'; @@ -133,14 +134,11 @@ export const getRulesFromObjects = async ( isAlertType(matchingRule) && matchingRule.params.immutable !== true ) { - const rule = internalRuleToAPIResponse(matchingRule, legacyActions[matchingRule.id]); - - // Fields containing runtime information shouldn't be exported. It causes import failures. - delete rule.execution_summary; - return { statusCode: 200, - rule, + rule: transformRuleToExportableFormat( + internalRuleToAPIResponse(matchingRule, legacyActions[matchingRule.id]) + ), }; } else { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index d024de62a95bf..3f65a49795d75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -98,6 +98,23 @@ export const transformAlertsToRules = ( return rules.map((rule) => internalRuleToAPIResponse(rule, legacyRuleActions[rule.id])); }; +/** + * Transforms a rule object to exportable format. Exportable format shouldn't contain runtime fields like + * `execution_summary` + */ +export const transformRuleToExportableFormat = ( + rule: RuleResponse +): Omit => { + const exportedRule = { + ...rule, + }; + + // Fields containing runtime information shouldn't be exported. It causes import failures. + delete exportedRule.execution_summary; + + return exportedRule; +}; + export const transformFindAlerts = ( ruleFindResults: FindResult, legacyRuleActions: Record diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts index 945388de7a54e..0946852e6a117 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts @@ -5,9 +5,10 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, @@ -19,6 +20,7 @@ import { getSimpleRuleOutput, getWebHookAction, removeServerGeneratedProperties, + waitForRuleSuccessOrStatus, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -49,8 +51,61 @@ export default ({ getService }: FtrProviderContext): void => { .expect('Content-Disposition', 'attachment; filename="export.ndjson"'); }); - it('should export a single rule with a rule_id', async () => { - await createRule(supertest, log, getSimpleRule()); + it('should validate exported rule schema when its exported by its rule_id', async () => { + const ruleId = 'rule-1'; + + const rule = await createRule(supertest, log, getSimpleRule(ruleId, true)); + + await waitForRuleSuccessOrStatus( + supertest, + log, + rule.id, + RuleExecutionStatus['partial failure'] + ); + // to properly execute the test on rule's data with runtime fields some delay is needed as + // ES Search API may return outdated data + // it causes a reliable delay so exported rule's SO contains runtime fields returned via ES Search API + // and will be removed after addressing this issue + await new Promise((r) => setTimeout(r, 1000)); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .send({ + objects: [{ rule_id: 'rule-1' }], + }) + .expect(200) + .parse(binaryToString); + + const exportedRule = JSON.parse(body.toString().split(/\n/)[0]); + + expectToMatchRuleSchema(exportedRule); + }); + + it('should validate all exported rules schema', async () => { + const ruleId1 = 'rule-1'; + const ruleId2 = 'rule-2'; + + const rule1 = await createRule(supertest, log, getSimpleRule(ruleId1, true)); + const rule2 = await createRule(supertest, log, getSimpleRule(ruleId2, true)); + + await waitForRuleSuccessOrStatus( + supertest, + log, + rule1.id, + RuleExecutionStatus['partial failure'] + ); + await waitForRuleSuccessOrStatus( + supertest, + log, + rule2.id, + RuleExecutionStatus['partial failure'] + ); + // to properly execute the test on rule's data with runtime fields some delay is needed as + // ES Search API may return outdated data + // it causes a reliable delay so exported rule's SO contains runtime fields returned via ES Search API + // and will be removed after addressing this issue + await new Promise((r) => setTimeout(r, 1000)); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_export`) @@ -59,10 +114,11 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200) .parse(binaryToString); - const bodySplitAndParsed = JSON.parse(body.toString().split(/\n/)[0]); - const bodyToTest = removeServerGeneratedProperties(bodySplitAndParsed); + const exportedRule1 = JSON.parse(body.toString().split(/\n/)[1]); + const exportedRule2 = JSON.parse(body.toString().split(/\n/)[0]); - expect(bodyToTest).to.eql(getSimpleRuleOutput()); + expectToMatchRuleSchema(exportedRule1); + expectToMatchRuleSchema(exportedRule2); }); it('should export a exported count with a single rule_id', async () => { @@ -77,7 +133,7 @@ export default ({ getService }: FtrProviderContext): void => { const bodySplitAndParsed = JSON.parse(body.toString().split(/\n/)[1]); - expect(bodySplitAndParsed).to.eql({ + expect(bodySplitAndParsed).toEqual({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, exported_count: 1, @@ -112,7 +168,7 @@ export default ({ getService }: FtrProviderContext): void => { const firstRule = removeServerGeneratedProperties(firstRuleParsed); const secondRule = removeServerGeneratedProperties(secondRuleParsed); - expect([firstRule, secondRule]).to.eql([ + expect([firstRule, secondRule]).toEqual([ getSimpleRuleOutput('rule-2'), getSimpleRuleOutput('rule-1'), ]); @@ -171,7 +227,7 @@ export default ({ getService }: FtrProviderContext): void => { ], throttle: 'rule', }; - expect(firstRule).to.eql(outputRule1); + expect(firstRule).toEqual(outputRule1); }); it('should export actions attached to 2 rules', async () => { @@ -224,8 +280,8 @@ export default ({ getService }: FtrProviderContext): void => { actions: [{ ...action, uuid: secondRule.actions[0].uuid }], throttle: 'rule', }; - expect(firstRule).to.eql(outputRule1); - expect(secondRule).to.eql(outputRule2); + expect(firstRule).toEqual(outputRule1); + expect(secondRule).toEqual(outputRule2); }); /** @@ -291,7 +347,7 @@ export default ({ getService }: FtrProviderContext): void => { const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); const firstRule = removeServerGeneratedProperties(firstRuleParsed); - expect(firstRule).to.eql(outputRule1); + expect(firstRule).toEqual(outputRule1); }); it('should be able to export 2 legacy actions on 1 rule', async () => { @@ -377,7 +433,7 @@ export default ({ getService }: FtrProviderContext): void => { const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); const firstRule = removeServerGeneratedProperties(firstRuleParsed); - expect(firstRule).to.eql(outputRule1); + expect(firstRule).toEqual(outputRule1); }); it('should be able to export 2 legacy actions on 2 rules', async () => { @@ -521,10 +577,50 @@ export default ({ getService }: FtrProviderContext): void => { const firstRule = removeServerGeneratedProperties(firstRuleParsed); const secondRule = removeServerGeneratedProperties(secondRuleParsed); - expect(firstRule).to.eql(outputRule2); - expect(secondRule).to.eql(outputRule1); + expect(firstRule).toEqual(outputRule2); + expect(secondRule).toEqual(outputRule1); }); }); }); }); }; + +function expectToMatchRuleSchema(obj: unknown): void { + expect(obj).toEqual({ + id: expect.any(String), + rule_id: expect.any(String), + enabled: expect.any(Boolean), + immutable: false, + updated_at: expect.any(String), + updated_by: expect.any(String), + created_at: expect.any(String), + created_by: expect.any(String), + name: expect.any(String), + tags: expect.arrayContaining([]), + interval: expect.any(String), + description: expect.any(String), + risk_score: expect.any(Number), + severity: expect.any(String), + output_index: expect.any(String), + author: expect.arrayContaining([]), + false_positives: expect.arrayContaining([]), + from: expect.any(String), + max_signals: expect.any(Number), + risk_score_mapping: expect.arrayContaining([]), + severity_mapping: expect.arrayContaining([]), + threat: expect.arrayContaining([]), + to: expect.any(String), + references: expect.arrayContaining([]), + version: expect.any(Number), + exceptions_list: expect.arrayContaining([]), + related_integrations: expect.arrayContaining([]), + required_fields: expect.arrayContaining([]), + setup: expect.any(String), + type: expect.any(String), + language: expect.any(String), + index: expect.arrayContaining([]), + query: expect.any(String), + throttle: expect.any(String), + actions: expect.arrayContaining([]), + }); +}