Skip to content

Commit

Permalink
[Security Solutions] Adds additional cypress tests and utils to value…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
FrankHassanabad authored Nov 11, 2020
1 parent f4126ea commit 34c80e5
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
192.168.100.14/24
3 changes: 3 additions & 0 deletions x-pack/plugins/security_solution/cypress/fixtures/ip_list.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
127.0.0.1
127.0.0.2
127.0.0.3
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,207 @@ import {
openValueListsModal,
selectValueListsFile,
uploadValueList,
selectValueListType,
deleteAllValueListsFromUI,
closeValueListsModal,
importValueList,
deleteValueListsFile,
exportValueList,
} from '../tasks/lists';
import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW } from '../screens/lists';

describe('value lists', () => {
describe('management modal', () => {
it('creates a keyword list from an uploaded file', () => {
beforeEach(() => {
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
waitForListsIndexToBeCreated();
goToManageAlertsDetectionRules();
waitForValueListsModalToBeLoaded();
});

afterEach(() => {
deleteAllValueListsFromUI();
});

it('can open and close the modal', () => {
openValueListsModal();
selectValueListsFile();
uploadValueList();
closeValueListsModal();
});

describe('create list types', () => {
beforeEach(() => {
openValueListsModal();
});

it('creates a "keyword" list from an uploaded file', () => {
const listName = 'value_list.txt';
selectValueListType('keyword');
selectValueListsFile(listName);
uploadValueList();

cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain(listName);
expect($row.text()).to.contain('Keywords');
});
});

it('creates a "text" list from an uploaded file', () => {
const listName = 'value_list.txt';
selectValueListType('text');
selectValueListsFile(listName);
uploadValueList();

cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain(listName);
expect($row.text()).to.contain('Text');
});
});

it('creates a "ip" list from an uploaded file', () => {
const listName = 'ip_list.txt';
selectValueListType('ip');
selectValueListsFile(listName);
uploadValueList();

cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain(listName);
expect($row.text()).to.contain('IP addresses');
});
});

it('creates a "ip_range" list from an uploaded file', () => {
const listName = 'cidr_list.txt';
selectValueListType('ip_range');
selectValueListsFile(listName);
uploadValueList();

cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain(listName);
expect($row.text()).to.contain('IP ranges');
});
});
});

describe('delete list types', () => {
it('deletes a "keyword" list from an uploaded file', () => {
const listName = 'value_list.txt';
importValueList(listName, 'keyword');
openValueListsModal();
deleteValueListsFile(listName);
cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).not.to.contain(listName);
});
});

it('deletes a "text" list from an uploaded file', () => {
const listName = 'value_list.txt';
importValueList(listName, 'text');
openValueListsModal();
deleteValueListsFile(listName);
cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).not.to.contain(listName);
});
});

it('deletes a "ip" from an uploaded file', () => {
const listName = 'ip_list.txt';
importValueList(listName, 'ip');
openValueListsModal();
deleteValueListsFile(listName);
cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).not.to.contain(listName);
});
});

it('deletes a "ip_range" from an uploaded file', () => {
const listName = 'cidr_list.txt';
importValueList(listName, 'ip_range');
openValueListsModal();
deleteValueListsFile(listName);
cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).not.to.contain(listName);
});
});
});

describe('export list types', () => {
beforeEach(() => {
cy.server();
cy.route('POST', '**/api/lists/items/_export?list_id=*').as('exportList');
});

it('exports a "keyword" list from an uploaded file', () => {
const listName = 'value_list.txt';
importValueList('value_list.txt', 'keyword');
openValueListsModal();
exportValueList();
cy.wait('@exportList').then((xhr) => {
cy.fixture(listName).then((list: string) => {
const [lineOne, lineTwo] = list.split('\n');
expect(xhr.responseBody).to.contain(lineOne);
expect(xhr.responseBody).to.contain(lineTwo);
});
});
});

it('exports a "text" list from an uploaded file', () => {
const listName = 'value_list.txt';
importValueList(listName, 'text');
openValueListsModal();
exportValueList();
cy.wait('@exportList').then((xhr) => {
cy.fixture(listName).then((list: string) => {
const [lineOne, lineTwo] = list.split('\n');
expect(xhr.responseBody).to.contain(lineOne);
expect(xhr.responseBody).to.contain(lineTwo);
});
});
});

it('exports a "ip" list from an uploaded file', () => {
const listName = 'ip_list.txt';
importValueList(listName, 'ip');
openValueListsModal();
exportValueList();
cy.wait('@exportList').then((xhr) => {
cy.fixture(listName).then((list: string) => {
const [lineOne, lineTwo] = list.split('\n');
expect(xhr.responseBody).to.contain(lineOne);
expect(xhr.responseBody).to.contain(lineTwo);
});
});
});

cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain('value_list.txt');
it('exports a "ip_range" list from an uploaded file', () => {
const listName = 'cidr_list.txt';
importValueList(listName, 'ip_range');
openValueListsModal();
exportValueList();
cy.wait('@exportList').then((xhr) => {
cy.fixture(listName).then((list: string) => {
const [lineOne] = list.split('\n');
expect(xhr.responseBody).to.contain(lineOne);
});
});
});
});
});
});
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ export const VALUE_LISTS_TABLE = '[data-test-subj="value-lists-table"]';
export const VALUE_LISTS_ROW = '.euiTableRow';
export const VALUE_LIST_FILE_PICKER = '[data-test-subj="value-list-file-picker"]';
export const VALUE_LIST_FILE_UPLOAD_BUTTON = '[data-test-subj="value-lists-form-import-action"]';
export const VALUE_LIST_TYPE_SELECTOR = '[data-test-subj="value-lists-form-select-type-action"]';
export const VALUE_LIST_DELETE_BUTTON = (name: string) =>
`[data-test-subj="action-delete-value-list-${name}"]`;
export const VALUE_LIST_FILES = '[data-test-subj*="action-delete-value-list-"]';
export const VALUE_LIST_CLOSE_BUTTON = '[data-test-subj="value-lists-modal-close-action"]';
export const VALUE_LIST_EXPORT_BUTTON = '[data-test-subj="action-export-value-list"]';
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Cypress.Commands.add(
},
(input, fileName, fileType = 'text/plain') => {
cy.fixture(fileName).then((content) => {
const blob = Cypress.Blob.base64StringToBlob(content, fileType);
const blob = Cypress.Blob.base64StringToBlob(btoa(content), fileType);
const testFile = new File([blob], fileName, { type: fileType });
const dataTransfer = new DataTransfer();

Expand Down
99 changes: 93 additions & 6 deletions x-pack/plugins/security_solution/cypress/tasks/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

import {
VALUE_LISTS_MODAL_ACTIVATOR,
VALUE_LIST_CLOSE_BUTTON,
VALUE_LIST_DELETE_BUTTON,
VALUE_LIST_EXPORT_BUTTON,
VALUE_LIST_FILES,
VALUE_LIST_FILE_PICKER,
VALUE_LIST_FILE_UPLOAD_BUTTON,
VALUE_LIST_TYPE_SELECTOR,
} from '../screens/lists';

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

export const openValueListsModal = () => {
cy.get(VALUE_LISTS_MODAL_ACTIVATOR).click();
export const openValueListsModal = (): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LISTS_MODAL_ACTIVATOR).click();
};

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

export const uploadValueList = () => {
cy.get(VALUE_LIST_FILE_UPLOAD_BUTTON).click();
export const selectValueListsFile = (file: string): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LIST_FILE_PICKER).attachFile(file).trigger('change', { force: true });
};

export const deleteValueListsFile = (file: string): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LIST_DELETE_BUTTON(file)).click();
};

