Skip to content

Commit 34c80e5

Browse files
[Security Solutions] Adds additional cypress tests and utils to value based lists (#83026)
## Summary Adds additional cypress tests and utils around value lists so that the percent of test driven development (TDD) possibilities will be easier for us to accomplish around bug fixes towards the next release. * Changes and adds data test subjects within value based lists * Fixes a bug where the list fixtures were not being converted to base64 before uploads within Cypress * Adds a [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) fixture * Adds tests for export, delete, close and open the dialog, and all the value based list types for the current modal. ### Checklist - [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
1 parent f4126ea commit 34c80e5

File tree

9 files changed

+294
-16
lines changed

9 files changed

+294
-16
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
192.168.100.14/24
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
127.0.0.1
2+
127.0.0.2
3+
127.0.0.3

x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts

Lines changed: 187 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,207 @@ import {
1717
openValueListsModal,
1818
selectValueListsFile,
1919
uploadValueList,
20+
selectValueListType,
21+
deleteAllValueListsFromUI,
22+
closeValueListsModal,
23+
importValueList,
24+
deleteValueListsFile,
25+
exportValueList,
2026
} from '../tasks/lists';
2127
import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW } from '../screens/lists';
2228

2329
describe('value lists', () => {
2430
describe('management modal', () => {
25-
it('creates a keyword list from an uploaded file', () => {
31+
beforeEach(() => {
2632
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
2733
waitForAlertsPanelToBeLoaded();
2834
waitForAlertsIndexToBeCreated();
2935
waitForListsIndexToBeCreated();
3036
goToManageAlertsDetectionRules();
3137
waitForValueListsModalToBeLoaded();
38+
});
39+
40+
afterEach(() => {
41+
deleteAllValueListsFromUI();
42+
});
43+
44+
it('can open and close the modal', () => {
3245
openValueListsModal();
33-
selectValueListsFile();
34-
uploadValueList();
46+
closeValueListsModal();
47+
});
48+
49+
describe('create list types', () => {
50+
beforeEach(() => {
51+
openValueListsModal();
52+
});
53+
54+
it('creates a "keyword" list from an uploaded file', () => {
55+
const listName = 'value_list.txt';
56+
selectValueListType('keyword');
57+
selectValueListsFile(listName);
58+
uploadValueList();
59+
60+
cy.get(VALUE_LISTS_TABLE)
61+
.find(VALUE_LISTS_ROW)
62+
.should(($row) => {
63+
expect($row.text()).to.contain(listName);
64+
expect($row.text()).to.contain('Keywords');
65+
});
66+
});
67+
68+
it('creates a "text" list from an uploaded file', () => {
69+
const listName = 'value_list.txt';
70+
selectValueListType('text');
71+
selectValueListsFile(listName);
72+
uploadValueList();
73+
74+
cy.get(VALUE_LISTS_TABLE)
75+
.find(VALUE_LISTS_ROW)
76+
.should(($row) => {
77+
expect($row.text()).to.contain(listName);
78+
expect($row.text()).to.contain('Text');
79+
});
80+
});
81+
82+
it('creates a "ip" list from an uploaded file', () => {
83+
const listName = 'ip_list.txt';
84+
selectValueListType('ip');
85+
selectValueListsFile(listName);
86+
uploadValueList();
87+
88+
cy.get(VALUE_LISTS_TABLE)
89+
.find(VALUE_LISTS_ROW)
90+
.should(($row) => {
91+
expect($row.text()).to.contain(listName);
92+
expect($row.text()).to.contain('IP addresses');
93+
});
94+
});
95+
96+
it('creates a "ip_range" list from an uploaded file', () => {
97+
const listName = 'cidr_list.txt';
98+
selectValueListType('ip_range');
99+
selectValueListsFile(listName);
100+
uploadValueList();
101+
102+
cy.get(VALUE_LISTS_TABLE)
103+
.find(VALUE_LISTS_ROW)
104+
.should(($row) => {
105+
expect($row.text()).to.contain(listName);
106+
expect($row.text()).to.contain('IP ranges');
107+
});
108+
});
109+
});
110+
111+
describe('delete list types', () => {
112+
it('deletes a "keyword" list from an uploaded file', () => {
113+
const listName = 'value_list.txt';
114+
importValueList(listName, 'keyword');
115+
openValueListsModal();
116+
deleteValueListsFile(listName);
117+
cy.get(VALUE_LISTS_TABLE)
118+
.find(VALUE_LISTS_ROW)
119+
.should(($row) => {
120+
expect($row.text()).not.to.contain(listName);
121+
});
122+
});
123+
124+
it('deletes a "text" list from an uploaded file', () => {
125+
const listName = 'value_list.txt';
126+
importValueList(listName, 'text');
127+
openValueListsModal();
128+
deleteValueListsFile(listName);
129+
cy.get(VALUE_LISTS_TABLE)
130+
.find(VALUE_LISTS_ROW)
131+
.should(($row) => {
132+
expect($row.text()).not.to.contain(listName);
133+
});
134+
});
135+
136+
it('deletes a "ip" from an uploaded file', () => {
137+
const listName = 'ip_list.txt';
138+
importValueList(listName, 'ip');
139+
openValueListsModal();
140+
deleteValueListsFile(listName);
141+
cy.get(VALUE_LISTS_TABLE)
142+
.find(VALUE_LISTS_ROW)
143+
.should(($row) => {
144+
expect($row.text()).not.to.contain(listName);
145+
});
146+
});
147+
148+
it('deletes a "ip_range" from an uploaded file', () => {
149+
const listName = 'cidr_list.txt';
150+
importValueList(listName, 'ip_range');
151+
openValueListsModal();
152+
deleteValueListsFile(listName);
153+
cy.get(VALUE_LISTS_TABLE)
154+
.find(VALUE_LISTS_ROW)
155+
.should(($row) => {
156+
expect($row.text()).not.to.contain(listName);
157+
});
158+
});
159+
});
160+
161+
describe('export list types', () => {
162+
beforeEach(() => {
163+
cy.server();
164+
cy.route('POST', '**/api/lists/items/_export?list_id=*').as('exportList');
165+
});
166+
167+
it('exports a "keyword" list from an uploaded file', () => {
168+
const listName = 'value_list.txt';
169+
importValueList('value_list.txt', 'keyword');
170+
openValueListsModal();
171+
exportValueList();
172+
cy.wait('@exportList').then((xhr) => {
173+
cy.fixture(listName).then((list: string) => {
174+
const [lineOne, lineTwo] = list.split('\n');
175+
expect(xhr.responseBody).to.contain(lineOne);
176+
expect(xhr.responseBody).to.contain(lineTwo);
177+
});
178+
});
179+
});
180+
181+
it('exports a "text" list from an uploaded file', () => {
182+
const listName = 'value_list.txt';
183+
importValueList(listName, 'text');
184+
openValueListsModal();
185+
exportValueList();
186+
cy.wait('@exportList').then((xhr) => {
187+
cy.fixture(listName).then((list: string) => {
188+
const [lineOne, lineTwo] = list.split('\n');
189+
expect(xhr.responseBody).to.contain(lineOne);
190+
expect(xhr.responseBody).to.contain(lineTwo);
191+
});
192+
});
193+
});
194+
195+
it('exports a "ip" list from an uploaded file', () => {
196+
const listName = 'ip_list.txt';
197+
importValueList(listName, 'ip');
198+
openValueListsModal();
199+
exportValueList();
200+
cy.wait('@exportList').then((xhr) => {
201+
cy.fixture(listName).then((list: string) => {
202+
const [lineOne, lineTwo] = list.split('\n');
203+
expect(xhr.responseBody).to.contain(lineOne);
204+
expect(xhr.responseBody).to.contain(lineTwo);
205+
});
206+
});
207+
});
35208

36-
cy.get(VALUE_LISTS_TABLE)
37-
.find(VALUE_LISTS_ROW)
38-
.should(($row) => {
39-
expect($row.text()).to.contain('value_list.txt');
209+
it('exports a "ip_range" list from an uploaded file', () => {
210+
const listName = 'cidr_list.txt';
211+
importValueList(listName, 'ip_range');
212+
openValueListsModal();
213+
exportValueList();
214+
cy.wait('@exportList').then((xhr) => {
215+
cy.fixture(listName).then((list: string) => {
216+
const [lineOne] = list.split('\n');
217+
expect(xhr.responseBody).to.contain(lineOne);
218+
});
40219
});
220+
});
41221
});
42222
});
43223
});

x-pack/plugins/security_solution/cypress/screens/lists.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ export const VALUE_LISTS_TABLE = '[data-test-subj="value-lists-table"]';
99
export const VALUE_LISTS_ROW = '.euiTableRow';
1010
export const VALUE_LIST_FILE_PICKER = '[data-test-subj="value-list-file-picker"]';
1111
export const VALUE_LIST_FILE_UPLOAD_BUTTON = '[data-test-subj="value-lists-form-import-action"]';
12+
export const VALUE_LIST_TYPE_SELECTOR = '[data-test-subj="value-lists-form-select-type-action"]';
13+
export const VALUE_LIST_DELETE_BUTTON = (name: string) =>
14+
`[data-test-subj="action-delete-value-list-${name}"]`;
15+
export const VALUE_LIST_FILES = '[data-test-subj*="action-delete-value-list-"]';
16+
export const VALUE_LIST_CLOSE_BUTTON = '[data-test-subj="value-lists-modal-close-action"]';
17+
export const VALUE_LIST_EXPORT_BUTTON = '[data-test-subj="action-export-value-list"]';

x-pack/plugins/security_solution/cypress/support/commands.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Cypress.Commands.add(
5858
},
5959
(input, fileName, fileType = 'text/plain') => {
6060
cy.fixture(fileName).then((content) => {
61-
const blob = Cypress.Blob.base64StringToBlob(content, fileType);
61+
const blob = Cypress.Blob.base64StringToBlob(btoa(content), fileType);
6262
const testFile = new File([blob], fileName, { type: fileType });
6363
const dataTransfer = new DataTransfer();
6464

x-pack/plugins/security_solution/cypress/tasks/lists.ts

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
import {
88
VALUE_LISTS_MODAL_ACTIVATOR,
9+
VALUE_LIST_CLOSE_BUTTON,
10+
VALUE_LIST_DELETE_BUTTON,
11+
VALUE_LIST_EXPORT_BUTTON,
12+
VALUE_LIST_FILES,
913
VALUE_LIST_FILE_PICKER,
1014
VALUE_LIST_FILE_UPLOAD_BUTTON,
15+
VALUE_LIST_TYPE_SELECTOR,
1116
} from '../screens/lists';
1217

1318
export const waitForListsIndexToBeCreated = () => {
@@ -23,14 +28,96 @@ export const waitForValueListsModalToBeLoaded = () => {
2328
cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('not.be.disabled');
2429
};
2530

26-
export const openValueListsModal = () => {
27-
cy.get(VALUE_LISTS_MODAL_ACTIVATOR).click();
31+
export const openValueListsModal = (): Cypress.Chainable<JQuery<HTMLElement>> => {
32+
return cy.get(VALUE_LISTS_MODAL_ACTIVATOR).click();
2833
};
2934

30-
export const selectValueListsFile = () => {
31-
cy.get(VALUE_LIST_FILE_PICKER).attachFile('value_list.txt').trigger('change', { force: true });
35+
export const closeValueListsModal = (): Cypress.Chainable<JQuery<HTMLElement>> => {
36+
return cy.get(VALUE_LIST_CLOSE_BUTTON).click();
3237
};
3338

34-
export const uploadValueList = () => {
35-
cy.get(VALUE_LIST_FILE_UPLOAD_BUTTON).click();
39+
export const selectValueListsFile = (file: string): Cypress.Chainable<JQuery<HTMLElement>> => {
40+
return cy.get(VALUE_LIST_FILE_PICKER).attachFile(file).trigger('change', { force: true });
41+
};
42+
43+
export const deleteValueListsFile = (file: string): Cypress.Chainable<JQuery<HTMLElement>> => {
44+
return cy.get(VALUE_LIST_DELETE_BUTTON(file)).click();
45+
};
46+
47+
export const selectValueListType = (type: string): Cypress.Chainable<JQuery<HTMLElement>> => {
48+
return cy.get(VALUE_LIST_TYPE_SELECTOR).select(type);
49+
};
50+
51+
export const uploadValueList = (): Cypress.Chainable<JQuery<HTMLElement>> => {
52+
return cy.get(VALUE_LIST_FILE_UPLOAD_BUTTON).click();
53+
};
54+
55+
export const exportValueList = (): Cypress.Chainable<JQuery<HTMLElement>> => {
56+
return cy.get(VALUE_LIST_EXPORT_BUTTON).click();
57+
};
58+
59+
/**
60+
* Given an array of value lists this will delete them all using Cypress Request and the lists REST API
61+
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
62+
*/
63+
export const deleteValueLists = (lists: string[]): Array<Cypress.Chainable<Cypress.Response>> => {
64+
return lists.map((list) => deleteValueList(list));
65+
};
66+
67+
/**
68+
* Given a single value list this will delete it using Cypress Request and lists REST API
69+
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
70+
*/
71+
export const deleteValueList = (list: string): Cypress.Chainable<Cypress.Response> => {
72+
return cy.request({
73+
method: 'DELETE',
74+
url: `api/lists?id=${list}`,
75+
headers: { 'kbn-xsrf': 'delete-lists' },
76+
});
77+
};
78+
79+
/**
80+
* Imports a single value list file this using Cypress Request and lists REST API
81+
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-import-list-items.html
82+
*/
83+
export const importValueList = (
84+
file: string,
85+
type: string
86+
): Cypress.Chainable<Cypress.Response> => {
87+
return cy.fixture(file).then((data) => {
88+
return cy.request({
89+
method: 'POST',
90+
url: `api/lists/items/_import?type=${type}`,
91+
encoding: 'binary',
92+
headers: {
93+
'kbn-xsrf': 'upload-value-lists',
94+
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryJLrRH89J8QVArZyv',
95+
},
96+
body: `------WebKitFormBoundaryJLrRH89J8QVArZyv\nContent-Disposition: form-data; name="file"; filename="${file}"\n\n${data}`,
97+
});
98+
});
99+
};
100+
101+
/**
102+
* If you are on the value lists from the UI, this will loop over all the HTML elements
103+
* that have action-delete-value-list-${list_name} and delete all of those value lists
104+
* using Cypress Request and the lists REST API.
105+
* If the UI does not contain any value based lists this will not fail. If the UI does
106+
* contain value based lists but the backend does not return a success on DELETE then this
107+
* will cause errors.
108+
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
109+
*/
110+
export const deleteAllValueListsFromUI = (): Array<Cypress.Chainable<Cypress.Response>> => {
111+
const lists = Cypress.$(VALUE_LIST_FILES)
112+
.toArray()
113+
.reduce<string[]>((accum, $el) => {
114+
const attribute = $el.getAttribute('data-test-subj');
115+
if (attribute != null) {
116+
const list = attribute.substr('data-test-subj-value-list'.length);
117+
return [...accum, list];
118+
} else {
119+
return accum;
120+
}
121+
}, []);
122+
return deleteValueLists(lists);
36123
};

x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export const ValueListsFormComponent: React.FC<ValueListsFormProps> = ({ onError
145145
<EuiFlexItem>
146146
<EuiFormRow label={i18n.LIST_TYPES_RADIO_LABEL}>
147147
<EuiSelect
148+
data-test-subj="value-lists-form-select-type-action"
148149
options={listFormOptions}
149150
value={type}
150151
onChange={handleRadioChange}

x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('ValueListsModal', () => {
114114

115115
await waitFor(() => {
116116
container
117-
.find('button[data-test-subj="action-delete-value-list"]')
117+
.find('button[data-test-subj="action-delete-value-list-some name"]')
118118
.first()
119119
.simulate('click');
120120
});

x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/table_helpers.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const buildColumns = (
8181
) : (
8282
<EuiButtonIcon
8383
aria-label={i18n.ACTION_DELETE_DESCRIPTION}
84-
data-test-subj="action-delete-value-list"
84+
data-test-subj={`action-delete-value-list-${item.name}`}
8585
iconType="trash"
8686
onClick={() => onDelete(item)}
8787
/>

0 commit comments

Comments
 (0)