Commit ebde1d3f authored by Gijs Hendriksen's avatar Gijs Hendriksen
Browse files

Merge branch 'fix/api-error-response-handling' into 'master'

Properly handle the invalid token response

Closes #21

See merge request !175
parents c3d6a2c8 06d6eb53
import {
url, apiUrl, defaultProfileImage,
tokenSelector, loggedInSelector, apiRequest, ServerError,
apiRequest,
apiUrl,
defaultProfileImage,
loggedInSelector,
ServerError,
tokenSelector,
url,
} from '../../app/utils/url';
const fetchPromiseResult = {
status: 200,
json: () => Promise.resolve('responseJson'),
clone: global.fetch,
};
global.fetch = jest.fn().mockReturnValue(
Promise.resolve({ status: 200, json: () => 'responseJson' }));
Promise.resolve(fetchPromiseResult));
fetchPromiseResult.clone = global.fetch;
jest.mock('react-native-locale-detector', () => 'en');
......@@ -63,7 +74,11 @@ describe('url helper', () => {
it('should throw a server error', () => {
expect.assertions(1);
const response = { status: 404, json: () => 'responseJson' };
const response = {
status: 404,
json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 404 }),
};
global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null)
.catch(e => expect(e).toEqual(new ServerError('Invalid status code: 404', response)));
......@@ -71,7 +86,11 @@ describe('url helper', () => {
it('should return an empty response on status 204', () => {
expect.assertions(1);
const response = { status: 204, json: () => 'responseJson' };
const response = {
status: 204,
json: () => Promise.resolve('responseJson'),
clone: () => ({ status: 204 }),
};
global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', {}, null)
.then(res => expect(res).toEqual({}));
......
export const LOGIN = 'LOGIN_LOGIN';
export const SUCCESS = 'LOGIN_SUCCESS';
export const LOGOUT = 'LOGIN_LOGOUT';
export const TOKEN_INVALID = 'LOGIN_TOKEN_INVALID';
export const PROFILE = 'LOGIN_PROFILE';
export const PROFILE_SUCCESS = 'LOGIN_PROFILE_SUCCESS';
......@@ -16,6 +17,10 @@ export function logout() {
return { type: LOGOUT };
}
export function tokenInvalid() {
return { type: TOKEN_INVALID };
}
export function profile(token) {
return { type: PROFILE, payload: { token } };
}
......
......@@ -62,6 +62,7 @@ export default function navigate(state = initialState, action = {}) {
drawerOpen: action.payload.drawerOpen,
};
}
case loginActions.TOKEN_INVALID:
case loginActions.LOGOUT: {
return initialState;
}
......
......@@ -23,6 +23,7 @@ export default function session(state = initialState, action = {}) {
displayName: action.payload.displayName,
photo: action.payload.photo,
};
case loginActions.TOKEN_INVALID:
case loginActions.LOGOUT:
return initialState;
default:
......
......@@ -52,6 +52,11 @@ const logout = function* logout() {
Snackbar.show({ title: 'Logout successful' });
};
const tokenInvalid = function* tokenInvalid() {
yield call(AsyncStorage.clear);
yield put(pushNotificationsActions.invalidate());
};
const profile = function* profile(action) {
const { token } = action.payload;
......@@ -81,6 +86,7 @@ const loginSaga = function* loginSaga() {
yield takeEvery(loginActions.LOGIN, login);
yield takeEvery(loginActions.LOGOUT, logout);
yield takeEvery(loginActions.PROFILE, profile);
yield takeEvery(loginActions.TOKEN_INVALID, tokenInvalid);
};
export default loginSaga;
......@@ -24,6 +24,9 @@ const welcome = function* welcome() {
const response = yield call(apiRequest, 'events', data, params);
yield put(welcomeActions.success(response.results));
} catch (error) {
if (error.name === 'TokenInvalidError') {
yield put(loginActions.tokenInvalid());
}
yield put(welcomeActions.failure());
}
};
......
......@@ -15,11 +15,34 @@ export const loggedInSelector = state => state.navigation.loggedIn;
export class ServerError extends Error {
constructor(message, response) {
super(message);
this.name = this.constructor.name;
this.name = 'ServerError';
this.response = response;
}
}
export class TokenInvalidError extends Error {
constructor(response) {
super('Invalid token');
this.name = 'TokenInvalidError';
this.response = response;
}
}
const detectInvalidToken = (response) => {
const responseCopy = response.clone();
return response.json().then((json) => {
if (response.status === 403) {
const contentLang = response.headers.get('content-language');
if ((contentLang === 'en' && json.detail === 'Invalid token.') ||
(contentLang === 'nl' && json.detail === 'Ongeldige token.')) {
throw new TokenInvalidError(responseCopy);
}
}
return responseCopy;
});
};
export const apiRequest = (route, fetchOpts, params) => {
const requestOptions = fetchOpts;
if (!requestOptions.headers) {
......@@ -35,6 +58,7 @@ export const apiRequest = (route, fetchOpts, params) => {
}
return fetch(`${apiUrl}/${route}/${query}`, requestOptions)
.then(detectInvalidToken)
.then((response) => {
if (response.status >= 400 && response.status <= 500) {
throw new ServerError(`Invalid status code: ${response.status}`, response);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment