Skip to content

Commit

Permalink
🥅(frontend) handle api errors
Browse files Browse the repository at this point in the history
- add hook to handle api errors.
- add related component tests
  • Loading branch information
daproclaima committed Sep 4, 2024
1 parent 726ffcc commit 293637d
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 1 deletion.
231 changes: 231 additions & 0 deletions src/frontend/apps/desk/src/api/__tests__/useAPIError.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { renderHook } from '@testing-library/react';

import { APIError } from '@/api';

import {
handleAPIErrorCause,
handleAPIServerError,
useAPIError,
} from '../useAPIError';

jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));

describe('handleAPIErrorCause', () => {
it('should handle specific errors and call handleError', () => {
const handleErrorMock = jest.fn();
const causes = ['Mail domain with this name already exists.'];

const errorParams = {
name: {
causes: ['Mail domain with this name already exists.'],
handleError: handleErrorMock,
},
};

const result = handleAPIErrorCause(causes, errorParams);

expect(handleErrorMock).toHaveBeenCalled();
expect(result).toEqual([]);
});

it('should handle multiple causes and return unhandled causes', () => {
const handleErrorMock = jest.fn();
const causes = [
'Mail domain with this name already exists.',
'Unhandled error',
];

const errorParams = {
name: {
causes: ['Mail domain with this name already exists.'],
handleError: handleErrorMock,
},
};

const result = handleAPIErrorCause(causes, errorParams);

expect(handleErrorMock).toHaveBeenCalled();
expect(result).toEqual(['Unhandled error']);
});
});

describe('handleAPIServerError', () => {
it('should prepend the server error message when there are other causes', () => {
const causes = ['Some other error'];
const serverErrorParams = {
defaultMessage: 'Server error',
};

const result = handleAPIServerError(causes, serverErrorParams);

expect(result).toEqual(['Server error', 'Some other error']);
});

it('should only return server error message when no other causes exist', () => {
const causes: string[] = [];
const serverErrorParams = {
defaultMessage: 'Server error',
};

const result = handleAPIServerError(causes, serverErrorParams);

expect(result).toEqual(['Server error']);
});

it('should call handleError when provided as a param', () => {
const handleErrorMock = jest.fn();
const causes: string[] = [];
const serverErrorParams = {
defaultMessage: 'Server error',
handleError: handleErrorMock,
};

handleAPIServerError(causes, serverErrorParams);

expect(handleErrorMock).toHaveBeenCalled();
});
});

describe('useAPIError', () => {
const handleErrorMock = jest.fn();
const handleServerErrorMock = jest.fn();

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

it('should handle Mail domain error correctly', () => {
const error = new APIError('client error', {
cause: ['Mail domain with this name already exists.'],
status: 400,
});

const { result } = renderHook(() =>
useAPIError({
error,
errorParams: {
name: {
causes: [
'Mail domain with this name already exists.',
'Mail domain with this Slug already exists.',
],
handleError: handleErrorMock,
},
},
serverErrorParams: {
handleError: handleServerErrorMock,
},
}),
);

expect(handleErrorMock).toHaveBeenCalled();
expect(result.current).toEqual([]);
});

it('should handle Mailbox error and domain secret error correctly', () => {
const error = new APIError('client error', {
cause: ['Mailbox with this Local_part and Domain already exists.'],
status: 400,
});

const { result } = renderHook(() =>
useAPIError({
error,
errorParams: {
local_part: {
causes: [
'Mailbox with this Local_part and Domain already exists.',
'Mail domain with this Slug already exists.',
],
handleError: handleErrorMock,
},
secret: {
causes: [
"Please configure your domain's secret before creating any mailbox.",
],
handleError: handleErrorMock,
},
},
serverErrorParams: {
handleError: handleServerErrorMock,
},
}),
);

expect(handleErrorMock).toHaveBeenCalledTimes(1);
expect(handleErrorMock).not.toHaveBeenCalledTimes(2);
expect(handleServerErrorMock).not.toHaveBeenCalled();
expect(result.current).toEqual([]);
});

it('should handle the server error and execute handleError provided in params', () => {
const error = new APIError('server error', { status: 500 });

const { result } = renderHook(() =>
useAPIError({
error,
errorParams: undefined,
serverErrorParams: {
handleError: handleServerErrorMock,
},
}),
);

expect(handleServerErrorMock).toHaveBeenCalled();
expect(result.current).toEqual([
'Your request cannot be processed because the server is experiencing an error. If the problem persists, ' +
'please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr',
]);
});

it('should handle error absence gracefully', () => {
const { result } = renderHook(() =>
useAPIError({
error: null,
errorParams: {
local_part: {
causes: [
'Mailbox with this Local_part and Domain already exists.',
'Mail domain with this Slug already exists.',
],
handleError: handleErrorMock,
},
secret: {
causes: [
"Please configure your domain's secret before creating any mailbox.",
],
handleError: handleErrorMock,
},
},
serverErrorParams: {
handleError: handleServerErrorMock,
},
}),
);

expect(handleServerErrorMock).not.toHaveBeenCalled();
expect(handleErrorMock).not.toHaveBeenCalled();
expect(result.current).toEqual([]);
});

it('should return error message from translation when no custom message is provided in params', () => {
const error = new APIError('server error', { status: 500 });

const { result } = renderHook(() =>
useAPIError({
error,
errorParams: undefined,
serverErrorParams: undefined,
}),
);

expect(result.current).toEqual([
'Your request cannot be processed because the server is experiencing an error. If the problem persists, ' +
'please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr',
]);
});
});
99 changes: 99 additions & 0 deletions src/frontend/apps/desk/src/api/useAPIError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import { APIError } from '@/api/index';

