Chore: Add tests for ChangePasswordPage and SendResetMailPage (#36313)

* added tests for changePassword and forgotPassword  component

* added tests for ChangePassword screen in user profile section

* addressed review changes
This commit is contained in:
Tharun Rajendran 2021-07-01 19:31:09 +05:30 committed by GitHub
parent 3cce67c044
commit 36592e0927
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 291 additions and 5 deletions

View File

@ -23,14 +23,16 @@ export const ChangePassword: FC<Props> = ({ onSubmit, onSkip }) => {
<Field label="New password" invalid={!!errors.newPassword} error={errors?.newPassword?.message}>
<Input
autoFocus
id="new-password"
type="password"
{...register('newPassword', {
required: 'New password required',
required: 'New password is required',
})}
/>
</Field>
<Field label="Confirm new password" invalid={!!errors.confirmNew} error={errors?.confirmNew?.message}>
<Input
id="confirm-new-password"
type="password"
{...register('confirmNew', {
required: 'Confirmed password is required',

View File

@ -0,0 +1,84 @@
import React from 'react';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { ChangePasswordPage, Props } from './ChangePasswordPage';
const postMock = jest.fn();
jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({
post: postMock,
}),
}));
jest.mock('app/core/config', () => {
return {
loginError: false,
buildInfo: {
version: 'v1.0',
commit: '1',
env: 'production',
edition: 'Open Source',
isEnterprise: false,
},
licenseInfo: {
stateInfo: '',
licenseUrl: '',
},
appSubUrl: '',
};
});
const props: Props = {
...getRouteComponentProps({
queryParams: { code: 'some code' },
}),
};
describe('ChangePassword Page', () => {
it('renders correctly', () => {
render(<ChangePasswordPage {...props} />);
expect(screen.getByLabelText('New password')).toBeInTheDocument();
expect(screen.getByLabelText('Confirm new password')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
});
it('should pass validation checks for password and confirm password field', async () => {
render(<ChangePasswordPage {...props} />);
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
expect(await screen.findByText('New password is required')).toBeInTheDocument();
expect(screen.getByText('Confirmed password is required')).toBeInTheDocument();
await act(async () => {
await userEvent.type(screen.getByLabelText('New password'), 'admin');
await userEvent.type(screen.getByLabelText('Confirm new password'), 'a');
expect(screen.getByText('Passwords must match!')).toBeInTheDocument();
await userEvent.type(screen.getByLabelText('Confirm new password'), 'dmin');
expect(screen.queryByText('Passwords must match!')).not.toBeInTheDocument();
});
});
it('should navigate to default url if change password is successful', async () => {
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn(),
},
});
postMock.mockResolvedValueOnce({ message: 'Logged in' });
render(<ChangePasswordPage {...props} />);
await userEvent.type(screen.getByLabelText('New password'), 'test');
await userEvent.type(screen.getByLabelText('Confirm new password'), 'test');
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
await waitFor(() =>
expect(postMock).toHaveBeenCalledWith('/api/user/password/reset', {
code: 'some code',
confirmPassword: 'test',
newPassword: 'test',
})
);
expect(window.location.assign).toHaveBeenCalledWith('/');
});
});

View File

@ -4,7 +4,7 @@ import { ChangePassword } from './ChangePassword';
import LoginCtrl from '../Login/LoginCtrl';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
interface Props extends GrafanaRouteComponentProps<{}, { code: string }> {}
export interface Props extends GrafanaRouteComponentProps<{}, { code: string }> {}
export const ChangePasswordPage: FC<Props> = (props) => {
return (

View File

@ -51,7 +51,11 @@ export const ForgottenPassword: FC = () => {
invalid={!!errors.userOrEmail}
error={errors?.userOrEmail?.message}
>
<Input placeholder="Email or username" {...register('userOrEmail', { required: true })} />
<Input
id="user-input"
placeholder="Email or username"
{...register('userOrEmail', { required: 'Email or username is required' })}
/>
</Field>
<HorizontalGroup>
<Button>Send reset email</Button>

View File

@ -0,0 +1,68 @@
import React from 'react';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SendResetMailPage } from './SendResetMailPage';
const postMock = jest.fn();
jest.mock('@grafana/runtime', () => ({
getBackendSrv: () => ({
post: postMock,
}),
}));
jest.mock('app/core/config', () => {
return {
buildInfo: {
version: 'v1.0',
commit: '1',
env: 'production',
edition: 'Open Source',
isEnterprise: false,
},
licenseInfo: {
stateInfo: '',
licenseUrl: '',
},
appSubUrl: '',
};
});
describe('VerifyEmail Page', () => {
it('renders correctly', () => {
render(<SendResetMailPage />);
expect(screen.getByText('Reset password')).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: /User Enter your information/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Send reset email' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Back to login' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Back to login' })).toHaveAttribute('href', '/login');
});
it('should pass validation checks for email field', async () => {
render(<SendResetMailPage />);
fireEvent.click(screen.getByRole('button', { name: 'Send reset email' }));
expect(await screen.findByText('Email or username is required')).toBeInTheDocument();
await act(async () => {
await userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com');
expect(screen.queryByText('Email is invalid')).not.toBeInTheDocument();
});
});
it('should show success meessage if reset-password is successful', async () => {
postMock.mockResolvedValueOnce({ message: 'Email sent' });
render(<SendResetMailPage />);
await userEvent.type(screen.getByRole('textbox', { name: /User Enter your information/i }), 'test@gmail.com');
fireEvent.click(screen.getByRole('button', { name: 'Send reset email' }));
await waitFor(() =>
expect(postMock).toHaveBeenCalledWith('/api/user/password/send-reset-email', {
userOrEmail: 'test@gmail.com',
})
);
expect(screen.getByText(/An email with a reset link/i)).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Back to login' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Back to login' })).toHaveAttribute('href', '/login');
});
});

