diff --git a/src/course-checklist/CourseChecklist.test.jsx b/src/course-checklist/CourseChecklist.test.jsx
index 53d52af77e..69057abf70 100644
--- a/src/course-checklist/CourseChecklist.test.jsx
+++ b/src/course-checklist/CourseChecklist.test.jsx
@@ -149,5 +149,20 @@ describe('CourseChecklistPage', () => {
});
});
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ const courseLaunchApiUrl = getCourseLaunchApiUrl({
+ courseId, gradedOnly: true, validateOras: true, all: true,
+ });
+ axiosMock.onGet(courseLaunchApiUrl).reply(403);
+
+ renderComponent();
+
+ await waitFor(() => {
+ const { launchChecklistStatus } = store.getState().courseChecklist.loadingStatus;
+ expect(launchChecklistStatus).toEqual(RequestStatus.DENIED);
+ expect(screen.getByRole('alert')).toBeInTheDocument();
+ });
+ });
});
});
diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx
index b7f8332eeb..bfe6c07705 100644
--- a/src/course-outline/CourseOutline.test.jsx
+++ b/src/course-outline/CourseOutline.test.jsx
@@ -2291,4 +2291,18 @@ describe('', () => {
expect(await screen.findByText('Please wait. Creating export file for course tags...')).toBeInTheDocument();
expect(await screen.findByText('An error has occurred creating the file')).toBeInTheDocument();
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getCourseOutlineIndexApiUrl(courseId))
+ .reply(403);
+
+ const { getByRole } = render();
+
+ await waitFor(() => {
+ expect(getByRole('alert')).toBeInTheDocument();
+ const { outlineIndexLoadingStatus } = store.getState().courseOutline.loadingStatus;
+ expect(outlineIndexLoadingStatus).toEqual(RequestStatus.DENIED);
+ });
+ });
});
diff --git a/src/course-team/CourseTeam.test.jsx b/src/course-team/CourseTeam.test.jsx
index 4f33788744..31f9c78fdc 100644
--- a/src/course-team/CourseTeam.test.jsx
+++ b/src/course-team/CourseTeam.test.jsx
@@ -1,4 +1,3 @@
-import React from 'react';
import {
render,
fireEvent,
@@ -18,6 +17,7 @@ import CourseTeam from './CourseTeam';
import messages from './messages';
import { USER_ROLES } from '../constants';
import { executeThunk } from '../utils';
+import { RequestStatus } from '../data/constants';
import { changeRoleTeamUserQuery, deleteCourseTeamQuery } from './data/thunk';
let axiosMock;
@@ -219,4 +219,18 @@ describe('', () => {
await executeThunk(changeRoleTeamUserQuery(courseId, 'staff@example.com', { role: USER_ROLES.admin }), store.dispatch);
expect(getAllByText('Admin')).toHaveLength(1);
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getCourseTeamApiUrl(courseId))
+ .reply(403);
+
+ const { getByRole } = render();
+
+ await waitFor(() => {
+ expect(getByRole('alert')).toBeInTheDocument();
+ const { loadingCourseTeamStatus } = store.getState().courseTeam;
+ expect(loadingCourseTeamStatus).toEqual(RequestStatus.DENIED);
+ });
+ });
});
diff --git a/src/course-updates/CourseUpdates.test.jsx b/src/course-updates/CourseUpdates.test.jsx
index 387d3b3c26..ab8e1a8022 100644
--- a/src/course-updates/CourseUpdates.test.jsx
+++ b/src/course-updates/CourseUpdates.test.jsx
@@ -1,5 +1,6 @@
-import React from 'react';
-import { render, waitFor, fireEvent } from '@testing-library/react';
+import {
+ render, waitFor, fireEvent,
+} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeMockApp } from '@edx/frontend-platform';
@@ -19,6 +20,7 @@ import {
} from './data/thunk';
import initializeStore from '../store';
import { executeThunk } from '../utils';
+import { RequestStatus } from '../data/constants';
import { courseUpdatesMock, courseHandoutsMock } from './__mocks__';
import CourseUpdates from './CourseUpdates';
import messages from './messages';
@@ -278,6 +280,24 @@ describe('', () => {
expect(getByTestId('course-handouts-edit-button')).toBeDisabled();
});
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getCourseUpdatesApiUrl(courseId))
+ .reply(403, courseUpdatesMock);
+ axiosMock
+ .onGet(getCourseHandoutApiUrl(courseId))
+ .reply(403);
+
+ const { getByTestId } = render();
+
+ await waitFor(() => {
+ expect(getByTestId('connectionErrorAlert')).toBeInTheDocument();
+ const { loadingStatuses } = store.getState().courseUpdates;
+ Object.values(loadingStatuses)
+ .some(status => expect(status).toEqual(RequestStatus.DENIED));
+ });
+ });
});
describe('saving failure API responses', () => {
diff --git a/src/export-page/CourseExportPage.test.jsx b/src/export-page/CourseExportPage.test.jsx
index 05db07d5bf..debfe72762 100644
--- a/src/export-page/CourseExportPage.test.jsx
+++ b/src/export-page/CourseExportPage.test.jsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { getConfig, initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
@@ -6,9 +5,10 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { Helmet } from 'react-helmet';
-
import Cookies from 'universal-cookie';
+
import initializeStore from '../store';
+import { RequestStatus } from '../data/constants';
import stepperMessages from './export-stepper/messages';
import modalErrorMessages from './export-modal-error/messages';
import { getExportStatusApiUrl, postExportCourseApiUrl } from './data/api';
@@ -137,4 +137,17 @@ describe('', () => {
expect(downloadButton).toBeInTheDocument();
expect(downloadButton.getAttribute('href')).toEqual('http://test-download-path.test');
});
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getExportStatusApiUrl(courseId))
+ .reply(403);
+ const { getByRole, container } = render();
+ const startExportButton = container.querySelector('.btn-primary');
+ fireEvent.click(startExportButton);
+ // eslint-disable-next-line no-promise-executor-return
+ await new Promise((r) => setTimeout(r, 3500));
+ expect(getByRole('alert')).toBeInTheDocument();
+ const { loadingStatus } = store.getState().courseExport;
+ expect(loadingStatus).toEqual(RequestStatus.DENIED);
+ });
});
diff --git a/src/grading-settings/GradingSettings.jsx b/src/grading-settings/GradingSettings.jsx
index 8ddd7a9bb5..bcb3a8f089 100644
--- a/src/grading-settings/GradingSettings.jsx
+++ b/src/grading-settings/GradingSettings.jsx
@@ -8,7 +8,6 @@ import {
useGradingSettings,
useGradingSettingUpdater,
} from 'CourseAuthoring/grading-settings/data/apiHooks';
-import { RequestStatus } from 'CourseAuthoring/data/constants';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
@@ -34,10 +33,12 @@ const GradingSettings = ({ courseId }) => {
const {
data: gradingSettings,
isLoading: isGradingSettingsLoading,
+ isError: isGradingSettingsError,
} = useGradingSettings(courseId);
const {
data: courseSettingsData,
isLoading: isCourseSettingsLoading,
+ isError: isCourseSettingsError,
} = useCourseSettings(courseId);
const {
mutate: updateGradingSettings,
@@ -48,7 +49,7 @@ const GradingSettings = ({ courseId }) => {
const courseAssignmentLists = gradingSettings?.courseAssignmentLists;
const courseGradingDetails = gradingSettings?.courseDetails;
- const isLoadingDenied = isCourseSettingsLoading === RequestStatus.DENIED;
+ const isLoadingDenied = isGradingSettingsError || isCourseSettingsError;
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const isLoading = isCourseSettingsLoading || isGradingSettingsLoading;
const [isQueryPending, setIsQueryPending] = useState(false);
diff --git a/src/group-configurations/GroupConfigurations.test.jsx b/src/group-configurations/GroupConfigurations.test.jsx
index 34486c368b..303b557f2c 100644
--- a/src/group-configurations/GroupConfigurations.test.jsx
+++ b/src/group-configurations/GroupConfigurations.test.jsx
@@ -103,4 +103,21 @@ describe('', () => {
RequestStatus.FAILED,
);
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getContentStoreApiUrl(courseId))
+ .reply(403);
+
+ await executeThunk(fetchGroupConfigurationsQuery(courseId), store.dispatch);
+
+ const { getByTestId } = renderComponent();
+
+ await waitFor(() => {
+ expect(getByTestId('connectionErrorAlert')).toBeInTheDocument();
+ expect(store.getState().groupConfigurations.loadingStatus).toBe(
+ RequestStatus.DENIED,
+ );
+ });
+ });
});
diff --git a/src/import-page/CourseImportPage.test.jsx b/src/import-page/CourseImportPage.test.jsx
index 8359312692..a72363171a 100644
--- a/src/import-page/CourseImportPage.test.jsx
+++ b/src/import-page/CourseImportPage.test.jsx
@@ -1,14 +1,14 @@
-import React from 'react';
import { initializeMockApp } from '@edx/frontend-platform';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { render, waitFor } from '@testing-library/react';
import { Helmet } from 'react-helmet';
-
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+
import Cookies from 'universal-cookie';
import initializeStore from '../store';
+import { RequestStatus } from '../data/constants';
import messages from './messages';
import CourseImportPage from './CourseImportPage';
import { getImportStatusApiUrl } from './data/api';
@@ -108,4 +108,17 @@ describe('', () => {
await new Promise((r) => setTimeout(r, 3500));
expect(getByText(stepperMessages.viewOutlineButton.defaultMessage)).toBeInTheDocument();
});
+
+ it('displays an alert and sets status to DENIED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getImportStatusApiUrl(courseId, 'testFileName.tar.gz'))
+ .reply(403);
+ cookies.get.mockReturnValue({ date: 1679787000, completed: false, fileName: 'testFileName.tar.gz' });
+ const { getByRole } = render();
+ // eslint-disable-next-line no-promise-executor-return
+ await new Promise((r) => setTimeout(r, 3500));
+ expect(getByRole('alert')).toBeInTheDocument();
+ const { loadingStatus } = store.getState().courseImport;
+ expect(loadingStatus).toEqual(RequestStatus.DENIED);
+ });
});
diff --git a/src/import-page/data/thunks.js b/src/import-page/data/thunks.js
index 95acb291ec..028fea2da2 100644
--- a/src/import-page/data/thunks.js
+++ b/src/import-page/data/thunks.js
@@ -1,4 +1,3 @@
-/* eslint-disable import/prefer-default-export */
import Cookies from 'universal-cookie';
import moment from 'moment';
@@ -32,7 +31,11 @@ export function fetchImportStatus(courseId, fileName) {
dispatch(updateLoadingStatus(RequestStatus.SUCCESSFUL));
return true;
} catch (error) {
- dispatch(updateLoadingStatus(RequestStatus.FAILED));
+ if (error.response && error.response.status === 403) {
+ dispatch(updateLoadingStatus(RequestStatus.DENIED));
+ } else {
+ dispatch(updateLoadingStatus(RequestStatus.FAILED));
+ }
return false;
}
};
diff --git a/src/textbooks/Textbook.test.jsx b/src/textbooks/Textbook.test.jsx
index b8437f114f..60af1e3026 100644
--- a/src/textbooks/Textbook.test.jsx
+++ b/src/textbooks/Textbook.test.jsx
@@ -6,6 +6,7 @@ import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import userEvent from '@testing-library/user-event';
+import { RequestStatus } from '../data/constants';
import initializeStore from '../store';
import { executeThunk } from '../utils';
import { getTextbooksApiUrl } from './data/api';
@@ -84,4 +85,19 @@ describe('', () => {
expect(queryAllByTestId('textbook-card')).toHaveLength(0);
});
});
+
+ it('displays an alert and sets status to FAILED when API responds with 403', async () => {
+ axiosMock
+ .onGet(getTextbooksApiUrl(courseId))
+ .reply(403);
+ await executeThunk(fetchTextbooksQuery(courseId), store.dispatch);
+ const { getByTestId } = renderComponent();
+
+ await waitFor(() => {
+ expect(getByTestId('connectionErrorAlert')).toBeInTheDocument();
+ expect(store.getState().textbooks.loadingStatus).toBe(
+ RequestStatus.FAILED,
+ );
+ });
+ });
});