Login: Add error handling for 500 status code
This handles the cases where some internal server error occurs and no error message is shown to users. VAN-95
This commit is contained in:
@@ -5,7 +5,7 @@ import { Alert, Hyperlink } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { processLink } from '../data/utils/dataUtils';
|
||||
import { INACTIVE_USER, NON_COMPLIANT_PASSWORD_EXCEPTION } from './data/constants';
|
||||
import { INACTIVE_USER, INTERNAL_SERVER_ERROR, NON_COMPLIANT_PASSWORD_EXCEPTION } from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const LoginFailureMessage = (props) => {
|
||||
@@ -60,6 +60,17 @@ const LoginFailureMessage = (props) => {
|
||||
);
|
||||
break;
|
||||
}
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
errorList = (
|
||||
<li key={INTERNAL_SERVER_ERROR}>
|
||||
<FormattedMessage
|
||||
id="login.internal.server.error.message"
|
||||
defaultMessage="An error has occurred. Try refreshing the page, or check your Internet connection."
|
||||
description="Error message that appears when server responds with 500 error code"
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// TODO: use errorCode instead of processing error messages on frontend
|
||||
errorList = value.trim().split('\n');
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export const NON_COMPLIANT_PASSWORD_EXCEPTION = 'NonCompliantPasswordException';
|
||||
export const INACTIVE_USER = 'inactive-user';
|
||||
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
|
||||
export const NON_COMPLIANT_PASSWORD_EXCEPTION = 'NonCompliantPasswordException';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { INTERNAL_SERVER_ERROR } from './constants';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
@@ -67,8 +68,12 @@ export function* handleLoginRequest(action) {
|
||||
));
|
||||
} catch (e) {
|
||||
const statusCodes = [400];
|
||||
if (e.response && statusCodes.includes(e.response.status)) {
|
||||
yield put(loginRequestFailure(camelCaseObject(e.response.data)));
|
||||
if (e.response) {
|
||||
if (statusCodes.includes(e.response.status)) {
|
||||
yield put(loginRequestFailure(camelCaseObject(e.response.data)));
|
||||
} else {
|
||||
yield put(loginRequestFailure(camelCaseObject({ errorCode: INTERNAL_SERVER_ERROR })));
|
||||
}
|
||||
}
|
||||
logError(e);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ describe('handleLoginRequest', () => {
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const loginErrorRespinse = {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: {
|
||||
@@ -244,7 +244,7 @@ describe('handleLoginRequest', () => {
|
||||
},
|
||||
};
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest')
|
||||
.mockImplementation(() => Promise.reject(loginErrorRespinse));
|
||||
.mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
@@ -257,7 +257,33 @@ describe('handleLoginRequest', () => {
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorRespinse.response.data)),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
|
||||
it('should handle 500 error code', async () => {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
errorCode: 'internal-server-error',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const loginRequest = jest.spyOn(api, 'loginRequest').mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
{ dispatch: (action) => dispatched.push(action) },
|
||||
handleLoginRequest,
|
||||
params,
|
||||
);
|
||||
|
||||
expect(dispatched).toEqual([
|
||||
actions.loginRequestBegin(),
|
||||
actions.loginRequestFailure(camelCaseObject(loginErrorResponse.response.data)),
|
||||
]);
|
||||
loginRequest.mockClear();
|
||||
});
|
||||
@@ -302,7 +328,7 @@ describe('handleNewUserRegistration', () => {
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const loginErrorRespinse = {
|
||||
const loginErrorResponse = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: {
|
||||
@@ -311,7 +337,7 @@ describe('handleNewUserRegistration', () => {
|
||||
},
|
||||
};
|
||||
const registerRequest = jest.spyOn(api, 'registerRequest')
|
||||
.mockImplementation(() => Promise.reject(loginErrorRespinse));
|
||||
.mockImplementation(() => Promise.reject(loginErrorResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
@@ -324,7 +350,7 @@ describe('handleNewUserRegistration', () => {
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.registerNewUserBegin(),
|
||||
actions.registerNewUserFailure(loginErrorRespinse.response.data),
|
||||
actions.registerNewUserFailure(loginErrorResponse.response.data),
|
||||
]);
|
||||
registerRequest.mockClear();
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import renderer from 'react-test-renderer';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import LoginFailureMessage from '../LoginFailure';
|
||||
import { INACTIVE_USER, NON_COMPLIANT_PASSWORD_EXCEPTION } from '../data/constants';
|
||||
import { INACTIVE_USER, INTERNAL_SERVER_ERROR, NON_COMPLIANT_PASSWORD_EXCEPTION } from '../data/constants';
|
||||
|
||||
describe('LoginFailureMessage', () => {
|
||||
let props = {};
|
||||
@@ -45,6 +45,22 @@ describe('LoginFailureMessage', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match internal server error message snapshot', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
errorCode: INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
};
|
||||
|
||||
const tree = renderer.create(
|
||||
<IntlProvider locale="en">
|
||||
<LoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match direct render of error message snapshot', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
|
||||
@@ -89,6 +89,28 @@ exports[`LoginFailureMessage should match inactive user error message snapshot 1
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LoginFailureMessage should match internal server error message snapshot 1`] = `
|
||||
<div
|
||||
className="fade alert alert-danger show"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
className="alert-heading h4"
|
||||
>
|
||||
<span>
|
||||
We couldn't sign you in.
|
||||
</span>
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
<span>
|
||||
An error has occurred. Try refreshing the page, or check your Internet connection.
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LoginFailureMessage should match non compliant password error message snapshot 1`] = `
|
||||
<div
|
||||
className="fade alert alert-danger show"
|
||||
|
||||
Reference in New Issue
Block a user