export const selectValueListType = (type: string): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LIST_TYPE_SELECTOR).select(type);
};

export const uploadValueList = (): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LIST_FILE_UPLOAD_BUTTON).click();
};

export const exportValueList = (): Cypress.Chainable<JQuery<HTMLElement>> => {
return cy.get(VALUE_LIST_EXPORT_BUTTON).click();
};

/**
* Given an array of value lists this will delete them all using Cypress Request and the lists REST API
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
*/
export const deleteValueLists = (lists: string[]): Array<Cypress.Chainable<Cypress.Response>> => {
return lists.map((list) => deleteValueList(list));
};

/**
* Given a single value list this will delete it using Cypress Request and lists REST API
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
*/
export const deleteValueList = (list: string): Cypress.Chainable<Cypress.Response> => {
return cy.request({
method: 'DELETE',
url: `api/lists?id=${list}`,
headers: { 'kbn-xsrf': 'delete-lists' },
});
};

/**
* Imports a single value list file this using Cypress Request and lists REST API
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-import-list-items.html
*/
export const importValueList = (
file: string,
type: string
): Cypress.Chainable<Cypress.Response> => {
return cy.fixture(file).then((data) => {
return cy.request({
method: 'POST',
url: `api/lists/items/_import?type=${type}`,
encoding: 'binary',
headers: {
'kbn-xsrf': 'upload-value-lists',
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryJLrRH89J8QVArZyv',
},
body: `------WebKitFormBoundaryJLrRH89J8QVArZyv\nContent-Disposition: form-data; name="file"; filename="${file}"\n\n${data}`,
});
});
};

/**
* If you are on the value lists from the UI, this will loop over all the HTML elements
* that have action-delete-value-list-${list_name} and delete all of those value lists
* using Cypress Request and the lists REST API.
* If the UI does not contain any value based lists this will not fail. If the UI does
* contain value based lists but the backend does not return a success on DELETE then this
* will cause errors.
* Ref: https://www.elastic.co/guide/en/security/current/lists-api-delete-container.html
*/
export const deleteAllValueListsFromUI = (): Array<Cypress.Chainable<Cypress.Response>> => {
const lists = Cypress.$(VALUE_LIST_FILES)
.toArray()
.reduce<string[]>((accum, $el) => {
const attribute = $el.getAttribute('data-test-subj');
if (attribute != null) {
const list = attribute.substr('data-test-subj-value-list'.length);
return [...accum, list];
} else {
return accum;
}
}, []);
return deleteValueLists(lists);
};
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export const ValueListsFormComponent: React.FC<ValueListsFormProps> = ({ onError
<EuiFlexItem>
<EuiFormRow label={i18n.LIST_TYPES_RADIO_LABEL}>
<EuiSelect
data-test-subj="value-lists-form-select-type-action"
options={listFormOptions}
value={type}
onChange={handleRadioChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe('ValueListsModal', () => {

await waitFor(() => {
container
.find('button[data-test-subj="action-delete-value-list"]')
.find('button[data-test-subj="action-delete-value-list-some name"]')
.first()
.simulate('click');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const buildColumns = (
) : (
<EuiButtonIcon
aria-label={i18n.ACTION_DELETE_DESCRIPTION}
data-test-subj="action-delete-value-list"
data-test-subj={`action-delete-value-list-${item.name}`}
iconType="trash"
onClick={() => onDelete(item)}
/>
Expand Down

0 comments on commit 34c80e5

Please sign in to comment.