diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts index 0fb8644f5e93c..b3de11539e130 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action_ess.ts @@ -5,15 +5,13 @@ * 2.0. */ -import { Rule } from '@kbn/alerting-plugin/common'; -import expect from '@kbn/expect'; +import expect from 'expect'; import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; import { BulkActionEditTypeEnum, BulkActionTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { createRule, deleteAllRules, @@ -30,10 +28,12 @@ import { getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, getRuleSOById, - getSimpleRule, getWebHookAction, } from '../../../utils'; +// Rule's interval must be less or equal rule action's interval +const MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION = '1h'; + export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const securitySolutionApi = getService('securitySolutionApi'); @@ -46,17 +46,14 @@ export default ({ getService }: FtrProviderContext): void => { const createWebHookConnector = () => createConnector(getWebHookAction()); - // Failing: See https://github.com/elastic/kibana/issues/173804 - // Failing: See https://github.com/elastic/kibana/issues/196470 - // Failing: See https://github.com/elastic/kibana/issues/196462 - describe.skip('@ess perform_bulk_action - ESS specific logic', () => { + describe('@ess perform_bulk_action - ESS specific logic', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); it('should delete rules and any associated legacy actions', async () => { const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ + const [connector, rule] = await Promise.all([ supertest .post(`/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -67,14 +64,22 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, getSimpleRule(ruleId, false)), + + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + }) + ), ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + await createLegacyRuleAction(supertest, rule.id, connector.body.id); // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe(rule.id); const { body } = await securitySolutionApi .performRulesBulkAction({ @@ -83,14 +88,14 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the deleted rule is returned with the response - expect(body.attributes.results.deleted[0].name).to.eql(rule1.name); + expect(body.attributes.results.deleted[0].name).toEqual(rule.name); // legacy sidecar action should be gone const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + expect(sidecarActionsPostResults.hits.hits.length).toBe(0); // Check that the updates have been persisted await securitySolutionApi.readRule({ query: { rule_id: ruleId } }).expect(404); @@ -98,7 +103,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should enable rules and migrate actions', async () => { const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ + const [connector, rule] = await Promise.all([ supertest .post(`/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -109,14 +114,23 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, { ...getSimpleRule(ruleId, false), index: ['*'] }), + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + index: ['*'], + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + enabled: false, + }) + ), ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + await createLegacyRuleAction(supertest, rule.id, connector.body.id); // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe(rule.id); const { body } = await securitySolutionApi .performRulesBulkAction({ @@ -125,10 +139,10 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(true); + expect(body.attributes.results.updated[0].enabled).toBeTruthy(); // Check that the updates have been persisted const { body: ruleBody } = await securitySolutionApi @@ -137,10 +151,10 @@ export default ({ getService }: FtrProviderContext): void => { // legacy sidecar action should be gone const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + expect(sidecarActionsPostResults.hits.hits.length).toBe(0); - expect(ruleBody.enabled).to.eql(true); - expect(ruleBody.actions).to.eql([ + expect(ruleBody.enabled).toBeTruthy(); + expect(ruleBody.actions).toEqual([ { action_type_id: '.slack', group: 'default', @@ -148,17 +162,17 @@ export default ({ getService }: FtrProviderContext): void => { params: { message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', }, - uuid: ruleBody.actions[0].uuid, + uuid: expect.any(String), frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, }, ]); // we want to ensure rule is executing successfully, to prevent any AAD issues related to partial update of rule SO - await waitForRuleSuccess({ id: rule1.id, supertest, log }); + await waitForRuleSuccess({ id: rule.id, supertest, log }); }); it('should disable rules and migrate actions', async () => { const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ + const [connector, rule] = await Promise.all([ supertest .post(`/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -169,14 +183,22 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, getSimpleRule(ruleId, true)), + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + enabled: true, + }) + ), ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + await createLegacyRuleAction(supertest, rule.id, connector.body.id); // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe(rule.id); const { body } = await securitySolutionApi .performRulesBulkAction({ @@ -185,10 +207,10 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(false); + expect(body.attributes.results.updated[0].enabled).toBeFalsy(); // Check that the updates have been persisted const { body: ruleBody } = await securitySolutionApi @@ -197,10 +219,10 @@ export default ({ getService }: FtrProviderContext): void => { // legacy sidecar action should be gone const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + expect(sidecarActionsPostResults.hits.hits.length).toBe(0); - expect(ruleBody.enabled).to.eql(false); - expect(ruleBody.actions).to.eql([ + expect(ruleBody.enabled).toBeFalsy(); + expect(ruleBody.actions).toEqual([ { action_type_id: '.slack', group: 'default', @@ -208,7 +230,7 @@ export default ({ getService }: FtrProviderContext): void => { params: { message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', }, - uuid: ruleBody.actions[0].uuid, + uuid: expect.any(String), frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, }, ]); @@ -227,14 +249,21 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, getSimpleRule(ruleId, true)), + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + }) + ), ]); await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe( ruleToDuplicate.id ); @@ -249,22 +278,21 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the duplicated rule is returned with the response - expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + expect(body.attributes.results.created[0].name).toBe(`${ruleToDuplicate.name} [Duplicate]`); // Check that the updates have been persisted const { body: rulesResponse } = await securitySolutionApi .findRules({ query: {} }) .expect(200); - expect(rulesResponse.total).to.eql(2); + expect(rulesResponse.total).toBe(2); rulesResponse.data.forEach((rule: RuleResponse) => { - const uuid = rule.actions[0].uuid; - expect(rule.actions).to.eql([ - { + expect(rule.actions).toEqual([ + expect.objectContaining({ action_type_id: '.slack', group: 'default', id: connector.body.id, @@ -272,9 +300,8 @@ export default ({ getService }: FtrProviderContext): void => { message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', }, - ...(uuid ? { uuid } : {}), frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, + }), ]); }); }); @@ -294,7 +321,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Check that the duplicated rule is returned with the correct rule_source - expect(body.attributes.results.created[0].rule_source).to.eql({ + expect(body.attributes.results.created[0].rule_source).toEqual({ type: 'internal', }); }); @@ -320,8 +347,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ + failed: 1, + skipped: 0, + succeeded: 0, + total: 1, + }); + expect(body.attributes.errors[0]).toEqual({ message: "Index patterns can't be added. ES|QL rule doesn't have index patterns property", status_code: 500, @@ -348,31 +380,41 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, getSimpleRule(ruleId, true)), + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + }) + ), ]); await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe( ruleToDuplicate.id ); - const { body: setTagsBody } = await securitySolutionApi.performRulesBulkAction({ - body: { - query: '', - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.set_tags, - value: ['reset-tag'], - }, - ], - }, - query: {}, - }); - expect(setTagsBody.attributes.summary).to.eql({ + const { body: setTagsBody } = await securitySolutionApi + .performRulesBulkAction({ + body: { + query: '', + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_tags, + value: ['reset-tag'], + }, + ], + }, + query: {}, + }) + .expect(200); + + expect(setTagsBody.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -386,11 +428,10 @@ export default ({ getService }: FtrProviderContext): void => { // Sidecar should be removed const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - - expect(setTagsRule.tags).to.eql(['reset-tag']); - expect(setTagsRule.actions).to.eql([ + expect(sidecarActionsPostResults.hits.hits.length).toBe(0); + expect(setTagsRule.tags).toEqual(['reset-tag']); + expect(setTagsRule.actions).toEqual([ { action_type_id: '.slack', group: 'default', @@ -399,7 +440,7 @@ export default ({ getService }: FtrProviderContext): void => { message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', }, - uuid: setTagsRule.actions[0].uuid, + uuid: expect.any(String), frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, }, ]); @@ -427,7 +468,14 @@ export default ({ getService }: FtrProviderContext): void => { webhookUrl: 'http://localhost:1234', }, }), - createRule(supertest, log, getSimpleRule(ruleId, true)), + createRule( + supertest, + log, + getCustomQueryRuleParams({ + rule_id: ruleId, + interval: MINIMUM_RULE_INTERVAL_FOR_LEGACY_ACTION, + }) + ), ]); // create a new connector const webHookConnector = await createWebHookConnector(); @@ -436,8 +484,8 @@ export default ({ getService }: FtrProviderContext): void => { // check for legacy sidecar action const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + expect(sidecarActionsResults.hits.hits.length).toBe(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).toBe( createdRule.id ); @@ -470,53 +518,51 @@ export default ({ getService }: FtrProviderContext): void => { ...webHookActionMock, id: webHookConnector.id, action_type_id: '.webhook', - uuid: body.attributes.results.updated[0].actions[0].uuid, + uuid: expect.any(String), frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, }, ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await securitySolutionApi .readRule({ query: { rule_id: ruleId } }) .expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); // Sidecar should be removed const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + expect(sidecarActionsPostResults.hits.hits.length).toBe(0); }); }); }); }); describe('legacy investigation fields', () => { - let ruleWithLegacyInvestigationField: Rule; - let ruleWithLegacyInvestigationFieldEmptyArray: Rule; - let ruleWithIntendedInvestigationField: RuleResponse; - - beforeEach(async () => { - ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFields() - ); - - ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() - ); - - ruleWithIntendedInvestigationField = await createRule(supertest, log, { - ...getSimpleRule('rule-with-investigation-field'), - name: 'Test investigation fields object', - investigation_fields: { field_names: ['host.name'] }, - }); - }); - it('should export rules with legacy investigation_fields and transform legacy field in response', async () => { + const [ + ruleWithLegacyInvestigationField, + ruleWithLegacyInvestigationFieldEmptyArray, + ruleWithIntendedInvestigationField, + ] = await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi .performRulesBulkAction({ body: { query: '', action: BulkActionTypeEnum.export }, @@ -533,21 +579,21 @@ export default ({ getService }: FtrProviderContext): void => { const exportedRuleWithLegacyInvestigationField = exportedRules.find( (rule) => rule.id === ruleWithLegacyInvestigationField.id ); - expect(exportedRuleWithLegacyInvestigationField.investigation_fields).to.eql({ + expect(exportedRuleWithLegacyInvestigationField.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); const exportedRuleWithLegacyInvestigationFieldEmptyArray = exportedRules.find( (rule) => rule.id === ruleWithLegacyInvestigationFieldEmptyArray.id ); - expect(exportedRuleWithLegacyInvestigationFieldEmptyArray.investigation_fields).to.eql( + expect(exportedRuleWithLegacyInvestigationFieldEmptyArray.investigation_fields).toEqual( undefined ); const exportedRuleWithInvestigationField = exportedRules.find( (rule) => rule.id === ruleWithIntendedInvestigationField.id ); - expect(exportedRuleWithInvestigationField.investigation_fields).to.eql({ + expect(exportedRuleWithInvestigationField.investigation_fields).toEqual({ field_names: ['host.name'], }); @@ -563,10 +609,10 @@ export default ({ getService }: FtrProviderContext): void => { JSON.parse(rule1).id ); - expect(isInvestigationFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldMigratedInSo).toBeFalsy(); const exportDetails = JSON.parse(exportDetailsJson); - expect(exportDetails).to.eql({ + expect(exportDetails).toEqual({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, exported_count: 3, @@ -586,6 +632,23 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should delete rules with investigation fields and transform legacy field in response', async () => { + const [ruleWithLegacyInvestigationField, ruleWithLegacyInvestigationFieldEmptyArray] = + await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi .performRulesBulkAction({ body: { query: '', action: BulkActionTypeEnum.delete }, @@ -593,22 +656,22 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); // Check that the deleted rule is returned with the response const names = body.attributes.results.deleted.map( (returnedRule: RuleResponse) => returnedRule.name ); - expect(names.includes('Test investigation fields')).to.eql(true); - expect(names.includes('Test investigation fields empty array')).to.eql(true); - expect(names.includes('Test investigation fields object')).to.eql(true); + expect(names.includes('Test investigation fields')).toBeTruthy(); + expect(names.includes('Test investigation fields empty array')).toBeTruthy(); + expect(names.includes('Test investigation fields object')).toBeTruthy(); const ruleWithLegacyField = body.attributes.results.deleted.find( (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ + expect(ruleWithLegacyField.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); @@ -627,6 +690,23 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should enable rules with legacy investigation fields and transform legacy field in response', async () => { + const [ruleWithLegacyInvestigationField, ruleWithLegacyInvestigationFieldEmptyArray] = + await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields({ enabled: false }) + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray({ enabled: false }) + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi .performRulesBulkAction({ body: { query: '', action: BulkActionTypeEnum.enable }, @@ -634,7 +714,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); // Check that the updated rule is returned with the response // and field transformed on response @@ -642,13 +722,13 @@ export default ({ getService }: FtrProviderContext): void => { body.attributes.results.updated.every( (returnedRule: RuleResponse) => returnedRule.enabled ) - ).to.eql(true); + ).toBeTruthy(); const ruleWithLegacyField = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ + expect(ruleWithLegacyField.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); @@ -656,12 +736,12 @@ export default ({ getService }: FtrProviderContext): void => { (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); + expect(ruleWithEmptyArray.investigation_fields).toBeUndefined(); const ruleWithIntendedType = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); + expect(ruleWithIntendedType.investigation_fields).toEqual({ field_names: ['host.name'] }); /** * Confirm type on SO so that it's clear in the tests whether it's expected that * the SO itself is migrated to the inteded object type, or if the transformation is @@ -677,27 +757,44 @@ export default ({ getService }: FtrProviderContext): void => { field_names: ['client.address', 'agent.name'], }); - expect(isInvestigationFieldMigratedInSo).to.eql(false); - expect(ruleSO?.alert?.enabled).to.eql(true); + expect(isInvestigationFieldMigratedInSo).toBeFalsy(); + expect(ruleSO?.alert?.enabled).toBeTruthy(); const { hits: { hits: [{ _source: ruleSO2 }], }, } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - expect(ruleSO?.alert?.enabled).to.eql(true); + expect(ruleSO2?.alert?.params?.investigationFields).toEqual([]); + expect(ruleSO?.alert?.enabled).toBeTruthy(); const { hits: { hits: [{ _source: ruleSO3 }], }, } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); - expect(ruleSO?.alert?.enabled).to.eql(true); + expect(ruleSO3?.alert?.params?.investigationFields).toEqual({ field_names: ['host.name'] }); + expect(ruleSO?.alert?.enabled).toBeTruthy(); }); it('should disable rules with legacy investigation fields and transform legacy field in response', async () => { + const [ruleWithLegacyInvestigationField, ruleWithLegacyInvestigationFieldEmptyArray] = + await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields({ enabled: true }) + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray({ enabled: false }) + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi .performRulesBulkAction({ body: { query: '', action: BulkActionTypeEnum.disable }, @@ -705,7 +802,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); // Check that the updated rule is returned with the response // and field transformed on response @@ -713,13 +810,13 @@ export default ({ getService }: FtrProviderContext): void => { body.attributes.results.updated.every( (returnedRule: RuleResponse) => !returnedRule.enabled ) - ).to.eql(true); + ).toBeTruthy(); const ruleWithLegacyField = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ + expect(ruleWithLegacyField.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); @@ -727,12 +824,12 @@ export default ({ getService }: FtrProviderContext): void => { (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); + expect(ruleWithEmptyArray.investigation_fields).toBeUndefined(); const ruleWithIntendedType = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); + expect(ruleWithIntendedType.investigation_fields).toEqual({ field_names: ['host.name'] }); /** * Confirm type on SO so that it's clear in the tests whether it's expected that @@ -748,7 +845,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyField.id ); - expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).toBeFalsy(); const isInvestigationFieldForRuleWithEmptyArraydMigratedInSo = await checkInvestigationFieldSoValue( @@ -759,7 +856,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithEmptyArray.id ); - expect(isInvestigationFieldForRuleWithEmptyArraydMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithEmptyArraydMigratedInSo).toBeFalsy(); const isInvestigationFieldForRuleWithIntendedTypeMigratedInSo = await checkInvestigationFieldSoValue( @@ -768,10 +865,30 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithIntendedType.id ); - expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).to.eql(true); + expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).toBeTruthy(); }); it('should duplicate rules with legacy investigation fields and transform field in response', async () => { + const [ + ruleWithLegacyInvestigationField, + ruleWithLegacyInvestigationFieldEmptyArray, + ruleWithIntendedInvestigationField, + ] = await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi .performRulesBulkAction({ body: { @@ -783,22 +900,22 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); // Check that the duplicated rule is returned with the response const names = body.attributes.results.created.map( (returnedRule: RuleResponse) => returnedRule.name ); - expect(names.includes('Test investigation fields [Duplicate]')).to.eql(true); - expect(names.includes('Test investigation fields empty array [Duplicate]')).to.eql(true); - expect(names.includes('Test investigation fields object [Duplicate]')).to.eql(true); + expect(names.includes('Test investigation fields [Duplicate]')).toBeTruthy(); + expect(names.includes('Test investigation fields empty array [Duplicate]')).toBeTruthy(); + expect(names.includes('Test investigation fields object [Duplicate]')).toBeTruthy(); // Check that the updates have been persisted const { body: rulesResponse } = await await securitySolutionApi .findRules({ query: {} }) .expect(200); - expect(rulesResponse.total).to.eql(6); + expect(rulesResponse.total).toBe(6); const ruleWithLegacyField = body.attributes.results.created.find( (returnedRule: RuleResponse) => @@ -827,7 +944,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyField.id ); - expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).toBeFalsy(); const isInvestigationFieldForRuleWithEmptyArrayMigratedInSo = await checkInvestigationFieldSoValue( @@ -836,7 +953,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithEmptyArray.id ); - expect(isInvestigationFieldForRuleWithEmptyArrayMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithEmptyArrayMigratedInSo).toBeFalsy(); /* It's duplicate of a rule with properly formatted "investigation fields". @@ -850,7 +967,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithIntendedType.id ); - expect(isInvestigationFieldForRuleWithIntendedTypeInSo).to.eql(true); + expect(isInvestigationFieldForRuleWithIntendedTypeInSo).toBeTruthy(); // ORIGINAL RULES - rules selected to be duplicated /** @@ -866,7 +983,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyInvestigationField.id ); - expect(isInvestigationFieldForOriginalRuleWithLegacyFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldForOriginalRuleWithLegacyFieldMigratedInSo).toBeFalsy(); const isInvestigationFieldForOriginalRuleWithEmptyArrayMigratedInSo = await checkInvestigationFieldSoValue( @@ -875,7 +992,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyInvestigationFieldEmptyArray.id ); - expect(isInvestigationFieldForOriginalRuleWithEmptyArrayMigratedInSo).to.eql(false); + expect(isInvestigationFieldForOriginalRuleWithEmptyArrayMigratedInSo).toBeFalsy(); /* Since this rule was created with intended "investigation fields" format, @@ -888,10 +1005,27 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithIntendedInvestigationField.id ); - expect(isInvestigationFieldForOriginalRuleWithIntendedTypeInSo).to.eql(true); + expect(isInvestigationFieldForOriginalRuleWithIntendedTypeInSo).toBeTruthy(); }); it('should edit rules with legacy investigation fields', async () => { + const [ruleWithLegacyInvestigationField, ruleWithLegacyInvestigationFieldEmptyArray] = + await Promise.all([ + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ), + createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ), + createRule(supertest, log, { + ...getCustomQueryRuleParams({ rule_id: 'rule-with-investigation-field' }), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }), + ]); + const { body } = await securitySolutionApi.performRulesBulkAction({ body: { query: '', @@ -905,7 +1039,7 @@ export default ({ getService }: FtrProviderContext): void => { }, query: {}, }); - expect(body.attributes.summary).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 3, @@ -918,23 +1052,23 @@ export default ({ getService }: FtrProviderContext): void => { (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ + expect(ruleWithLegacyField.investigation_fields).toEqual({ field_names: ['client.address', 'agent.name'], }); - expect(ruleWithLegacyField.tags).to.eql(['reset-tag']); + expect(ruleWithLegacyField.tags).toEqual(['reset-tag']); const ruleWithEmptyArray = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); - expect(ruleWithEmptyArray.tags).to.eql(['reset-tag']); + expect(ruleWithEmptyArray.investigation_fields).toBeUndefined(); + expect(ruleWithEmptyArray.tags).toEqual(['reset-tag']); const ruleWithIntendedType = body.attributes.results.updated.find( (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); - expect(ruleWithIntendedType.tags).to.eql(['reset-tag']); + expect(ruleWithIntendedType.investigation_fields).toEqual({ field_names: ['host.name'] }); + expect(ruleWithIntendedType.tags).toEqual(['reset-tag']); /** * Confirm type on SO so that it's clear in the tests whether it's expected that @@ -948,7 +1082,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyInvestigationField.id ); - expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithLegacyFieldMigratedInSo).toBeFalsy(); const isInvestigationFieldForRuleWithEmptyArrayFieldMigratedInSo = await checkInvestigationFieldSoValue( @@ -957,7 +1091,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithLegacyInvestigationFieldEmptyArray.id ); - expect(isInvestigationFieldForRuleWithEmptyArrayFieldMigratedInSo).to.eql(false); + expect(isInvestigationFieldForRuleWithEmptyArrayFieldMigratedInSo).toBeFalsy(); const isInvestigationFieldForRuleWithIntendedTypeMigratedInSo = await checkInvestigationFieldSoValue( @@ -966,7 +1100,7 @@ export default ({ getService }: FtrProviderContext): void => { es, ruleWithIntendedType.id ); - expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).to.eql(true); + expect(isInvestigationFieldForRuleWithIntendedTypeMigratedInSo).toBeTruthy(); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts index 55056748a0945..9b315dbd9dbb5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_legacy_investigation_fields.ts @@ -7,7 +7,9 @@ import { InternalRuleCreate } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; -export const getRuleSavedObjectWithLegacyInvestigationFields = (): InternalRuleCreate => +export const getRuleSavedObjectWithLegacyInvestigationFields = ( + rewrites?: Partial +): InternalRuleCreate => ({ name: 'Test investigation fields', tags: ['migration'], @@ -54,6 +56,7 @@ export const getRuleSavedObjectWithLegacyInvestigationFields = (): InternalRuleC responseActions: undefined, alertSuppression: undefined, dataViewId: undefined, + ...rewrites?.params, }, schedule: { interval: '5m', @@ -61,11 +64,14 @@ export const getRuleSavedObjectWithLegacyInvestigationFields = (): InternalRuleC enabled: false, actions: [], throttle: null, + ...rewrites, // cast is due to alerting API expecting rule_type_id // and our internal schema expecting alertTypeId } as unknown as InternalRuleCreate); -export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = (): InternalRuleCreate => +export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = ( + rewrites?: Partial +): InternalRuleCreate => ({ name: 'Test investigation fields empty array', tags: ['migration'], @@ -112,6 +118,7 @@ export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = (): Int responseActions: undefined, alertSuppression: undefined, dataViewId: undefined, + ...rewrites?.params, }, schedule: { interval: '5m', @@ -119,6 +126,7 @@ export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = (): Int enabled: false, actions: [], throttle: null, + ...rewrites, // cast is due to alerting API expecting rule_type_id // and our internal schema expecting alertTypeId } as unknown as InternalRuleCreate);