type ErrorParams = {
[fieldName: string]: {
causes: string[];
causeShown?: string;
handleError: () => void;
};
};

type ServerErrorParams = {
defaultMessage?: string;
handleError?: () => void;
};

export type useAPIErrorParams = {
error: APIError | null;
errorParams?: ErrorParams;
serverErrorParams?: ServerErrorParams;
};

export const handleAPIErrorCause = (
causes: string[],
errorParams: ErrorParams,
): string[] =>
causes.reduce((arrayCauses, cause) => {
const foundErrorParams = Object.values(errorParams).find((params) =>
params.causes.includes(cause),
);

if (foundErrorParams) {
if (foundErrorParams.causeShown) {
arrayCauses.push(foundErrorParams.causeShown);
}
foundErrorParams.handleError();
} else {
arrayCauses.push(cause);
}

return arrayCauses;
}, [] as string[]);

export const handleAPIServerError = (
causes: string[],
serverErrorParams: Omit<ServerErrorParams, 'defaultMessage'> & {
defaultMessage: string;
},
): string[] => {
causes = causes.length
? [serverErrorParams.defaultMessage, ...causes]
: [serverErrorParams.defaultMessage];

if (typeof serverErrorParams?.handleError === 'function') {
serverErrorParams.handleError();
}

return causes;
};

export const useAPIError = ({
error,
errorParams,
serverErrorParams,
}: useAPIErrorParams): string[] => {
const [errorCauses, setErrorCauses] = React.useState<string[]>([]);
const { t } = useTranslation();

React.useEffect(() => {
if (error) {
let causes: string[] =
error.cause?.length && errorParams
? handleAPIErrorCause(error.cause, errorParams)
: [];

if (error?.status === 500 || !error?.status) {
causes = handleAPIServerError(causes, {
defaultMessage:
serverErrorParams?.defaultMessage ||
t(
'Your request cannot be processed because the server is experiencing an error. If the problem ' +
'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr',
),
handleError: serverErrorParams?.handleError || undefined,
});
}

setErrorCauses((stateCauses) =>
causes && JSON.stringify(causes) !== JSON.stringify(stateCauses)
? causes
: stateCauses,
);
}
}, [error, errorParams, serverErrorParams, t]);

return errorCauses;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './useMailDomains';
export * from './useMailDomain';
export * from './useCreateMailbox';
export * from './useMailboxes';
export * from './useCreateMailDomain';
export * from './useAddMailDomain';

0 comments on commit 293637d

Please sign in to comment.