/* eslint-disable @typescript-eslint/camelcase */ import expect from 'app/test/unexpected'; import sinon from 'sinon'; import request from 'app/services/request'; import * as authentication from 'app/services/api/authentication'; import * as accounts from 'app/services/api/accounts'; describe('authentication api', () => { let server; beforeEach(() => { server = sinon.createFakeServer({ autoRespond: true, }); ['get', 'post'].forEach(method => { server[method] = (url, resp = {}, status = 200, headers = {}) => { server.respondWith(method, url, [ status, { 'Content-Type': 'application/json', ...headers }, JSON.stringify(resp), ]); }; }); }); afterEach(() => { server.restore(); }); describe('#login', () => { const params = { login: 'foo', password: 'secret', rememberMe: false, }; beforeEach(() => { sinon.stub(request, 'post').named('request.post'); (request.post as any).returns(Promise.resolve()); }); afterEach(() => { (request.post as any).restore(); }); it('should post to login api', () => { authentication.login(params); expect(request.post, 'to have a call satisfying', [ '/api/authentication/login', params, {}, ]); }); it('should disable any token', () => { authentication.login(params); expect(request.post, 'to have a call satisfying', [ '/api/authentication/login', params, { token: null }, ]); }); }); describe('#validateToken()', () => { const validToken = 'foo'; const validRefreshToken = 'bar'; const user = { id: 1 }; const validateTokenArgs: [number, string, string] = [ user.id, validToken, validRefreshToken, ]; beforeEach(() => { sinon.stub(accounts, 'getInfo'); (accounts.getInfo as any).returns(Promise.resolve(user)); }); afterEach(() => { (accounts.getInfo as any).restore(); }); it('should request accounts.getInfo', () => expect( authentication.validateToken(...validateTokenArgs), 'to be fulfilled', ).then(() => { expect(accounts.getInfo, 'to have a call satisfying', [ user.id, validToken, ]); })); it('should resolve with both tokens and user object', () => expect( authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', { token: validToken, refreshToken: validRefreshToken, user, }, )); it('rejects if token has a bad type', () => expect( authentication.validateToken(user.id, {} as any), 'to be rejected with', 'token must be a string', )); it('should allow empty refreshToken', () => expect( authentication.validateToken(user.id, 'foo', null), 'to be fulfilled', )); it('rejects if accounts.getInfo request is unexpectedly failed', () => { const error = 'Something wrong'; (accounts.getInfo as any).returns(Promise.reject(error)); return expect( authentication.validateToken(...validateTokenArgs), 'to be rejected with', error, ); }); describe('when token is expired', () => { const expiredResponse = { name: 'Unauthorized', message: 'Token expired', code: 0, status: 401, type: 'yii\\web\\UnauthorizedHttpException', }; const newToken = 'baz'; beforeEach(() => { sinon.stub(authentication, 'requestToken'); (accounts.getInfo as any) .onCall(0) .returns(Promise.reject(expiredResponse)); (authentication.requestToken as any).returns(Promise.resolve(newToken)); }); afterEach(() => { (authentication.requestToken as any).restore(); }); it('resolves with new token and user object', async () => { server.post('/api/authentication/refresh-token', { access_token: newToken, refresh_token: validRefreshToken, success: true, expires_in: 50000, }); await expect( authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', { token: newToken, refreshToken: validRefreshToken, user }, ); expect( server.requests[0].requestBody, 'to equal', `refresh_token=${validRefreshToken}`, ); }); it('rejects if token request failed', () => { const error = { error: 'Unexpected error example' }; server.post('/api/authentication/refresh-token', error, 500); return expect( authentication.validateToken(...validateTokenArgs), 'to be rejected with', error, ); }); }); describe('when token is incorrect', () => { const expiredResponse = { name: 'Unauthorized', message: 'Incorrect token', code: 0, status: 401, type: 'yii\\web\\UnauthorizedHttpException', }; const newToken = 'baz'; beforeEach(() => { (accounts.getInfo as any) .onCall(0) .returns(Promise.reject(expiredResponse)); }); it('resolves with new token and user object', async () => { server.post('/api/authentication/refresh-token', { access_token: newToken, refresh_token: validRefreshToken, success: true, expires_in: 50000, }); await expect( authentication.validateToken(...validateTokenArgs), 'to be fulfilled with', { token: newToken, refreshToken: validRefreshToken, user }, ); expect( server.requests[0].requestBody, 'to equal', `refresh_token=${validRefreshToken}`, ); }); it('rejects if token request failed', () => { const error = { error: 'Unexpected error example' }; server.post('/api/authentication/refresh-token', error, 500); return expect( authentication.validateToken(...validateTokenArgs), 'to be rejected with', error, ); }); }); }); describe('#logout', () => { beforeEach(() => { sinon.stub(request, 'post').named('request.post'); }); afterEach(() => { (request.post as any).restore(); }); it('should request logout api', () => { authentication.logout(); expect(request.post, 'to have a call satisfying', [ '/api/authentication/logout', {}, {}, ]); }); it('returns a promise', () => { (request.post as any).returns(Promise.resolve()); return expect(authentication.logout(), 'to be fulfilled'); }); it('overrides token if provided', () => { const token = 'foo'; authentication.logout(token); expect(request.post, 'to have a call satisfying', [ '/api/authentication/logout', {}, { token }, ]); }); }); describe('#requestToken', () => { const refreshToken = 'refresh-token'; beforeEach(() => { sinon.stub(request, 'post').named('request.post'); }); afterEach(() => { (request.post as any).restore(); }); it('should request refresh-token api', () => { (request.post as any).returns(Promise.resolve({})); authentication.requestToken(refreshToken); expect(request.post, 'to have a call satisfying', [ '/api/authentication/refresh-token', { refresh_token: refreshToken, // eslint-disable-line }, {}, ]); }); it('should disable bearer auth for request', () => { (request.post as any).returns(Promise.resolve({})); authentication.requestToken(refreshToken); expect(request.post, 'to have a call satisfying', [ '/api/authentication/refresh-token', { refresh_token: refreshToken, // eslint-disable-line }, { token: null }, ]); }); it('should resolve with token', () => { const token = 'token'; (request.post as any).returns( Promise.resolve({ access_token: token, // eslint-disable-line }), ); return expect( authentication.requestToken(refreshToken), 'to be fulfilled with', token, ); }); }); });