fix: handling malformed library ids as not found errors (#66)

This commit is contained in:
Jacobo Dominguez
2026-02-18 15:38:09 -06:00
committed by GitHub
parent 8c4eeb2c09
commit 2e5c104430
3 changed files with 27 additions and 8 deletions

View File

@@ -11,7 +11,7 @@ const ThrowError = ({ error }: { error:Error }) => {
describe('LibrariesErrorFallback', () => { describe('LibrariesErrorFallback', () => {
it('renders Access Denied for 401', () => { it('renders Access Denied for 401', () => {
const error = { name: '', message: 'NO_ACCESS', customAtributtes: { httpErrorStatus: 401 } }; const error = { name: '', message: 'NO_ACCESS', customAttributes: { httpErrorStatus: 401 } };
renderWrapper( renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback}> <ErrorBoundary FallbackComponent={LibrariesErrorFallback}>
<ThrowError error={error} /> <ThrowError error={error} />
@@ -21,8 +21,19 @@ describe('LibrariesErrorFallback', () => {
expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument();
}); });
it('renders Not Found for 400 error', () => {
const error = { name: '', message: 'Axios Error (Response): 400', customAttributes: { httpErrorStatus: 400 } };
renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback}>
<ThrowError error={error} />
</ErrorBoundary>,
);
expect(screen.getByText(/Page Not Found/i)).toBeInTheDocument();
expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument();
});
it('renders Not Found for 404', () => { it('renders Not Found for 404', () => {
const error = { name: '', message: 'NOT_FOUND', customAtributtes: { httpErrorStatus: 404 } }; const error = { name: '', message: 'NOT_FOUND', customAttributes: { httpErrorStatus: 404 } };
renderWrapper( renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback}> <ErrorBoundary FallbackComponent={LibrariesErrorFallback}>
<ThrowError error={error} /> <ThrowError error={error} />
@@ -33,7 +44,7 @@ describe('LibrariesErrorFallback', () => {
}); });
it('renders Server Error for 500 and shows reload', async () => { it('renders Server Error for 500 and shows reload', async () => {
const error = { name: '', message: 'SERVER_ERROR', customAtributtes: { httpErrorStatus: 500 } }; const error = { name: '', message: 'SERVER_ERROR', customAttributes: { httpErrorStatus: 500 } };
renderWrapper( renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback}> <ErrorBoundary FallbackComponent={LibrariesErrorFallback}>
<ThrowError error={error} /> <ThrowError error={error} />
@@ -45,7 +56,7 @@ describe('LibrariesErrorFallback', () => {
}); });
it('renders generic error for other error error', () => { it('renders generic error for other error error', () => {
const error = { name: '', message: 'SOMETHING_ELSE', customAtributtes: { httpErrorStatus: 418 } }; const error = { name: '', message: 'SOMETHING_ELSE', customAttributes: { httpErrorStatus: 418 } };
renderWrapper( renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback}> <ErrorBoundary FallbackComponent={LibrariesErrorFallback}>
<ThrowError error={error} /> <ThrowError error={error} />
@@ -59,7 +70,7 @@ describe('LibrariesErrorFallback', () => {
// Simulate error with a refetch function // Simulate error with a refetch function
const refetch = jest.fn(); const refetch = jest.fn();
const error = { const error = {
name: '', message: 'SERVER_ERROR', customAtributtes: { httpErrorStatus: 500 }, refetch, name: '', message: 'SERVER_ERROR', customAttributes: { httpErrorStatus: 500 }, refetch,
}; };
renderWrapper( renderWrapper(
<ErrorBoundary FallbackComponent={LibrariesErrorFallback} onReset={refetch}> <ErrorBoundary FallbackComponent={LibrariesErrorFallback} onReset={refetch}>

View File

@@ -5,7 +5,9 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { import {
Button, Container, Hyperlink, Row, Button, Container, Hyperlink, Row,
} from '@openedx/paragon'; } from '@openedx/paragon';
import { CustomErrors, ERROR_STATUS } from '@src/constants'; import {
CustomErrors, ERROR_STATUS, STATUS_400, STATUS_404,
} from '@src/constants';
import messages from './messages'; import messages from './messages';
@@ -18,11 +20,14 @@ const getErrorConfig = ({ errorMessage, errorStatus }) => {
showBackButton: true, showBackButton: true,
}); });
} }
// 400 errors are handled as 404 Not Found to avoid exposing potential sensitive information
// about the existence of resources and handling malformed library ids in the URL
if (errorMessage === CustomErrors.NOT_FOUND || ERROR_STATUS.NOT_FOUND.includes(errorStatus)) { if (errorMessage === CustomErrors.NOT_FOUND || ERROR_STATUS.NOT_FOUND.includes(errorStatus)) {
const statusCode = errorStatus === STATUS_400 ? STATUS_404 : errorStatus;
return ({ return ({
title: messages['error.page.title.notFound'], title: messages['error.page.title.notFound'],
description: messages['error.page.message.notFound'], description: messages['error.page.message.notFound'],
statusCode: errorStatus || ERROR_STATUS.NOT_FOUND[0], statusCode: statusCode || STATUS_404,
showBackButton: true, showBackButton: true,
}); });
} }

View File

@@ -10,8 +10,11 @@ type ErrorStatusCode = {
[key in CustomErrors]: number[]; [key in CustomErrors]: number[];
}; };
export const STATUS_400 = 400;
export const STATUS_404 = 404;
export const ERROR_STATUS: ErrorStatusCode = { export const ERROR_STATUS: ErrorStatusCode = {
[CustomErrors.NO_ACCESS]: [403, 401], [CustomErrors.NO_ACCESS]: [403, 401],
[CustomErrors.NOT_FOUND]: [404], [CustomErrors.NOT_FOUND]: [400, 404],
[CustomErrors.SERVER_ERROR]: [500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511], [CustomErrors.SERVER_ERROR]: [500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511],
}; };