Skip to content

Commit

Permalink
[Security Solution][Detections] Integration test for Editing a Rule (#…
Browse files Browse the repository at this point in the history
…77090)

* Add cypress test around editing a detection rule

Right now this just navigates around and verifies that the form is
correctly repopulated; next step will be to modify/asset some changes.

* Add assertions for editing a rule

We already were asserting on the population of the Edit form after
creation; this additionally makes modifications, saves them, and asserts
the resulting values on the Rule Details page.

* Remove unused imports

* Inline our cypress expectations

So that expectation failures are less obfuscated, the decision was
previously made to abstract user navigation into functions, but to leave
expectations directly within the test body.

* Dynamically assert Rule Details based on titles

Rule Details are unfortunately unstructured: they're an array of <dt>s
and <dd>s without any hierarchy. To address this, tests
were previously hardcoding the order of these fields, and assertions
were performed by querying for all <dd>s and then indexing with the
hardcoded number (e.g. ABOUT_FALSE_POSITIVES).

However, in addition to being unstructured, these fields are also
_dynamic_, and will be present/absent depending on the data of the given
rule. Thus, we started needing multiple orderings for the different
combinations of rule fields/rule types.

In the absence of refactoring how we build rule details, I'm introducing
a simple helper function to fetch the relevant <dd> by the corresponding
<dt>s text. This should be more robust to change and more declarative.

* Fix bad merge conflict

Lots of these variables no longer exist upstream and this new test
needed to be refactored.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
rylnd and elasticmachine authored Sep 15, 2020
1 parent 309fe76 commit 6dd558e
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 302 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { newRule } from '../objects/rule';
import { newRule, existingRule } from '../objects/rule';

