mirror of
https://github.com/elyby/accounts-frontend.git
synced 2026-10-21 13:13:37 +05:30
Upgrade Cypress and fix broken E2E tests
This commit is contained in:
31 files changed
+643
-1566
No files matched your search
@@ -51,8 +51,6 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-globals': 'off',
|
||||
// Cypress plugins/index.js is a plain Node CommonJS file
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
// chai's `expect().to.be.true`-style assertions are bare member expressions
|
||||
// (no call at the end), which this rule otherwise flags as a no-op
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
|
||||
@@ -26,6 +26,8 @@ const changeContextSpringConfig = {
|
||||
|
||||
const { helpLinks: helpLinksStyles } = helpLinks;
|
||||
|
||||
const fieldsetStyles: CSSProperties = { border: 0, padding: 0, margin: 0, minWidth: 0 };
|
||||
|
||||
type PanelId = string;
|
||||
|
||||
/**
|
||||
@@ -267,7 +269,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
</MeasureHeight>
|
||||
</div>
|
||||
</Panel>
|
||||
<div className={helpLinksStyles} data-testid="auth-controls-secondary">
|
||||
<div className={helpLinksStyles}>
|
||||
{panels.map((config) => this.getLinks(config))}
|
||||
</div>
|
||||
</Form>
|
||||
@@ -368,7 +370,12 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
if (length === 1) {
|
||||
if (!this.wasAutoFocused) {
|
||||
// Don't steal the focus from a field the user has already started typing into during the transition.
|
||||
// The leaving panel's fields are disabled, so a focus that remained there doesn't count
|
||||
const { activeElement } = document;
|
||||
const isUserTyping = activeElement?.matches('input:enabled, textarea:enabled');
|
||||
|
||||
if (!this.wasAutoFocused && !isUserTyping) {
|
||||
this.body?.autoFocus();
|
||||
}
|
||||
|
||||
@@ -429,7 +436,12 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={`header/${key}`} style={transitionStyle}>
|
||||
<div
|
||||
key={`header/${key}`}
|
||||
style={transitionStyle}
|
||||
data-testid="auth-header"
|
||||
{...this.getActiveAttrs(key)}
|
||||
>
|
||||
{hasBackButton ? backButton : null}
|
||||
<div style={scrollStyle}>{Title}</div>
|
||||
</div>
|
||||
@@ -462,13 +474,22 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
style={transitionStyle}
|
||||
state={this.shouldMeasureHeight()}
|
||||
onMeasure={(height) => this.onUpdateHeight(height, key)}
|
||||
data-testid="auth-body"
|
||||
{...this.getActiveAttrs(key)}
|
||||
>
|
||||
{React.cloneElement(Body, {
|
||||
// @ts-ignore
|
||||
ref: (body) => {
|
||||
this.body = body;
|
||||
},
|
||||
})}
|
||||
{this.renderFieldset(
|
||||
key,
|
||||
React.cloneElement(Body, {
|
||||
// @ts-ignore
|
||||
ref: (body) => {
|
||||
// During the transition the leaving panel is still mounted and its ref may be called
|
||||
// after the active one's, so take only the active panel's body
|
||||
if (key === this.state.panelId) {
|
||||
this.body = body;
|
||||
}
|
||||
},
|
||||
}),
|
||||
)}
|
||||
</MeasureHeight>
|
||||
);
|
||||
}
|
||||
@@ -479,7 +500,12 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
const transitionStyle = this.getDefaultTransitionStyles(key, style as unknown as AnimationStyle);
|
||||
|
||||
return (
|
||||
<div key={`footer/${key}`} style={transitionStyle}>
|
||||
<div
|
||||
key={`footer/${key}`}
|
||||
style={transitionStyle}
|
||||
data-testid="auth-controls"
|
||||
{...this.getActiveAttrs(key)}
|
||||
>
|
||||
{Footer}
|
||||
</div>
|
||||
);
|
||||
@@ -491,7 +517,12 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
const transitionStyle = this.getDefaultTransitionStyles(key, style as unknown as AnimationStyle);
|
||||
|
||||
return (
|
||||
<div key={`links/${key}`} style={transitionStyle}>
|
||||
<div
|
||||
key={`links/${key}`}
|
||||
style={transitionStyle}
|
||||
data-testid="auth-controls-secondary"
|
||||
{...this.getActiveAttrs(key)}
|
||||
>
|
||||
{Links}
|
||||
</div>
|
||||
);
|
||||
@@ -518,6 +549,31 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* While a transition is running, the leaving panel is still mounted inside the same <form>. Disabling its
|
||||
* fields excludes them from the form's validation and serialization, so a submit made during the transition
|
||||
* isn't blocked by the required fields of the previous panel.
|
||||
*
|
||||
* This is not probably what happens in the real life, but E2E tests works too fast and constantly bugs with that
|
||||
*/
|
||||
renderFieldset(key: PanelId, children: ReactNode): ReactElement {
|
||||
return (
|
||||
<fieldset disabled={key !== this.state.panelId} style={fieldsetStyles}>
|
||||
{children}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This helps E2E correctly select elements skipping those, that are still presented in the DOM during
|
||||
* panel transition to the next one.
|
||||
*/
|
||||
getActiveAttrs(key: PanelId): Record<string, string> {
|
||||
return {
|
||||
'data-e2e-panel-active': key === this.state.panelId ? 'true' : 'false',
|
||||
};
|
||||
}
|
||||
|
||||
translate(value: number, direction: 'X' | 'Y' = 'X', unit: '%' | 'px' = '%'): CSSProperties {
|
||||
return {
|
||||
WebkitTransform: `translate${direction}(${value}${unit})`,
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Panel: FC<PanelProps> = ({ title, icon, children }) => {
|
||||
|
||||
export const PanelHeader: FC<PropsWithChildren<any>> = ({ children }) => {
|
||||
return (
|
||||
<div className={styles.header} data-testid="auth-header">
|
||||
<div className={styles.header}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -38,7 +38,7 @@ export const PanelHeader: FC<PropsWithChildren<any>> = ({ children }) => {
|
||||
|
||||
export const PanelBody: FC<PropsWithChildren<any>> = ({ children }) => {
|
||||
return (
|
||||
<div className={styles.body} data-testid="auth-body">
|
||||
<div className={styles.body}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -46,7 +46,7 @@ export const PanelBody: FC<PropsWithChildren<any>> = ({ children }) => {
|
||||
|
||||
export const PanelFooter: FC<PropsWithChildren<any>> = ({ children }) => {
|
||||
return (
|
||||
<div className={styles.footer} data-testid="auth-controls">
|
||||
<div className={styles.footer}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { CaptchaID } from 'app/services/captcha';
|
||||
import { Skin } from 'app/components/ui';
|
||||
import captcha from 'app/services/captcha';
|
||||
import captcha, { registerE2eCallback } from 'app/services/captcha';
|
||||
import logger from 'app/services/logger';
|
||||
import { ComponentLoader } from 'app/components/ui/loader';
|
||||
|
||||
@@ -26,7 +26,11 @@ export default class Captcha extends FormInputComponent<
|
||||
delay: 0,
|
||||
};
|
||||
|
||||
unregisterE2eCallback?: () => void;
|
||||
|
||||
componentDidMount() {
|
||||
this.unregisterE2eCallback = registerE2eCallback(this.setCode);
|
||||
|
||||
setTimeout(() => {
|
||||
const { current: el } = this.elRef;
|
||||
|
||||
@@ -47,6 +51,10 @@ export default class Captcha extends FormInputComponent<
|
||||
}, this.props.delay);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unregisterE2eCallback?.();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { skin } = this.props;
|
||||
|
||||
|
||||
@@ -7,6 +7,19 @@ let sitekey: string;
|
||||
|
||||
export type CaptchaID = string;
|
||||
|
||||
// For E2E testing purposes only: lets tests pass the captcha by calling `window.e2eCaptchaSetCode(code)`.
|
||||
// The code is passed to every mounted captcha, so a test doesn't depend on which one of them (e.g. during
|
||||
// a panel transition) was rendered last
|
||||
const e2eCallbacks = new Set<(code: string) => void>();
|
||||
|
||||
(window as any).e2eCaptchaSetCode = (code: string) => e2eCallbacks.forEach((callback) => callback(code));
|
||||
|
||||
export function registerE2eCallback(callback: (code: string) => void): () => void {
|
||||
e2eCallbacks.add(callback);
|
||||
|
||||
return () => e2eCallbacks.delete(callback);
|
||||
}
|
||||
|
||||
class Captcha {
|
||||
/**
|
||||
* @param {DOMNode|string} el - dom node or id of element where to render captcha
|
||||
@@ -27,9 +40,6 @@ class Captcha {
|
||||
onSetCode: (code: string) => void;
|
||||
},
|
||||
): Promise<CaptchaID> {
|
||||
// for testing purposes only
|
||||
(window as any).e2eCaptchaSetCode = callback;
|
||||
|
||||
return this.loadApi().then(() =>
|
||||
(window as any).grecaptcha.render(el, {
|
||||
sitekey,
|
||||
@@ -43,8 +53,6 @@ class Captcha {
|
||||
* @param {string} captchaId - captcha id, returned from render promise
|
||||
*/
|
||||
reset(captchaId: CaptchaID) {
|
||||
delete (window as any).e2eCaptchaSetCode;
|
||||
|
||||
this.loadApi().then(() => (window as any).grecaptcha.reset(captchaId));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
projectId: 'uftjbg',
|
||||
chromeWebSecurity: false,
|
||||
// Cypress 16's native network interception in Chromium-based browsers randomly hangs multi-spec runs.
|
||||
// Route them through the legacy path until it's fixed: https://github.com/cypress-io/cypress/issues/34969
|
||||
forceHttp1: true,
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:8080',
|
||||
specPattern: 'cypress/e2e/**/*.test.ts',
|
||||
// Restores the "Run X specs" button in the spec list (removed by default in Cypress 10+)
|
||||
experimentalRunAllSpecs: true,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"testFiles": ["**/*.test.ts"],
|
||||
"chromeWebSecurity": false,
|
||||
"projectId": "uftjbg"
|
||||
}
|
||||
+26
-49
@@ -5,15 +5,10 @@ describe('Forgot / reset password', () => {
|
||||
const captchaCode = 'captchaCode';
|
||||
const emailMask = 'fo*@gm*l.**m';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/forgot-password',
|
||||
response: {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
cy.intercept('POST', '/api/authentication/forgot-password', {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
}).as('forgot');
|
||||
|
||||
@@ -35,10 +30,11 @@ describe('Forgot / reset password', () => {
|
||||
// @ts-ignore
|
||||
win.e2eCaptchaSetCode(captchaCode);
|
||||
});
|
||||
cy.get('[type=submit]').should('have.length', 1).click();
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@forgot')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -57,15 +53,10 @@ describe('Forgot / reset password', () => {
|
||||
const login = 'foo';
|
||||
const emailMask = 'fo*@gm*l.**m';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/forgot-password',
|
||||
response: {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
cy.intercept('POST', '/api/authentication/forgot-password', {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
}).as('forgot');
|
||||
|
||||
@@ -89,10 +80,11 @@ describe('Forgot / reset password', () => {
|
||||
// @ts-ignore
|
||||
win.e2eCaptchaSetCode(captchaCode);
|
||||
});
|
||||
cy.get('[type=submit]').should('have.length', 1).click();
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@forgot')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -109,15 +101,10 @@ describe('Forgot / reset password', () => {
|
||||
const login = 'foo';
|
||||
const emailMask = 'fo*@gm*l.**m';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/forgot-password',
|
||||
response: {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
cy.intercept('POST', '/api/authentication/forgot-password', {
|
||||
success: true,
|
||||
data: {
|
||||
emailMask,
|
||||
},
|
||||
}).as('forgot');
|
||||
|
||||
@@ -130,10 +117,10 @@ describe('Forgot / reset password', () => {
|
||||
// @ts-ignore
|
||||
win.e2eCaptchaSetCode(captchaCode);
|
||||
});
|
||||
cy.get('[type=submit]').should('have.length', 1).click();
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@forgot')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -149,17 +136,12 @@ describe('Forgot / reset password', () => {
|
||||
const key = 'key';
|
||||
const newPassword = 'newPassword';
|
||||
|
||||
cy.server();
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
updateState: false,
|
||||
rawApiResp: true,
|
||||
}).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/recover-password',
|
||||
response: account,
|
||||
}).as('recover');
|
||||
cy.intercept('POST', '/api/authentication/recover-password', account).as('recover');
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
@@ -184,10 +166,10 @@ describe('Forgot / reset password', () => {
|
||||
cy.get('[name=key]').type(key);
|
||||
cy.get('[name=newPassword]').type(newPassword);
|
||||
cy.get('[name=newRePassword]').type(newPassword);
|
||||
cy.get('[type=submit]').should('have.length', 1).click();
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@recover')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -202,17 +184,12 @@ describe('Forgot / reset password', () => {
|
||||
const key = 'key';
|
||||
const newPassword = 'newPassword';
|
||||
|
||||
cy.server();
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
updateState: false,
|
||||
rawApiResp: true,
|
||||
}).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/recover-password',
|
||||
response: account,
|
||||
}).as('recover');
|
||||
cy.intercept('POST', '/api/authentication/recover-password', account).as('recover');
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
@@ -223,10 +200,10 @@ describe('Forgot / reset password', () => {
|
||||
cy.get('[name=key]').should('have.attr', 'readonly');
|
||||
cy.get('[name=newPassword]').type(newPassword);
|
||||
cy.get('[name=newRePassword]').type(newPassword);
|
||||
cy.get('[type=submit]').should('have.length', 1).click();
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@recover')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
+5
-20
@@ -80,18 +80,12 @@ describe('User with invalid token and refreshToken', () => {
|
||||
});
|
||||
|
||||
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.intercept(`/api/v1/accounts/${account2.id}`).as('account');
|
||||
cy.intercept('POST', '/api/authentication/logout').as('logout');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.wait('@account').its('status').should('eq', 401);
|
||||
cy.wait('@account').its('response.statusCode').should('eq', 401);
|
||||
|
||||
cy.findByTestId('toolbar').contains(account2.username).click();
|
||||
cy.findByTestId('toolbar').contains('Log out').click();
|
||||
@@ -102,20 +96,16 @@ describe('User with invalid token and refreshToken', () => {
|
||||
});
|
||||
|
||||
it('should allow enter new login from choose account', () => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
url: `/api/v1/accounts/${account2.id}`,
|
||||
}).as('account');
|
||||
cy.intercept(`/api/v1/accounts/${account2.id}`).as('account');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.wait('@account').its('status').should('eq', 401);
|
||||
cy.wait('@account').its('response.statusCode').should('eq', 401);
|
||||
|
||||
cy.url().should('include', '/password');
|
||||
|
||||
cy.get('[data-e2e-go-back]').click();
|
||||
|
||||
cy.get('[name=password]').should('not.exist'); // wait till panel transition end
|
||||
cy.url().should('include', '/choose-account');
|
||||
|
||||
cy.contains('Log into another').click();
|
||||
@@ -128,7 +118,6 @@ describe('User with invalid token and refreshToken', () => {
|
||||
|
||||
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').should('eq', '/');
|
||||
@@ -160,9 +149,6 @@ describe('User with invalid token and refreshToken', () => {
|
||||
|
||||
cy.url().should('include', '/password');
|
||||
|
||||
// TODO: remove wait and fix logic so that
|
||||
// it won't show 'Please enter E‑mail or username' error
|
||||
cy.wait(1000);
|
||||
cy.get('[name="password"]').type(`${account1.password}{enter}`);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
@@ -189,7 +175,6 @@ describe('User with invalid token and refreshToken', () => {
|
||||
cy.url().should('contain', '/password');
|
||||
|
||||
cy.get('[data-e2e-go-back]').click();
|
||||
cy.get('[name=password]').should('not.exist'); // wait till panel transition end
|
||||
cy.contains('[type=submit]', 'Log into another account').click();
|
||||
cy.contains('a', 'Create new account').click();
|
||||
|
||||
+48
-81
@@ -44,11 +44,7 @@ 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.intercept('POST', '/api/oauth2/v1/complete**').as('complete');
|
||||
|
||||
cy.login({ accounts: ['default'] });
|
||||
|
||||
@@ -66,11 +62,7 @@ describe('OAuth', () => {
|
||||
});
|
||||
|
||||
it('should authenticate using static page with code', () => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/oauth2/v1/complete**',
|
||||
}).as('complete');
|
||||
cy.intercept('POST', '/api/oauth2/v1/complete**').as('complete');
|
||||
|
||||
cy.login({ accounts: ['default'] });
|
||||
|
||||
@@ -200,19 +192,15 @@ describe('OAuth', () => {
|
||||
describe('Permissions prompt', () => {
|
||||
// TODO: remove api mocks, when we will be able to revoke permissions
|
||||
it('should prompt for permissions', () => {
|
||||
cy.server();
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
cy.intercept('POST', new RegExp('/api/oauth2/v1/complete'), {
|
||||
// NOTE: can not use cypress glob syntax, because it will break due to
|
||||
// '%2F%2F' (//) in redirect_uri
|
||||
// url: '/api/oauth2/v1/complete/*',
|
||||
url: new RegExp('/api/oauth2/v1/complete'),
|
||||
response: {
|
||||
// '/api/oauth2/v1/complete/*',
|
||||
statusCode: 401,
|
||||
body: {
|
||||
statusCode: 401,
|
||||
error: 'accept_required',
|
||||
},
|
||||
status: 401,
|
||||
}).as('complete');
|
||||
|
||||
cy.login({ accounts: ['default'] });
|
||||
@@ -230,7 +218,8 @@ describe('OAuth', () => {
|
||||
|
||||
assertPermissions();
|
||||
|
||||
cy.server({ enable: false });
|
||||
// stop stubbing so the click below hits the real backend
|
||||
cy.intercept('POST', new RegExp('/api/oauth2/v1/complete'), (req) => req.continue());
|
||||
|
||||
cy.findByTestId('auth-controls').contains('Approve').click();
|
||||
|
||||
@@ -238,19 +227,15 @@ describe('OAuth', () => {
|
||||
});
|
||||
|
||||
it('should redirect to error page, when permission request declined', () => {
|
||||
cy.server();
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
cy.intercept('POST', new RegExp('/api/oauth2/v1/complete'), {
|
||||
// NOTE: can not use cypress glob syntax, because it will break due to
|
||||
// '%2F%2F' (//) in redirect_uri
|
||||
// url: '/api/oauth2/v1/complete/*',
|
||||
url: new RegExp('/api/oauth2/v1/complete'),
|
||||
response: {
|
||||
// '/api/oauth2/v1/complete/*',
|
||||
statusCode: 401,
|
||||
body: {
|
||||
statusCode: 401,
|
||||
error: 'accept_required',
|
||||
},
|
||||
status: 401,
|
||||
}).as('complete');
|
||||
|
||||
cy.login({ accounts: ['default'] });
|
||||
@@ -267,7 +252,8 @@ describe('OAuth', () => {
|
||||
|
||||
assertPermissions();
|
||||
|
||||
cy.server({ enable: false });
|
||||
// stop stubbing so the click below hits the real backend
|
||||
cy.intercept('POST', new RegExp('/api/oauth2/v1/complete'), (req) => req.continue());
|
||||
|
||||
cy.findByTestId('auth-controls-secondary').contains('Decline').click();
|
||||
|
||||
@@ -342,25 +328,16 @@ describe('OAuth', () => {
|
||||
|
||||
cy.url().should('include', '/password');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
response: {
|
||||
success: false,
|
||||
errors: { totp: 'error.totp_required' },
|
||||
},
|
||||
cy.intercept('POST', '/api/authentication/login', {
|
||||
success: false,
|
||||
errors: { totp: 'error.totp_required' },
|
||||
});
|
||||
|
||||
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
||||
|
||||
cy.url().should('include', '/mfa');
|
||||
|
||||
// Undo request response mock
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
});
|
||||
cy.intercept('POST', '/api/authentication/login', (req) => req.continue());
|
||||
|
||||
cy.get('[name=totp]').type('123456{enter}');
|
||||
|
||||
@@ -371,26 +348,21 @@ describe('OAuth', () => {
|
||||
describe('Deleted account', () => {
|
||||
it('should show account switcher and then abort oauth and redirect to profile', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: account.id,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: account.username,
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: account.email,
|
||||
isActive: true,
|
||||
isDeleted: true, // force user into the deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: account.id,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: account.username,
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: account.email,
|
||||
isActive: true,
|
||||
isDeleted: true, // force user into the deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
||||
|
||||
@@ -412,26 +384,21 @@ describe('OAuth', () => {
|
||||
|
||||
cy.url().should('include', '/password');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // force user into the deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // force user into the deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
||||
|
||||
+13
-42
@@ -6,24 +6,15 @@ describe('Register', () => {
|
||||
const captchaCode = 'captchaCode';
|
||||
const activationKey = 'activationKey';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup',
|
||||
response: {
|
||||
success: true,
|
||||
},
|
||||
cy.intercept('POST', '/api/signup', {
|
||||
success: true,
|
||||
}).as('signup');
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
updateState: false,
|
||||
rawApiResp: true,
|
||||
}).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup/confirm',
|
||||
response: account,
|
||||
}).as('activate');
|
||||
cy.intercept('POST', '/api/signup/confirm', account).as('activate');
|
||||
});
|
||||
cy.visit('/');
|
||||
|
||||
@@ -47,7 +38,7 @@ describe('Register', () => {
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@signup')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -65,7 +56,7 @@ describe('Register', () => {
|
||||
|
||||
cy.get('[name=key]').type(`${activationKey}{enter}`);
|
||||
|
||||
cy.wait('@activate').its('requestBody').should('eq', `key=${activationKey}`);
|
||||
cy.wait('@activate').its('request.body').should('eq', `key=${activationKey}`);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
@@ -76,13 +67,8 @@ describe('Register', () => {
|
||||
const password = String(Date.now());
|
||||
const captchaCode = 'captchaCode';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup',
|
||||
response: {
|
||||
success: true,
|
||||
},
|
||||
cy.intercept('POST', '/api/signup', {
|
||||
success: true,
|
||||
}).as('signup');
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
@@ -122,17 +108,12 @@ describe('Register', () => {
|
||||
it('should allow activation', () => {
|
||||
const activationKey = 'activationKey';
|
||||
|
||||
cy.server();
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
updateState: false,
|
||||
rawApiResp: true,
|
||||
}).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup/confirm',
|
||||
response: account,
|
||||
}).as('activate');
|
||||
cy.intercept('POST', '/api/signup/confirm', account).as('activate');
|
||||
});
|
||||
cy.visit('/register');
|
||||
|
||||
@@ -142,7 +123,7 @@ describe('Register', () => {
|
||||
|
||||
cy.get('[name=key]').type(`${activationKey}{enter}`);
|
||||
|
||||
cy.wait('@activate').its('requestBody').should('eq', `key=${activationKey}`);
|
||||
cy.wait('@activate').its('request.body').should('eq', `key=${activationKey}`);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
@@ -150,17 +131,12 @@ describe('Register', () => {
|
||||
it('should read activation key from url', () => {
|
||||
const activationKey = 'activationKey';
|
||||
|
||||
cy.server();
|
||||
cy.login({
|
||||
accounts: ['default'],
|
||||
updateState: false,
|
||||
rawApiResp: true,
|
||||
}).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup/confirm',
|
||||
response: account,
|
||||
}).as('activate');
|
||||
cy.intercept('POST', '/api/signup/confirm', account).as('activate');
|
||||
});
|
||||
cy.visit(`/activation/${activationKey}`);
|
||||
|
||||
@@ -168,7 +144,7 @@ describe('Register', () => {
|
||||
cy.get('[name=key]').should('have.attr', 'readonly');
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@activate').its('requestBody').should('eq', `key=${activationKey}`);
|
||||
cy.wait('@activate').its('request.body').should('eq', `key=${activationKey}`);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
@@ -177,12 +153,7 @@ describe('Register', () => {
|
||||
const email = `${Date.now()}@gmail.com`;
|
||||
const captchaCode = 'captchaCode';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/signup/repeat-message',
|
||||
response: { success: true },
|
||||
}).as('resend');
|
||||
cy.intercept('POST', '/api/signup/repeat-message', { success: true }).as('resend');
|
||||
cy.visit('/register');
|
||||
|
||||
cy.findByTestId('auth-controls-secondary').contains('not received').click();
|
||||
@@ -199,7 +170,7 @@ describe('Register', () => {
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@resend')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should('eq', new URLSearchParams({ email, captcha: captchaCode }).toString());
|
||||
|
||||
cy.location('pathname').should('eq', '/activation');
|
||||
+72
-109
@@ -93,14 +93,9 @@ describe('Sign in / Log out', () => {
|
||||
|
||||
cy.url().should('include', '/password');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
response: {
|
||||
success: false,
|
||||
errors: { totp: 'error.totp_required' },
|
||||
},
|
||||
cy.intercept('POST', '/api/authentication/login', {
|
||||
success: false,
|
||||
errors: { totp: 'error.totp_required' },
|
||||
});
|
||||
|
||||
cy.get('[name=password]').type(account1.password);
|
||||
@@ -108,14 +103,11 @@ describe('Sign in / Log out', () => {
|
||||
|
||||
cy.url().should('include', '/mfa');
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/authentication/login',
|
||||
}).as('login');
|
||||
cy.intercept('POST', '/api/authentication/login', (req) => req.continue()).as('login');
|
||||
|
||||
cy.get('[name=totp]').type('123{enter}');
|
||||
|
||||
cy.wait('@login').its('requestBody').should('include', 'totp=123');
|
||||
cy.wait('@login').its('request.body').should('include', 'totp=123');
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
});
|
||||
@@ -143,37 +135,29 @@ describe('Sign in / Log out', () => {
|
||||
cy.get('[name=password]').type(account1.password);
|
||||
cy.get('[name=rememberMe]').should('be.checked');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account1.id}/rules`,
|
||||
}).as('rulesAgreement');
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('POST', `/api/v1/accounts/${account1.id}/rules`).as('rulesAgreement');
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse);
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.location('pathname').should('eq', '/accept-rules');
|
||||
|
||||
cy.get('[type=submit]').last().click(); // add .last() to match the new state during its transition
|
||||
cy.wait('@rulesAgreement').its('requestBody').should('be.empty');
|
||||
cy.get('[type=submit]').click();
|
||||
cy.wait('@rulesAgreement').its('request.body').should('be.empty');
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
cy.findByTestId('profile-index').should('contain', account1.username);
|
||||
@@ -189,26 +173,21 @@ describe('Sign in / Log out', () => {
|
||||
cy.get('[name=password]').type(account1.password);
|
||||
cy.get('[name=rememberMe]').should('be.checked');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse);
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
@@ -230,26 +209,21 @@ describe('Sign in / Log out', () => {
|
||||
cy.get('[name=password]').type(account1.password);
|
||||
cy.get('[name=rememberMe]').should('be.checked');
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // force user to accept updated user agreement
|
||||
} as UserResponse);
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
@@ -259,34 +233,27 @@ describe('Sign in / Log out', () => {
|
||||
|
||||
cy.location('pathname').should('eq', '/profile/delete');
|
||||
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
}).as('deleteAccount');
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // mock deleted state since the delete will not perform the real request
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // rules still aren't accepted
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account1.id}`).as('deleteAccount');
|
||||
cy.intercept('GET', `/api/v1/accounts/${account1.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // mock deleted state since the delete will not perform the real request
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: true, // rules still aren't accepted
|
||||
} as UserResponse);
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -294,16 +261,12 @@ describe('Sign in / Log out', () => {
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account1.id}`,
|
||||
response: { success: true },
|
||||
}).as('deleteAccount');
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account1.id}`, { success: true }).as('deleteAccount');
|
||||
|
||||
confirmWithPassword(account1.password);
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
+4
-14
@@ -17,19 +17,9 @@ describe('Applications', () => {
|
||||
});
|
||||
|
||||
it('should add website app', () => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/v1/oauth2/*/reset',
|
||||
}).as('revoke');
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/oauth2/*',
|
||||
}).as('delete');
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: '/api/v1/oauth2/*/reset?regenerateSecret',
|
||||
}).as('revokeSecret');
|
||||
cy.intercept('POST', '/api/v1/oauth2/*/reset').as('revoke');
|
||||
cy.intercept('DELETE', '/api/v1/oauth2/*').as('delete');
|
||||
cy.intercept('POST', '/api/v1/oauth2/*/reset?regenerateSecret').as('revokeSecret');
|
||||
|
||||
cy.visit('/dev/applications');
|
||||
|
||||
@@ -66,7 +56,7 @@ describe('Applications', () => {
|
||||
|
||||
// test reset client secret
|
||||
cy.findByTestId('client-secret').then(([el]) => {
|
||||
const prevSecret = el.value;
|
||||
const prevSecret = (el as HTMLInputElement).value;
|
||||
|
||||
cy.findByTestId('client-secret').should('have.value', prevSecret);
|
||||
cy.contains('Reset Client Secret').click();
|
||||
+16
-36
@@ -6,21 +6,11 @@ describe('Profile — Change Email', () => {
|
||||
const key2 = 'key1232';
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/email-verification`,
|
||||
}).as('requestEmailChange');
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/new-email-verification`,
|
||||
response: { success: true },
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/email-verification`).as('requestEmailChange');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/new-email-verification`, {
|
||||
success: true,
|
||||
}).as('verifyNewEmail');
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/email`,
|
||||
response: { success: true },
|
||||
}).as('saveEmail');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/email`, { success: true }).as('saveEmail');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
@@ -31,7 +21,7 @@ describe('Profile — Change Email', () => {
|
||||
cy.contains('Send E‑mail').click();
|
||||
|
||||
cy.wait('@requestEmailChange')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -39,16 +29,14 @@ describe('Profile — Change Email', () => {
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/email-verification`,
|
||||
response: { success: true },
|
||||
}).as('requestEmailChange');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/email-verification`, { success: true }).as(
|
||||
'requestEmailChange',
|
||||
);
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@requestEmailChange')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -63,7 +51,7 @@ describe('Profile — Change Email', () => {
|
||||
cy.findByTestId('step2').find('[name=email]').type(`${account.email}{enter}`);
|
||||
|
||||
cy.wait('@verifyNewEmail')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -78,7 +66,7 @@ describe('Profile — Change Email', () => {
|
||||
cy.findByTestId('step3').find('[name=key]').type(`${key2}{enter}`);
|
||||
|
||||
cy.wait('@saveEmail')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -107,11 +95,8 @@ describe('Profile — Change Email', () => {
|
||||
const key = 'key123';
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/new-email-verification`,
|
||||
response: { success: true },
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/new-email-verification`, {
|
||||
success: true,
|
||||
}).as('verifyNewEmail');
|
||||
|
||||
cy.visit(`/profile/change-email/step2/${key}`);
|
||||
@@ -121,7 +106,7 @@ describe('Profile — Change Email', () => {
|
||||
cy.findByTestId('step2').find('[name=email]').type(`${account.email}{enter}`);
|
||||
|
||||
cy.wait('@verifyNewEmail')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -138,12 +123,7 @@ describe('Profile — Change Email', () => {
|
||||
const key = 'key123';
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/email`,
|
||||
response: { success: true },
|
||||
}).as('saveEmail');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/email`, { success: true }).as('saveEmail');
|
||||
|
||||
cy.visit(`/profile/change-email/step3/${key}`);
|
||||
|
||||
@@ -152,7 +132,7 @@ describe('Profile — Change Email', () => {
|
||||
|
||||
cy.findByTestId('change-email').find('[type=submit]').click();
|
||||
|
||||
cy.wait('@saveEmail').its('requestBody').should(
|
||||
cy.wait('@saveEmail').its('request.body').should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
key,
|
||||
+8
-20
@@ -3,11 +3,7 @@ import { openSectionByName, confirmWithPassword } from './utils';
|
||||
describe('Profile — Change password', () => {
|
||||
it('should change password', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/password`,
|
||||
}).as('password');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/password`).as('password');
|
||||
cy.visit('/');
|
||||
|
||||
openSectionByName('Password');
|
||||
@@ -20,7 +16,7 @@ describe('Profile — Change password', () => {
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@password')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -34,7 +30,7 @@ describe('Profile — Change password', () => {
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@password')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -51,15 +47,10 @@ describe('Profile — Change password', () => {
|
||||
|
||||
it('should close password popup if form has errors', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/password`,
|
||||
response: {
|
||||
success: false,
|
||||
errors: {
|
||||
password: 'force popup to be shown',
|
||||
},
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/password`, {
|
||||
success: false,
|
||||
errors: {
|
||||
password: 'force popup to be shown',
|
||||
},
|
||||
}).as('password');
|
||||
cy.visit('/');
|
||||
@@ -75,10 +66,7 @@ describe('Profile — Change password', () => {
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
// disable response mocks
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/password`,
|
||||
}).as('password');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/password`, (req) => req.continue()).as('password');
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
+19
-31
@@ -4,33 +4,24 @@ import { openSectionByName, confirmWithPassword } from './utils';
|
||||
|
||||
describe('Profile — Change Username', () => {
|
||||
it('should change username', () => {
|
||||
cy.server();
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'FooBar',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'FooBar',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/username`,
|
||||
}).as('username');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/username`).as('username');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
@@ -41,13 +32,10 @@ describe('Profile — Change Username', () => {
|
||||
cy.get('[name=username]').type(`{selectall}${account.username}{enter}`);
|
||||
|
||||
// unmock accounts route
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, (req) => req.continue());
|
||||
|
||||
cy.wait('@username')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -59,7 +47,7 @@ describe('Profile — Change Username', () => {
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@username')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -0,0 +1,60 @@
|
||||
import { openSectionByName, confirmWithPassword } from './utils';
|
||||
import { UserResponse } from 'app/services/api/accounts';
|
||||
|
||||
describe('Profile — Delete account', () => {
|
||||
it('should delete account', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account.id}`).as('deleteAccount');
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`);
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
openSectionByName('Account deletion');
|
||||
|
||||
cy.location('pathname').should('eq', '/profile/delete');
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
password: '',
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account.id}`, { success: true }).as('deleteAccount');
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // mock deleted state since the delete will not perform the real request
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
password: account.password,
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
|
||||
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-6
@@ -5,12 +5,7 @@ describe('feedback popup', () => {
|
||||
const subject = 'Hello world';
|
||||
const message = 'This is a feedback message';
|
||||
|
||||
cy.server();
|
||||
cy.route({
|
||||
url: '/api/feedback',
|
||||
method: 'POST',
|
||||
response: { success: true },
|
||||
});
|
||||
cy.intercept('POST', '/api/feedback', { success: true });
|
||||
|
||||
cy.login({ accounts: ['default'] });
|
||||
|
||||
+2
-7
@@ -24,12 +24,7 @@ describe('Change locale', () => {
|
||||
|
||||
it('should change locale from the profile', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/language`,
|
||||
response: { success: true },
|
||||
}).as('language');
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/language`, { success: true }).as('language');
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
@@ -41,7 +36,7 @@ describe('Change locale', () => {
|
||||
|
||||
cy.findByTestId('languages-list-item').contains('Belarusian').click();
|
||||
|
||||
cy.wait('@language').its('requestBody').should('eq', 'lang=be');
|
||||
cy.wait('@language').its('request.body').should('eq', 'lang=be');
|
||||
|
||||
cy.findByTestId('language-switcher').should('not.be.visible');
|
||||
cy.findByTestId('profile-index').should('contain', 'Беларуская');
|
||||
+47
-66
@@ -7,11 +7,9 @@ describe('Profile — mfa', () => {
|
||||
const totp = 'totp123';
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/two-factor-auth`,
|
||||
response: { success: false, errors: { password: 'foo' } },
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/two-factor-auth`, {
|
||||
success: false,
|
||||
errors: { password: 'foo' },
|
||||
}).as('mfaSaved');
|
||||
|
||||
cy.visit('/');
|
||||
@@ -38,7 +36,7 @@ describe('Profile — mfa', () => {
|
||||
cy.get('[name=totp]').type(`${totp}{enter}`);
|
||||
|
||||
cy.wait('@mfaSaved')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -47,35 +45,29 @@ describe('Profile — mfa', () => {
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/two-factor-auth`,
|
||||
response: { success: true },
|
||||
}).as('mfaSaved');
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: true, // fake enabled mfa
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/two-factor-auth`, { success: true }).as(
|
||||
'mfaSaved',
|
||||
);
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: true, // fake enabled mfa
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@mfaSaved')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -93,30 +85,24 @@ describe('Profile — mfa', () => {
|
||||
const totp = 'totp123';
|
||||
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: true, // fake enabled mfa
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account.id}/two-factor-auth`,
|
||||
response: { success: false, errors: { password: 'foo' } },
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: true, // fake enabled mfa
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account.id}/two-factor-auth`, {
|
||||
success: false,
|
||||
errors: { password: 'foo' },
|
||||
}).as('mfaSaved');
|
||||
|
||||
cy.visit('/');
|
||||
@@ -131,7 +117,7 @@ describe('Profile — mfa', () => {
|
||||
cy.get('[name=totp]').type(`${totp}{enter}`);
|
||||
|
||||
cy.wait('@mfaSaved')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -140,21 +126,16 @@ describe('Profile — mfa', () => {
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account.id}/two-factor-auth`,
|
||||
response: { success: true },
|
||||
}).as('mfaSaved');
|
||||
cy.intercept('DELETE', `/api/v1/accounts/${account.id}/two-factor-auth`, { success: true }).as(
|
||||
'mfaSaved',
|
||||
);
|
||||
// unmock accounts route
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
});
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, (req) => req.continue());
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@mfaSaved')
|
||||
.its('requestBody')
|
||||
.its('request.body')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
@@ -0,0 +1,50 @@
|
||||
import { UserResponse } from 'app/services/api/accounts';
|
||||
|
||||
describe('Profile — Restore account', () => {
|
||||
it('should restore account', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'FooBar',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // force deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
cy.intercept('POST', `/api/v1/accounts/${account.id}/restore`, { success: true }).as('restoreAccount');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.intercept('GET', `/api/v1/accounts/${account.id}`, {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false, // force deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse);
|
||||
|
||||
cy.findByTestId('deletedAccount').contains('Restore account').click();
|
||||
|
||||
cy.wait('@restoreAccount');
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
|
||||
cy.findByTestId('profile-index').should('contain', account.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
File renamed without changes.
@@ -1,75 +0,0 @@
|
||||
import { openSectionByName, confirmWithPassword } from './utils';
|
||||
import { UserResponse } from 'app/services/api/accounts';
|
||||
|
||||
describe('Profile — Delete account', () => {
|
||||
it('should delete account', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
}).as('deleteAccount');
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
openSectionByName('Account deletion');
|
||||
|
||||
cy.location('pathname').should('eq', '/profile/delete');
|
||||
|
||||
cy.get('[type=submit]').click();
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('requestBody')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
password: '',
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.route({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: { success: true },
|
||||
}).as('deleteAccount');
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // mock deleted state since the delete will not perform the real request
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
|
||||
confirmWithPassword(account.password);
|
||||
|
||||
cy.wait('@deleteAccount')
|
||||
.its('requestBody')
|
||||
.should(
|
||||
'eq',
|
||||
new URLSearchParams({
|
||||
password: account.password,
|
||||
}).toString(),
|
||||
);
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
|
||||
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { UserResponse } from 'app/services/api/accounts';
|
||||
|
||||
describe('Profile — Restore account', () => {
|
||||
it('should restore account', () => {
|
||||
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'FooBar',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: true, // force deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: `/api/v1/accounts/${account.id}/restore`,
|
||||
response: { success: true },
|
||||
}).as('restoreAccount');
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.route({
|
||||
method: 'GET',
|
||||
url: `/api/v1/accounts/${account.id}`,
|
||||
response: {
|
||||
id: 7,
|
||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||
username: 'SleepWalker',
|
||||
isOtpEnabled: false,
|
||||
registeredAt: 1475568334,
|
||||
lang: 'en',
|
||||
elyProfileLink: 'http://ely.by/u7',
|
||||
email: 'danilenkos@auroraglobal.com',
|
||||
isActive: true,
|
||||
isDeleted: false, // force deleted state
|
||||
passwordChangedAt: 1476075696,
|
||||
hasMojangUsernameCollision: true,
|
||||
shouldAcceptRules: false,
|
||||
} as UserResponse,
|
||||
});
|
||||
|
||||
cy.findByTestId('deletedAccount').contains('Restore account').click();
|
||||
|
||||
cy.wait('@restoreAccount');
|
||||
|
||||
cy.location('pathname').should('eq', '/');
|
||||
|
||||
cy.findByTestId('profile-index').should('contain', account.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
// export default (on, config) => {
|
||||
// // `on` is used to hook into various events Cypress emits
|
||||
// // `config` is the resolved Cypress config
|
||||
// };
|
||||
const wp = require('@cypress/webpack-preprocessor');
|
||||
|
||||
// for some reason loader can not locate babel.config. So we load it manually
|
||||
const config = require('../../../babel.config');
|
||||
const babelEnvName = 'browser-development';
|
||||
|
||||
module.exports = (on) => {
|
||||
const options = {
|
||||
webpackOptions: {
|
||||
mode: 'development',
|
||||
// webpack will transpile TS and JS files
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.json'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.[tj]s$/,
|
||||
exclude: [/node_modules/],
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
envName: babelEnvName,
|
||||
cacheDirectory: true,
|
||||
// We don't have the webpack's API object, so just provide necessary methods
|
||||
...config({
|
||||
env(value) {
|
||||
// see @babel/core/lib/config/helpers/config-api.js
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return value === babelEnvName;
|
||||
case 'function':
|
||||
return value(babelEnvName);
|
||||
|
||||
case 'undefined':
|
||||
return babelEnvName;
|
||||
default:
|
||||
if (Array.isArray(value)) {
|
||||
throw new Error('Unimplemented env() argument');
|
||||
}
|
||||
|
||||
throw new Error('Invalid env() argument');
|
||||
}
|
||||
},
|
||||
cache: {
|
||||
using() {},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
on('file:preprocessor', wp(options));
|
||||
};
|
||||
@@ -1,34 +1,44 @@
|
||||
import '@testing-library/cypress/add-commands';
|
||||
|
||||
import { account1, account2 } from '../fixtures/accounts';
|
||||
import { account1, account2 } from '../fixtures/accounts.json';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
// During a panel transition PanelTransition (app/components/auth/PanelTransition.tsx) keeps both the leaving
|
||||
// and the entering panels in the DOM. Each panel is marked with `data-e2e-panel-active`, so the queries below
|
||||
// ignore everything inside an inactive panel and tests don't need to know about the transitions at all.
|
||||
const panelAwareQueries: Array<keyof Cypress.Chainable> = [
|
||||
'get',
|
||||
'contains',
|
||||
'findByLabelText',
|
||||
'findAllByLabelText',
|
||||
'findByPlaceholderText',
|
||||
'findAllByPlaceholderText',
|
||||
'findByText',
|
||||
'findAllByText',
|
||||
'findByDisplayValue',
|
||||
'findAllByDisplayValue',
|
||||
'findByAltText',
|
||||
'findAllByAltText',
|
||||
'findByTitle',
|
||||
'findAllByTitle',
|
||||
'findByRole',
|
||||
'findAllByRole',
|
||||
'findByTestId',
|
||||
'findAllByTestId',
|
||||
];
|
||||
|
||||
const accountsMap = {
|
||||
panelAwareQueries.forEach((command) => {
|
||||
Cypress.Commands.overwriteQuery(command, function (originalFn, ...args) {
|
||||
const getElements = originalFn.apply(this, args);
|
||||
|
||||
return (subject: unknown) => {
|
||||
return getElements(subject).filter(
|
||||
(_index: number, el: HTMLElement) => !el.closest('[data-e2e-panel-active="false"]'),
|
||||
);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const accountsMap: Record<AccountAlias, typeof account1> = {
|
||||
default: account1,
|
||||
default2: account2,
|
||||
};
|
||||
@@ -36,14 +46,10 @@ const accountsMap = {
|
||||
Cypress.Commands.add('login', async ({ accounts, updateState = true, rawApiResp = false }) => {
|
||||
const accountsData = await Promise.all(
|
||||
accounts.map(async (account) => {
|
||||
let credentials;
|
||||
const credentials = accountsMap[account];
|
||||
|
||||
if (account) {
|
||||
credentials = accountsMap[account];
|
||||
|
||||
if (!credentials) {
|
||||
throw new Error(`Unknown account name: ${account}`);
|
||||
}
|
||||
if (!credentials) {
|
||||
throw new Error(`Unknown account name: ${account}`);
|
||||
}
|
||||
|
||||
const resp = await fetch('/api/authentication/login', {
|
||||
@@ -82,7 +88,7 @@ Cypress.Commands.add('login', async ({ accounts, updateState = true, rawApiResp
|
||||
return { accounts: accountsData };
|
||||
});
|
||||
|
||||
function createState(accounts) {
|
||||
function createState(accounts: Array<{ id: number }>) {
|
||||
return {
|
||||
accounts: {
|
||||
available: accounts,
|
||||
@@ -0,0 +1,28 @@
|
||||
import './commands';
|
||||
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
// The browser extends the system's language. Not everyone so cool to use English on their workstation,
|
||||
// so we must force browser's language to be English to let tests, based on buttons labels, work
|
||||
Object.defineProperty(win.navigator, 'languages', {
|
||||
get() {
|
||||
return ['en-US', 'en'];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Headless Chromium-based browsers deny the clipboard access by default, which makes the app's "copy" buttons
|
||||
// reject with an unhandled error. Grant the permission like a real user would do it
|
||||
if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {
|
||||
cy.wrap(
|
||||
Cypress.automation('remote:debugger:protocol', {
|
||||
command: 'Browser.grantPermissions',
|
||||
params: {
|
||||
permissions: ['clipboardReadWrite', 'clipboardSanitizedWrite'],
|
||||
origin: Cypress.config('baseUrl'),
|
||||
},
|
||||
}),
|
||||
{ log: false },
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import './commands';
|
||||
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
// Remove fetch to enable correct api mocking with the cypress xhr mocks
|
||||
win.fetch = null;
|
||||
// The browser extends the system's language. Not everyone so cool to use English on their workstation,
|
||||
// so we must force browser's language to be English to let tests, based on buttons labels, work
|
||||
Object.defineProperty(win.navigator, 'languages', {
|
||||
get() {
|
||||
return ['en-US', 'en'];
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "account.ely.by e2e test suit",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "cypress open",
|
||||
"test": "cypress open --e2e --browser chromium",
|
||||
"test:ci": "cypress run"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -12,9 +12,7 @@
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "^5.4.1",
|
||||
"@testing-library/cypress": "^6.0.0",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"cypress": "^4.6.0"
|
||||
"@testing-library/cypress": "^10.0.3",
|
||||
"cypress": "^16.1.1"
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -1,16 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es6", "dom"],
|
||||
"types": ["cypress", "@types/testing-library__cypress"],
|
||||
"resolveJsonModule": true,
|
||||
"noImplicitAny": false
|
||||
// cypress ships its own types (not under @types/), and root doesn't declare an
|
||||
// explicit "types" array, so these need to be listed here to get `cy`/`Cypress`
|
||||
// globals without an explicit reference/import in every spec file.
|
||||
// "node" is for the Cypress config and the app's sources, reachable through the `app/...` type imports
|
||||
"types": ["cypress", "@testing-library/cypress", "node"],
|
||||
// fixtures are imported as `*.json`; root disables this because it conflicts with
|
||||
// `*.intl.json` overrides in the app, which doesn't apply to the test suite
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["cypress/**/*"]
|
||||
"include": ["../@types", "cypress/**/*", "cypress.config.ts"]
|
||||
}
|
||||
Reference in new issue
Block a user