Add tests for auth middlewares

This commit is contained in:
SleepWalker 2016-08-09 22:17:49 +03:00
parent 5d054c3baa
commit 54664044eb
5 changed files with 223 additions and 8 deletions

View File

@ -4,6 +4,8 @@ import request from 'services/request';
import bearerHeaderMiddleware from './middlewares/bearerHeaderMiddleware'; import bearerHeaderMiddleware from './middlewares/bearerHeaderMiddleware';
import refreshTokenMiddleware from './middlewares/refreshTokenMiddleware'; import refreshTokenMiddleware from './middlewares/refreshTokenMiddleware';
let promise;
/** /**
* Initializes User state with the fresh data * Initializes User state with the fresh data
* *
@ -12,10 +14,14 @@ import refreshTokenMiddleware from './middlewares/refreshTokenMiddleware';
* @return {Promise} - a promise, that resolves in User state * @return {Promise} - a promise, that resolves in User state
*/ */
export function factory(store) { export function factory(store) {
if (promise) {
return promise;
}
request.addMiddleware(refreshTokenMiddleware(store)); request.addMiddleware(refreshTokenMiddleware(store));
request.addMiddleware(bearerHeaderMiddleware(store)); request.addMiddleware(bearerHeaderMiddleware(store));
return new Promise((resolve, reject) => { promise = new Promise((resolve, reject) => {
const {user} = store.getState(); const {user} = store.getState();
if (user.token) { if (user.token) {
@ -26,4 +32,6 @@ export function factory(store) {
// auto-detect guests language // auto-detect guests language
store.dispatch(changeLang(user.lang)).then(resolve, reject); store.dispatch(changeLang(user.lang)).then(resolve, reject);
}); });
return promise;
} }

View File

@ -16,7 +16,7 @@ export default function refreshTokenMiddleware({dispatch, getState}) {
const {isGuest, refreshToken, token} = getState().user; const {isGuest, refreshToken, token} = getState().user;
const isRefreshTokenRequest = data.url.includes('refresh-token'); const isRefreshTokenRequest = data.url.includes('refresh-token');
if (isGuest || isRefreshTokenRequest || !token) { if (isGuest || isRefreshTokenRequest) {
return data; return data;
} }
@ -65,14 +65,14 @@ export default function refreshTokenMiddleware({dispatch, getState}) {
function requestAccessToken(refreshToken, dispatch) { function requestAccessToken(refreshToken, dispatch) {
let promise; let promise;
if (refreshToken) { if (refreshToken) {
promise = authentication.refreshToken(refreshToken); promise = authentication.requestToken(refreshToken);
} else { } else {
promise = Promise.reject(); promise = Promise.reject();
} }
return promise return promise
.then((resp) => dispatch(updateUser({ .then(({token}) => dispatch(updateUser({
token: resp.access_token token
}))) })))
.catch(() => dispatch(logout())); .catch(() => dispatch(logout()));
} }

View File

@ -36,10 +36,19 @@ export default {
); );
}, },
refreshToken(refresh_token) { /**
* Request new access token using a refreshToken
*
* @param {string} refreshToken
*
* @return {Promise} - resolves to {token}
*/
requestToken(refreshToken) {
return request.post( return request.post(
'/api/authentication/refresh-token', '/api/authentication/refresh-token',
{refresh_token} {refresh_token: refreshToken}
); ).then((resp) => ({
token: resp.access_token
}));
} }
}; };

View File

@ -0,0 +1,44 @@
import expect from 'unexpected';
import bearerHeaderMiddleware from 'components/user/middlewares/bearerHeaderMiddleware';
describe('bearerHeaderMiddleware', () => {
it('should set Authorization header', () => {
const token = 'foo';
const middleware = bearerHeaderMiddleware({
getState: () => ({
user: {token}
})
});
const data = {
options: {
headers: {}
}
};
middleware.before(data);
expect(data.options.headers, 'to satisfy', {
Authorization: `Bearer ${token}`
});
});
it('should not set Authorization header if no token', () => {
const middleware = bearerHeaderMiddleware({
getState: () => ({
user: {}
})
});
const data = {
options: {
headers: {}
}
};
middleware.before(data);
expect(data.options.headers.Authorization, 'to be undefined');
});
});

