[Defend workflows] Add Cypress tests for Endpoint reassignment functi… (#151887)

This commit is contained in:
Patryk Kopyciński 2023-02-24 16:19:30 +01:00 committed by GitHub
parent b907c51e0e
commit e7e66a8944
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 330 additions and 124 deletions

View file

@ -51,6 +51,8 @@ export function defineCypressConfig(options?: Cypress.ConfigOptions<any>) {
on(event, task);
}, config);
return config;
}
},
},

View file

@ -5,15 +5,91 @@
* 2.0.
*/
import type { Agent } from '@kbn/fleet-plugin/common';
import { ENDPOINT_VM_NAME } from '../../tasks/common';
import {
getAgentByHostName,
getEndpointIntegrationVersion,
reassignAgentPolicy,
} from '../../tasks/fleet';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import { getEndpointListPath } from '../../../common/routing';
import { login } from '../../tasks/login';
import {
AGENT_HOSTNAME_CELL,
TABLE_ROW_ACTIONS,
TABLE_ROW_ACTIONS_MENU,
AGENT_POLICY_CELL,
} from '../../screens/endpoints';
import {
FLEET_REASSIGN_POLICY_MODAL,
FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON,
} from '../../screens/fleet';
describe('Endpoints page', () => {
const endpointHostname = Cypress.env(ENDPOINT_VM_NAME);
beforeEach(() => {
login();
});
it('Loads the endpoints page', () => {
cy.visit('/app/security/administration/endpoints');
it('Shows endpoint on the list', () => {
cy.visit(getEndpointListPath({ name: 'endpointList' }));
cy.contains('Hosts running Elastic Defend').should('exist');
cy.getByTestSubj(AGENT_HOSTNAME_CELL).should('have.text', endpointHostname);
});
describe('Endpoint reassignment', () => {
let response: IndexedFleetEndpointPolicyResponse;
let initialAgentData: Agent;
before(() => {
getAgentByHostName(endpointHostname).then((agentData) => {
initialAgentData = agentData;
});
getEndpointIntegrationVersion().then((version) => {
cy.task<IndexedFleetEndpointPolicyResponse>('indexFleetEndpointPolicy', {
policyName: `Reassign ${Math.random().toString(36).substr(2, 5)}`,
endpointPackageVersion: version,
}).then((data) => {
response = data;
});
});
});
beforeEach(() => {
login();
});
after(() => {
if (initialAgentData?.policy_id) {
reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id);
}
if (response) {
cy.task('deleteIndexedFleetEndpointPolicies', response);
}
});
it('User can reassign a single endpoint to a different Agent Configuration', () => {
cy.visit(getEndpointListPath({ name: 'endpointList' }));
const hostname = cy
.getByTestSubj(AGENT_HOSTNAME_CELL)
.filter(`:contains("${endpointHostname}")`);
const tableRow = hostname.parents('tr');
tableRow.getByTestSubj(TABLE_ROW_ACTIONS).click();
cy.getByTestSubj(TABLE_ROW_ACTIONS_MENU).contains('Reassign agent policy').click();
cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL)
.find('select')
.select(response.agentPolicies[0].name);
cy.getByTestSubj(FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON).click();
cy.getByTestSubj(AGENT_HOSTNAME_CELL)
.filter(`:contains("${endpointHostname}")`)
.should('exist');
cy.getByTestSubj(AGENT_HOSTNAME_CELL)
.filter(`:contains("${endpointHostname}")`)
.parents('tr')
.getByTestSubj(AGENT_POLICY_CELL)
.should('have.text', response.agentPolicies[0].name);
});
});
});

View file

