From 36592e092784ea1cc8ab6fde1a7e30756ec8e28a Mon Sep 17 00:00:00 2001 From: Tharun Rajendran Date: Thu, 1 Jul 2021 19:31:09 +0530 Subject: [PATCH] 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 --- .../ForgottenPassword/ChangePassword.tsx | 4 +- .../ChangePasswordPage.test.tsx | 84 ++++++++++++ .../ForgottenPassword/ChangePasswordPage.tsx | 2 +- .../ForgottenPassword/ForgottenPassword.tsx | 6 +- .../SendResetMailPage.test.tsx | 68 ++++++++++ .../features/profile/ChangePasswordForm.tsx | 8 +- .../profile/ChangePasswordPage.test.tsx | 122 ++++++++++++++++++ .../features/profile/ChangePasswordPage.tsx | 2 +- 8 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 public/app/core/components/ForgottenPassword/ChangePasswordPage.test.tsx create mode 100644 public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx create mode 100644 public/app/features/profile/ChangePasswordPage.test.tsx diff --git a/public/app/core/components/ForgottenPassword/ChangePassword.tsx b/public/app/core/components/ForgottenPassword/ChangePassword.tsx index c966f792151..c85ca19f0f6 100644 --- a/public/app/core/components/ForgottenPassword/ChangePassword.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePassword.tsx @@ -23,14 +23,16 @@ export const ChangePassword: FC = ({ onSubmit, onSkip }) => { ({ + 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(); + + 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(); + + 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(); + + 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('/'); + }); +}); diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx index cf469a033db..55bb5da34f3 100644 --- a/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx @@ -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) => { return ( diff --git a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx index 268e9d767f7..94b4f0b2ea8 100644 --- a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx +++ b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx @@ -51,7 +51,11 @@ export const ForgottenPassword: FC = () => { invalid={!!errors.userOrEmail} error={errors?.userOrEmail?.message} > - + diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx new file mode 100644 index 00000000000..c7b6f87630c --- /dev/null +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.test.tsx @@ -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(); + 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(); + + 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(); + + 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'); + }); +}); diff --git a/public/app/features/profile/ChangePasswordForm.tsx b/public/app/features/profile/ChangePasswordForm.tsx index 4344d5e01aa..9afe5d3d1f5 100644 --- a/public/app/features/profile/ChangePasswordForm.tsx +++ b/public/app/features/profile/ChangePasswordForm.tsx @@ -34,11 +34,16 @@ export const ChangePasswordForm: FC = ({ user, onChangePassword, isSaving return ( <> - + = ({ user, onChangePassword, isSaving = {}) { + 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(); + + 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(); + 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; + }); +}); diff --git a/public/app/features/profile/ChangePasswordPage.tsx b/public/app/features/profile/ChangePasswordPage.tsx index 67d63d8c9a7..4300451d0f7 100644 --- a/public/app/features/profile/ChangePasswordPage.tsx +++ b/public/app/features/profile/ChangePasswordPage.tsx @@ -31,7 +31,7 @@ const mapDispatchToProps = { const connector = connect(mapStateToProps, mapDispatchToProps); -type Props = OwnProps & ConnectedProps; +export type Props = OwnProps & ConnectedProps; export function ChangePasswordPage({ navModel, loadUser, isUpdating, user, changePassword }: Props) { useMount(() => loadUser());