Add more auth/oauth/multi-acc related test cases

This commit is contained in:
SleepWalker 2019-12-28 11:28:25 +02:00
parent 0325f0aac4
commit 951f538ee5
8 changed files with 267 additions and 18 deletions

View File

@ -66,9 +66,10 @@ export class AccountSwitcher extends React.Component<Props> {
styles.accountSwitcher, styles.accountSwitcher,
styles[`${skin}AccountSwitcher`], styles[`${skin}AccountSwitcher`],
)} )}
data-testid="account-switcher"
> >
{highlightActiveAccount ? ( {highlightActiveAccount && (
<div className={styles.item}> <div className={styles.item} data-testid="active-account">
<div <div
className={clsx( className={clsx(
styles.accountIcon, styles.accountIcon,
@ -97,6 +98,7 @@ export class AccountSwitcher extends React.Component<Props> {
<div className={styles.link}> <div className={styles.link}>
<a <a
className={styles.link} className={styles.link}
data-testid="logout-account"
onClick={this.onRemove(activeAccount)} onClick={this.onRemove(activeAccount)}
href="#" href="#"
> >
@ -106,11 +108,13 @@ export class AccountSwitcher extends React.Component<Props> {
</div> </div>
</div> </div>
</div> </div>
) : null} )}
{available.map((account, index) => ( {available.map((account, index) => (
<div <div
className={clsx(styles.item, styles.accountSwitchItem)} className={clsx(styles.item, styles.accountSwitchItem)}
key={account.id} key={account.id}
data-e2e-account-id={account.id}
onClick={this.onSwitch(account)} onClick={this.onSwitch(account)}
> >
<div <div
@ -125,6 +129,7 @@ export class AccountSwitcher extends React.Component<Props> {
{allowLogout ? ( {allowLogout ? (
<div <div
className={styles.logoutIcon} className={styles.logoutIcon}
data-testid="logout-account"
onClick={this.onRemove(account)} onClick={this.onRemove(account)}
/> />
) : ( ) : (
@ -141,6 +146,7 @@ export class AccountSwitcher extends React.Component<Props> {
<Link to="/login" onClick={this.props.onAfterAction}> <Link to="/login" onClick={this.props.onAfterAction}>
<Button <Button
color={COLOR_WHITE} color={COLOR_WHITE}
data-testid="add-account"
block block
small small
className={styles.addAccount} className={styles.addAccount}

View File

@ -89,10 +89,12 @@ class RootPage extends React.PureComponent<{
<Route path="/rules" component={RulesPage} /> <Route path="/rules" component={RulesPage} />
<Route path="/dev" component={DevPage} /> <Route path="/dev" component={DevPage} />
{!user.isGuest && ( <AuthFlowRoute
<PrivateRoute exact path="/" component={ProfilePage} /> exact
)} path="/"
key="indexPage"
component={user.isGuest ? AuthPage : ProfilePage}
/>
<AuthFlowRoute path="/" component={AuthPage} /> <AuthFlowRoute path="/" component={AuthPage} />
<Route component={PageNotFound} /> <Route component={PageNotFound} />

View File

@ -77,6 +77,7 @@ export default class AuthFlow implements AuthContext {
onReady: () => void; onReady: () => void;
navigate: (route: string, options: { replace?: boolean }) => void; navigate: (route: string, options: { replace?: boolean }) => void;
currentRequest: Request; currentRequest: Request;
oAuthStateRestored = false;
dispatch: (action: { [key: string]: any }) => void; dispatch: (action: { [key: string]: any }) => void;
getState: () => RootState; getState: () => RootState;
@ -184,9 +185,6 @@ export default class AuthFlow implements AuthContext {
} }
} }
/**
* @returns {object} - current request object
*/
getRequest() { getRequest() {
return { return {
path: '', path: '',
@ -296,6 +294,12 @@ export default class AuthFlow implements AuthContext {
return; return;
} }
if (this.oAuthStateRestored) {
return;
}
this.oAuthStateRestored = true;
try { try {
const data = JSON.parse(localStorage.getItem('oauthData')); const data = JSON.parse(localStorage.getItem('oauthData'));
const expirationTime = 2 * 60 * 60 * 1000; // 2h const expirationTime = 2 * 60 * 60 * 1000; // 2h

View File

@ -1,6 +1,6 @@
{ {
"account1": { "account1": {
"id": "7", "id": 7,
"username": "SleepWalker", "username": "SleepWalker",
"email": "danilenkos@auroraglobal.com", "email": "danilenkos@auroraglobal.com",
"login": "SleepWalker", "login": "SleepWalker",

View File

@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { account1 } from '../../fixtures/accounts.json';
const defaults = { const defaults = {
client_id: 'ely', client_id: 'ely',
redirect_uri: 'http://ely.by/authorization/oauth', redirect_uri: 'http://ely.by/authorization/oauth',
@ -14,6 +16,29 @@ it('should complete oauth', () => {
cy.url().should('equal', 'https://ely.by/'); cy.url().should('equal', 'https://ely.by/');
}); });
it('should restore previous oauthData if any', () => {
localStorage.setItem(
'oauthData',
JSON.stringify({
timestamp: Date.now() - 3600,
payload: {
clientId: 'ely',
redirectUrl: 'http://ely.by/authorization/oauth',
responseType: 'code',
description: null,
scope: 'account_info account_email',
loginHint: null,
state: null,
},
}),
);
cy.login({ accounts: ['default'] });
cy.visit('/');
cy.url().should('equal', 'https://ely.by/');
});
it('should ask to choose an account if user has multiple', () => { it('should ask to choose an account if user has multiple', () => {
cy.login({ accounts: ['default', 'default2'] }).then( cy.login({ accounts: ['default', 'default2'] }).then(
({ accounts: [account] }) => { ({ accounts: [account] }) => {
@ -72,6 +97,20 @@ it('should prompt for permissions', () => {
cy.url().should('match', /^http:\/\/localhost:8080\/?\?code=[^&]+&state=$/); cy.url().should('match', /^http:\/\/localhost:8080\/?\?code=[^&]+&state=$/);
}); });
it('should allow sign in during oauth (guest oauth)', () => {
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
cy.url().should('include', '/login');
cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password');
cy.get('[name=password]').type(`${account1.password}{enter}`);
cy.url().should('equal', 'https://ely.by/');
});
// TODO: enable, when backend api will return correct response on auth decline // TODO: enable, when backend api will return correct response on auth decline
xit('should redirect to error page, when permission request declined', () => { xit('should redirect to error page, when permission request declined', () => {
cy.server(); cy.server();
@ -170,7 +209,28 @@ describe('login_hint', () => {
describe('prompts', () => { describe('prompts', () => {
it('should prompt for account', () => { it('should prompt for account', () => {
cy.login({ accounts: ['default'] }); cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
cy.visit(
`/oauth2/v1/ely?${new URLSearchParams({
...defaults,
prompt: 'select_account',
})}`,
);
cy.url().should('include', '/oauth/choose-account');
cy.getByTestId('auth-header').should('contain', 'Choose an account');
cy.getByTestId('auth-body')
.contains(account.email)
.click();
cy.url().should('equal', 'https://ely.by/');
});
});
it('should allow sign in with another account', () => {
cy.login({ accounts: ['default2'] });
cy.visit( cy.visit(
`/oauth2/v1/ely?${new URLSearchParams({ `/oauth2/v1/ely?${new URLSearchParams({
@ -181,7 +241,19 @@ describe('prompts', () => {
cy.url().should('include', '/oauth/choose-account'); cy.url().should('include', '/oauth/choose-account');
cy.getByTestId('auth-header').should('contain', 'Choose an account'); cy.getByTestId('auth-controls')
.contains('another account')
.click();
cy.url().should('include', '/login');
cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password');
cy.get('[name=password]').type(`${account1.password}{enter}`);
cy.url().should('equal', 'https://ely.by/');
}); });
it('should prompt for permissions', () => { it('should prompt for permissions', () => {
@ -258,6 +330,33 @@ describe('prompts', () => {
); );
}); });
}); });
it('should allow sign in during oauth (guest oauth)', () => {
cy.visit(
`/oauth2/v1/ely?${new URLSearchParams({
...defaults,
client_id: 'tlauncher',
redirect_uri: 'http://localhost:8080',
prompt: 'select_account,consent',
})}`,
);
cy.url().should('include', '/login');
cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password');
cy.get('[name=password]').type(`${account1.password}{enter}`);
assertPermissions();
cy.getByTestId('auth-controls')
.contains('Approve')
.click();
cy.url().should('match', /^http:\/\/localhost:8080\/?\?code=[^&]+&state=$/);
});
}); });
describe('static pages', () => { describe('static pages', () => {

View File

@ -1,8 +1,10 @@
import { account1 } from '../../fixtures/accounts.json'; import { account1, account2 } from '../../fixtures/accounts.json';
it('should sign in', () => { it('should sign in', () => {
cy.visit('/'); cy.visit('/');
cy.url().should('include', '/login');
cy.get('[name=login]').type(`${account1.login}{enter}`); cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password'); cy.url().should('include', '/password');
@ -122,3 +124,129 @@ it('should sign in with totp', () => {
cy.location('pathname').should('eq', '/'); cy.location('pathname').should('eq', '/');
}); });
it('should allow logout', () => {
cy.login({ accounts: ['default'] });
cy.visit('/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.getByTestId('active-account')
.getByTestId('logout-account')
.click();
cy.location('pathname').should('eq', '/login');
cy.getByTestId('toolbar').should('contain', 'Join');
});
describe('multi account', () => {
it('should allow sign in with another account', () => {
cy.login({ accounts: ['default2'] });
cy.visit('/');
cy.getByTestId('toolbar')
.contains(account2.username)
.click();
cy.getByTestId('active-account').should('have.length', 1);
cy.get('[data-e2e-account-id]').should('have.length', 0);
cy.getByTestId('add-account').click();
cy.location('pathname').should('eq', '/login');
cy.get('[data-e2e-go-back]').should('exist');
cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password');
cy.get('[name=password]').type(`${account1.password}{enter}`);
cy.location('pathname').should('eq', '/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.getByTestId('active-account').should('have.length', 1);
cy.get('[data-e2e-account-id]').should('have.length', 1);
cy.get('[data-e2e-account-id]')
.getByTestId('logout-account')
.click();
});
it('should go back to profile from login screen', () => {
cy.login({ accounts: ['default'] });
cy.visit('/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.getByTestId('add-account').click();
cy.location('pathname').should('eq', '/login');
cy.get('[data-e2e-go-back]').click();
cy.location('pathname').should('eq', '/');
});
it('should allow logout active account', () => {
cy.login({ accounts: ['default', 'default2'] });
cy.visit('/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.getByTestId('active-account')
.getByTestId('logout-account')
.click();
cy.getByTestId('toolbar')
.contains(account2.username)
.click();
cy.get('[data-e2e-account-id]').should('have.length', 0);
});
it('should not allow log in the same account twice', () => {
cy.login({ accounts: ['default'] });
cy.visit('/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.getByTestId('active-account').should('have.length', 1);
cy.get('[data-e2e-account-id]').should('have.length', 0);
cy.getByTestId('add-account').click();
cy.location('pathname').should('eq', '/login');
cy.get('[data-e2e-go-back]').should('exist');
cy.get('[name=login]').type(`${account1.login}{enter}`);
cy.url().should('include', '/password');
cy.get('[name=password]').type(`${account1.password}{enter}`);
cy.location('pathname').should('eq', '/');
cy.getByTestId('toolbar')
.contains(account1.username)
.click();
cy.get('[data-e2e-account-id]').should('have.length', 0);
});
});

View File

@ -27,8 +27,8 @@ import { account1, account2 } from '../fixtures/accounts';
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
const accountsMap = { const accountsMap = {
default: account2, default: account1,
default2: account1, default2: account2,
}; };
Cypress.Commands.add( Cypress.Commands.add(
@ -82,8 +82,18 @@ Cypress.Commands.add(
}, },
); );
Cypress.Commands.add('getByTestId', (id, options) => Cypress.Commands.add(
cy.get(`[data-testid=${id}]`, options), 'getByTestId',
{ prevSubject: 'optional' },
(subject, id, options) => {
const selector = `[data-testid=${id}]`;
if (subject) {
return cy.wrap(subject.find(selector));
}
return cy.get(selector, options);
},
); );
function createState(accounts) { function createState(accounts) {