View File

@ -34,11 +34,16 @@ export const ChangePasswordForm: FC<Props> = ({ user, onChangePassword, isSaving
return (
<>
<Field label="Old password" invalid={!!errors.oldPassword} error={errors?.oldPassword?.message}>
<Input type="password" {...register('oldPassword', { required: 'Old password is required' })} />
<Input
id="old-password"
type="password"
{...register('oldPassword', { required: 'Old password is required' })}
/>
</Field>
<Field label="New password" invalid={!!errors.newPassword} error={errors?.newPassword?.message}>
<Input
id="new-password"
type="password"
{...register('newPassword', {
required: 'New password is required',
@ -52,6 +57,7 @@ export const ChangePasswordForm: FC<Props> = ({ user, onChangePassword, isSaving
<Field label="Confirm password" invalid={!!errors.confirmNew} error={errors?.confirmNew?.message}>
<Input
id="confirm-new-password"
type="password"
{...register('confirmNew', {
required: 'New password confirmation is required',

View File

@ -0,0 +1,122 @@
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import config from 'app/core/config';
import { Props, ChangePasswordPage } from './ChangePasswordPage';
import { initialUserState } from './state/reducers';
import { getNavModel } from '../../core/selectors/navModel';
import { backendSrv } from '../../core/services/backend_srv';
const defaultProps: Props = {
...initialUserState,
user: {
id: 1,
name: 'Test User',
email: 'test@test.com',
login: 'test',
isDisabled: false,
isGrafanaAdmin: false,
orgId: 0,
authLabels: ['github'],
},
navModel: getNavModel(
{
'profile-settings': {
icon: 'sliders-v-alt',
id: 'profile-settings',
parentItem: {
id: 'profile',
text: 'Test User',
img: '/avatar/46d229b033af06a191ff2267bca9ae56',
url: '/profile',
},
text: 'Preferences',
url: '/profile',
},
},
'profile-settings'
),
loadUser: jest.fn(),
changePassword: jest.fn(),
};
async function getTestContext(overrides: Partial<Props> = {}) {
jest.clearAllMocks();
jest.spyOn(backendSrv, 'get').mockResolvedValue({
id: 1,
name: 'Test User',
email: 'test@test.com',
login: 'test',
isDisabled: false,
isGrafanaAdmin: false,
orgId: 0,
});
const props = { ...defaultProps, ...overrides };
const { rerender } = render(<ChangePasswordPage {...props} />);
await waitFor(() => expect(props.loadUser).toHaveBeenCalledTimes(1));
return { rerender, props };
}
describe('ChangePasswordPage', () => {
it('should show loading placeholder', async () => {
await getTestContext({ user: null });
expect(screen.getByText(/loading \.\.\./i)).toBeInTheDocument();
});
it('should show change password form when user has loaded', async () => {
await getTestContext();
expect(screen.getByText('Change Your Password')).toBeInTheDocument();
expect(screen.getByLabelText('Old password')).toBeInTheDocument();
expect(screen.getByLabelText('New password')).toBeInTheDocument();
expect(screen.getByLabelText('Confirm password')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Change Password' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Cancel' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Cancel' })).toHaveAttribute('href', '/profile');
});
it('should call changePassword if change password is valid', async () => {
const { props } = await getTestContext();
await userEvent.type(screen.getByLabelText('Old password'), 'test');
await userEvent.type(screen.getByLabelText('New password'), 'admin');
await userEvent.type(screen.getByLabelText('Confirm password'), 'admin');
fireEvent.click(screen.getByRole('button', { name: 'Change Password' }));
await waitFor(() => {
expect(props.changePassword).toHaveBeenCalledTimes(1);
expect(props.changePassword).toHaveBeenCalledWith(
{
confirmNew: 'admin',
newPassword: 'admin',
oldPassword: 'test',
},
expect.anything()
);
});
});
it('should cannot change password form if ldap or authProxy enabled', async () => {
config.ldapEnabled = true;
const { rerender } = await getTestContext();
expect(
screen.getByText('You cannot change password when LDAP or auth proxy authentication is enabled.')
).toBeInTheDocument();
config.ldapEnabled = false;
config.authProxyEnabled = true;
rerender(<ChangePasswordPage {...defaultProps} />);
expect(
screen.getByText('You cannot change password when LDAP or auth proxy authentication is enabled.')
).toBeInTheDocument();
config.authProxyEnabled = false;
});
it('should show cannot change password if disableLoginForm is true and auth', async () => {
config.disableLoginForm = true;
await getTestContext();
expect(screen.getByText('Password cannot be changed here.')).toBeInTheDocument();
config.disableLoginForm = false;
});
});

View File

@ -31,7 +31,7 @@ const mapDispatchToProps = {
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = OwnProps & ConnectedProps<typeof connector>;
export type Props = OwnProps & ConnectedProps<typeof connector>;
export function ChangePasswordPage({ navModel, loadUser, isUpdating, user, changePassword }: Props) {
useMount(() => loadUser());