mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
#303: properly handling json and request errors. Added InternalServerError
This commit is contained in:
@@ -5,6 +5,7 @@ import { updateUser, setGuest } from 'components/user/actions';
|
|||||||
import { setLocale } from 'components/i18n/actions';
|
import { setLocale } from 'components/i18n/actions';
|
||||||
import { setAccountSwitcher } from 'components/auth/actions';
|
import { setAccountSwitcher } from 'components/auth/actions';
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
import { InternalServerError } from 'services/request';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
add,
|
add,
|
||||||
@@ -36,7 +37,7 @@ export function authenticate({token, refreshToken}) {
|
|||||||
return (dispatch, getState) =>
|
return (dispatch, getState) =>
|
||||||
authentication.validateToken({token, refreshToken})
|
authentication.validateToken({token, refreshToken})
|
||||||
.catch((resp = {}) => {
|
.catch((resp = {}) => {
|
||||||
if (resp.originalResponse && resp.originalResponse.status >= 500) {
|
if (resp instanceof InternalServerError) {
|
||||||
// delegate error recovering to the later logic
|
// delegate error recovering to the later logic
|
||||||
return Promise.reject(resp);
|
return Promise.reject(resp);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
|
import { InternalServerError } from 'services/request';
|
||||||
|
|
||||||
export default function BsodMiddleware(dispatchBsod, logger) {
|
export default function BsodMiddleware(dispatchBsod, logger) {
|
||||||
return {
|
return {
|
||||||
catch(resp) {
|
catch(resp = {}) {
|
||||||
if (resp
|
if (resp instanceof InternalServerError
|
||||||
&& resp.originalResponse
|
|| (resp.originalResponse
|
||||||
&& /404|5\d\d/.test(resp.originalResponse.status)
|
&& /404|5\d\d/.test(resp.originalResponse.status)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
dispatchBsod();
|
dispatchBsod();
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { getJwtPayload } from 'functions';
|
import { getJwtPayload } from 'functions';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
import { InternalServerError } from 'services/request';
|
||||||
import { updateToken, logoutAll } from 'components/accounts/actions';
|
import { updateToken, logoutAll } from 'components/accounts/actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +73,7 @@ function requestAccessToken(refreshToken, dispatch) {
|
|||||||
return authentication.requestToken(refreshToken)
|
return authentication.requestToken(refreshToken)
|
||||||
.then(({token}) => dispatch(updateToken(token)))
|
.then(({token}) => dispatch(updateToken(token)))
|
||||||
.catch((resp = {}) => {
|
.catch((resp = {}) => {
|
||||||
if (resp.originalResponse && resp.originalResponse.status >= 500) {
|
if (resp instanceof InternalServerError) {
|
||||||
return Promise.reject(resp);
|
return Promise.reject(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
src/services/request/InternalServerError.js
Normal file
27
src/services/request/InternalServerError.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
function InternalServerError(error, resp) {
|
||||||
|
error = error || {};
|
||||||
|
|
||||||
|
this.name = 'InternalServerError';
|
||||||
|
this.message = 'InternalServerError';
|
||||||
|
this.stack = (new Error()).stack;
|
||||||
|
|
||||||
|
if (resp) {
|
||||||
|
this.originalResponse = resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.message) {
|
||||||
|
this.message = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
this.message = error;
|
||||||
|
} else {
|
||||||
|
this.error = error;
|
||||||
|
Object.assign(this, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InternalServerError.prototype = Object.create(Error.prototype);
|
||||||
|
InternalServerError.prototype.constructor = InternalServerError;
|
||||||
|
|
||||||
|
|
||||||
|
export default InternalServerError;
|
@@ -1,3 +1,6 @@
|
|||||||
import request from './request';
|
import request from './request';
|
||||||
|
import InternalServerError from './InternalServerError';
|
||||||
|
|
||||||
export default request;
|
export default request;
|
||||||
|
|
||||||
|
export { InternalServerError };
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
|
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
|
||||||
|
import InternalServerError from './InternalServerError';
|
||||||
|
|
||||||
const middlewareLayer = new PromiseMiddlewareLayer();
|
const middlewareLayer = new PromiseMiddlewareLayer();
|
||||||
|
|
||||||
@@ -65,12 +66,27 @@ export default {
|
|||||||
|
|
||||||
|
|
||||||
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
||||||
const toJSON = (resp) => resp.json().then((json) => {
|
const toJSON = (resp = {}) => {
|
||||||
json.originalResponse = resp;
|
if (!resp.json) {
|
||||||
|
// e.g. 'TypeError: Failed to fetch' due to CORS
|
||||||
|
throw new InternalServerError(resp);
|
||||||
|
}
|
||||||
|
|
||||||
return json;
|
return resp.json().then((json) => {
|
||||||
|
json.originalResponse = resp;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}, (error) => Promise.reject(
|
||||||
|
new InternalServerError(error, resp)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {
|
||||||
|
if (resp.originalResponse.status >= 500) {
|
||||||
|
throw new InternalServerError(resp, resp.originalResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw resp;
|
||||||
});
|
});
|
||||||
const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {throw resp;});
|
|
||||||
const handleResponseSuccess = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
const handleResponseSuccess = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
||||||
|
|
||||||
function doFetch(url, options = {}) {
|
function doFetch(url, options = {}) {
|
||||||
|
@@ -4,6 +4,7 @@ import sinon from 'sinon';
|
|||||||
import { routeActions } from 'react-router-redux';
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
import { InternalServerError } from 'services/request';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
@@ -133,9 +134,7 @@ describe('components/accounts/actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('rejects when 5xx without logouting', () => {
|
it('rejects when 5xx without logouting', () => {
|
||||||
const resp = {
|
const resp = new InternalServerError(null, {status: 500});
|
||||||
originalResponse: {status: 500}
|
|
||||||
};
|
|
||||||
|
|
||||||
authentication.validateToken.returns(Promise.reject(resp));
|
authentication.validateToken.returns(Promise.reject(resp));
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import sinon from 'sinon';
|
|||||||
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';
|
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';
|
||||||
|
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
|
import { InternalServerError } from 'services/request';
|
||||||
import { updateToken } from 'components/accounts/actions';
|
import { updateToken } from 'components/accounts/actions';
|
||||||
|
|
||||||
const refreshToken = 'foo';
|
const refreshToken = 'foo';
|
||||||
@@ -145,11 +146,7 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not logout if request failed with 5xx', () => {
|
it('should not logout if request failed with 5xx', () => {
|
||||||
const resp = {
|
const resp = new InternalServerError(null, {status: 500});
|
||||||
originalResponse: {
|
|
||||||
status: 500
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
authentication.requestToken.returns(Promise.reject(resp));
|
authentication.requestToken.returns(Promise.reject(resp));
|
||||||
|
|
||||||
|
55
tests/services/request/request.test.js
Normal file
55
tests/services/request/request.test.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import expect from 'unexpected';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import request from 'services/request';
|
||||||
|
import { InternalServerError } from 'services/request';
|
||||||
|
|
||||||
|
describe('services/request', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(window, 'fetch').named('fetch');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
window.fetch.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('InternalServerError', () => {
|
||||||
|
it('should wrap fetch error', () => {
|
||||||
|
const resp = new TypeError('Fetch error');
|
||||||
|
|
||||||
|
fetch.returns(Promise.reject(resp));
|
||||||
|
|
||||||
|
return expect(request.get('/foo'), 'to be rejected')
|
||||||
|
.then((error) => {
|
||||||
|
expect(error, 'to be an', InternalServerError);
|
||||||
|
expect(error.originalResponse, 'to be undefined');
|
||||||
|
expect(error.message, 'to equal', resp.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap json errors', () => {
|
||||||
|
const resp = new Response('bad resp format', {status: 200});
|
||||||
|
|
||||||
|
fetch.returns(Promise.resolve(resp));
|
||||||
|
|
||||||
|
return expect(request.get('/foo'), 'to be rejected')
|
||||||
|
.then((error) => {
|
||||||
|
expect(error, 'to be an', InternalServerError);
|
||||||
|
expect(error.originalResponse, 'to be', resp);
|
||||||
|
expect(error.message, 'to contain', 'JSON');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap 5xx errors', () => {
|
||||||
|
const resp = new Response('{}', {status: 500});
|
||||||
|
|
||||||
|
fetch.returns(Promise.resolve(resp));
|
||||||
|
|
||||||
|
return expect(request.get('/foo'), 'to be rejected')
|
||||||
|
.then((error) => {
|
||||||
|
expect(error, 'to be an', InternalServerError);
|
||||||
|
expect(error.originalResponse, 'to be', resp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user