Skip to content

Commit 8698a34

Browse files
authored
[EDR Workflows] Add cypress test coverage for Endpoint exceptions OR operator (#266687)
## Summary This PR adds e2e cypress test coverage to the OR operator support on the new Endpoint exceptions form: - [x] from Artifacts page - [x] from Alerts page 🎉 All tests passed! - [kibana-flaky-test-suite-runner#12490](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/12490) ## TODO - [x] revert temporary cypress config change for flaky runner 5837829 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed
1 parent fb866f1 commit 8698a34

4 files changed

Lines changed: 295 additions & 3 deletions

File tree

x-pack/solutions/security/plugins/security_solution/public/management/cypress/e2e/artifacts/endpoint_exceptions.cy.ts

Lines changed: 270 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@
66
*/
77
import * as essSecurityHeaders from '@kbn/test-suites-xpack-security/security_solution_cypress/cypress/screens/security_header';
88
import * as serverlessSecurityHeaders from '@kbn/test-suites-xpack-security/security_solution_cypress/cypress/screens/serverless_security_header';
9-
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
9+
import {
10+
ENDPOINT_ARTIFACT_LISTS,
11+
EXCEPTION_LIST_ITEM_URL,
12+
} from '@kbn/securitysolution-list-constants';
13+
import { recurse } from 'cypress-recurse';
1014
import { ENDPOINT_EXCEPTIONS_PER_POLICY_OPT_IN_ROUTE } from '../../../../../common/endpoint/constants';
1115
import {
16+
APP_ALERTS_PATH,
1217
APP_ENDPOINT_EXCEPTIONS_PATH,
1318
APP_MANAGE_PATH,
1419
APP_PATH,
@@ -24,6 +29,10 @@ import {
2429
removeAllArtifacts,
2530
} from '../../tasks/artifacts';
2631
import { getArtifactsListTestDataForArtifact } from '../../fixtures/artifacts_page';
32+
import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts';
33+
import type { ReturnTypeFromChainable } from '../../types';
34+
import { performUserActions, type FormAction } from '../../tasks/perform_user_actions';
35+
import { getArtifactListEmptyStateAddButton } from '../../screens';
2736

2837
describe(
2938
'Endpoint exceptions - under Security Management/Assets',
@@ -102,6 +111,7 @@ describe(
102111
});
103112
});
104113

