-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security Solution] POC reusing types and avoiding data-driven #143383
Conversation
Pinging @elastic/security-detections-response (Team:Detections and Resp) |
Pinging @elastic/security-solution (Team: SecuritySolution) |
@elasticmachine merge upstream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MadameSheema the changes look good, it's a good improvement.
Though there is a comment for an improvement.
Tags, | ||
} from '../../common/detection_engine/schemas/common'; | ||
|
||
export const getQuery = (): Query => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MadameSheema is there a special point in using parameterless functions for the default test data? As far as I see such functions can be replaced with constants, for example for getQuery
it will be
export const DEFAULT_QUERY = 'host.name: *' as const;
The name DEFAULT_QUERY
can vary but should represent the intent. Which also can be just QUERY
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SOME_QUERY
might make sense when you don't care about the actual value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks a bit raw, but I think this is the right direction. LGTM as the first step towards it 👍 Many thanks @MadameSheema!
export const getQuery = (): Query => { | ||
return 'host.name: *'; | ||
}; | ||
|
||
export const getRuleName = (): Name => { | ||
return 'Test Rule'; | ||
}; | ||
|
||
export const getDescription = (): Description => { | ||
return 'The rule description'; | ||
}; | ||
|
||
export const getSeverity = (): Severity => { | ||
return 'high'; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a test, normally you'd need many of these, which will lead to long import statements and inferior DX (every tiny bit is not available until you import it).
import {
getDefaultIndexPatterns,
getDescription,
getFalsePositives,
getFrom,
getInterval,
getQuery,
getReferenceUrls,
getRiskScore,
getRuleName,
getSeverity,
getTags,
getThreat,
getThreatSubtechnique,
getThreatTechnique,
} from '../../data/detection_engine';
It's not a production code and we don't need tree-shaking here => we could combine all these functions in an object to make it easier to use them. Something like that:
export const ruleFields = {
getName(): Name {
return 'Test Rule';
},
getDescription(): Description {
return 'The rule description';
},
// etc
};
import { ruleFields } from '../../objects/rule_fields';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it! Thanks for the suggestion.
x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts
Show resolved
Hide resolved
fillScheduleRuleAndContinue(this.rule); | ||
|
||
cy.log('Filling define section'); | ||
importSavedQuery(this.timelineId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably an off-topic, because it seems like you kept the previous behavior of the test, but I think in a basic e2e test we should be filling in regular (non-saved) query.
Same for severity, risk score, and other defaultable or optional fields below.
I'd have one basic e2e test that fills in minimal (and only required) fields, maybe another one that fills in all the fields including optional and defaultable, and potentially a few special tests (like one for saved queries).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I kept the previous behavior, I listed it as a Next steps
Agree if we need to fill ALL the fields for each one of the different creation rule tests
. We can chat about it (although I believe we are on the same page). And do it in a follow-up PR to not compromise the current coverage we might have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And do it in a follow-up PR to not compromise the current coverage we might have.
Sure, there's no rush with that 👍
|
||
// expect define step to repopulate | ||
cy.get(DEFINE_EDIT_BUTTON).click(); | ||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', this.rule.customQuery); | ||
cy.get(CUSTOM_QUERY_INPUT).should('have.value', getQuery()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where this query is coming from? This assertion is brittle, because if somewhere under the hood it starts using a different query, this will break with a false negative result.
Please do something like that:
const query = getQuery();
// ...
fillQuery(query);
// ...
cy.get(CUSTOM_QUERY_INPUT).should('have.value', query);
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('eql', this.rule.name); | ||
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', getRuleName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here and other similar places in this test.
const ruleName = getRuleName();
// ...
fillRuleName(ruleName);
// ...
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', ruleName);
Sometimes you don't really care about exact value that will be filled in. Like here, you probably care that some rule name will be filled, and then whatever was filled will be shown when you switch back to the About step. In this case the code could be simplified this way:
export const fillRuleName = (ruleName: string = getRuleName()) => {
cy.get(RULE_NAME_INPUT).clear({ force: true }).type(ruleName, { force: true });
return ruleName;
};
const ruleName = fillRuleName();
// ...
cy.get(RULE_NAME_INPUT).invoke('val').should('eql', ruleName);
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); | ||
|
||
goToRuleDetails(); | ||
|
||
cy.get(RULE_NAME_HEADER).should('contain', `${this.rule.name}`); | ||
cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); | ||
cy.log('Asserting rule details'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry off-topic again, but I think this test does too much which is irrelevant to rule creation functionality.
In order to check if the rule was created properly it should be sufficient to
- fetch the created rule via the API and assert its fields
- validate that after creation the user is navigated to the rules page
- and the rules table is refreshed and shows the newly created rule
Rule Details page is a separate feature that should be covered by separate e2e tests.
fillRuleName(); | ||
fillDescription(); | ||
fillSeverity(); | ||
fillRiskScore(); | ||
fillRuleTags(); | ||
expandAdvancedSettings(); | ||
fillReferenceUrls(); | ||
fillFalsePositiveExamples(); | ||
fillThreat(); | ||
fillThreatTechnique(); | ||
fillThreatSubtechnique(); | ||
fillNote(); | ||
continueWithNextSection(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd group these into an object as well which would improve readability, encapsulate details and simplify imports:
aboutStep.fillRuleName();
aboutStep.fillDescription();
// etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for improvements @MadameSheema
Query, | ||
References, | ||
Tags, | ||
} from '../../common/detection_engine/schemas/common'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding reusing already defined types: types imported here are defined for the values that are used on API level. I.e. ones that are used when rules are created/updated through API calls.
But the functions defined here are used for filling control values in form, which can not be the same type
This leads to a situation like this, where risk scores have to be converted to a string type, which can be consumed by the cypress method.
export const fillRiskScore = (riskScore: string = getRiskScore().toString()) => {
cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${riskScore}`, { force: true });
};
It might be ok though, just something that caught my eye
@elasticmachine merge upstream |
💚 Build Succeeded
Metrics [docs]Unknown metric groupsESLint disabled in files
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
💚 All backports created successfully
Note: Successful backport PRs will be merged automatically after passing CI. Questions ?Please refer to the Backport tool documentation |
…ic#143383) Co-authored-by: Kibana Machine <[email protected]> (cherry picked from commit 3d7f1fb)
💚 All backports created successfully
Note: Successful backport PRs will be merged automatically after passing CI. Questions ?Please refer to the Backport tool documentation |
…ic#143383) Co-authored-by: Kibana Machine <[email protected]> (cherry picked from commit 3d7f1fb)
…143383) (#145512) # Backport This will backport the following commits from `main` to `8.6`: - [[Security Solution] POC reusing types and avoiding data-driven (#143383)](#143383) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Gloria Hornero","email":"[email protected]"},"sourceCommit":{"committedDate":"2022-11-17T09:04:38Z","message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Detections and Resp","Team: SecuritySolution","Team:Detection Rules","Team:Detection Alerts","v8.6.0","v8.7.0"],"number":143383,"url":"https://github.com/elastic/kibana/pull/143383","mergeCommit":{"message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/143383","number":143383,"mergeCommit":{"message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b"}}]}] BACKPORT-->
…143383) (#145513) # Backport This will backport the following commits from `main` to `8.6`: - [[Security Solution] POC reusing types and avoiding data-driven (#143383)](#143383) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Gloria Hornero","email":"[email protected]"},"sourceCommit":{"committedDate":"2022-11-17T09:04:38Z","message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Detections and Resp","Team: SecuritySolution","Team:Detection Rules","Team:Detection Alerts","v8.6.0","v8.7.0"],"number":143383,"url":"https://github.com/elastic/kibana/pull/143383","mergeCommit":{"message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/143383","number":143383,"mergeCommit":{"message":"[Security Solution] POC reusing types and avoiding data-driven (#143383)\n\nCo-authored-by: Kibana Machine <[email protected]>","sha":"3d7f1fb1cf3e8adc1984fdb85456c8b92664de2b"}}]}] BACKPORT--> Co-authored-by: Gloria Hornero <[email protected]>
Summary
This PR is a little POC about reusing existing types and trying to avoid data-driven inside the tests, as part of the on-week project.
Please note the following:
Creates and enables a new rule
forCustom query rules
fillSeverity
we are not updating the types yet to avoid breaking the current tests. That would be changed as wellReusing existing types
We started having custom types and interfaces in Cypress in order to:
As the suite has grown, so have grown the types and interfaces. In the specific case of the interfaces we have ended up with a weird mix of values needed in both UI and API not representing anymore what we originally wanted and making the current tests harder to maintain and expand over time. Mostly because with the interfaces we have know we are duplicating a lot of information.
The overall idea now is to remove all the custom-created types and interfaces and reuse the ones we already have in our application as well as just created small pieces of mock data that would be used for each one of the tests when needed.
Avoid data-driven inside the tests.
When the rule creation tests originally were implemented, the idea was for the methods that interacts with the UI to hide the implementation/logic of the fulfillment of the different sections with the aim of making the tests easier to extend and maintain.
This idea worked at the beginning when we just had a few rules and the logic was more simple than the one we have now. Currently this model has turned the other way around making the development of new tests and the maintenance of the current ones more complex.
Taking advantage of the new types, we are creating also small pieces of mocked data that we are using to fill just what we need/want.
Next steps
getNote()
vsgetInvestigationGuide