@ -64,8 +64,8 @@ const visitArtifactTab = (tabId: string) => {
const visitPolicyDetailsPage = () => {
cy.visit('/app/security/administration/policy');
cy.getBySel('policyNameCellLink').eq(0).click({ force: true });
cy.getBySel('policyDetailsPage').should('exist');
cy.getByTestSubj('policyNameCellLink').eq(0).click({ force: true });
cy.getByTestSubj('policyDetailsPage').should('exist');
cy.get('#settings').should('exist'); // waiting for Policy Settings tab
};
@ -99,18 +99,18 @@ describe('Artifact tabs in Policy Details page', () => {
loginWithPrivilegeRead(testData.privilegePrefix);
visitArtifactTab(testData.tabId);
cy.getBySel('policy-artifacts-empty-unexisting').should('exist');
cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist');
cy.getBySel('unexisting-manage-artifacts-button').should('not.exist');
cy.getByTestSubj('unexisting-manage-artifacts-button').should('not.exist');
});
it(`[ALL] User can add ${testData.title} artifact`, () => {
loginWithPrivilegeAll();
visitArtifactTab(testData.tabId);
cy.getBySel('policy-artifacts-empty-unexisting').should('exist');
cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist');
cy.getBySel('unexisting-manage-artifacts-button').should('exist').click();
cy.getByTestSubj('unexisting-manage-artifacts-button').should('exist').click();
const { formActions, checkResults } = testData.create;
@ -118,18 +118,18 @@ describe('Artifact tabs in Policy Details page', () => {
// Add a per policy artifact - but not assign it to any policy
cy.get('[data-test-subj$="-perPolicy"]').click(); // test-subjects are generated in different formats, but all ends with -perPolicy
cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click();
cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click();
// Check new artifact is in the list
for (const checkResult of checkResults) {
cy.getBySel(checkResult.selector).should('have.text', checkResult.value);
cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value);
}
cy.getBySel('policyDetailsPage').should('not.exist');
cy.getBySel('backToOrigin').contains(/^Back to .+ policy$/);
cy.getByTestSubj('policyDetailsPage').should('not.exist');
cy.getByTestSubj('backToOrigin').contains(/^Back to .+ policy$/);
cy.getBySel('backToOrigin').click();
cy.getBySel('policyDetailsPage').should('exist');
cy.getByTestSubj('backToOrigin').click();
cy.getByTestSubj('policyDetailsPage').should('exist');
});
});
@ -144,34 +144,34 @@ describe('Artifact tabs in Policy Details page', () => {
loginWithPrivilegeRead(testData.privilegePrefix);
visitArtifactTab(testData.tabId);
cy.getBySel('policy-artifacts-empty-unassigned').should('exist');
cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist');
cy.getBySel('unassigned-manage-artifacts-button').should('not.exist');
cy.getBySel('unassigned-assign-artifacts-button').should('not.exist');
cy.getByTestSubj('unassigned-manage-artifacts-button').should('not.exist');
cy.getByTestSubj('unassigned-assign-artifacts-button').should('not.exist');
});
it(`[ALL] User can Manage and Assign ${testData.title} artifacts`, () => {
loginWithPrivilegeAll();
visitArtifactTab(testData.tabId);
cy.getBySel('policy-artifacts-empty-unassigned').should('exist');
cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist');
// Manage artifacts
cy.getBySel('unassigned-manage-artifacts-button').should('exist').click();
cy.getByTestSubj('unassigned-manage-artifacts-button').should('exist').click();
cy.location('pathname').should(
'equal',
`/app/security/administration/${testData.urlPath}`
);
cy.getBySel('backToOrigin').click();
cy.getByTestSubj('backToOrigin').click();
// Assign artifacts
cy.getBySel('unassigned-assign-artifacts-button').should('exist').click();
cy.getByTestSubj('unassigned-assign-artifacts-button').should('exist').click();
cy.getBySel('artifacts-assign-flyout').should('exist');
cy.getBySel('artifacts-assign-confirm-button').should('be.disabled');
cy.getByTestSubj('artifacts-assign-flyout').should('exist');
cy.getByTestSubj('artifacts-assign-confirm-button').should('be.disabled');
cy.getBySel(`${testData.artifactName}_checkbox`).click();
cy.getBySel('artifacts-assign-confirm-button').click();
cy.getByTestSubj(`${testData.artifactName}_checkbox`).click();
cy.getByTestSubj('artifacts-assign-confirm-button').click();
});
});
@ -189,17 +189,17 @@ describe('Artifact tabs in Policy Details page', () => {
visitArtifactTab(testData.tabId);
// List of artifacts
cy.getBySel('artifacts-collapsed-list-card').should('have.length', 1);
cy.getBySel('artifacts-collapsed-list-card-header-titleHolder').contains(
cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1);
cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains(
testData.artifactName
);
// Cannot assign artifacts
cy.getBySel('artifacts-assign-button').should('not.exist');
cy.getByTestSubj('artifacts-assign-button').should('not.exist');
// Cannot remove from policy
cy.getBySel('artifacts-collapsed-list-card-header-actions-button').click();
cy.getBySel('remove-from-policy-action').should('not.exist');
cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click();
cy.getByTestSubj('remove-from-policy-action').should('not.exist');
});
it(`[ALL] User can see ${testData.title} artifacts and can assign or remove artifacts from policy`, () => {
@ -207,20 +207,20 @@ describe('Artifact tabs in Policy Details page', () => {
visitArtifactTab(testData.tabId);
// List of artifacts
cy.getBySel('artifacts-collapsed-list-card').should('have.length', 1);
cy.getBySel('artifacts-collapsed-list-card-header-titleHolder').contains(
cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1);
cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains(
testData.artifactName
);
// Assign artifacts
cy.getBySel('artifacts-assign-button').should('exist').click();
cy.getBySel('artifacts-assign-flyout').should('exist');
cy.getBySel('artifacts-assign-cancel-button').click();
cy.getByTestSubj('artifacts-assign-button').should('exist').click();
cy.getByTestSubj('artifacts-assign-flyout').should('exist');
cy.getByTestSubj('artifacts-assign-cancel-button').click();
// Remove from policy
cy.getBySel('artifacts-collapsed-list-card-header-actions-button').click();
cy.getBySel('remove-from-policy-action').click();
cy.getBySel('confirmModalConfirmButton').click();
cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click();
cy.getByTestSubj('remove-from-policy-action').click();
cy.getByTestSubj('confirmModalConfirmButton').click();
cy.contains('Successfully removed');
});

View file

@ -51,10 +51,10 @@ describe('Artifacts pages', () => {
describe(`When on the ${testData.title} entries list`, () => {
it(`no access - should show no privileges callout`, () => {
loginWithoutAccess(`/app/security/administration/${testData.urlPath}`);
cy.getBySel('noPrivilegesPage').should('exist');
cy.getBySel('empty-page-feature-action').should('exist');
cy.getBySel(testData.emptyState).should('not.exist');
cy.getBySel(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist');
cy.getByTestSubj('noPrivilegesPage').should('exist');
cy.getByTestSubj('empty-page-feature-action').should('exist');
cy.getByTestSubj(testData.emptyState).should('not.exist');
cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist');
});
it(`read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, () => {
@ -62,33 +62,33 @@ describe('Artifacts pages', () => {
testData.privilegePrefix,
`/app/security/administration/${testData.urlPath}`
);
cy.getBySel(testData.emptyState).should('exist');
cy.getBySel(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist');
cy.getByTestSubj(testData.emptyState).should('exist');
cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist');
});
it(`write - should show empty state page if there is no ${testData.title} entry and the add button exists`, () => {
loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`);
cy.getBySel(testData.emptyState).should('exist');
cy.getBySel(`${testData.pagePrefix}-emptyState-addButton`).should('exist');
cy.getByTestSubj(testData.emptyState).should('exist');
cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('exist');
});
it(`write - should create new ${testData.title} entry`, () => {
loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`);
// Opens add flyout
cy.getBySel(`${testData.pagePrefix}-emptyState-addButton`).click();
cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click();
performUserActions(testData.create.formActions);
// Submit create artifact form
cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click();
cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click();
// Check new artifact is in the list
for (const checkResult of testData.create.checkResults) {
cy.getBySel(checkResult.selector).should('have.text', checkResult.value);
cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value);
}
// Title is shown after adding an item
cy.getBySel('header-page-title').contains(testData.title);
cy.getByTestSubj('header-page-title').contains(testData.title);
});
it(`read - should not be able to update/delete an existing ${testData.title} entry`, () => {
@ -96,10 +96,10 @@ describe('Artifacts pages', () => {
testData.privilegePrefix,
`/app/security/administration/${testData.urlPath}`
);
cy.getBySel('header-page-title').contains(testData.title);
cy.getBySel(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist');
cy.getBySel(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist');
cy.getBySel(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist');
cy.getByTestSubj('header-page-title').contains(testData.title);
cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist');
cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist');
cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist');
});
it(`read - should not be able to create a new ${testData.title} entry`, () => {
@ -107,39 +107,39 @@ describe('Artifacts pages', () => {
testData.privilegePrefix,
`/app/security/administration/${testData.urlPath}`
);
cy.getBySel('header-page-title').contains(testData.title);
cy.getBySel(`${testData.pagePrefix}-pageAddButton`).should('not.exist');
cy.getByTestSubj('header-page-title').contains(testData.title);
cy.getByTestSubj(`${testData.pagePrefix}-pageAddButton`).should('not.exist');
});
it(`write - should be able to update an existing ${testData.title} entry`, () => {
loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`);
// Opens edit flyout
cy.getBySel(`${testData.pagePrefix}-card-header-actions-button`).click();
cy.getBySel(`${testData.pagePrefix}-card-cardEditAction`).click();
cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click();
cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).click();
performUserActions(testData.update.formActions);
// Submit edit artifact form
cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click();
cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click();
for (const checkResult of testData.create.checkResults) {
cy.getBySel(checkResult.selector).should('have.text', checkResult.value);
cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value);
}
// Title still shown after editing an item
cy.getBySel('header-page-title').contains(testData.title);
cy.getByTestSubj('header-page-title').contains(testData.title);
});
it(`write - should be able to delete the existing ${testData.title} entry`, () => {
loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`);
// Remove it
cy.getBySel(`${testData.pagePrefix}-card-header-actions-button`).click();
cy.getBySel(`${testData.pagePrefix}-card-cardDeleteAction`).click();
cy.getBySel(`${testData.pagePrefix}-deleteModal-submitButton`).click();
cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).click();
cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).click();
cy.getByTestSubj(`${testData.pagePrefix}-deleteModal-submitButton`).click();
// No card visible after removing it
cy.getBySel(testData.delete.card).should('not.exist');
cy.getByTestSubj(testData.delete.card).should('not.exist');
// Empty state is displayed after removing last item
cy.getBySel(testData.emptyState).should('exist');
cy.getByTestSubj(testData.emptyState).should('exist');
});
});
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const AGENT_HOSTNAME_CELL = 'hostnameCellLink';
export const AGENT_POLICY_CELL = 'policyNameCellLink';
export const TABLE_ROW_ACTIONS = 'endpointTableRowActions';
export const TABLE_ROW_ACTIONS_MENU = 'tableRowActionsMenuPanel';

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const FLEET_REASSIGN_POLICY_MODAL = 'agentReassignPolicyModal';
export const FLEET_REASSIGN_POLICY_MODAL_CONFIRM_BUTTON = 'confirmModalConfirmButton';

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// / <reference types="cypress" />
import { createKbnClient } from '../../../../scripts/endpoint/common/stack_services';
import type { IndexedFleetEndpointPolicyResponse } from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
import {
indexFleetEndpointPolicy,
deleteIndexedFleetEndpointPolicies,
} from '../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
export const dataLoaders = (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => {
const kbnClient = createKbnClient({
url: config.env.KIBANA_URL,
username: config.env.ELASTICSEARCH_USERNAME,
password: config.env.ELASTICSEARCH_PASSWORD,
});
on('task', {
indexFleetEndpointPolicy: async ({
policyName,
endpointPackageVersion,
}: {
policyName: string;
endpointPackageVersion: string;
}) => {
return indexFleetEndpointPolicy(kbnClient, policyName, endpointPackageVersion);
},
deleteIndexedFleetEndpointPolicies: async (indexData: IndexedFleetEndpointPolicyResponse) => {
return deleteIndexedFleetEndpointPolicies(kbnClient, indexData);
},
});
};

View file

@ -31,13 +31,17 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
getBySel(...args: Parameters<Cypress.Chainable['get']>): Chainable<JQuery<HTMLElement>>;
getByTestSubj(...args: Parameters<Cypress.Chainable['get']>): Chainable<JQuery<HTMLElement>>;
}
}
}
Cypress.Commands.add('getBySel', (selector, ...args) =>
cy.get(`[data-test-subj="${selector}"]`, ...args)
);
Cypress.Commands.addQuery('getByTestSubj', function getByTestSubj(selector, options) {
const getFn = cy.now('get', `[data-test-subj="${selector}"]`, options) as (
subject: Cypress.Chainable<JQuery<HTMLElement>>
) => Cypress.Chainable<JQuery<HTMLElement>>;
return (subject) => getFn(subject);
});
Cypress.on('uncaught:exception', () => false);

View file

@ -6,6 +6,10 @@
*/
import { PACKAGE_POLICY_API_ROOT } from '@kbn/fleet-plugin/common';
import type {
ExceptionListItemSchema,
ExceptionListSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import {
ENDPOINT_ARTIFACT_LISTS,
@ -13,8 +17,7 @@ import {
EXCEPTION_LIST_ITEM_URL,
EXCEPTION_LIST_URL,
} from '@kbn/securitysolution-list-constants';
const API_HEADER = { 'kbn-xsrf': 'kibana' };
import { request } from './common';
export const removeAllArtifacts = () => {
for (const listId of ENDPOINT_ARTIFACT_LIST_IDS) {
@ -23,10 +26,9 @@ export const removeAllArtifacts = () => {
};
export const removeExceptionsList = (listId: string) => {
cy.request({
request({
method: 'DELETE',
url: `${EXCEPTION_LIST_URL}?list_id=${listId}&namespace_type=agnostic`,
headers: API_HEADER,
failOnStatusCode: false,
}).then(({ status }) => {
expect(status).to.be.oneOf([200, 404]); // should either be success or not found
@ -42,10 +44,9 @@ const ENDPOINT_ARTIFACT_LIST_TYPES = {
};
export const createArtifactList = (listId: string) => {
cy.request({
request<ExceptionListSchema>({
method: 'POST',
url: EXCEPTION_LIST_URL,
headers: API_HEADER,
body: {
name: listId,
description: 'This is a test list',
@ -61,11 +62,9 @@ export const createArtifactList = (listId: string) => {
};
export const createPerPolicyArtifact = (name: string, body: object, policyId?: 'all' | string) => {
cy.request({
request<ExceptionListItemSchema>({
method: 'POST',
url: EXCEPTION_LIST_ITEM_URL,
headers: API_HEADER,
body: {
name,
description: '',

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const ENDPOINT_VM_NAME = 'ENDPOINT_VM_NAME';
export const API_AUTH = {
user: Cypress.env('ELASTICSEARCH_USERNAME'),
pass: Cypress.env('ELASTICSEARCH_PASSWORD'),
};
export const API_HEADERS = { 'kbn-xsrf': 'cypress' };
export const request = <T = unknown>(
options: Partial<Cypress.RequestOptions>
): Cypress.Chainable<Cypress.Response<T>> =>
cy.request<T>({
auth: API_AUTH,
headers: API_HEADERS,
...options,
});

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Agent, GetAgentsResponse, GetInfoResponse } from '@kbn/fleet-plugin/common';
import { agentRouteService, epmRouteService } from '@kbn/fleet-plugin/common';
import type { PutAgentReassignResponse } from '@kbn/fleet-plugin/common/types';
import { request } from './common';
export const getEndpointIntegrationVersion = (): Cypress.Chainable<string> =>
request<GetInfoResponse>({
url: epmRouteService.getInfoPath('endpoint'),
method: 'GET',
}).then((response) => response.body.item.version);
export const getAgentByHostName = (hostname: string): Cypress.Chainable<Agent> =>
request<GetAgentsResponse>({
url: agentRouteService.getListPath(),
method: 'GET',
qs: {
kuery: `local_metadata.host.hostname: "${hostname}"`,
},
}).then((response) => response.body.items[0]);
export const reassignAgentPolicy = (
agentId: string,
agentPolicyId: string
): Cypress.Chainable<Cypress.Response<PutAgentReassignResponse>> =>
request<PutAgentReassignResponse>({
url: agentRouteService.getReassignPath(agentId),
method: 'PUT',
body: {
policy_id: agentPolicyId,
},
});

View file

@ -8,17 +8,17 @@
import { isEmpty } from 'lodash';
import { BASE_ENDPOINT_ROUTE } from '../../../../common/endpoint/constants';
import { runEndpointLoaderScript } from './run_endpoint_loader';
import { request } from './common';
// Checks for Endpoint data and creates it if needed
export const loadEndpointDataForEventFiltersIfNeeded = () => {
cy.request({
request({
method: 'POST',
url: `${BASE_ENDPOINT_ROUTE}/suggestions/eventFilters`,
body: {
field: 'agent.type',
query: '',
},
headers: { 'kbn-xsrf': 'kibana' },
failOnStatusCode: false,
}).then(({ body }) => {
if (isEmpty(body)) {

View file

@ -11,6 +11,7 @@ import * as yaml from 'js-yaml';
import type { UrlObject } from 'url';
import Url from 'url';
import type { Role } from '@kbn/security-plugin/common';
import { request } from './common';
import { getT1Analyst } from '../../../../scripts/endpoint/common/roles_users/t1_analyst';
import { getT2Analyst } from '../../../../scripts/endpoint/common/roles_users/t2_analyst';
import { getHunter } from '../../../../scripts/endpoint/common/roles_users/hunter';
@ -79,13 +80,6 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD';
*/
const LOGIN_API_ENDPOINT = '/internal/security/login';
const API_AUTH = {
user: Cypress.env(ELASTICSEARCH_USERNAME),
pass: Cypress.env(ELASTICSEARCH_PASSWORD),
};
const API_HEADERS = { 'kbn-xsrf': 'cypress' };
/**
* cy.visit will default to the baseUrl which uses the default kibana test user
* This function will override that functionality in cy.visit by building the baseUrl
@ -151,52 +145,40 @@ export const createRoleAndUser = (role: ROLE) => {
};
export const createCustomRoleAndUser = (role: string, rolePrivileges: Omit<Role, 'name'>) => {
const env = getCurlScriptEnvVars();
// post the role
cy.request({
request({
method: 'PUT',
url: `${env.KIBANA_URL}/api/security/role/${role}`,
url: `/api/security/role/${role}`,
body: rolePrivileges,
headers: API_HEADERS,
auth: API_AUTH,
});
// post the user associated with the role to elasticsearch
cy.request({
request({
method: 'POST',
url: `${env.KIBANA_URL}/internal/security/users/${role}`,
headers: API_HEADERS,
url: `/internal/security/users/${role}`,
body: {
username: role,
password: Cypress.env(ELASTICSEARCH_PASSWORD),
roles: [role],
},
auth: API_AUTH,
});
};
export const deleteRoleAndUser = (role: ROLE) => {
const env = getCurlScriptEnvVars();
cy.request({
request({
method: 'DELETE',
auth: API_AUTH,
headers: API_HEADERS,
url: `${env.KIBANA_URL}/internal/security/users/${role}`,
url: `/internal/security/users/${role}`,
});
cy.request({
request({
method: 'DELETE',
auth: API_AUTH,
headers: API_HEADERS,
url: `${env.KIBANA_URL}/api/security/role/${role}`,
url: `/api/security/role/${role}`,
});
};
export const loginWithUser = (user: User) => {
const url = Cypress.config().baseUrl;
cy.request({
request({
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
@ -227,7 +209,7 @@ export const loginWithCustomRole = async (role: string, rolePrivileges: Omit<Rol
port: Cypress.env('configport'),
} as UrlObject);
cy.log(`origin: ${theUrl}`);
cy.request({
request({
body: {
providerType: 'basic',
providerName: 'basic',
@ -282,7 +264,7 @@ const loginViaEnvironmentCredentials = () => {
);
// programmatically authenticate without interacting with the Kibana login page
cy.request({
request({
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
@ -313,7 +295,7 @@ const loginViaConfig = () => {
const config = yaml.safeLoad(kibanaDevYml);
// programmatically authenticate without interacting with the Kibana login page
cy.request({
request({
body: {
providerType: 'basic',
providerName: 'basic',

View file

@ -25,7 +25,7 @@ const performAction = (action: FormAction) => {
if (action.customSelector) {
element = cy.get(action.customSelector);
} else {
element = cy.getBySel(action.selector || '');
element = cy.getByTestSubj(action.selector || '');
}
if (action.type === 'click') {

View file

@ -1,7 +1,8 @@
{
"extends": "../../../../../../tsconfig.base.json",
"include": [
"**/*"
"**/*",
"../cypress_endpoint.config.ts"
],
"exclude": [
"target/**/*"
@ -26,5 +27,6 @@
"@kbn/securitysolution-list-constants",
"@kbn/fleet-plugin",
"@kbn/securitysolution-io-ts-list-types",
"@kbn/cypress-config",
]
}

View file

@ -6,6 +6,8 @@
*/
import { defineCypressConfig } from '@kbn/cypress-config';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { dataLoaders } from './cypress/support/data_loaders';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
@ -37,5 +39,8 @@ export default defineCypressConfig({
supportFile: 'public/management/cypress/support/e2e.ts',
specPattern: 'public/management/cypress/e2e/endpoint/*.cy.{js,jsx,ts,tsx}',
experimentalRunAllSpecs: true,
setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
dataLoaders(on, config);
},
},
});

View file

@ -12,7 +12,7 @@ import { useUserPrivileges } from '../../../../../common/components/user_privile
import { useWithShowEndpointResponder } from '../../../../hooks';
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
import { APP_UI_ID } from '../../../../../../common/constants';
import { getEndpointDetailsPath } from '../../../../common/routing';
import { getEndpointDetailsPath, getEndpointListPath } from '../../../../common/routing';
import type { HostMetadata, MaybeImmutable } from '../../../../../../common/endpoint/types';
import { useEndpointSelector } from './hooks';
import { agentPolicies, uiQueryParams } from '../../store/selectors';
@ -236,6 +236,12 @@ export const useEndpointActionItems = (
agentId: fleetAgentId,
})[1]
}?openReassignFlyout=true`,
state: {
onDoneNavigateTo: [
APP_UI_ID,
{ path: getEndpointListPath({ name: 'endpointList' }) },
],
},
},
href: `${getAppUrl({ appId: 'fleet' })}${
pagePathGetters.agent_details({

View file

@ -17,6 +17,7 @@
"exclude": [
"target/**/*",
"**/cypress/**",
"public/management/cypress_endpoint.config.ts",
],
"kbn_references": [
"@kbn/core",

View file

@ -21,6 +21,8 @@ export class AgentManager extends Manager {
public async setup() {
this.vmName = await enrollEndpointHost();
return this.vmName;
}
public cleanup() {

View file

@ -14,9 +14,11 @@ import { AgentManager } from './agent';
import { FleetManager } from './fleet_server';
import { getLatestAvailableAgentVersion } from './utils';
type RunnerEnv = Record<string, string | undefined>;
async function withFleetAgent(
{ getService }: FtrProviderContext,
runner: (runnerEnv: Record<string, string>) => Promise<void>
runner: (runnerEnv: RunnerEnv) => Promise<void>
) {
const log = getService('log');
const config = getService('config');
@ -40,9 +42,9 @@ async function withFleetAgent(
const agentManager = new AgentManager(log);
await fleetManager.setup();
await agentManager.setup();
const agentVmName = await agentManager.setup();
try {
await runner({});
await runner({ agentVmName });
} finally {
agentManager.cleanup();
fleetManager.cleanup();
@ -58,10 +60,16 @@ export async function DefendWorkflowsCypressVisualTestRunner(context: FtrProvide
}
export async function DefendWorkflowsCypressEndpointTestRunner(context: FtrProviderContext) {
await withFleetAgent(context, () => startDefendWorkflowsCypress(context, 'dw:endpoint:open'));
await withFleetAgent(context, (runnerEnv) =>
startDefendWorkflowsCypress(context, 'dw:endpoint:open', runnerEnv)
);
}
function startDefendWorkflowsCypress(context: FtrProviderContext, cypressCommand: string) {
function startDefendWorkflowsCypress(
context: FtrProviderContext,
cypressCommand: 'dw:endpoint:open' | 'dw:open' | 'dw:run',
runnerEnv?: RunnerEnv
) {
const log = context.getService('log');
const config = context.getService('config');
return withProcRunner(log, async (procs) => {
@ -91,6 +99,7 @@ function startDefendWorkflowsCypress(context: FtrProviderContext, cypressCommand
hostname: config.get('servers.kibana.hostname'),
port: config.get('servers.kibana.port'),
}),
CYPRESS_ENDPOINT_VM_NAME: runnerEnv?.agentVmName,
...process.env,
},
wait: true,

View file

@ -7,7 +7,7 @@
import axios from 'axios';
import semver from 'semver';
import { filter } from 'lodash';
import { map } from 'lodash';
import { KbnClient } from '@kbn/test';
/**
@ -20,9 +20,7 @@ export const getLatestAvailableAgentVersion = async (kbnClient: KbnClient): Prom
const kbnStatus = await kbnClient.status.get();
const agentVersions = await axios
.get('https://artifacts-api.elastic.co/v1/versions')
.then((response) =>
filter(response.data.versions, (versionString) => !versionString.includes('SNAPSHOT'))
);
.then((response) => map(response.data.versions, (version) => version.split('-SNAPSHOT')[0]));
let version =
semver.maxSatisfying(agentVersions, `<=${kbnStatus.version.number}`) ??