114+
// Only running on ESS, because on Serverless we cannot remove the opt-in status SO
105115
describe('Per-policy opt-in behaviour', { tags: ['@ess'] }, () => {
106116
before(() => {
107117
removeAllArtifacts();
@@ -143,5 +153,264 @@ describe(
143153
});
144154
});
145155
});
156+
157+
describe('OR operator', { tags: ['@ess', '@serverless'] }, () => {
158+
let endpointData: ReturnTypeFromChainable<typeof indexEndpointHosts> | undefined;
159+
160+
const artifactNameActions: FormAction[] = [
161+
{
162+
type: 'input',
163+
selector: 'endpointExceptions-form-name-input',
164+
value: 'Endpoint exception name',
165+
},
166+
{
167+
type: 'input',
168+
selector: 'endpointExceptions-form-description-input',
169+
value: 'This is the endpoint exception description',
170+
},
171+
];
172+
173+
const firstConditionActions: FormAction[] = [
174+
{
175+
type: 'input',
176+
selector: 'fieldAutocompleteComboBox',
177+
value: 'agent.version',
178+
},
179+
{
180+
type: 'click',
181+
selector: 'valuesAutocompleteMatch',
182+
},
183+
{
184+
type: 'input',
185+
selector: 'valuesAutocompleteMatch',
186+
value: '1234',
187+
},
188+
{
189+
type: 'click',
190+
selector: 'endpointExceptions-form-description-input',
191+
},
192+
];
193+
194+
before(() => {
195+
indexEndpointHosts().then((indexEndpoints) => {
196+
endpointData = indexEndpoints;
197+
});
198+
});
199+
200+
beforeEach(() => {
201+
removeAllArtifacts();
202+
});
203+
204+
after(() => {
205+
removeAllArtifacts();
206+
207+
endpointData?.cleanup();
208+
endpointData = undefined;
209+
});
210+
211+
const addConditionWithOR = (field: string, value: string) => {
212+
cy.getByTestSubj('exceptionsOrButton').click();
213+
214+
cy.getByTestSubj('fieldAutocompleteComboBox').last().type(field);
215+
cy.getByTestSubj('valuesAutocompleteMatch').last().click();
216+
cy.getByTestSubj('valuesAutocompleteMatch').last().type(value);
217+
};
218+
219+
const shouldHaveConditionsOnScreen = (conditions: string[]) =>
220+
cy
221+
.getByTestSubj('endpointExceptionsListPage-card-criteriaConditions-condition')
222+
.then(($conditions) => {
223+
const conditionsText = $conditions.map((_, element) => Cypress.$(element).text()).get();
224+
225+
expect(conditionsText).to.include.members(conditions);
226+
});
227+
228+
describe('on Artifacts page', () => {
229+
it('should create 2 artifacts when using 1 OR operator during CREATE', () => {
230+
cy.intercept('POST', EXCEPTION_LIST_ITEM_URL).as('createExceptionItem');
231+
232+
login();
233+
cy.visit(APP_ENDPOINT_EXCEPTIONS_PATH);
234+
235+
getArtifactListEmptyStateAddButton('endpointExceptions').click();
236+
237+
performUserActions(artifactNameActions);
238+
performUserActions(firstConditionActions);
239+
240+
addConditionWithOR('agent.type', 'endpoint');
241+
242+
cy.getByTestSubj('endpointExceptionsListPage-flyout-submitButton').click();
243+
244+
// There should be 2 artifacts created
245+
cy.get('@createExceptionItem.all').should('have.length', 2);
246+
247+
// All with same name
248+
cy.getByTestSubj('endpointExceptionsListPage-card-header-title')
249+
.should('have.length', 2)
250+
.each((card) => expect(card).to.have.text('Endpoint exception name'));
251+
252+
// and different conditions
253+
shouldHaveConditionsOnScreen(['AND agent.versionIS 1234', 'AND agent.typeIS endpoint']);
254+
});
255+
256+
it('should create 3 artifacts when using 2 OR operators during CREATE', () => {
257+
cy.intercept('POST', EXCEPTION_LIST_ITEM_URL).as('createExceptionItem');
258+
259+
login();
260+
cy.visit(APP_ENDPOINT_EXCEPTIONS_PATH);
261+
262+
getArtifactListEmptyStateAddButton('endpointExceptions').click();
263+
264+
performUserActions(artifactNameActions);
265+
performUserActions(firstConditionActions);
266+
267+
addConditionWithOR('agent.type', 'endpoint');
268+
addConditionWithOR('host.user.email', 'cheese');
269+
270+
cy.getByTestSubj('endpointExceptionsListPage-flyout-submitButton').click();
271+
272+
// There should be 3 artifacts created
273+
cy.get('@createExceptionItem.all').should('have.length', 3);
274+
275+
// All with same name
276+
cy.getByTestSubj('endpointExceptionsListPage-card-header-title')
277+
.should('have.length', 3)
278+
.each((card) => expect(card).to.have.text('Endpoint exception name'));
279+
280+
// and different conditions
281+
shouldHaveConditionsOnScreen([
282+
'AND agent.versionIS 1234',
283+
'AND agent.typeIS endpoint',
284+
'AND host.user.emailIS cheese',
285+
]);
286+
});
287+
288+
it('should create multiple artifacts when using OR operator during EDIT', () => {
289+
login();
290+
cy.visit(APP_ENDPOINT_EXCEPTIONS_PATH);
291+
292+
// Create one artifact
293+
getArtifactListEmptyStateAddButton('endpointExceptions').click();
294+
performUserActions(artifactNameActions);
295+
performUserActions(firstConditionActions);
296+
cy.getByTestSubj('endpointExceptionsListPage-flyout-submitButton').click();
297+
cy.getByTestSubj('endpointExceptionsListPage-card').should('have.length', 1);
298+
299+
// Open artifact to edit
300+
cy.getByTestSubj('endpointExceptionsListPage-card-header-actions-button').click();
301+
cy.getByTestSubj('endpointExceptionsListPage-card-cardEditAction').click();
302+
303+
addConditionWithOR('agent.type', 'endpoint');
304+
addConditionWithOR('host.user.email', 'cheese');
305+
306+
cy.intercept('PUT', EXCEPTION_LIST_ITEM_URL).as('updateExceptionItem');
307+
cy.intercept('POST', EXCEPTION_LIST_ITEM_URL).as('createExceptionItem');
308+
309+
cy.getByTestSubj('endpointExceptionsListPage-flyout-submitButton').click();
310+
311+
// There should be 1 artifact edited and 2 new created
312+
cy.get('@updateExceptionItem.all').should('have.length', 1);
313+
cy.get('@createExceptionItem.all').should('have.length', 2);
314+
315+
// All 3 with the same name
316+
cy.getByTestSubj('endpointExceptionsListPage-card-header-title')
317+
.should('have.length', 3)
318+
.each((card) => expect(card).to.have.text('Endpoint exception name'));
319+
320+
// and different conditions
321+
shouldHaveConditionsOnScreen([
322+
'AND agent.versionIS 1234',
323+
'AND agent.typeIS endpoint',
324+
'AND host.user.emailIS cheese',
325+
]);
326+
});
327+
});
328+
329+
describe('on Alerts page', () => {
330+
const clearPrefilledConditions = () =>
331+
recurse(
332+
() => {
333+
cy.getByTestSubj('builderItemEntryDeleteButton').first().click();
334+
return cy.getByTestSubj('builderItemEntryDeleteButton').first();
335+
},
336+
337+
// recurse until first button is disabled
338+
(firstDeleteButton) => firstDeleteButton.prop('disabled') === true,
339+
340+
{ delay: 100 }
341+
);
342+
343+
it('should create 2 artifacts when using 1 OR operator during CREATE', () => {
344+
cy.intercept('POST', EXCEPTION_LIST_ITEM_URL).as('createExceptionItem');
345+
346+
login();
347+
cy.visit(APP_ALERTS_PATH);
348+
349+
cy.getByTestSubj('timeline-context-menu-button').first().click();
350+
cy.getByTestSubj('add-endpoint-exception-menu-item').click();
351+
352+
clearPrefilledConditions();
353+
354+
performUserActions(artifactNameActions);
355+
performUserActions(firstConditionActions);
356+
357+
addConditionWithOR('agent.type', 'endpoint');
358+
359+
cy.getByTestSubj(`add-endpoint-exception-confirm-button`).click();
360+
361+
// There should be 2 artifacts created
362+
cy.get('@createExceptionItem.all').should('have.length', 2);
363+
364+
// Navigate to Endpoint Exceptions page to check the artifacts
365+
cy.visit(APP_ENDPOINT_EXCEPTIONS_PATH);
366+
367+
// All with same name
368+
cy.getByTestSubj('endpointExceptionsListPage-card-header-title')
369+
.should('have.length', 2)
370+
.each((card) => expect(card).to.have.text('Endpoint exception name'));
371+
372+
// and different conditions
373+
shouldHaveConditionsOnScreen(['AND agent.versionIS 1234', 'AND agent.typeIS endpoint']);
374+
});
375+
376+
it('should create 3 artifacts when using 2 OR operators during CREATE', () => {
377+
cy.intercept('POST', EXCEPTION_LIST_ITEM_URL).as('createExceptionItem');
378+
379+
login();
380+
cy.visit(APP_ALERTS_PATH);
381+
382+
cy.getByTestSubj('timeline-context-menu-button').first().click();
383+
cy.getByTestSubj('add-endpoint-exception-menu-item').click();
384+
385+
clearPrefilledConditions();
386+
387+
performUserActions(artifactNameActions);
388+
performUserActions(firstConditionActions);
389+
390+
addConditionWithOR('agent.type', 'endpoint');
391+
addConditionWithOR('host.user.email', 'cheese');
392+
393+
cy.getByTestSubj(`add-endpoint-exception-confirm-button`).click();
394+
395+
// There should be 3 artifacts created
396+
cy.get('@createExceptionItem.all').should('have.length', 3);
397+
398+
// Navigate to Endpoint Exceptions page to check the artifacts
399+
cy.visit(APP_ENDPOINT_EXCEPTIONS_PATH);
400+
401+
// All with same name
402+
cy.getByTestSubj('endpointExceptionsListPage-card-header-title')
403+
.should('have.length', 3)
404+
.each((card) => expect(card).to.have.text('Endpoint exception name'));
405+
406+
// and different conditions
407+
shouldHaveConditionsOnScreen([
408+
'AND agent.versionIS 1234',
409+
'AND agent.typeIS endpoint',
410+
'AND host.user.emailIS cheese',
411+
]);
412+
});
413+
});
414+
});
146415
}
147416
);