View File

@ -0,0 +1,154 @@
import expect from 'unexpected';
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';
import authentication from 'services/api/authentication';
const refreshToken = 'foo';
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0NzA3NjE0NDMsImV4cCI6MTQ3MDc2MTQ0MywiaWF0IjoxNDcwNzYxNDQzLCJqdGkiOiJpZDEyMzQ1NiJ9.gWdnzfQQvarGpkbldUvB8qdJZSVkvdNtCbhbbl2yJW8';
// valid till 2100 year
const validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0NzA3NjE5NzcsImV4cCI6NDEwMjQ0NDgwMCwiaWF0IjoxNDcwNzYxOTc3LCJqdGkiOiJpZDEyMzQ1NiJ9.M4KY4QgHOUzhpAZjWoHJbGsEJPR-RBsJ1c1BKyxvAoU';
describe('refreshTokenMiddleware', () => {
let middleware;
let getState;
let dispatch;
beforeEach(() => {
sinon.stub(authentication, 'requestToken').named('authentication.requestToken');
getState = sinon.stub().named('store.getState');
dispatch = sinon.stub().named('store.dispatch');
middleware = refreshTokenMiddleware({getState, dispatch});
});
afterEach(() => {
authentication.requestToken.restore();
});
describe('#before', () => {
it('should request new token', () => {
getState.returns({
user: {
token: expiredToken,
refreshToken,
isGuest: false
}
});
const data = {
url: 'foo',
options: {
headers: {}
}
};
authentication.requestToken.returns(Promise.resolve({token: validToken}));
return middleware.before(data).then((resp) => {
expect(resp, 'to satisfy', data);
expect(authentication.requestToken, 'to have a call satisfying', [
refreshToken
]);
});
});
it('should not be applied for guests', () => {
getState.returns({
user: {
isGuest: true
}
});
authentication.requestToken.returns(Promise.resolve({token: validToken}));
const data = {url: 'foo'};
const resp = middleware.before(data);
expect(resp, 'to satisfy', data);
expect(authentication.requestToken, 'was not called');
});
it('should not apply to refresh-token request', () => {
getState.returns({
user: {}
});
authentication.requestToken.returns(Promise.resolve({token: validToken}));
const data = {url: '/refresh-token'};
const resp = middleware.before(data);
expect(resp, 'to satisfy', data);
expect(authentication.requestToken, 'was not called');
});
xit('should update user with new token'); // TODO: need a way to test, that action was called
xit('should logout if token request failed', () => {
getState.returns({
user: {
token: expiredToken,
refreshToken,
isGuest: false
}
});
authentication.requestToken.returns(Promise.reject());
return middleware.before({url: 'foo'}).then((resp) => {
// TODO: need a way to test, that action was called
expect(dispatch, 'to have a call satisfying', logout);
});
});
});
describe('#catch', () => {
it('should request new token', () => {
getState.returns({
user: {
refreshToken
}
});
const restart = sinon.stub().named('restart');
const data = {
url: 'foo',
options: {
headers: {}
}
};
authentication.requestToken.returns(Promise.resolve({token: validToken}));
return middleware.catch({
status: 401,
message: 'Token expired'
}, restart).then(() => {
expect(authentication.requestToken, 'to have a call satisfying', [
refreshToken
]);
expect(restart, 'was called');
});
});
xit('should logout user if token cannot be refreshed'); // TODO: need a way to test, that action was called
it('should pass the rest of failed requests through', () => {
const resp = {};
const promise = middleware.catch(resp);
expect(promise, 'to be rejected');
return promise.catch((actual) => {
expect(actual, 'to be', resp);
});
});
});
});