import {
CUSTOM_RULES_BTN,
Expand All @@ -16,26 +16,16 @@ import {
SHOWING_RULES_TEXT,
} from '../screens/alerts_detection_rules';
import {
ABOUT_FALSE_POSITIVES,
ABOUT_INVESTIGATION_NOTES,
ABOUT_MITRE,
ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
ABOUT_SEVERITY,
ABOUT_STEP,
ABOUT_TAGS,
ABOUT_URLS,
DEFINITION_CUSTOM_QUERY,
DEFINITION_INDEX_PATTERNS,
DEFINITION_TIMELINE,
DEFINITION_STEP,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
SCHEDULE_LOOPBACK,
SCHEDULE_RUNS,
SCHEDULE_STEP,
getDescriptionForTitle,
ABOUT_DETAILS,
DEFINITION_DETAILS,
SCHEDULE_DETAILS,
} from '../screens/rule_details';

import {
Expand All @@ -53,18 +43,38 @@ import {
selectNumberOfRules,
waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded,
waitForRulesToBeLoaded,
editFirstRule,
} from '../tasks/alerts_detection_rules';
import {
createAndActivateRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
expectDefineFormToRepopulateAndContinue,
expectAboutFormToRepopulateAndContinue,
goToAboutStepTab,
goToScheduleStepTab,
goToActionsStepTab,
fillAboutRule,
} from '../tasks/create_new_rule';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';

import { DETECTIONS_URL } from '../urls/navigation';
import {
ACTIONS_THROTTLE_INPUT,
CUSTOM_QUERY_INPUT,
DEFINE_INDEX_INPUT,
RULE_NAME_INPUT,
RULE_DESCRIPTION_INPUT,
TAGS_FIELD,
SEVERITY_DROPDOWN,
RISK_INPUT,
SCHEDULE_INTERVAL_AMOUNT_INPUT,
SCHEDULE_INTERVAL_UNITS_INPUT,
DEFINE_EDIT_BUTTON,
DEFINE_CONTINUE_BUTTON,
ABOUT_EDIT_BUTTON,
ABOUT_CONTINUE_BTN,
} from '../screens/create_new_rule';
import { saveEditedRule } from '../tasks/edit_rule';

describe('Detection rules, custom', () => {
before(() => {
Expand All @@ -84,8 +94,19 @@ describe('Detection rules, custom', () => {
goToCreateNewRule();
fillDefineCustomRuleWithImportedQueryAndContinue(newRule);
fillAboutRuleAndContinue(newRule);
expectDefineFormToRepopulateAndContinue(newRule);
expectAboutFormToRepopulateAndContinue(newRule);

// expect define step to repopulate
cy.get(DEFINE_EDIT_BUTTON).click();
cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', newRule.customQuery);
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist');

// expect about step to populate
cy.get(ABOUT_EDIT_BUTTON).click();
cy.get(RULE_NAME_INPUT).invoke('val').should('eq', newRule.name);
cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
cy.get(ABOUT_CONTINUE_BTN).should('not.exist');

createAndActivateRule();

cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)');
Expand Down Expand Up @@ -142,32 +163,35 @@ describe('Detection rules, custom', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newRule.name} Beta`);

cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newRule.description);
cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newRule.severity);
cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newRule.riskScore);
cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
cy.get(ABOUT_STEP)
.eq(ABOUT_FALSE_POSITIVES)
.invoke('text')
.should('eql', expectedFalsePositives);
cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
cy.get(ABOUT_DETAILS).within(() => {
getDescriptionForTitle('Severity').invoke('text').should('eql', newRule.severity);
getDescriptionForTitle('Risk score').invoke('text').should('eql', newRule.riskScore);
getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
getDescriptionForTitle('False positive examples')
.invoke('text')
.should('eql', expectedFalsePositives);
getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
});

cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);

cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
cy.wrap(patterns).each((pattern, index) => {
cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
});
cy.get(DEFINITION_DETAILS).within(() => {
getDescriptionForTitle('Index patterns')
.invoke('text')
.should('eql', expectedIndexPatterns.join(''));
getDescriptionForTitle('Custom query')
.invoke('text')
.should('eql', `${newRule.customQuery} `);
getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query');
getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
});

cy.get(SCHEDULE_DETAILS).within(() => {
getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
cy.get(DEFINITION_STEP)
.eq(DEFINITION_CUSTOM_QUERY)
.invoke('text')
.should('eql', `${newRule.customQuery} `);
cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');

cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});

Expand Down Expand Up @@ -233,4 +257,94 @@ describe('Deletes custom rules', () => {
.should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`);
});
});

it('Allows a rule to be edited', () => {
editFirstRule();

// expect define step to populate
cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', existingRule.customQuery);
if (existingRule.index && existingRule.index.length > 0) {
cy.get(DEFINE_INDEX_INPUT).invoke('text').should('eq', existingRule.index.join(''));
}

goToAboutStepTab();

// expect about step to populate
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
cy.get(RULE_DESCRIPTION_INPUT).invoke('text').should('eql', existingRule.description);
cy.get(TAGS_FIELD).invoke('text').should('eql', existingRule.tags.join(''));

cy.get(SEVERITY_DROPDOWN).invoke('text').should('eql', existingRule.severity);
cy.get(RISK_INPUT).invoke('val').should('eql', existingRule.riskScore);

goToScheduleStepTab();

// expect schedule step to populate
const intervalParts = existingRule.interval && existingRule.interval.match(/[0-9]+|[a-zA-Z]+/g);
if (intervalParts) {
const [amount, unit] = intervalParts;
cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount);
cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', unit);
} else {
throw new Error('Cannot assert scheduling info on a rule without an interval');
}

goToActionsStepTab();

cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions');

goToAboutStepTab();

const editedRule = {
...existingRule,
severity: 'Medium',
description: 'Edited Rule description',
};

fillAboutRule(editedRule);
saveEditedRule();

const expectedTags = editedRule.tags.join('');
const expectedIndexPatterns =
editedRule.index && editedRule.index.length
? editedRule.index
: [
'apm-*-transaction*',
'auditbeat-*',
'endgame-*',
'filebeat-*',
'logs-*',
'packetbeat-*',
'winlogbeat-*',
];

cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${editedRule.name} Beta`);

cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', editedRule.description);
cy.get(ABOUT_DETAILS).within(() => {
getDescriptionForTitle('Severity').invoke('text').should('eql', editedRule.severity);
getDescriptionForTitle('Risk score').invoke('text').should('eql', editedRule.riskScore);
getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
});

cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', editedRule.note);

cy.get(DEFINITION_DETAILS).within(() => {
getDescriptionForTitle('Index patterns')
.invoke('text')
.should('eql', expectedIndexPatterns.join(''));
getDescriptionForTitle('Custom query')
.invoke('text')
.should('eql', `${editedRule.customQuery} `);
getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query');
getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
});

if (editedRule.interval) {
cy.get(SCHEDULE_DETAILS).within(() => {
getDescriptionForTitle('Runs every').invoke('text').should('eql', editedRule.interval);
});
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,16 @@ import {
SEVERITY,
} from '../screens/alerts_detection_rules';
import {
ABOUT_FALSE_POSITIVES,
ABOUT_DETAILS,
ABOUT_INVESTIGATION_NOTES,
ABOUT_MITRE,
ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
ABOUT_SEVERITY,
ABOUT_STEP,
ABOUT_TAGS,
ABOUT_URLS,
DEFINITION_CUSTOM_QUERY,
DEFINITION_INDEX_PATTERNS,
DEFINITION_TIMELINE,
DEFINITION_STEP,
DEFINITION_DETAILS,
getDescriptionForTitle,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
SCHEDULE_LOOPBACK,
SCHEDULE_RUNS,
SCHEDULE_STEP,
SCHEDULE_DETAILS,
} from '../screens/rule_details';

import {
Expand Down Expand Up @@ -136,32 +126,34 @@ describe('Detection rules, EQL', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${eqlRule.name} Beta`);

cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', eqlRule.description);
cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', eqlRule.severity);
cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', eqlRule.riskScore);
cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
cy.get(ABOUT_STEP)
.eq(ABOUT_FALSE_POSITIVES)
.invoke('text')
.should('eql', expectedFalsePositives);
cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
cy.get(ABOUT_DETAILS).within(() => {
getDescriptionForTitle('Severity').invoke('text').should('eql', eqlRule.severity);
getDescriptionForTitle('Risk score').invoke('text').should('eql', eqlRule.riskScore);
getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
getDescriptionForTitle('False positive examples')
.invoke('text')
.should('eql', expectedFalsePositives);
getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
});

cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);

cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
cy.wrap(patterns).each((pattern, index) => {
cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
});
cy.get(DEFINITION_DETAILS).within(() => {
getDescriptionForTitle('Index patterns')
.invoke('text')
.should('eql', expectedIndexPatterns.join(''));
getDescriptionForTitle('Custom query')
.invoke('text')
.should('eql', `${eqlRule.customQuery} `);
getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Event Correlation');
getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
});

cy.get(SCHEDULE_DETAILS).within(() => {
getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
cy.get(DEFINITION_STEP)
.eq(DEFINITION_CUSTOM_QUERY)
.invoke('text')
.should('eql', `${eqlRule.customQuery} `);
cy.get(DEFINITION_STEP).eq(2).invoke('text').should('eql', 'Event Correlation');
cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');

cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});
Loading

0 comments on commit 6dd558e

Please sign in to comment.