Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ describe('ReviewDetailsAssessmentStage', () => {
it('navigates to SESSION_EXPIRED when getReviewById returns 403', async () => {
const user = userEvent.setup();
const mockGetReviewById = vi.spyOn(getReviewsModule, 'getReviewById');
mockGetReviewById.mockRejectedValue({ code: '403' });
mockGetReviewById.mockRejectedValue({ response: { status: 403 } });

render(
<ReviewDetailsAssessmentStage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const ReviewDetailsAssessmentStage = ({
);
} catch (e) {
const error = e as AxiosError;
if (error.code === '403') {
if (error.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
return;
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @vitest-environment happy-dom
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi, Mock } from 'vitest';
import ReviewDetailsCompleteStage from './ReviewDetailsCompleteStage';
import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
import { CompleteState } from '../../../../pages/adminRoutesPage/AdminRoutesPage';
import { routeChildren } from '../../../../types/generic/routes';
import { routeChildren, routes } from '../../../../types/generic/routes';
import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
import {
Expand Down Expand Up @@ -732,5 +732,65 @@ describe('ReviewDetailsCompletePage', () => {
expectedRequest,
);
});

it('should navigate to session expired page if patchReview throws 403', async () => {
vi.mocked(mockPatchReview).mockRejectedValueOnce({ response: { status: 403 } });

const reviewData = new ReviewDetails(
'test-review-id',
DOCUMENT_TYPE.LLOYD_GEORGE,
'2023-10-01T00:00:00Z',
'test',
'2023-10-01T00:00:00Z',
'rejected',
'1',
mockPatientDetails.nhsNumber,
);
mockReviewUploadDocuments[0].ref = 'doc-ref-id';

render(
<ReviewDetailsCompleteStage
completeState={CompleteState.PATIENT_MATCHED}
reviewData={reviewData}
reviewUploadDocuments={mockReviewUploadDocuments}
/>,
);

await waitFor(() => {
expect(mockPatchReview).toHaveBeenCalled();
expect(mockNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
});
});

it('should navigate to server error page if patchReview throws 500', async () => {
vi.mocked(mockPatchReview).mockRejectedValueOnce({ response: { status: 500 } });

const reviewData = new ReviewDetails(
'test-review-id',
DOCUMENT_TYPE.LLOYD_GEORGE,
'2023-10-01T00:00:00Z',
'test',
'2023-10-01T00:00:00Z',
'rejected',
'1',
mockPatientDetails.nhsNumber,
);
mockReviewUploadDocuments[0].ref = 'doc-ref-id';

render(
<ReviewDetailsCompleteStage
completeState={CompleteState.PATIENT_MATCHED}
reviewData={reviewData}
reviewUploadDocuments={mockReviewUploadDocuments}
/>,
);

await waitFor(() => {
expect(mockPatchReview).toHaveBeenCalled();
expect(mockNavigate).toHaveBeenCalledWith(
expect.stringContaining(routes.SERVER_ERROR),
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ const ReviewDetailsCompleteStage = ({
setLoading(false);
} catch (e) {
const error = e as AxiosError;
if (error.code === '403') {
if (error.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
} else {
navigate(routes.SERVER_ERROR + errorToParams(error));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import ReviewsDetailsPageComponent from './ReviewsDetailsStage';
import { runAxeTest } from '../../../../helpers/test/axeTestHelper';
import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
Expand All @@ -16,27 +16,24 @@ import {
DOCUMENT_UPLOAD_STATE,
} from '../../../../types/pages/UploadDocumentsPage/types';
import { NHS_NUMBER_UNKNOWN } from '../../../../helpers/constants/numbers';

const mockNavigate = vi.fn();
const mockSetPatientDetails = vi.fn();
const mockUsePatientDetailsContext = vi.fn();
const mockUseSessionContext = vi.fn();
import * as handlePatientSearchModule from '../../../../helpers/utils/handlePatientSearch';
import { routes } from '../../../../types/generic/routes';

vi.mock('react-router-dom', async (): Promise<unknown> => {
const actual = await vi.importActual('react-router-dom');
return {
...actual,
useNavigate: () => mockNavigate,
useNavigate: (): Mock => mockNavigate,
useParams: (): { reviewId: string } => ({ reviewId: 'test-review-123' }),
};
});

vi.mock('../../../../providers/patientProvider/PatientProvider', () => ({
usePatientDetailsContext: (): unknown => mockUsePatientDetailsContext(),
usePatientDetailsContext: (): Mock => mockUsePatientDetailsContext(),
}));

vi.mock('../../../../providers/sessionProvider/SessionProvider', () => ({
useSessionContext: (): unknown => mockUseSessionContext(),
useSessionContext: (): Mock => mockUseSessionContext(),
}));

vi.mock('../../../../helpers/hooks/useRole', () => ({
Expand Down Expand Up @@ -69,14 +66,17 @@ vi.mock('../../../../helpers/requests/getReviews', () => ({
}),
}));

vi.mock('../../../../helpers/utils/handlePatientSearch', () => ({
handleSearch: vi.fn().mockResolvedValue(undefined),
}));
vi.mock('../../../../helpers/utils/handlePatientSearch');

vi.mock('../../../../helpers/utils/waitForSeconds', () => ({
default: vi.fn().mockResolvedValue(undefined),
}));

const mockNavigate = vi.fn();
const mockSetPatientDetails = vi.fn();
const mockUsePatientDetailsContext = vi.fn();
const mockUseSessionContext = vi.fn();

const renderComponent = (reviewData?: ReviewDetails, reviewSnoMed?: DOCUMENT_TYPE): void => {
const currentReviewData =
reviewData ??
Expand Down Expand Up @@ -751,7 +751,7 @@ describe('ReviewDetailsStage', () => {
});

it('navigates to SESSION_EXPIRED on 403 error', async () => {
const mockLoadReviewData = vi.fn().mockRejectedValue({ code: '403' });
const mockLoadReviewData = vi.fn().mockRejectedValue({ response: { status: 403 } });

render(
<ReviewsDetailsPageComponent
Expand Down Expand Up @@ -1165,6 +1165,32 @@ describe('ReviewDetailsStage', () => {
expect(screen.getByText("Van Der Berg, O'Brien")).toBeInTheDocument();
});
});

it('navigates to session expired page when patient search returns 403', async () => {
vi.spyOn(handlePatientSearchModule, 'handleSearch').mockRejectedValueOnce({
response: { status: 403 },
});

renderComponent(mockReviewData);

await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith(routes.SESSION_EXPIRED);
});
});

it('navigates to server error page when patient search returns 500', async () => {
vi.spyOn(handlePatientSearchModule, 'handleSearch').mockRejectedValueOnce({
response: { status: 500 },
});

renderComponent(mockReviewData);

await waitFor(() => {
expect(mockNavigate).toHaveBeenCalledWith(
expect.stringContaining(routes.SERVER_ERROR),
);
});
});
});

describe('Review Data Handling', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const ReviewsDetailsStage = ({
const [session] = useSessionContext();
const [showError, setShowError] = useState(false);
const errorSummaryRef = useRef<HTMLDivElement>(null);
const fetchingPatientDetailsRef = useRef(false);
const isFetchingReviewDetailsRef = useRef(false);

const baseUrl = useBaseAPIUrl();
Expand Down Expand Up @@ -152,9 +153,9 @@ const ReviewsDetailsStage = ({
setisLoadingPatientDetails(false);
return;
}

const getPatientDetails = async (): Promise<void> => {
if (!isFetchingReviewDetailsRef.current) {
isFetchingReviewDetailsRef.current = true;
try {
await handlePatientSearch({
nhsNumber: reviewData.nhsNumber,
setSearchingState: () => {},
Expand All @@ -169,16 +170,27 @@ const ReviewsDetailsStage = ({
featureFlags: config.featureFlags,
});
setisLoadingPatientDetails(false);
} catch (error) {
const err = error as AxiosError;
if (err.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
} else {
navigate(routes.SERVER_ERROR + errorToParams(err));
}
}
};
getPatientDetails();

if (!fetchingPatientDetailsRef.current) {
fetchingPatientDetailsRef.current = true;
getPatientDetails();
}
}, [reviewId]);

useEffect(() => {
const loadData = async (): Promise<void> => {
let retryCount = 0;
const maxRetries = 5;
const retryDelayMs = 1000;
const maxRetries = 10;
const retryDelayMs = 3;

while (retryCount < maxRetries) {
try {
Expand All @@ -199,7 +211,7 @@ const ReviewsDetailsStage = ({
await waitForSeconds(retryDelayMs);
} else {
const error = e as AxiosError;
if (error.code === '403') {
if (error.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
return;
}
Expand All @@ -209,7 +221,11 @@ const ReviewsDetailsStage = ({
}
}
};
loadData();

if (!isFetchingReviewDetailsRef.current) {
isFetchingReviewDetailsRef.current = true;
loadData();
}
}, [patientDetails, setPatientDetails]);

const { register, handleSubmit } = useForm<FormData>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ describe('ReviewsPage', () => {

describe('Error Handling', () => {
it('navigates to session expired when search returns 403', async () => {
mockGetReviews.mockRejectedValueOnce({ code: '403' });
mockGetReviews.mockRejectedValueOnce({ response: { status: 403 } });
renderComponent();

await waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export const ReviewsPage = ({ setReviewData }: ReviewsPageProps): React.JSX.Elem
}
} catch (e) {
const error = e as AxiosError;
if (error.code === '403') {
if (error.response?.status === 403) {
navigate(routes.SESSION_EXPIRED);
return;
}
Expand Down
Loading
Loading