#303: properly handling json and request errors. Added InternalServerError

This commit is contained in:
SleepWalker 2017-02-26 13:40:07 +02:00
parent 1c467bc7bd
commit 8127041acb
9 changed files with 120 additions and 18 deletions

View File

@ -5,6 +5,7 @@ import { updateUser, setGuest } from 'components/user/actions';
import { setLocale } from 'components/i18n/actions';
import { setAccountSwitcher } from 'components/auth/actions';
import logger from 'services/logger';
import { InternalServerError } from 'services/request';
import {
add,
@ -36,7 +37,7 @@ export function authenticate({token, refreshToken}) {
return (dispatch, getState) =>
authentication.validateToken({token, refreshToken})
.catch((resp = {}) => {
if (resp.originalResponse && resp.originalResponse.status >= 500) {
if (resp instanceof InternalServerError) {
// delegate error recovering to the later logic
return Promise.reject(resp);
}

View File

@ -1,9 +1,12 @@
import { InternalServerError } from 'services/request';
export default function BsodMiddleware(dispatchBsod, logger) {
return {
catch(resp) {
if (resp
&& resp.originalResponse
&& /404|5\d\d/.test(resp.originalResponse.status)
catch(resp = {}) {
if (resp instanceof InternalServerError
|| (resp.originalResponse
&& /404|5\d\d/.test(resp.originalResponse.status)
)
) {
dispatchBsod();

View File

@ -1,6 +1,7 @@
import { getJwtPayload } from 'functions';
import authentication from 'services/api/authentication';
import logger from 'services/logger';
import { InternalServerError } from 'services/request';
import { updateToken, logoutAll } from 'components/accounts/actions';
/**
@ -72,7 +73,7 @@ function requestAccessToken(refreshToken, dispatch) {
return authentication.requestToken(refreshToken)
.then(({token}) => dispatch(updateToken(token)))
.catch((resp = {}) => {
if (resp.originalResponse && resp.originalResponse.status >= 500) {
if (resp instanceof InternalServerError) {
return Promise.reject(resp);
}

View 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;

View File

@ -1,3 +1,6 @@
import request from './request';
import InternalServerError from './InternalServerError';
export default request;
export { InternalServerError };

View File

@ -1,4 +1,5 @@
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
import InternalServerError from './InternalServerError';
const middlewareLayer = new PromiseMiddlewareLayer();
@ -65,12 +66,27 @@ export default {
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
const toJSON = (resp) => resp.json().then((json) => {
json.originalResponse = resp;
const toJSON = (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);
function doFetch(url, options = {}) {

View File

@ -4,6 +4,7 @@ import sinon from 'sinon';
import { routeActions } from 'react-router-redux';
import logger from 'services/logger';
import { InternalServerError } from 'services/request';
import authentication from 'services/api/authentication';
import {
authenticate,
@ -133,9 +134,7 @@ describe('components/accounts/actions', () => {
});
it('rejects when 5xx without logouting', () => {
const resp = {
originalResponse: {status: 500}
};
const resp = new InternalServerError(null, {status: 500});
authentication.validateToken.returns(Promise.reject(resp));

View File

@ -4,6 +4,7 @@ import sinon from 'sinon';
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';
import authentication from 'services/api/authentication';
import { InternalServerError } from 'services/request';
import { updateToken } from 'components/accounts/actions';
const refreshToken = 'foo';
@ -145,11 +146,7 @@ describe('refreshTokenMiddleware', () => {
});
it('should not logout if request failed with 5xx', () => {
const resp = {
originalResponse: {
status: 500
}
};
const resp = new InternalServerError(null, {status: 500});
authentication.requestToken.returns(Promise.reject(resp));

View 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);
});
});
});
});