diff --git a/src/reset-password/data/sagas.js b/src/reset-password/data/sagas.js
index 30f18077..1147ed73 100644
--- a/src/reset-password/data/sagas.js
+++ b/src/reset-password/data/sagas.js
@@ -14,7 +14,7 @@ import {
} from './actions';
import { validateToken, resetPassword } from './service';
-import { INTERNAL_SERVER_ERROR } from '../../data/constants';
+import { INTERNAL_SERVER_ERROR, API_RATELIMIT_ERROR } from '../../data/constants';
// Services
export function* handleValidateToken(action) {
@@ -28,7 +28,13 @@ export function* handleValidateToken(action) {
yield put(validateTokenFailure(isValid));
}
} catch (err) {
- yield put(validateTokenFailure(INTERNAL_SERVER_ERROR));
+ const statusCodes = [429];
+ if (err.response && statusCodes.includes(err.response.status)) {
+ yield put(validateTokenFailure(API_RATELIMIT_ERROR));
+ } else {
+ yield put(validateTokenFailure(INTERNAL_SERVER_ERROR));
+ }
+
logError(err);
}
}
@@ -46,7 +52,12 @@ export function* handleResetPassword(action) {
yield put(resetPasswordFailure(resetErrors));
}
} catch (err) {
- yield put(resetPasswordFailure(INTERNAL_SERVER_ERROR));
+ const statusCodes = [429];
+ if (err.response && statusCodes.includes(err.response.status)) {
+ yield put(resetPasswordFailure(API_RATELIMIT_ERROR));
+ } else {
+ yield put(resetPasswordFailure(INTERNAL_SERVER_ERROR));
+ }
logError(err);
}
}
diff --git a/src/reset-password/data/tests/sagas.test.js b/src/reset-password/data/tests/sagas.test.js
index 843db8ee..d78fbcde 100644
--- a/src/reset-password/data/tests/sagas.test.js
+++ b/src/reset-password/data/tests/sagas.test.js
@@ -10,7 +10,7 @@ import {
import { handleResetPassword, handleValidateToken } from '../sagas';
import * as api from '../service';
import initializeMockLogging from '../../../setupTest';
-import { INTERNAL_SERVER_ERROR } from '../../../data/constants';
+import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../../data/constants';
const { loggingService } = initializeMockLogging();
@@ -51,9 +51,17 @@ describe('handleResetPassword', () => {
resetPassword.mockClear();
});
- it('should call service and dispatch error action', async () => {
+ it('should call service and dispatch internal server error action', async () => {
+ const errorResponse = {
+ response: {
+ status: 500,
+ data: {
+ errorCode: INTERNAL_SERVER_ERROR,
+ },
+ },
+ };
const resetPassword = jest.spyOn(api, 'resetPassword')
- .mockImplementation(() => Promise.reject());
+ .mockImplementation(() => Promise.reject(errorResponse));
const dispatched = [];
await runSaga(
@@ -66,6 +74,30 @@ describe('handleResetPassword', () => {
expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(INTERNAL_SERVER_ERROR)]);
resetPassword.mockClear();
});
+
+ it('should call service and dispatch ratelimit error', async () => {
+ const errorResponse = {
+ response: {
+ status: 429,
+ data: {
+ errorCode: API_RATELIMIT_ERROR,
+ },
+ },
+ };
+ const resetPassword = jest.spyOn(api, 'resetPassword')
+ .mockImplementation(() => Promise.reject(errorResponse));
+
+ const dispatched = [];
+ await runSaga(
+ { dispatch: (action) => dispatched.push(action) },
+ handleResetPassword,
+ params,
+ );
+
+ expect(resetPassword).toHaveBeenCalledTimes(1);
+ expect(dispatched).toEqual([resetPasswordBegin(), resetPasswordFailure(API_RATELIMIT_ERROR)]);
+ resetPassword.mockClear();
+ });
});
describe('handleValidateToken', () => {
@@ -80,9 +112,17 @@ describe('handleValidateToken', () => {
loggingService.logError.mockReset();
});
- it('check server error on api failure', async () => {
+ it('check internal server error on api failure', async () => {
+ const errorResponse = {
+ response: {
+ status: 500,
+ data: {
+ errorCode: INTERNAL_SERVER_ERROR,
+ },
+ },
+ };
const validateToken = jest.spyOn(api, 'validateToken')
- .mockImplementation(() => Promise.reject());
+ .mockImplementation(() => Promise.reject(errorResponse));
const dispatched = [];
await runSaga(
@@ -93,5 +133,30 @@ describe('handleValidateToken', () => {
expect(validateToken).toHaveBeenCalledTimes(1);
expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(INTERNAL_SERVER_ERROR)]);
+ validateToken.mockClear();
+ });
+
+ it('should call service and dispatch ratelimit error', async () => {
+ const errorResponse = {
+ response: {
+ status: 429,
+ data: {
+ errorCode: API_RATELIMIT_ERROR,
+ },
+ },
+ };
+ const validateToken = jest.spyOn(api, 'validateToken')
+ .mockImplementation(() => Promise.reject(errorResponse));
+
+ const dispatched = [];
+ await runSaga(
+ { dispatch: (action) => dispatched.push(action) },
+ handleValidateToken,
+ params,
+ );
+
+ expect(validateToken).toHaveBeenCalledTimes(1);
+ expect(dispatched).toEqual([validateTokenBegin(), validateTokenFailure(API_RATELIMIT_ERROR)]);
+ validateToken.mockClear();
});
});
diff --git a/src/reset-password/messages.js b/src/reset-password/messages.js
index 87c31541..b0579473 100644
--- a/src/reset-password/messages.js
+++ b/src/reset-password/messages.js
@@ -71,6 +71,11 @@ const messages = defineMessages({
defaultMessage: 'Token validation failure',
description: 'Failed to validate reset password token error message.',
},
+ 'reset.server.ratelimit.error': {
+ id: 'reset.server.ratelimit.error',
+ defaultMessage: 'Too many requests.',
+ description: 'Too many request at server end point',
+ },
});
export default messages;
diff --git a/src/reset-password/tests/ResetPasswordPage.test.jsx b/src/reset-password/tests/ResetPasswordPage.test.jsx
index a701c34c..25427cb1 100644
--- a/src/reset-password/tests/ResetPasswordPage.test.jsx
+++ b/src/reset-password/tests/ResetPasswordPage.test.jsx
@@ -11,7 +11,7 @@ import { resetPassword } from '../data/actions';
import { APIFailureMessage } from '../../common-components';
import ResetPasswordPage from '../ResetPasswordPage';
-import { INTERNAL_SERVER_ERROR } from '../../data/constants';
+import { API_RATELIMIT_ERROR, INTERNAL_SERVER_ERROR } from '../../data/constants';
jest.mock('@edx/frontend-platform/auth');
@@ -268,4 +268,24 @@ describe('ResetPasswordPage', () => {
const resetPasswordPage = mount(reduxWrapper(
));
expect(resetPasswordPage.find(
)).toBeTruthy();
});
+
+ it('check failure banner rendered on validate token api ratelimit', () => {
+ props = {
+ ...props,
+ status: 'invalid',
+ errors: API_RATELIMIT_ERROR,
+ };
+ const resetPasswordPage = mount(reduxWrapper(
));
+ expect(resetPasswordPage.find(
)).toBeTruthy();
+ });
+
+ it('check failure banner rendered on reset password api ratelimit', () => {
+ props = {
+ ...props,
+ status: 'failure',
+ errors: API_RATELIMIT_ERROR,
+ };
+ const resetPasswordPage = mount(reduxWrapper(
));
+ expect(resetPasswordPage.find(
)).toBeTruthy();
+ });
});