import expect from 'test/unexpected'; import sinon from 'sinon'; import CompleteState from 'services/authFlow/CompleteState'; import LoginState from 'services/authFlow/LoginState'; import ActivationState from 'services/authFlow/ActivationState'; import AcceptRulesState from 'services/authFlow/AcceptRulesState'; import FinishState from 'services/authFlow/FinishState'; import PermissionsState from 'services/authFlow/PermissionsState'; import ChooseAccountState from 'services/authFlow/ChooseAccountState'; import { bootstrap, expectState, expectNavigate, expectRun } from './helpers'; describe('CompleteState', () => { let state; let context; let mock; beforeEach(() => { state = new CompleteState(); const data = bootstrap(); context = data.context; mock = data.mock; }); afterEach(() => { mock.verify(); }); describe('#enter', () => { it('should navigate to / for authenticated', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: {} }); expectNavigate(mock, '/'); state.enter(context); }); it('should transition to login for guests', () => { context.getState.returns({ user: { isGuest: true }, auth: {} }); expectState(mock, LoginState); state.enter(context); }); it('should transition to activation if account is not activated', () => { context.getState.returns({ user: { isGuest: false }, auth: {} }); expectState(mock, ActivationState); state.enter(context); }); it('should transition to accept-rules if shouldAcceptRules', () => { context.getState.returns({ user: { shouldAcceptRules: true, isActive: true, isGuest: false }, auth: {} }); expectState(mock, AcceptRulesState); state.enter(context); }); it('should transition to activation with higher priority than shouldAcceptRules', () => { context.getState.returns({ user: { shouldAcceptRules: true, isGuest: false }, auth: {} }); expectState(mock, ActivationState); state.enter(context); }); it('should transition to finish state if code is present', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', code: 'XXX' } } }); expectState(mock, FinishState); state.enter(context); }); it('should transition to permissions state if acceptRequired', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', acceptRequired: true } } }); expectState(mock, PermissionsState); state.enter(context); }); it('should transition to permissions state if prompt=consent', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: ['consent'] } } }); expectState(mock, PermissionsState); state.enter(context); }); it('should transition to ChooseAccountState if user has multiple accs and switcher enabled', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ {id: 1}, {id: 2} ], active: 1 }, auth: { isSwitcherEnabled: true, oauth: { clientId: 'ely.by', prompt: [] } } }); expectState(mock, ChooseAccountState); state.enter(context); }); it('should NOT transition to ChooseAccountState if user has multiple accs and switcher disabled', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ {id: 1}, {id: 2} ], active: 1 }, auth: { isSwitcherEnabled: false, oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun(mock, 'oAuthComplete', {}) .returns({then() {}}); state.enter(context); }); it('should transition to ChooseAccountState if prompt=select_account and switcher enabled', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ {id: 1} ], active: 1 }, auth: { isSwitcherEnabled: true, oauth: { clientId: 'ely.by', prompt: ['select_account'] } } }); expectState(mock, ChooseAccountState); state.enter(context); }); it('should NOT transition to ChooseAccountState if prompt=select_account and switcher disabled', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ {id: 1} ], active: 1 }, auth: { isSwitcherEnabled: false, oauth: { clientId: 'ely.by', prompt: ['select_account'] } } }); expectRun(mock, 'oAuthComplete', {}) .returns({then() {}}); state.enter(context); }); }); describe('when user completes oauth', () => { it('should run oAuthComplete', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun( mock, 'oAuthComplete', sinon.match.object ).returns({then() {}}); state.enter(context); }); it('should listen for auth success/failure', () => { context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun( mock, 'oAuthComplete', sinon.match.object ).returns({then(success, fail) { expect(success, 'to be a', 'function'); expect(fail, 'to be a', 'function'); }}); state.enter(context); }); it('should run redirect by default', () => { const expectedUrl = 'foo/bar'; const promise = Promise.resolve({redirectUri: expectedUrl}); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun( mock, 'oAuthComplete', sinon.match.object ).returns(promise); expectRun( mock, 'redirect', expectedUrl ); state.enter(context); return promise.catch(mock.verify.bind(mock)); }); const testOAuth = (type, resp, expectedInstance) => { const promise = Promise[type](resp); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun( mock, 'oAuthComplete', sinon.match.object ).returns(promise); expectState(mock, expectedInstance); state.enter(context); return promise.catch(mock.verify.bind(mock)); }; it('should transition to finish state if rejected with static_page', () => testOAuth('resolve', {redirectUri: 'static_page'}, FinishState) ); it('should transition to finish state if rejected with static_page_with_code', () => testOAuth('resolve', {redirectUri: 'static_page_with_code'}, FinishState) ); it('should transition to login state if rejected with unauthorized', () => testOAuth('reject', {unauthorized: true}, LoginState) ); it('should transition to permissions state if rejected with acceptRequired', () => testOAuth('reject', {acceptRequired: true}, PermissionsState) ); describe('when loginHint is set', () => { const testSuccessLoginHint = (field) => { const account = { id: 9, email: 'some@email.com', username: 'thatUsername' }; context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ account ], active: 100 }, auth: { oauth: { clientId: 'ely.by', loginHint: account[field], prompt: [] } } }); expectRun(mock, 'setAccountSwitcher', false); expectRun(mock, 'authenticate', account) .returns(Promise.resolve()); expectState(mock, CompleteState); return expect(state.enter(context), 'to be fulfilled'); }; it('should authenticate account if id matches', () => testSuccessLoginHint('id') ); it('should authenticate account if email matches', () => testSuccessLoginHint('email') ); it('should authenticate account if username matches', () => testSuccessLoginHint('username') ); it('should not authenticate if account is already authenticated', () => { const account = { id: 9, email: 'some@email.com', username: 'thatUsername' }; context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [ account ], active: account.id, }, auth: { oauth: { clientId: 'ely.by', loginHint: account.id, prompt: [] } } }); expectRun(mock, 'setAccountSwitcher', false); expectRun(mock, 'oAuthComplete', {}) .returns({then: () => Promise.resolve()}); return expect(state.enter(context), 'to be fulfilled'); }); it('should not authenticate if account was not found and continue auth', () => { const account = { id: 9, email: 'some@email.com', username: 'thatUsername' }; context.getState.returns({ user: { isActive: true, isGuest: false }, accounts: { available: [{id: 1}], active: 1 }, auth: { oauth: { clientId: 'ely.by', loginHint: account.id, prompt: [] } } }); expectRun(mock, 'oAuthComplete', {}) .returns({then: () => Promise.resolve()}); return expect(state.enter(context), 'to be fulfilled'); }); }); }); describe('permissions accept', () => { it('should set flags, when user accepted permissions', () => { state = new CompleteState(); expect(state.isPermissionsAccepted, 'to be undefined'); state = new CompleteState({accept: undefined}); expect(state.isPermissionsAccepted, 'to be undefined'); state = new CompleteState({accept: true}); expect(state.isPermissionsAccepted, 'to be true'); state = new CompleteState({accept: false}); expect(state.isPermissionsAccepted, 'to be false'); }); it('should run oAuthComplete passing accept: true', () => { const expected = {accept: true}; state = new CompleteState(expected); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); mock.expects('run').once().withExactArgs( 'oAuthComplete', sinon.match(expected) ).returns({then() {}}); state.enter(context); }); it('should run oAuthComplete passing accept: false', () => { const expected = {accept: false}; state = new CompleteState(expected); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [] } } }); expectRun( mock, 'oAuthComplete', sinon.match(expected) ).returns({then() {}}); state.enter(context); }); it('should run oAuthComplete passing accept: true, while acceptRequired: true', () => { // acceptRequired may block user accept/decline actions, so we need // to check that they are accessible const expected = {accept: true}; state = new CompleteState(expected); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [], acceptRequired: true } } }); expectRun( mock, 'oAuthComplete', sinon.match(expected) ).returns({then() {}}); state.enter(context); }); it('should run oAuthComplete passing accept: false, while acceptRequired: true', () => { // acceptRequired may block user accept/decline actions, so we need // to check that they are accessible const expected = {accept: false}; state = new CompleteState(expected); context.getState.returns({ user: { isActive: true, isGuest: false }, auth: { oauth: { clientId: 'ely.by', prompt: [], acceptRequired: true } } }); expectRun( mock, 'oAuthComplete', sinon.match(expected) ).returns({then() {}}); state.enter(context); }); }); });