Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#261: Improve Cold Start requests by adding timeout and blocking user input #264

Merged
merged 7 commits into from
Oct 24, 2024
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