#245: cover loginHint in CompleteState with tests

This commit is contained in:
SleepWalker 2017-01-29 13:42:51 +02:00
parent acf912d979
commit 7178ac0b88
3 changed files with 191 additions and 64 deletions

View File

@ -17,7 +17,7 @@ export default class CompleteState extends AbstractState {
}
enter(context) {
const {auth = {}, user, accounts} = context.getState();
const {auth = {}, user} = context.getState();
if (user.isGuest) {
context.setState(new LoginState());
@ -26,67 +26,75 @@ export default class CompleteState extends AbstractState {
} else if (user.shouldAcceptRules) {
context.setState(new AcceptRulesState());
} else if (auth.oauth && auth.oauth.clientId) {
let isSwitcherEnabled = auth.isSwitcherEnabled;
if (auth.oauth.loginHint) {
const account = accounts.available.filter((account) =>
account.id === auth.oauth.loginHint * 1
|| account.email === auth.oauth.loginHint
|| account.username === auth.oauth.loginHint
)[0];
if (account) {
// disable switching, because we are know the account, user must be authorized with
context.run('setAccountSwitcher', false);
isSwitcherEnabled = false;
if (account.id !== accounts.active.id) {
// lets switch user to an account, that is needed for auth
return context.run('authenticate', account)
.then(() => context.setState(new CompleteState()));
}
}
}
if (isSwitcherEnabled
&& (accounts.available.length > 1
|| auth.oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE)
)
) {
context.setState(new ChooseAccountState());
} else if (auth.oauth.code) {
context.setState(new FinishState());
} else {
const data = {};
if (typeof this.isPermissionsAccepted !== 'undefined') {
data.accept = this.isPermissionsAccepted;
} else if (auth.oauth.acceptRequired || auth.oauth.prompt.includes(PROMPT_PERMISSIONS)) {
context.setState(new PermissionsState());
return;
}
// TODO: it seams that oAuthComplete may be a separate state
return context.run('oAuthComplete', data).then((resp) => {
// TODO: пусть в стейт попадает флаг или тип авторизации
// вместо волшебства над редирект урлой
if (resp.redirectUri.indexOf('static_page') === 0) {
context.setState(new FinishState());
} else {
return new Promise(() => {
// do not resolve promise to make loader visible and
// overcome app rendering
context.run('redirect', resp.redirectUri);
});
}
}, (resp) => {
if (resp.unauthorized) {
context.setState(new LoginState());
} else if (resp.acceptRequired) {
context.setState(new PermissionsState());
}
});
}
return this.processOAuth(context);
} else {
context.navigate('/');
}
}
processOAuth(context) {
const {auth = {}, accounts} = context.getState();
let isSwitcherEnabled = auth.isSwitcherEnabled;
const loginHint = auth.oauth.loginHint;
if (loginHint) {
const account = accounts.available.filter((account) =>
account.id === loginHint * 1
|| account.email === loginHint
|| account.username === loginHint
)[0];
if (account) {
// disable switching, because we are know the account, user must be authorized with
context.run('setAccountSwitcher', false);
isSwitcherEnabled = false;
if (account.id !== accounts.active.id) {
// lets switch user to an account, that is needed for auth
return context.run('authenticate', account)
.then(() => context.setState(new CompleteState()));
}
}
}
if (isSwitcherEnabled
&& (accounts.available.length > 1
|| auth.oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE)
)
) {
context.setState(new ChooseAccountState());
} else if (auth.oauth.code) {
context.setState(new FinishState());
} else {
const data = {};
if (typeof this.isPermissionsAccepted !== 'undefined') {
data.accept = this.isPermissionsAccepted;
} else if (auth.oauth.acceptRequired || auth.oauth.prompt.includes(PROMPT_PERMISSIONS)) {
context.setState(new PermissionsState());
return;
}
// TODO: it seams that oAuthComplete may be a separate state
return context.run('oAuthComplete', data).then((resp) => {
// TODO: пусть в стейт попадает флаг или тип авторизации
// вместо волшебства над редирект урлой
if (resp.redirectUri.indexOf('static_page') === 0) {
context.setState(new FinishState());
} else {
return new Promise(() => {
// do not resolve promise to make loader visible and
// overcome app rendering
context.run('redirect', resp.redirectUri);
});
}
}, (resp) => {
if (resp.unauthorized) {
context.setState(new LoginState());
} else if (resp.acceptRequired) {
context.setState(new PermissionsState());
}
});
}
}
}

View File

@ -1,4 +1,5 @@
import expect from 'unexpected';
import sinon from 'sinon';
import CompleteState from 'services/authFlow/CompleteState';
import LoginState from 'services/authFlow/LoginState';
@ -135,7 +136,7 @@ describe('CompleteState', () => {
});
});
describe('oAuthComplete', () => {
describe('when user completes oauth', () => {
it('should run oAuthComplete', () => {
context.getState.returns({
user: {
@ -185,7 +186,7 @@ describe('CompleteState', () => {
state.enter(context);
});
it('should transition run redirect by default', () => {
it('should run redirect by default', () => {
const expectedUrl = 'foo/bar';
const promise = Promise.resolve({redirectUri: expectedUrl});
@ -261,6 +262,122 @@ describe('CompleteState', () => {
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', () => {

View File

@ -2,6 +2,8 @@
* A helpers for testing states in isolation from AuthFlow
*/
import sinon from 'sinon';
export function bootstrap() {
const context = {
getState: sinon.stub(),
@ -28,9 +30,9 @@ export function expectState(mock, state) {
export function expectNavigate(mock, route, options) {
if (options) {
return mock.expects('navigate').once().withExactArgs(route, sinon.match(options));
} else {
return mock.expects('navigate').once().withExactArgs(route);
}
return mock.expects('navigate').once().withExactArgs(route);
}
export function expectRun(mock, ...args) {