accounts-frontend/tests/services/authFlow/CompleteState.test.js

644 lines
19 KiB
JavaScript

import expect from '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: {
id: 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: {
id: 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: {
id: 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: {
id: 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: {
id: 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
},
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: {id: 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);
});
});
});