Skip to content

Commit

Permalink
feat: Improve Cold Start requests by adding timeout and blocking user…
Browse files Browse the repository at this point in the history
… input | [#261]

* feat: handle loading and server timeout in StartMenu | [#261]

* feat: handle register loading and timeout in RegisterMenu | [#261]

* feat: cancel requests when timeout for login + autologin + register | [#261]

* fix: revert mocked token | [#261]

* feat: move loading circle into the button | [#261]

* test: add unit tests for login + auto login + register request timeout | [#261]

* refactor: remove unused and refactor css | [#261]
  • Loading branch information
gigigimay authored Oct 24, 2024
1 parent 6c2e117 commit 93bf0d4
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 219 deletions.
392 changes: 224 additions & 168 deletions Electron/src/__tests__/pages/RegisterMenu.test.tsx

Large diffs are not rendered by default.

60 changes: 59 additions & 1 deletion Electron/src/__tests__/pages/StartMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import {
import '@testing-library/jest-dom';
import StartMenu from 'pages/StartMenu/StartMenu';
import { getToken } from 'utils/token';
import timeout from 'utils/timeout';
import { CancelablePromise } from 'swagger/api';
import { LoginService } from '../../swagger/api/services/LoginService';

jest.mock('../../swagger/api/services/LoginService');
jest.mock('utils/token');
jest.mock('utils/timeout');

describe('StartMenu Component', () => {
const setIsLoggedMock = jest.fn();
const setIsSigningUpMock = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

test('renders the component and displays title and form', async () => {
Expand Down Expand Up @@ -80,7 +84,25 @@ describe('StartMenu Component', () => {
).mockResolvedValue(undefined);
(getToken as jest.Mock).mockReturnValue('mock-jwt-token');

await act(async () => {
render(
<StartMenu
setIsLogged={setIsLoggedMock}
setIsSigningUp={setIsSigningUpMock}
/>,
);
});

expect(setIsLoggedMock).toHaveBeenCalledWith(true);
});

test('handles auto-login timeout on component mount and displays error popover', async () => {
(getToken as jest.Mock).mockReturnValue('mock-jwt-token');
const mockLoginPromise = new CancelablePromise(() => {});
(
LoginService.loginUserWithJwtLoginTokenTokenPost as jest.Mock
).mockImplementationOnce(() => mockLoginPromise);
(timeout as jest.Mock).mockRejectedValueOnce(new Error('Timeout'));

await act(async () => {
render(
Expand All @@ -91,7 +113,10 @@ describe('StartMenu Component', () => {
);
});

expect(setIsLoggedMock).toHaveBeenCalledWith(true);
expect(mockLoginPromise.isCancelled).toBe(true);
expect(
screen.getByText('El servidor esta iniciándose'),
).toBeInTheDocument();
});

test('handles login error and displays error popover', async () => {
Expand Down Expand Up @@ -124,6 +149,39 @@ describe('StartMenu Component', () => {
).toBeInTheDocument();
});

test('handles login timeout and displays error popover', async () => {
const mockLoginPromise = new CancelablePromise(() => {});
(LoginService.loginUserLoginPost as jest.Mock).mockImplementationOnce(
() => mockLoginPromise,
);
(timeout as jest.Mock).mockRejectedValueOnce(new Error('Timeout'));

await act(async () => {
render(
<StartMenu
setIsLogged={setIsLoggedMock}
setIsSigningUp={setIsSigningUpMock}
/>,
);
});

fireEvent.change(screen.getByPlaceholderText('Nombre de usuario'), {
target: { value: 'user123' },
});
fireEvent.change(screen.getByPlaceholderText('Contraseña'), {
target: { value: 'password123' },
});
await act(async () => {
fireEvent.click(screen.getByText('Iniciar sesión'));
});

expect(mockLoginPromise.isCancelled).toBe(true);
expect(setIsLoggedMock).toHaveBeenCalledWith(false);
expect(
screen.getByText('El servidor esta iniciándose'),
).toBeInTheDocument();
});

test('handles register button click', async () => {
await act(async () => {
render(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CircularProgress from '@mui/material/CircularProgress/';

export default function LoadingCircleSmall() {
return (
<CircularProgress
style={{ width: '1rem', height: 'auto', margin: 'auto 0.5rem' }}
sx={{
' & .MuiCircularProgress-circle': {
color: 'var(--pure-white)',
},
'& .css-zk81sn-MuiCircularProgress-root': {
width: '3rem',
},
}}
/>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getTokenRole } from 'utils/token';
import { InfoPopoverType } from 'components/AdvancedUIComponents/InfoPopOver/types/InfoPopover';
import ConfirmationModal from 'components/AdvancedUIComponents/InfoPopOver/InfoPopover';
import UserType from 'utils/role';
import LoadingCircleSmall from 'components/AdvancedUIComponents/LoadingCircle/LoadingCircleSmallNoPadding';
import LoadingCircleSmall from 'components/AdvancedUIComponents/LoadingCircle/LoadingCircleSmall';
import { getGenreFromString } from 'utils/genre';
import GenreOption from './GenreOption/GenreOption';
import styles from './addSongPlayListAccordion.module.css';
Expand Down
2 changes: 2 additions & 0 deletions Electron/src/global/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Global {
export const songArchitecture: SongArchitecture =
SongArchitecture.BLOB_ARCHITECTURE;

export const coldStartRequestTimeout = 5000;

export interface HandleUrlChangeResponse {
canGoBack: boolean | undefined;
canGoForward: boolean | undefined;
Expand Down
29 changes: 25 additions & 4 deletions Electron/src/pages/StartMenu/RegisterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
PropsInfoPopover,
} from 'components/AdvancedUIComponents/InfoPopOver/types/InfoPopover';
import timeout from 'utils/timeout';
import Global from 'global/global';
import LoadingCircleSmall from 'components/AdvancedUIComponents/LoadingCircle/LoadingCircleSmall';
import { CancelablePromise } from 'swagger/api';
import styles from './startMenu.module.css';
import SpotifyElectronLogo from '../../assets/imgs/SpotifyElectronLogo.png';
import { UsersService } from '../../swagger/api/services/UsersService';
Expand Down Expand Up @@ -39,6 +42,9 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
null,
);

/* Loading state */
const [loading, setLoading] = useState(false);

const handleRegister = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (
Expand Down Expand Up @@ -76,16 +82,22 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
return;
}

let createUserPromise: CancelablePromise<any> | null = null;
try {
const createUserPromise = UsersService.createUserUsersPost(
setLoading(true);

createUserPromise = UsersService.createUserUsersPost(
formData.name,
formData.photo,
formData.password,
);

await Promise.race([createUserPromise, timeout(3000)]);
setisOpenPopover(true);
await Promise.race([
createUserPromise,
timeout(Global.coldStartRequestTimeout),
]);

setisOpenPopover(true);
setPropsPopOver({
title: 'Usuario registrado',
description: 'El usuario ha sido registrado con éxito',
Expand All @@ -97,12 +109,15 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
},
});
} catch (error: unknown) {
setLoading(false);
console.log('Unable to register');

let title: string;
let description: string;

if (error instanceof Error && error.message === 'Timeout') {
createUserPromise?.cancel();

title = 'El servidor esta iniciándose';
description =
'El servidor esta iniciándose (cold-start), inténtelo de nuevo en 1 minuto';
Expand Down Expand Up @@ -155,6 +170,7 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
id="name"
placeholder="Nombre de usuario"
onChange={handleChange}
disabled={loading}
spellCheck={false}
required
/>
Expand All @@ -170,6 +186,7 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
id="photo"
placeholder="Foto de perfil"
onChange={handleChange}
disabled={loading}
spellCheck={false}
required
/>
Expand All @@ -187,6 +204,7 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
id="password"
placeholder="Contraseña"
onChange={handleChange}
disabled={loading}
spellCheck={false}
required
/>
Expand All @@ -203,6 +221,7 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
id="confirmpassword"
placeholder="Confirma tu contraseña"
onChange={handleChange}
disabled={loading}
spellCheck={false}
required
/>
Expand All @@ -215,9 +234,10 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
backgroundColor: 'var(--app-logo-color)',
}}
className={`${styles.registerButton}`}
disabled={loading}
onClick={handleRegister}
>
Registrar
Registrar {loading && <LoadingCircleSmall />}
</button>
</form>

Expand All @@ -231,6 +251,7 @@ export default function RegisterMenu({ setIsSigningUp }: PropsRegisterMenu) {
</p>
<button
onClick={handleClickLogin}
disabled={loading}
type="button"
style={{
color: 'var(--pure-white)',
Expand Down
Loading

0 comments on commit 93bf0d4

Please sign in to comment.