Display server errors on the Reset Password Form (#1154)

This commit is contained in:
Mike Stone 2017-02-07 14:46:08 -05:00 committed by Jason Meller
parent 3120acf8f3
commit 482ff32be6
6 changed files with 70 additions and 42 deletions

View file

@ -595,9 +595,12 @@ describe('Kolide - API client', () => {
});
it('return errors correctly for unsuccessful requests', (done) => {
const error = 'Resource not found';
const errorResponse = {
message: 'Resource not found',
errors: [{ name: 'base', reason: 'Resource not found' }],
};
const passwordResetToken = 'invalid-password-reset-token';
const request = invalidResetPasswordRequest(newPassword, passwordResetToken, error);
const request = invalidResetPasswordRequest(newPassword, passwordResetToken, errorResponse);
const formData = {
new_password: newPassword,
password_reset_token: passwordResetToken,
@ -605,10 +608,7 @@ describe('Kolide - API client', () => {
Kolide.resetPassword(formData)
.then(done)
.catch((errorResponse) => {
const { response } = errorResponse;
expect(response).toEqual({ error });
.catch(() => {
expect(request.isDone()).toEqual(true);
done();
});

View file

@ -1,18 +1,22 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { noop } from 'lodash';
import { noop, size } from 'lodash';
import { push } from 'react-router-redux';
import debounce from '../../utilities/debounce';
import { resetPassword } from '../../redux/nodes/components/ResetPasswordPage/actions';
import ResetPasswordForm from '../../components/forms/ResetPasswordForm';
import StackedWhiteBoxes from '../../components/StackedWhiteBoxes';
import { performRequiredPasswordReset } from '../../redux/nodes/auth/actions';
import userInterface from '../../interfaces/user';
import debounce from 'utilities/debounce';
import { clearResetPasswordErrors, resetPassword } from 'redux/nodes/components/ResetPasswordPage/actions';
import ResetPasswordForm from 'components/forms/ResetPasswordForm';
import StackedWhiteBoxes from 'components/StackedWhiteBoxes';
import { performRequiredPasswordReset } from 'redux/nodes/auth/actions';
import userInterface from 'interfaces/user';
export class ResetPasswordPage extends Component {
static propTypes = {
dispatch: PropTypes.func,
errors: PropTypes.shape({
base: PropTypes.string,
new_password: PropTypes.string,
}),
token: PropTypes.string,
user: userInterface,
};
@ -31,6 +35,16 @@ export class ResetPasswordPage extends Component {
return false;
}
onResetErrors = () => {
const { dispatch, errors } = this.props;
if (size(errors)) {
dispatch(clearResetPasswordErrors);
}
return false;
}
onSubmit = debounce((formData) => {
const { dispatch, token, user } = this.props;
@ -46,7 +60,8 @@ export class ResetPasswordPage extends Component {
return dispatch(resetPassword(resetPasswordData))
.then(() => {
return dispatch(push('/login'));
});
})
.catch(() => false);
})
handleLeave = (location) => {
@ -61,11 +76,13 @@ export class ResetPasswordPage extends Component {
const passwordUpdateParams = { password };
return dispatch(performRequiredPasswordReset(passwordUpdateParams))
.then(() => { return dispatch(push('/')); });
.then(() => { return dispatch(push('/')); })
.catch(() => false);
}
render () {
const { handleLeave, onSubmit } = this;
const { handleLeave, onResetErrors, onSubmit } = this;
const { errors } = this.props;
return (
<StackedWhiteBoxes
@ -73,7 +90,11 @@ export class ResetPasswordPage extends Component {
leadText="Create a new password using at least one letter, one numeral and seven characters."
onLeave={handleLeave}
>
<ResetPasswordForm handleSubmit={onSubmit} />
<ResetPasswordForm
handleSubmit={onSubmit}
onChangeFunc={onResetErrors}
serverErrors={errors}
/>
</StackedWhiteBoxes>
);
}

View file

@ -1,4 +1,5 @@
import Kolide from '../../../../kolide';
import { formatErrorResponse } from 'redux/nodes/entities/base/helpers';
import Kolide from 'kolide';
export const CLEAR_RESET_PASSWORD_ERRORS = 'CLEAR_RESET_PASSWORD_ERRORS';
export const RESET_PASSWORD_ERROR = 'RESET_PASSWORD_ERROR';
@ -6,11 +7,11 @@ export const RESET_PASSWORD_REQUEST = 'RESET_PASSWORD_REQUEST';
export const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS';
export const clearResetPasswordErrors = { type: CLEAR_RESET_PASSWORD_ERRORS };
export const resetPasswordError = (error) => {
export const resetPasswordError = (errors) => {
return {
type: RESET_PASSWORD_ERROR,
payload: {
error,
errors,
},
};
};
@ -27,9 +28,10 @@ export const resetPassword = (formData) => {
return dispatch(resetPasswordSuccess);
})
.catch((response) => {
const { error } = response;
const errorsObject = formatErrorResponse(response);
dispatch(resetPasswordError(errorsObject));
dispatch(resetPasswordError(error));
throw response;
});
};

View file

@ -6,7 +6,7 @@ import {
} from './actions';
export const initialState = {
error: null,
errors: {},
loading: false,
};
@ -15,12 +15,12 @@ export default (state = initialState, { type, payload }) => {
case CLEAR_RESET_PASSWORD_ERRORS:
return {
...state,
error: null,
errors: {},
};
case RESET_PASSWORD_ERROR:
return {
...state,
error: payload.error,
errors: payload.errors,
loading: false,
};
case RESET_PASSWORD_REQUEST:
@ -31,7 +31,7 @@ export default (state = initialState, { type, payload }) => {
case RESET_PASSWORD_SUCCESS:
return {
...state,
error: null,
errors: {},
loading: false,
};
default:

View file

@ -22,12 +22,12 @@ describe('ResetPasswordPage - reducer', () => {
it('changes the loading state to true', () => {
const errorState = {
...initialState,
error: 'Something went wrong',
errors: { base: 'Something went wrong' },
};
expect(reducer(errorState, clearResetPasswordErrors)).toEqual({
...errorState,
error: null,
errors: {},
});
});
});
@ -45,23 +45,23 @@ describe('ResetPasswordPage - reducer', () => {
it('changes the loading state to false and errors to null', () => {
const loadingStateWithError = {
loading: true,
error: 'Something went wrong',
errors: { base: 'Something went wrong' },
};
expect(reducer(loadingStateWithError, resetPasswordSuccess)).toEqual({
loading: false,
error: null,
errors: {},
});
});
});
describe('resetPasswordError', () => {
it('changes the loading state to false and sets the error state', () => {
const error = 'There was an error with your request';
const errors = { base: 'There was an error with your request' };
expect(reducer(initialState, resetPasswordError(error))).toEqual({
expect(reducer(initialState, resetPasswordError(errors))).toEqual({
...initialState,
error,
errors,
loading: false,
});
});
@ -98,18 +98,23 @@ describe('ResetPasswordPage - reducer', () => {
new_password: newPassword,
password_reset_token: token,
};
const error = 'Something went wrong';
const invalidRequest = invalidResetPasswordRequest(newPassword, token, error);
const errors = [{ name: 'base', reason: 'Something went wrong' }];
const errorResponse = {
status: 422,
message: 'Something went wrong',
errors,
};
const invalidRequest = invalidResetPasswordRequest(newPassword, token, errorResponse);
const store = reduxMockStore();
store.dispatch(resetPassword(formData))
.then(done)
.catch((errorResponse) => {
.catch(() => {
const actions = store.getActions();
const { response } = errorResponse;
expect(response).toEqual({ error });
expect(actions).toInclude(resetPasswordError(error));
expect(actions).toInclude(resetPasswordError({
base: 'Something went wrong',
http_status: 422,
}));
expect(invalidRequest.isDone()).toEqual(true);
done();
});

View file

@ -347,13 +347,13 @@ export const validRevokeInviteRequest = (bearerToken, invite) => {
.reply(200, {});
};
export const invalidResetPasswordRequest = (password, token, error) => {
export const invalidResetPasswordRequest = (password, token, errorResponse) => {
return nock('http://localhost:8080')
.post('/api/v1/kolide/reset_password', JSON.stringify({
new_password: password,
password_reset_token: token,
}))
.reply(422, { error });
.reply(422, errorResponse);
};
export const validRunQueryRequest = (bearerToken, data) => {