mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* wip * convert flaky jest test to functional test * improvement from review * fix * fix i18n Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
This commit is contained in:
parent
4e592db5db
commit
cb0625ed88
9 changed files with 395 additions and 352 deletions
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 { ChangePasswordFormValues } from './change_password_flyout';
|
||||
import { validateChangePasswordForm } from './change_password_flyout';
|
||||
|
||||
describe('ChangePasswordFlyout', () => {
|
||||
describe('#validateChangePasswordForm', () => {
|
||||
describe('for current user', () => {
|
||||
it('should show an error when it is current user with no current password', () => {
|
||||
expect(
|
||||
validateChangePasswordForm({ password: 'changeme', confirm_password: 'changeme' }, true)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"current_password": "Enter your current password.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show errors when there is no new password', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
password: undefined,
|
||||
confirm_password: 'changeme',
|
||||
} as unknown as ChangePasswordFormValues,
|
||||
true
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"current_password": "Enter your current password.",
|
||||
"password": "Enter a new password.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show errors when the new password is not at least 6 characters', () => {
|
||||
expect(validateChangePasswordForm({ password: '12345', confirm_password: '12345' }, true))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"current_password": "Enter your current password.",
|
||||
"password": "Password must be at least 6 characters.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show errors when new password does not match confirmation password', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
password: 'changeme',
|
||||
confirm_password: 'notTheSame',
|
||||
},
|
||||
true
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"confirm_password": "Passwords do not match.",
|
||||
"current_password": "Enter your current password.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show NO errors', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
current_password: 'oldpassword',
|
||||
password: 'changeme',
|
||||
confirm_password: 'changeme',
|
||||
},
|
||||
true
|
||||
)
|
||||
).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for another user', () => {
|
||||
it('should show errors when there is no new password', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
password: undefined,
|
||||
confirm_password: 'changeme',
|
||||
} as unknown as ChangePasswordFormValues,
|
||||
false
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"password": "Enter a new password.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show errors when the new password is not at least 6 characters', () => {
|
||||
expect(validateChangePasswordForm({ password: '1234', confirm_password: '1234' }, false))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"password": "Password must be at least 6 characters.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show errors when new password does not match confirmation password', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
password: 'changeme',
|
||||
confirm_password: 'notTheSame',
|
||||
},
|
||||
false
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"confirm_password": "Passwords do not match.",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should show NO errors', () => {
|
||||
expect(
|
||||
validateChangePasswordForm(
|
||||
{
|
||||
password: 'changeme',
|
||||
confirm_password: 'changeme',
|
||||
},
|
||||
false
|
||||
)
|
||||
).toMatchInlineSnapshot(`Object {}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -44,6 +44,49 @@ export interface ChangePasswordFlyoutProps {
|
|||
onSuccess?(): void;
|
||||
}
|
||||
|
||||
export const validateChangePasswordForm = (
|
||||
values: ChangePasswordFormValues,
|
||||
isCurrentUser: boolean
|
||||
) => {
|
||||
const errors: ValidationErrors<typeof values> = {};
|
||||
|
||||
if (isCurrentUser) {
|
||||
if (!values.current_password) {
|
||||
errors.current_password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError',
|
||||
{
|
||||
defaultMessage: 'Enter your current password.',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.password) {
|
||||
errors.password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.passwordRequiredError',
|
||||
{
|
||||
defaultMessage: 'Enter a new password.',
|
||||
}
|
||||
);
|
||||
} else if (values.password.length < 6) {
|
||||
errors.password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.passwordInvalidError',
|
||||
{
|
||||
defaultMessage: 'Password must be at least 6 characters.',
|
||||
}
|
||||
);
|
||||
} else if (values.password !== values.confirm_password) {
|
||||
errors.confirm_password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError',
|
||||
{
|
||||
defaultMessage: 'Passwords do not match.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
export const ChangePasswordFlyout: FunctionComponent<ChangePasswordFlyoutProps> = ({
|
||||
username,
|
||||
defaultValues = {
|
||||
|
@ -99,52 +142,7 @@ export const ChangePasswordFlyout: FunctionComponent<ChangePasswordFlyoutProps>
|
|||
}
|
||||
}
|
||||
},
|
||||
validate: async (values) => {
|
||||
const errors: ValidationErrors<typeof values> = {};
|
||||
|
||||
if (isCurrentUser) {
|
||||
if (!values.current_password) {
|
||||
errors.current_password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.currentPasswordRequiredError',
|
||||
{
|
||||
defaultMessage: 'Enter your current password.',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.password) {
|
||||
errors.password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.passwordRequiredError',
|
||||
{
|
||||
defaultMessage: 'Enter a new password.',
|
||||
}
|
||||
);
|
||||
} else if (values.password.length < 6) {
|
||||
errors.password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.passwordInvalidError',
|
||||
{
|
||||
defaultMessage: 'Password must be at least 6 characters.',
|
||||
}
|
||||
);
|
||||
} else if (!values.confirm_password) {
|
||||
errors.confirm_password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.confirmPasswordRequiredError',
|
||||
{
|
||||
defaultMessage: 'Passwords do not match.',
|
||||
}
|
||||
);
|
||||
} else if (values.password !== values.confirm_password) {
|
||||
errors.confirm_password = i18n.translate(
|
||||
'xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError',
|
||||
{
|
||||
defaultMessage: 'Passwords do not match.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
validate: async (values) => validateChangePasswordForm(values, isCurrentUser),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
|
@ -246,6 +244,7 @@ export const ChangePasswordFlyout: FunctionComponent<ChangePasswordFlyoutProps>
|
|||
isInvalid={form.touched.current_password && !!form.errors.current_password}
|
||||
autoComplete="current-password"
|
||||
inputRef={firstFieldRef}
|
||||
data-test-subj="editUserChangePasswordCurrentPasswordInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
) : null}
|
||||
|
@ -268,6 +267,7 @@ export const ChangePasswordFlyout: FunctionComponent<ChangePasswordFlyoutProps>
|
|||
isInvalid={form.touched.password && !!form.errors.password}
|
||||
autoComplete="new-password"
|
||||
inputRef={isCurrentUser ? undefined : firstFieldRef}
|
||||
data-test-subj="editUserChangePasswordNewPasswordInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
|
@ -284,6 +284,7 @@ export const ChangePasswordFlyout: FunctionComponent<ChangePasswordFlyoutProps>
|
|||
defaultValue={form.values.confirm_password}
|
||||
isInvalid={form.touched.confirm_password && !!form.errors.confirm_password}
|
||||
autoComplete="new-password"
|
||||
data-test-subj="editUserChangePasswordConfirmPasswordInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
|
|
@ -5,21 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fireEvent, render, waitFor, within } from '@testing-library/react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
|
||||
import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock';
|
||||
import { securityMock } from '../../../mocks';
|
||||
import { Providers } from '../users_management_app';
|
||||
import { EditUserPage } from './edit_user_page';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
}));
|
||||
|
||||
const userMock = {
|
||||
username: 'jdoe',
|
||||
full_name: '',
|
||||
|
@ -28,13 +23,7 @@ const userMock = {
|
|||
roles: ['superuser'],
|
||||
};
|
||||
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/115473
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/115474
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/116889
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/117081
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/116891
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/116890
|
||||
describe.skip('EditUserPage', () => {
|
||||
describe('EditUserPage', () => {
|
||||
const coreStart = coreMock.createStart();
|
||||
let history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] });
|
||||
const authc = securityMock.createSetup().authc;
|
||||
|
@ -135,263 +124,4 @@ describe.skip('EditUserPage', () => {
|
|||
|
||||
await findByText(/Role .deprecated_role. is deprecated. Use .new_role. instead/i);
|
||||
});
|
||||
|
||||
it('updates user when submitting form and redirects back', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole, findByLabelText } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.change(await findByLabelText('Full name'), { target: { value: 'John Doe' } });
|
||||
fireEvent.change(await findByLabelText('Email address'), {
|
||||
target: { value: 'jdoe@elastic.co' },
|
||||
});
|
||||
fireEvent.click(await findByRole('button', { name: 'Update user' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/users/jdoe');
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role');
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe', {
|
||||
body: JSON.stringify({
|
||||
...userMock,
|
||||
full_name: 'John Doe',
|
||||
email: 'jdoe@elastic.co',
|
||||
}),
|
||||
});
|
||||
expect(history.location.pathname).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when user form submission fails', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
coreStart.http.post.mockRejectedValueOnce(new Error('Error message'));
|
||||
|
||||
const { findByRole, findByLabelText } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.change(await findByLabelText('Full name'), { target: { value: 'John Doe' } });
|
||||
fireEvent.change(await findByLabelText('Email address'), {
|
||||
target: { value: 'jdoe@elastic.co' },
|
||||
});
|
||||
fireEvent.click(await findByRole('button', { name: 'Update user' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security/users/jdoe');
|
||||
expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role');
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe', {
|
||||
body: JSON.stringify({
|
||||
...userMock,
|
||||
full_name: 'John Doe',
|
||||
email: 'jdoe@elastic.co',
|
||||
}),
|
||||
});
|
||||
expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
text: 'Error message',
|
||||
title: "Could not update user 'jdoe'",
|
||||
});
|
||||
expect(history.location.pathname).toBe('/edit/jdoe');
|
||||
});
|
||||
});
|
||||
|
||||
it('changes password of other user when submitting form and closes dialog', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
authc.getCurrentUser.mockResolvedValueOnce(
|
||||
mockAuthenticatedUser({ ...userMock, username: 'elastic' })
|
||||
);
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Change password' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.change(await within(dialog).findByLabelText('New password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('Confirm password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' }));
|
||||
|
||||
expect(await findByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/password', {
|
||||
body: JSON.stringify({
|
||||
newPassword: 'changeme',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('changes password of current user when submitting form and closes dialog', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
authc.getCurrentUser.mockResolvedValueOnce(mockAuthenticatedUser(userMock));
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Change password' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.change(await within(dialog).findByLabelText('Current password'), {
|
||||
target: { value: '123456' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('New password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('Confirm password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' }));
|
||||
|
||||
expect(await findByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/password', {
|
||||
body: JSON.stringify({
|
||||
newPassword: 'changeme',
|
||||
password: '123456',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when change password form submission fails', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
authc.getCurrentUser.mockResolvedValueOnce(
|
||||
mockAuthenticatedUser({ ...userMock, username: 'elastic' })
|
||||
);
|
||||
coreStart.http.post.mockRejectedValueOnce(new Error('Error message'));
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Change password' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.change(await within(dialog).findByLabelText('New password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('Confirm password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
|
||||
text: 'Error message',
|
||||
title: 'Could not change password',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates change password form', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
authc.getCurrentUser.mockResolvedValueOnce(mockAuthenticatedUser(userMock));
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Change password' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Change password' }));
|
||||
await within(dialog).findByText(/Enter your current password/i);
|
||||
await within(dialog).findByText(/Enter a new password/i);
|
||||
|
||||
fireEvent.change(await within(dialog).findByLabelText('Current password'), {
|
||||
target: { value: 'changeme' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('New password'), {
|
||||
target: { value: '111' },
|
||||
});
|
||||
await within(dialog).findAllByText(/Password must be at least 6 characters/i);
|
||||
|
||||
fireEvent.change(await within(dialog).findByLabelText('New password'), {
|
||||
target: { value: '123456' },
|
||||
});
|
||||
fireEvent.change(await within(dialog).findByLabelText('Confirm password'), {
|
||||
target: { value: '111' },
|
||||
});
|
||||
await within(dialog).findAllByText(/Passwords do not match/i);
|
||||
});
|
||||
|
||||
it('deactivates user when confirming and closes dialog', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Deactivate user' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Deactivate user' }));
|
||||
|
||||
expect(await findByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/_disable');
|
||||
});
|
||||
|
||||
it('activates user when confirming and closes dialog', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce({ ...userMock, enabled: false });
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
coreStart.http.post.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole, findAllByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
const [enableButton] = await findAllByRole('button', { name: 'Activate user' });
|
||||
fireEvent.click(enableButton);
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Activate user' }));
|
||||
|
||||
expect(await findByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/users/jdoe/_enable');
|
||||
});
|
||||
|
||||
it('deletes user when confirming and redirects back', async () => {
|
||||
coreStart.http.get.mockResolvedValueOnce(userMock);
|
||||
coreStart.http.get.mockResolvedValueOnce([]);
|
||||
coreStart.http.delete.mockResolvedValueOnce({});
|
||||
|
||||
const { findByRole } = render(
|
||||
<Providers services={coreStart} authc={authc} history={history}>
|
||||
<EditUserPage username={userMock.username} />
|
||||
</Providers>
|
||||
);
|
||||
|
||||
fireEvent.click(await findByRole('button', { name: 'Delete user' }));
|
||||
const dialog = await findByRole('dialog');
|
||||
fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete user' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(coreStart.http.delete).toHaveBeenLastCalledWith('/internal/security/users/jdoe');
|
||||
expect(history.location.pathname).toBe('/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -211,7 +211,11 @@ export const EditUserPage: FunctionComponent<EditUserPageProps> = ({ username })
|
|||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => setAction('changePassword')} size="s">
|
||||
<EuiButton
|
||||
onClick={() => setAction('changePassword')}
|
||||
size="s"
|
||||
data-test-subj="editUserChangePasswordButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.users.editUserPage.changePasswordButton"
|
||||
defaultMessage="Change password"
|
||||
|
@ -242,7 +246,11 @@ export const EditUserPage: FunctionComponent<EditUserPageProps> = ({ username })
|
|||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => setAction('enableUser')} size="s">
|
||||
<EuiButton
|
||||
onClick={() => setAction('enableUser')}
|
||||
size="s"
|
||||
data-test-subj="editUserEnableUserButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.users.editUserPage.enableUserButton"
|
||||
defaultMessage="Activate user"
|
||||
|
@ -271,7 +279,11 @@ export const EditUserPage: FunctionComponent<EditUserPageProps> = ({ username })
|
|||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => setAction('disableUser')} size="s">
|
||||
<EuiButton
|
||||
onClick={() => setAction('disableUser')}
|
||||
size="s"
|
||||
data-test-subj="editUserDisableUserButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.users.editUserPage.disableUserButton"
|
||||
defaultMessage="Deactivate user"
|
||||
|
@ -304,7 +316,12 @@ export const EditUserPage: FunctionComponent<EditUserPageProps> = ({ username })
|
|||
</EuiDescriptionList>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => setAction('deleteUser')} size="s" color="danger">
|
||||
<EuiButton
|
||||
onClick={() => setAction('deleteUser')}
|
||||
size="s"
|
||||
color="danger"
|
||||
data-test-subj="editUserDeleteUserButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.users.editUserPage.deleteUserButton"
|
||||
defaultMessage="Delete user"
|
||||
|
|
|
@ -43,6 +43,7 @@ export interface UserFormValues {
|
|||
username?: string;
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
current_password?: string;
|
||||
password?: string;
|
||||
confirm_password?: string;
|
||||
roles: readonly string[];
|
||||
|
|
|
@ -19974,7 +19974,6 @@
|
|||
"xpack.security.management.users.changePasswordFlyout.confirmButton": "{isSubmitting, select, true{パスワードを変更しています…} other{パスワードの変更}}",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError": "パスワードが一致していません。",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordLabel": "パスワードの確認",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordRequiredError": "パスワードが一致していません。",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmSystemPasswordButton": "{isSubmitting, select, true{パスワードを変更しています…} other{パスワードの変更}}",
|
||||
"xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError": "無効なパスワードです。",
|
||||
"xpack.security.management.users.changePasswordFlyout.currentPasswordLabel": "現在のパスワード",
|
||||
|
|
|
@ -20269,7 +20269,6 @@
|
|||
"xpack.security.management.users.changePasswordFlyout.confirmButton": "{isSubmitting, select, true{正在更改密码……} other{更改密码}}",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordInvalidError": "密码不匹配。",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordLabel": "确认密码",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmPasswordRequiredError": "密码不匹配。",
|
||||
"xpack.security.management.users.changePasswordFlyout.confirmSystemPasswordButton": "{isSubmitting, select, true{正在更改密码……} other{更改密码}}",
|
||||
"xpack.security.management.users.changePasswordFlyout.currentPasswordInvalidError": "密码无效。",
|
||||
"xpack.security.management.users.changePasswordFlyout.currentPasswordLabel": "当前密码",
|
||||
|
|
|
@ -9,12 +9,24 @@ import expect from '@kbn/expect';
|
|||
import { keyBy } from 'lodash';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
import type { UserFormValues } from '../../../../plugins/security/public/management/users/edit_user/user_form';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['security', 'settings']);
|
||||
const config = getService('config');
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const toasts = getService('toasts');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('users', function () {
|
||||
const optionalUser: UserFormValues = {
|
||||
username: 'OptionalUser',
|
||||
password: 'OptionalUserPwd',
|
||||
confirm_password: 'OptionalUserPwd',
|
||||
roles: ['superuser'],
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
log.debug('users');
|
||||
await PageObjects.settings.navigateTo();
|
||||
|
@ -44,32 +56,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should add new user', async function () {
|
||||
await PageObjects.security.createUser({
|
||||
const userLee: UserFormValues = {
|
||||
username: 'Lee',
|
||||
password: 'LeePwd',
|
||||
confirm_password: 'LeePwd',
|
||||
full_name: 'LeeFirst LeeLast',
|
||||
email: 'lee@myEmail.com',
|
||||
roles: ['kibana_admin'],
|
||||
});
|
||||
};
|
||||
await PageObjects.security.createUser(userLee);
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
log.debug('actualUsers = %j', users);
|
||||
expect(users.Lee.roles).to.eql(['kibana_admin']);
|
||||
expect(users.Lee.fullname).to.eql('LeeFirst LeeLast');
|
||||
expect(users.Lee.email).to.eql('lee@myEmail.com');
|
||||
expect(users.Lee.roles).to.eql(userLee.roles);
|
||||
expect(users.Lee.fullname).to.eql(userLee.full_name);
|
||||
expect(users.Lee.email).to.eql(userLee.email);
|
||||
expect(users.Lee.reserved).to.be(false);
|
||||
});
|
||||
|
||||
it('should add new user with optional fields left empty', async function () {
|
||||
await PageObjects.security.createUser({
|
||||
username: 'OptionalUser',
|
||||
password: 'OptionalUserPwd',
|
||||
confirm_password: 'OptionalUserPwd',
|
||||
roles: [],
|
||||
});
|
||||
await PageObjects.security.createUser(optionalUser);
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
log.debug('actualUsers = %j', users);
|
||||
expect(users.OptionalUser.roles).to.eql(['']);
|
||||
expect(users.OptionalUser.roles).to.eql(optionalUser.roles);
|
||||
expect(users.OptionalUser.fullname).to.eql('');
|
||||
expect(users.OptionalUser.email).to.eql('');
|
||||
expect(users.OptionalUser.reserved).to.be(false);
|
||||
|
@ -115,5 +123,101 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
expect(roles.monitoring_user.reserved).to.be(true);
|
||||
expect(roles.monitoring_user.deprecated).to.be(false);
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
before(async () => {
|
||||
await PageObjects.security.clickElasticsearchUsers();
|
||||
});
|
||||
|
||||
describe('update user profile', () => {
|
||||
it('when submitting form and redirects back', async () => {
|
||||
optionalUser.full_name = 'Optional User';
|
||||
optionalUser.email = 'optionalUser@elastic.co';
|
||||
|
||||
await PageObjects.security.updateUserProfile(optionalUser);
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
|
||||
expect(users.OptionalUser.roles).to.eql(optionalUser.roles);
|
||||
expect(users.OptionalUser.fullname).to.eql(optionalUser.full_name);
|
||||
expect(users.OptionalUser.email).to.eql(optionalUser.email);
|
||||
expect(users.OptionalUser.reserved).to.be(false);
|
||||
expect(await browser.getCurrentUrl()).to.contain('app/management/security/users');
|
||||
});
|
||||
});
|
||||
|
||||
describe('change password', () => {
|
||||
before(async () => {
|
||||
await toasts.dismissAllToasts();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await PageObjects.security.submitUpdateUserForm();
|
||||
await toasts.dismissAllToasts();
|
||||
});
|
||||
after(async () => {
|
||||
await PageObjects.security.forceLogout();
|
||||
await PageObjects.security.login();
|
||||
await PageObjects.settings.navigateTo();
|
||||
await PageObjects.security.clickElasticsearchUsers();
|
||||
});
|
||||
it('of other user when submitting form', async () => {
|
||||
optionalUser.password = 'NewOptionalUserPwd';
|
||||
optionalUser.confirm_password = 'NewOptionalUserPwd';
|
||||
|
||||
await PageObjects.security.updateUserPassword(optionalUser);
|
||||
await retry.waitFor('', async () => {
|
||||
const toastCount = await toasts.getToastCount();
|
||||
return toastCount >= 1;
|
||||
});
|
||||
const successToast = await toasts.getToastElement(1);
|
||||
expect(await successToast.getVisibleText()).to.be(
|
||||
`Password changed for '${optionalUser.username}'.`
|
||||
);
|
||||
});
|
||||
|
||||
it('of current user when submitting form', async () => {
|
||||
optionalUser.current_password = 'NewOptionalUserPwd';
|
||||
optionalUser.password = 'NewOptionalUserPwd_2';
|
||||
optionalUser.confirm_password = 'NewOptionalUserPwd_2';
|
||||
|
||||
await PageObjects.security.forceLogout();
|
||||
await PageObjects.security.login(optionalUser.username, optionalUser.current_password);
|
||||
await PageObjects.settings.navigateTo();
|
||||
await PageObjects.security.clickElasticsearchUsers();
|
||||
|
||||
await PageObjects.security.updateUserPassword(optionalUser, true);
|
||||
await retry.waitFor('', async () => {
|
||||
const toastCount = await toasts.getToastCount();
|
||||
return toastCount >= 1;
|
||||
});
|
||||
const successToast = await toasts.getToastElement(1);
|
||||
expect(await successToast.getVisibleText()).to.be(
|
||||
`Password changed for '${optionalUser.username}'.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Deactivate/Activate user', () => {
|
||||
it('deactivates user when confirming', async () => {
|
||||
await PageObjects.security.deactivatesUser(optionalUser);
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
expect(users.OptionalUser.enabled).to.be(false);
|
||||
});
|
||||
|
||||
it('activates user when confirming', async () => {
|
||||
await PageObjects.security.activatesUser(optionalUser);
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
expect(users.OptionalUser.enabled).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete user', () => {
|
||||
it('when confirming and closes dialog', async () => {
|
||||
await PageObjects.security.deleteUser(optionalUser.username ?? '');
|
||||
const users = keyBy(await PageObjects.security.getElasticsearchUsers(), 'username');
|
||||
expect(users).to.not.have.key(optionalUser.username ?? '');
|
||||
expect(await browser.getCurrentUrl()).to.contain('app/management/security/users');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -389,7 +389,7 @@ export class SecurityPageObject extends FtrService {
|
|||
// findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases
|
||||
const isUserReserved = (await user.findAllByTestSubject('userReserved', 1)).length > 0;
|
||||
const isUserDeprecated = (await user.findAllByTestSubject('userDeprecated', 1)).length > 0;
|
||||
|
||||
const isEnabled = (await user.findAllByTestSubject('userDisabled', 1)).length === 0;
|
||||
users.push({
|
||||
username: await usernameElement.getVisibleText(),
|
||||
fullname: await fullnameElement.getVisibleText(),
|
||||
|
@ -397,6 +397,7 @@ export class SecurityPageObject extends FtrService {
|
|||
roles: (await rolesElement.getVisibleText()).split('\n').map((role) => role.trim()),
|
||||
reserved: isUserReserved,
|
||||
deprecated: isUserDeprecated,
|
||||
enabled: isEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -459,10 +460,23 @@ export class SecurityPageObject extends FtrService {
|
|||
}
|
||||
}
|
||||
|
||||
async updateUserProfileForm(user: UserFormValues) {
|
||||
if (user.full_name) {
|
||||
await this.find.setValue('[name=full_name]', user.full_name);
|
||||
}
|
||||
if (user.email) {
|
||||
await this.find.setValue('[name=email]', user.email);
|
||||
}
|
||||
}
|
||||
|
||||
async submitCreateUserForm() {
|
||||
await this.find.clickByButtonText('Create user');
|
||||
}
|
||||
|
||||
async submitUpdateUserForm() {
|
||||
await this.find.clickByButtonText('Update user');
|
||||
}
|
||||
|
||||
async createUser(user: UserFormValues) {
|
||||
await this.clickElasticsearchUsers();
|
||||
await this.clickCreateNewUser();
|
||||
|
@ -470,6 +484,62 @@ export class SecurityPageObject extends FtrService {
|
|||
await this.submitCreateUserForm();
|
||||
}
|
||||
|
||||
async clickUserByUserName(username: string) {
|
||||
await this.find.clickByDisplayedLinkText(username);
|
||||
await this.header.awaitGlobalLoadingIndicatorHidden();
|
||||
}
|
||||
|
||||
async updateUserPassword(user: UserFormValues, isCurrentUser: boolean = false) {
|
||||
await this.clickUserByUserName(user.username ?? '');
|
||||
await this.testSubjects.click('editUserChangePasswordButton');
|
||||
if (isCurrentUser) {
|
||||
await this.testSubjects.setValue(
|
||||
'editUserChangePasswordCurrentPasswordInput',
|
||||
user.current_password ?? ''
|
||||
);
|
||||
}
|
||||
await this.testSubjects.setValue('editUserChangePasswordNewPasswordInput', user.password ?? '');
|
||||
await this.testSubjects.setValue(
|
||||
'editUserChangePasswordConfirmPasswordInput',
|
||||
user.confirm_password ?? ''
|
||||
);
|
||||
await this.testSubjects.click('formFlyoutSubmitButton');
|
||||
}
|
||||
|
||||
async updateUserProfile(user: UserFormValues) {
|
||||
await this.clickUserByUserName(user.username ?? '');
|
||||
await this.updateUserProfileForm(user);
|
||||
await this.submitUpdateUserForm();
|
||||
}
|
||||
|
||||
async deactivatesUser(user: UserFormValues) {
|
||||
await this.clickUserByUserName(user.username ?? '');
|
||||
await this.testSubjects.click('editUserDisableUserButton');
|
||||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
await this.submitUpdateUserForm();
|
||||
}
|
||||
|
||||
async activatesUser(user: UserFormValues) {
|
||||
await this.clickUserByUserName(user.username ?? '');
|
||||
await this.testSubjects.click('editUserEnableUserButton');
|
||||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
await this.submitUpdateUserForm();
|
||||
}
|
||||
|
||||
async deleteUser(username: string) {
|
||||
this.log.debug('Delete user ' + username);
|
||||
await this.clickUserByUserName(username);
|
||||
|
||||
this.log.debug('Find delete button and click');
|
||||
await this.find.clickByButtonText('Delete user');
|
||||
await this.common.sleep(2000);
|
||||
|
||||
const confirmText = await this.testSubjects.getVisibleText('confirmModalBodyText');
|
||||
this.log.debug('Delete user alert text = ' + confirmText);
|
||||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
return confirmText;
|
||||
}
|
||||
|
||||
async addRole(roleName: string, roleObj: Role) {
|
||||
const self = this;
|
||||
|
||||
|
@ -548,19 +618,4 @@ export class SecurityPageObject extends FtrService {
|
|||
await this.find.clickByCssSelector(`[role=option][title="${role}"]`);
|
||||
await this.testSubjects.click('comboBoxToggleListButton');
|
||||
}
|
||||
|
||||
async deleteUser(username: string) {
|
||||
this.log.debug('Delete user ' + username);
|
||||
await this.find.clickByDisplayedLinkText(username);
|
||||
await this.header.awaitGlobalLoadingIndicatorHidden();
|
||||
|
||||
this.log.debug('Find delete button and click');
|
||||
await this.find.clickByButtonText('Delete user');
|
||||
await this.common.sleep(2000);
|
||||
|
||||
const confirmText = await this.testSubjects.getVisibleText('confirmModalBodyText');
|
||||
this.log.debug('Delete user alert text = ' + confirmText);
|
||||
await this.testSubjects.click('confirmModalConfirmButton');
|
||||
return confirmText;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue