mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-02-23 18:57:37 +05:30
Fix the rest of the tests
This commit is contained in:
parent
f8ae8282ed
commit
5ca4c323c7
@ -71,7 +71,12 @@ class RootPage extends React.PureComponent<{
|
|||||||
>
|
>
|
||||||
<div className={styles.header} data-testid="toolbar">
|
<div className={styles.header} data-testid="toolbar">
|
||||||
<div className={styles.headerContent}>
|
<div className={styles.headerContent}>
|
||||||
<Link to="/" className={styles.logo} onClick={onLogoClick}>
|
<Link
|
||||||
|
to="/"
|
||||||
|
className={styles.logo}
|
||||||
|
onClick={onLogoClick}
|
||||||
|
data-testid="home-page"
|
||||||
|
>
|
||||||
<Message {...messages.siteName} />
|
<Message {...messages.siteName} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className={styles.userbar}>
|
<div className={styles.userbar}>
|
||||||
|
@ -68,7 +68,7 @@ describe('AuthFlow.functional', () => {
|
|||||||
it('should redirect guest / -> /login', () => {
|
it('should redirect guest / -> /login', () => {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
|
|
||||||
expect(flow.navigate, 'was called once');
|
expect(flow.navigate, 'was called twice');
|
||||||
expect(flow.navigate, 'to have a call satisfying', ['/login']);
|
expect(flow.navigate, 'to have a call satisfying', ['/login']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,8 +80,12 @@ describe('AuthFlow.functional', () => {
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
navigate('/');
|
navigate('/');
|
||||||
|
|
||||||
expect(flow.navigate, 'was called twice');
|
expect(flow.navigate, 'was called thrice');
|
||||||
expect(flow.navigate, 'to have a call satisfying', ['/login']);
|
expect(flow.navigate, 'to have calls satisfying', [
|
||||||
|
['/login'],
|
||||||
|
['/login'],
|
||||||
|
['/login'],
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -115,12 +115,6 @@ export default class AuthFlow implements AuthContext {
|
|||||||
options.replace = true;
|
options.replace = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentRequest = {
|
|
||||||
path: route,
|
|
||||||
params: {},
|
|
||||||
query: new URLSearchParams(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.replace) {
|
if (this.replace) {
|
||||||
this.replace(route);
|
this.replace(route);
|
||||||
}
|
}
|
||||||
@ -288,18 +282,18 @@ export default class AuthFlow implements AuthContext {
|
|||||||
*
|
*
|
||||||
* @returns {bool} - whether oauth state is being restored
|
* @returns {bool} - whether oauth state is being restored
|
||||||
*/
|
*/
|
||||||
private restoreOAuthState() {
|
private restoreOAuthState(): boolean {
|
||||||
if (/^\/(register|oauth2)/.test(this.getRequest().path)) {
|
|
||||||
// allow register or the new oauth requests
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.oAuthStateRestored) {
|
if (this.oAuthStateRestored) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oAuthStateRestored = true;
|
this.oAuthStateRestored = true;
|
||||||
|
|
||||||
|
if (/^\/(register|oauth2)/.test(this.getRequest().path)) {
|
||||||
|
// allow register or the new oauth requests
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -105,7 +105,7 @@ export default class CompleteState extends AbstractState {
|
|||||||
(resp: { redirectUri: string }) => {
|
(resp: { redirectUri: string }) => {
|
||||||
// TODO: пусть в стейт попадает флаг или тип авторизации
|
// TODO: пусть в стейт попадает флаг или тип авторизации
|
||||||
// вместо волшебства над редирект урлой
|
// вместо волшебства над редирект урлой
|
||||||
if (resp.redirectUri.indexOf('static_page') === 0) {
|
if (resp.redirectUri.includes('static_page')) {
|
||||||
context.setState(new FinishState());
|
context.setState(new FinishState());
|
||||||
} else {
|
} else {
|
||||||
return context.run('redirect', resp.redirectUri);
|
return context.run('redirect', resp.redirectUri);
|
||||||
|
@ -12,7 +12,7 @@ export default class LoginState extends AbstractState {
|
|||||||
const { user } = context.getState();
|
const { user } = context.getState();
|
||||||
|
|
||||||
const isUserAddsSecondAccount =
|
const isUserAddsSecondAccount =
|
||||||
!user.isGuest && /login|password/.test(context.getRequest().path); // TODO: improve me
|
!user.isGuest && /login|password/.test(location.pathname); // TODO: improve me
|
||||||
|
|
||||||
// TODO: it may not allow user to leave password state till he click back or enters password
|
// TODO: it may not allow user to leave password state till he click back or enters password
|
||||||
if (login) {
|
if (login) {
|
||||||
|
@ -15,9 +15,11 @@ import { AuthContext } from './AuthFlow';
|
|||||||
|
|
||||||
export default class PasswordState extends AbstractState {
|
export default class PasswordState extends AbstractState {
|
||||||
enter(context: AuthContext) {
|
enter(context: AuthContext) {
|
||||||
const { login } = getCredentials(context.getState());
|
const { login, isTotpRequired } = getCredentials(context.getState());
|
||||||
|
|
||||||
if (login) {
|
if (isTotpRequired) {
|
||||||
|
context.setState(new MfaState());
|
||||||
|
} else if (login) {
|
||||||
context.navigate('/password');
|
context.navigate('/password');
|
||||||
} else {
|
} else {
|
||||||
context.setState(new CompleteState());
|
context.setState(new CompleteState());
|
||||||
|
@ -9,31 +9,25 @@ singleAccount.accounts.available = singleAccount.accounts.available.filter(
|
|||||||
account => account.id === singleAccount.accounts.active,
|
account => account.id === singleAccount.accounts.active,
|
||||||
);
|
);
|
||||||
|
|
||||||
describe("when user's token and refreshToken are invalid", () => {
|
describe('User with invalid token and refreshToken', () => {
|
||||||
before(() =>
|
before(() => {
|
||||||
// ensure we always have one account with correct token
|
// ensure we always have one account with correct token
|
||||||
cy.visit('/').then(() =>
|
cy.login({
|
||||||
fetch('/api/authentication/login', {
|
accounts: ['default'],
|
||||||
method: 'POST',
|
updateState: false,
|
||||||
headers: {
|
rawApiResp: true,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
}).then(({ accounts: [resp] }) => {
|
||||||
},
|
const account = multiAccount.accounts.available.find(
|
||||||
body: `login=${account1.login}&password=${account1.password}`,
|
item => item.username === account1.username,
|
||||||
})
|
);
|
||||||
.then(resp => resp.json())
|
|
||||||
.then(resp => {
|
|
||||||
const account = multiAccount.accounts.available.find(
|
|
||||||
item => item.username === account1.username,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error('Can not find an account');
|
throw new Error('Can not find an account');
|
||||||
}
|
}
|
||||||
|
|
||||||
account.token = resp.access_token;
|
account.token = resp.access_token;
|
||||||
}),
|
});
|
||||||
),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
beforeEach(() =>
|
beforeEach(() =>
|
||||||
localStorage.setItem('redux-storage', JSON.stringify(multiAccount)),
|
localStorage.setItem('redux-storage', JSON.stringify(multiAccount)),
|
||||||
@ -46,7 +40,7 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
|
|
||||||
cy.get('[name="password"]').type(`${account2.password}{enter}`);
|
cy.get('[name="password"]').type(`${account2.password}{enter}`);
|
||||||
|
|
||||||
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
|
cy.location('pathname').should('eq', '/');
|
||||||
cy.contains('account preferences');
|
cy.contains('account preferences');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,18 +54,15 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
.contains('Ely.by')
|
.contains('Ely.by')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
// TODO: currently we can not skip redirect to /, but we will in future
|
cy.url().should('include', '/password');
|
||||||
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
|
|
||||||
cy.url({ timeout: 15000 }).should('include', '/password');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow select account', () => {
|
it('should allow select account', () => {
|
||||||
// TODO: need a way to get valid token for one of the accounts
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
cy.get('[data-e2e-go-back]').click();
|
cy.get('[data-e2e-go-back]').click();
|
||||||
|
|
||||||
cy.url().should('include', '/choose-account');
|
cy.location('pathname').should('eq', '/choose-account');
|
||||||
|
|
||||||
cy.get('[data-e2e-content]')
|
cy.get('[data-e2e-content]')
|
||||||
.contains(account2.email)
|
.contains(account2.email)
|
||||||
@ -80,8 +71,9 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
cy.get('[data-e2e-content]')
|
cy.get('[data-e2e-content]')
|
||||||
.contains(account1.username)
|
.contains(account1.username)
|
||||||
.click();
|
.click();
|
||||||
|
cy.get('[name="password"]').type(`${account2.password}{enter}`);
|
||||||
|
|
||||||
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
|
cy.location('pathname').should('eq', '/');
|
||||||
cy.contains('account preferences');
|
cy.contains('account preferences');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,12 +93,20 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow logout', () => {
|
it('should allow logout', () => {
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
url: `/api/v1/accounts/${account2.id}`,
|
||||||
|
}).as('account');
|
||||||
|
cy.route({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/authentication/logout',
|
||||||
|
}).as('logout');
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
cy.get('@fetch', { timeout: 15000 }).should(
|
cy.wait('@account')
|
||||||
'be.calledWith',
|
.its('status')
|
||||||
`/api/v1/accounts/${account2.id}`,
|
.should('eq', 401);
|
||||||
);
|
|
||||||
|
|
||||||
cy.getByTestId('toolbar')
|
cy.getByTestId('toolbar')
|
||||||
.contains(account2.username)
|
.contains(account2.username)
|
||||||
@ -115,10 +115,7 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
.contains('Log out')
|
.contains('Log out')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.get('@fetch', { timeout: 15000 }).should(
|
cy.wait('@logout');
|
||||||
'be.calledWith',
|
|
||||||
'/api/authentication/logout',
|
|
||||||
);
|
|
||||||
cy.getByTestId('toolbar')
|
cy.getByTestId('toolbar')
|
||||||
.contains(account2.email)
|
.contains(account2.email)
|
||||||
.should('not.exist');
|
.should('not.exist');
|
||||||
@ -128,12 +125,16 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow enter new login from choose account', () => {
|
it('should allow enter new login from choose account', () => {
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
url: `/api/v1/accounts/${account2.id}`,
|
||||||
|
}).as('account');
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
cy.get('@fetch', { timeout: 15000 }).should(
|
cy.wait('@account')
|
||||||
'be.calledWith',
|
.its('status')
|
||||||
`/api/v1/accounts/${account2.id}`,
|
.should('eq', 401);
|
||||||
);
|
|
||||||
|
|
||||||
cy.url().should('include', '/password');
|
cy.url().should('include', '/password');
|
||||||
|
|
||||||
@ -152,9 +153,10 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
|
|
||||||
cy.get('[name=password]').type(account1.password);
|
cy.get('[name=password]').type(account1.password);
|
||||||
cy.get('[name=rememberMe]').should('be.checked');
|
cy.get('[name=rememberMe]').should('be.checked');
|
||||||
|
cy.get('[type=submit]').should('have.length', 1); // wait till transition ends
|
||||||
cy.get('[type=submit]').click();
|
cy.get('[type=submit]').click();
|
||||||
|
|
||||||
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
|
cy.location('pathname').should('eq', '/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow logout from all accounts while choosing an account', () => {
|
it('should allow logout from all accounts while choosing an account', () => {
|
||||||
@ -195,7 +197,7 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
cy.get('[name="password"]').type(`${account1.password}{enter}`);
|
cy.get('[name="password"]').type(`${account1.password}{enter}`);
|
||||||
|
|
||||||
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
|
cy.location('pathname').should('eq', '/');
|
||||||
cy.contains('account preferences');
|
cy.contains('account preferences');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -254,7 +256,7 @@ describe("when user's token and refreshToken are invalid", () => {
|
|||||||
|
|
||||||
cy.get('[name=password]').type(`${account2.password}{enter}`);
|
cy.get('[name=password]').type(`${account2.password}{enter}`);
|
||||||
|
|
||||||
cy.url({ timeout: 15000 }).should('contain', '/oauth/choose-account');
|
cy.url().should('contain', '/oauth/choose-account');
|
||||||
|
|
||||||
cy.get('[data-e2e-content]')
|
cy.get('[data-e2e-content]')
|
||||||
.contains(account2.username)
|
.contains(account2.username)
|
||||||
|
@ -161,8 +161,8 @@ describe('OAuth', () => {
|
|||||||
...defaults,
|
...defaults,
|
||||||
// suggest preferred username
|
// suggest preferred username
|
||||||
// https://docs.ely.by/ru/oauth.html#id3
|
// https://docs.ely.by/ru/oauth.html#id3
|
||||||
login_hint: account.id,
|
login_hint: String(account.id),
|
||||||
})}`,
|
}).toString()}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.url().should('equal', 'https://ely.by/');
|
cy.url().should('equal', 'https://ely.by/');
|
||||||
@ -368,6 +368,12 @@ describe('OAuth', () => {
|
|||||||
|
|
||||||
describe('static pages', () => {
|
describe('static pages', () => {
|
||||||
it('should authenticate using static page', () => {
|
it('should authenticate using static page', () => {
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/oauth2/v1/complete**',
|
||||||
|
}).as('complete');
|
||||||
|
|
||||||
cy.login({ accounts: ['default'] });
|
cy.login({ accounts: ['default'] });
|
||||||
|
|
||||||
cy.visit(
|
cy.visit(
|
||||||
@ -378,10 +384,18 @@ describe('OAuth', () => {
|
|||||||
})}`,
|
})}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cy.wait('@complete');
|
||||||
|
|
||||||
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
|
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should authenticate using static page with code', () => {
|
it('should authenticate using static page with code', () => {
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/oauth2/v1/complete**',
|
||||||
|
}).as('complete');
|
||||||
|
|
||||||
cy.login({ accounts: ['default'] });
|
cy.login({ accounts: ['default'] });
|
||||||
|
|
||||||
cy.visit(
|
cy.visit(
|
||||||
@ -392,6 +406,8 @@ describe('OAuth', () => {
|
|||||||
})}`,
|
})}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cy.wait('@complete');
|
||||||
|
|
||||||
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
|
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
|
||||||
|
|
||||||
cy.getByTestId('oauth-code-container').should(
|
cy.getByTestId('oauth-code-container').should(
|
||||||
@ -404,25 +420,21 @@ describe('OAuth', () => {
|
|||||||
// https://github.com/cypress-io/cypress/issues/2752
|
// https://github.com/cypress-io/cypress/issues/2752
|
||||||
cy.getByTestId('oauth-code-container')
|
cy.getByTestId('oauth-code-container')
|
||||||
.contains('Copy')
|
.contains('Copy')
|
||||||
.click({
|
.click();
|
||||||
// TODO: forcing, because currently we have needless re-renders, that causing
|
|
||||||
// button to disappear for some time and to be unclickable
|
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertPermissions() {
|
|
||||||
cy.url().should('include', '/oauth/permissions');
|
|
||||||
|
|
||||||
cy.getByTestId('auth-header').should('contain', 'Application permissions');
|
|
||||||
cy.getByTestId('auth-body').should(
|
|
||||||
'contain',
|
|
||||||
'Access to your profile data (except E‑mail)',
|
|
||||||
);
|
|
||||||
cy.getByTestId('auth-body').should(
|
|
||||||
'contain',
|
|
||||||
'Access to your E‑mail address',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function assertPermissions() {
|
||||||
|
cy.url().should('include', '/oauth/permissions');
|
||||||
|
|
||||||
|
cy.getByTestId('auth-header').should('contain', 'Application permissions');
|
||||||
|
cy.getByTestId('auth-body').should(
|
||||||
|
'contain',
|
||||||
|
'Access to your profile data (except E‑mail)',
|
||||||
|
);
|
||||||
|
cy.getByTestId('auth-body').should(
|
||||||
|
'contain',
|
||||||
|
'Access to your E‑mail address',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -46,6 +46,16 @@ describe('Sign in / Log out', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should force to /login page', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
|
||||||
|
cy.getByTestId('home-page').click();
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
});
|
||||||
|
|
||||||
it('should sign in without remember me', () => {
|
it('should sign in without remember me', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
|
14
tests-e2e/cypress/support/index.d.ts
vendored
14
tests-e2e/cypress/support/index.d.ts
vendored
@ -30,8 +30,20 @@ declare namespace Cypress {
|
|||||||
/**
|
/**
|
||||||
* Whether return raw api response without any conversion. Defaults to: `false`
|
* Whether return raw api response without any conversion. Defaults to: `false`
|
||||||
*/
|
*/
|
||||||
rawApiResp?: boolean;
|
rawApiResp?: false;
|
||||||
}): Promise<{ accounts: TAccount[] }>;
|
}): Promise<{ accounts: TAccount[] }>;
|
||||||
|
login(options: {
|
||||||
|
accounts: AccountAlias[];
|
||||||
|
updateState?: boolean;
|
||||||
|
rawApiResp: true;
|
||||||
|
}): Promise<{
|
||||||
|
accounts: {
|
||||||
|
success: true;
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
}[];
|
||||||
|
}>;
|
||||||
|
|
||||||
getByTestId<S = any>(
|
getByTestId<S = any>(
|
||||||
id: string,
|
id: string,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user