Fix the rest of the tests

This commit is contained in:
SleepWalker 2019-12-29 18:26:51 +02:00
parent f8ae8282ed
commit 5ca4c323c7
10 changed files with 128 additions and 87 deletions

View File

@ -71,7 +71,12 @@ class RootPage extends React.PureComponent<{
>
<div className={styles.header} data-testid="toolbar">
<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} />
</Link>
<div className={styles.userbar}>

View File

@ -68,7 +68,7 @@ describe('AuthFlow.functional', () => {
it('should redirect guest / -> /login', () => {
navigate('/');
expect(flow.navigate, 'was called once');
expect(flow.navigate, 'was called twice');
expect(flow.navigate, 'to have a call satisfying', ['/login']);
});
@ -80,8 +80,12 @@ describe('AuthFlow.functional', () => {
navigate('/login');
navigate('/');
expect(flow.navigate, 'was called twice');
expect(flow.navigate, 'to have a call satisfying', ['/login']);
expect(flow.navigate, 'was called thrice');
expect(flow.navigate, 'to have calls satisfying', [
['/login'],
['/login'],
['/login'],
]);
});
});

View File

@ -115,12 +115,6 @@ export default class AuthFlow implements AuthContext {
options.replace = true;
}
this.currentRequest = {
path: route,
params: {},
query: new URLSearchParams(),
};
if (this.replace) {
this.replace(route);
}
@ -288,18 +282,18 @@ export default class AuthFlow implements AuthContext {
*
* @returns {bool} - whether oauth state is being restored
*/
private restoreOAuthState() {
if (/^\/(register|oauth2)/.test(this.getRequest().path)) {
// allow register or the new oauth requests
return;
}
private restoreOAuthState(): boolean {
if (this.oAuthStateRestored) {
return;
return false;
}
this.oAuthStateRestored = true;
if (/^\/(register|oauth2)/.test(this.getRequest().path)) {
// allow register or the new oauth requests
return false;
}
try {
const data = JSON.parse(localStorage.getItem('oauthData'));
const expirationTime = 2 * 60 * 60 * 1000; // 2h

View File

@ -105,7 +105,7 @@ export default class CompleteState extends AbstractState {
(resp: { redirectUri: string }) => {
// TODO: пусть в стейт попадает флаг или тип авторизации
// вместо волшебства над редирект урлой
if (resp.redirectUri.indexOf('static_page') === 0) {
if (resp.redirectUri.includes('static_page')) {
context.setState(new FinishState());
} else {
return context.run('redirect', resp.redirectUri);

View File

@ -12,7 +12,7 @@ export default class LoginState extends AbstractState {
const { user } = context.getState();
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
if (login) {

View File

@ -15,9 +15,11 @@ import { AuthContext } from './AuthFlow';
export default class PasswordState extends AbstractState {
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');
} else {
context.setState(new CompleteState());

View File

@ -9,31 +9,25 @@ singleAccount.accounts.available = singleAccount.accounts.available.filter(
account => account.id === singleAccount.accounts.active,
);
describe("when user's token and refreshToken are invalid", () => {
before(() =>
describe('User with invalid token and refreshToken', () => {
before(() => {
// ensure we always have one account with correct token
cy.visit('/').then(() =>
fetch('/api/authentication/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: `login=${account1.login}&password=${account1.password}`,
})
.then(resp => resp.json())
.then(resp => {
const account = multiAccount.accounts.available.find(
item => item.username === account1.username,
);
cy.login({
accounts: ['default'],
updateState: false,
rawApiResp: true,
}).then(({ accounts: [resp] }) => {
const account = multiAccount.accounts.available.find(
item => item.username === account1.username,
);
if (!account) {
throw new Error('Can not find an account');
}
if (!account) {
throw new Error('Can not find an account');
}
account.token = resp.access_token;
}),
),
);
account.token = resp.access_token;
});
});
beforeEach(() =>
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.location('pathname', { timeout: 15000 }).should('eq', '/');
cy.location('pathname').should('eq', '/');
cy.contains('account preferences');
});
@ -60,18 +54,15 @@ describe("when user's token and refreshToken are invalid", () => {
.contains('Ely.by')
.click();
// TODO: currently we can not skip redirect to /, but we will in future
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
cy.url({ timeout: 15000 }).should('include', '/password');
cy.url().should('include', '/password');
});
it('should allow select account', () => {
// TODO: need a way to get valid token for one of the accounts
cy.visit('/');
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]')
.contains(account2.email)
@ -80,8 +71,9 @@ describe("when user's token and refreshToken are invalid", () => {
cy.get('[data-e2e-content]')
.contains(account1.username)
.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');
});
@ -101,12 +93,20 @@ describe("when user's token and refreshToken are invalid", () => {
});
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.get('@fetch', { timeout: 15000 }).should(
'be.calledWith',
`/api/v1/accounts/${account2.id}`,
);
cy.wait('@account')
.its('status')
.should('eq', 401);
cy.getByTestId('toolbar')
.contains(account2.username)
@ -115,10 +115,7 @@ describe("when user's token and refreshToken are invalid", () => {
.contains('Log out')
.click();
cy.get('@fetch', { timeout: 15000 }).should(
'be.calledWith',
'/api/authentication/logout',
);
cy.wait('@logout');
cy.getByTestId('toolbar')
.contains(account2.email)
.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', () => {
cy.server();
cy.route({
url: `/api/v1/accounts/${account2.id}`,
}).as('account');
cy.visit('/');
cy.get('@fetch', { timeout: 15000 }).should(
'be.calledWith',
`/api/v1/accounts/${account2.id}`,
);
cy.wait('@account')
.its('status')
.should('eq', 401);
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=rememberMe]').should('be.checked');
cy.get('[type=submit]').should('have.length', 1); // wait till transition ends
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', () => {
@ -195,7 +197,7 @@ describe("when user's token and refreshToken are invalid", () => {
cy.wait(1000);
cy.get('[name="password"]').type(`${account1.password}{enter}`);
cy.location('pathname', { timeout: 15000 }).should('eq', '/');
cy.location('pathname').should('eq', '/');
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.url({ timeout: 15000 }).should('contain', '/oauth/choose-account');
cy.url().should('contain', '/oauth/choose-account');
cy.get('[data-e2e-content]')
.contains(account2.username)

View File

@ -161,8 +161,8 @@ describe('OAuth', () => {
...defaults,
// suggest preferred username
// 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/');
@ -368,6 +368,12 @@ describe('OAuth', () => {
describe('static pages', () => {
it('should authenticate using static page', () => {
cy.server();
cy.route({
method: 'POST',
url: '/api/oauth2/v1/complete**',
}).as('complete');
cy.login({ accounts: ['default'] });
cy.visit(
@ -378,10 +384,18 @@ describe('OAuth', () => {
})}`,
);
cy.wait('@complete');
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
});
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.visit(
@ -392,6 +406,8 @@ describe('OAuth', () => {
})}`,
);
cy.wait('@complete');
cy.url().should('include', 'oauth/finish#{%22auth_code%22:');
cy.getByTestId('oauth-code-container').should(
@ -404,25 +420,21 @@ describe('OAuth', () => {
// https://github.com/cypress-io/cypress/issues/2752
cy.getByTestId('oauth-code-container')
.contains('Copy')
.click({
// TODO: forcing, because currently we have needless re-renders, that causing
// button to disappear for some time and to be unclickable
force: true,
});
.click();
});
});
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 Email)',
);
cy.getByTestId('auth-body').should(
'contain',
'Access to your Email 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 Email)',
);
cy.getByTestId('auth-body').should(
'contain',
'Access to your Email address',
);
}

View File

@ -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', () => {
cy.visit('/');

View File

@ -30,8 +30,20 @@ declare namespace Cypress {
/**
* Whether return raw api response without any conversion. Defaults to: `false`
*/
rawApiResp?: boolean;
rawApiResp?: false;
}): 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>(
id: string,