x-pack/solutions/security/plugins/security_solution/public/management/cypress/e2e/serverless/roles/complete_with_endpoint_roles_threat_intelligence_analyst.cy.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ describe(
5353
});
5454

5555
describe('for role: threat_intelligence_analyst', () => {
56-
const deniedPages = allPages.filter(({ id }) => id !== 'blocklist' && id !== 'endpointList');
56+
const deniedPages = allPages.filter(
57+
({ id }) => id !== 'blocklist' && id !== 'endpointList' && id !== 'endpointExceptions'
58+
);
5759

5860
beforeEach(() => {
5961
login(ROLE.threat_intelligence_analyst);
@@ -70,6 +72,13 @@ describe(
7072
);
7173
});
7274

75+
it(`should have CRUD access to: Endpoint exceptions`, () => {
76+
cy.visit(pageById.endpointExceptions.url);
77+
getArtifactListEmptyStateAddButton(
78+
pageById.endpointExceptions.id as EndpointArtifactPageId
79+
).should('exist');
80+
});
81+
7382
for (const { url, title } of deniedPages) {
7483
it(`should NOT have access to: ${title}`, () => {
7584
cy.visit(url);

x-pack/solutions/security/plugins/security_solution/public/management/cypress/screens/artifacts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { UserAuthzAccessLevel } from './types';
1313

1414
const artifactPageTopTestSubjPrefix: Readonly<Record<EndpointArtifactPageId, string>> = {
1515
trustedApps: 'trustedAppsListPage',
16+
endpointExceptions: 'endpointExceptionsListPage',
1617
trustedDevices: 'trustedDevicesList',
1718
eventFilters: 'EventFiltersListPage',
1819
hostIsolationExceptions: 'hostIsolationExceptionsListPage',

x-pack/solutions/security/plugins/security_solution/public/management/cypress/screens/page_reference.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { keyBy } from 'lodash';
99
import {
1010
APP_BLOCKLIST_PATH,
11+
APP_ENDPOINT_EXCEPTIONS_PATH,
1112
APP_ENDPOINTS_PATH,
1213
APP_EVENT_FILTERS_PATH,
1314
APP_HOST_ISOLATION_EXCEPTIONS_PATH,
@@ -20,6 +21,7 @@ import {
2021
export interface EndpointManagementPageMap {
2122
endpointList: EndpointManagementPage;
2223
policyList: EndpointManagementPage;
24+
endpointExceptions: EndpointManagementPage;
2325
trustedApps: EndpointManagementPage;
2426
trustedDevices: EndpointManagementPage;
2527
eventFilters: EndpointManagementPage;
@@ -31,7 +33,12 @@ export interface EndpointManagementPageMap {
3133
export type EndpointManagementPageId = keyof EndpointManagementPageMap;
3234
export type EndpointArtifactPageId = keyof Pick<
3335
EndpointManagementPageMap,
34-
'trustedApps' | 'trustedDevices' | 'eventFilters' | 'hostIsolationExceptions' | 'blocklist'
36+
| 'trustedApps'
37+
| 'trustedDevices'
38+
| 'eventFilters'
39+
| 'hostIsolationExceptions'
40+
| 'blocklist'
41+
| 'endpointExceptions'
3542
>;
3643

3744
interface EndpointManagementPage {
@@ -55,6 +62,12 @@ export const getEndpointManagementPageList = (): EndpointManagementPage[] => {
5562
url: APP_POLICIES_PATH,
5663
pageTestSubj: 'policyListPage',
5764
},
65+
{
66+
id: 'endpointExceptions',
67+
title: 'Endpoint Exceptions Page',
68+
url: APP_ENDPOINT_EXCEPTIONS_PATH,
69+
pageTestSubj: 'endpointExceptionsListPage-container',
70+
},
5871
{
5972
id: 'trustedApps',
6073
title: 'Trusted Apps Page',

0 commit comments

Comments
 (0)