Skip to content

Commit

Permalink
fix: use a new CourseMeta property to restrict exam access (#59)
Browse files Browse the repository at this point in the history
Co-authored-by: Simon Chen <[email protected]>
  • Loading branch information
schenedx and Simon Chen authored Feb 16, 2022
1 parent 6e22b76 commit 6919561
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 5 deletions.
29 changes: 24 additions & 5 deletions src/exam/Exam.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useContext, useEffect } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert, Spinner } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { ExamTimerBlock } from '../timer';
import Instructions from '../instructions';
import ExamStateContext from '../context';
import ExamAPIError from './ExamAPIError';
import { ExamStatus, ExamType } from '../constants';
import messages from './messages';

/**
* Exam component is intended to render exam instructions before and after exam.
Expand All @@ -19,7 +20,7 @@ import { ExamStatus, ExamType } from '../constants';
* @constructor
*/
const Exam = ({
isGated, isTimeLimited, originalUserIsStaff, isIntegritySignatureEnabled, children,
isGated, isTimeLimited, originalUserIsStaff, isIntegritySignatureEnabled, canAccessProctoredExams, children, intl,
}) => {
const state = useContext(ExamStateContext);
const {
Expand Down Expand Up @@ -51,14 +52,19 @@ const Exam = ({
return false;
};

const [hasProctoredExamAccess, setHasProctoredExamAccess] = useState(true);

useEffect(() => {
if (examId) {
getProctoringSettings();
}
if (examType !== ExamType.TIMED) {
// Only exclude Timed Exam when restricting access to exams
setHasProctoredExamAccess(canAccessProctoredExams);
}
if (examType === ExamType.PROCTORED) {
getVerificationData();
}

// this makes sure useEffect gets called only one time after the exam has been fetched
// we can't leave this empty since initially exam is just an empty object, so
// API calls above would not get triggered
Expand All @@ -72,6 +78,16 @@ const Exam = ({
);
}

if (!hasProctoredExamAccess) {
// If the user cannot acces proctoring exam, and the current exam is a proctoring exam,
// we want to display a message box to let learner know they have no access.
return (
<div data-testid="no-access" className="d-flex justify-content-center align-items-center flex-column">
{intl.formatMessage(messages.proctoredExamAccessDenied)}
</div>
);
}

const sequenceContent = <>{children}</>;

return (
Expand Down Expand Up @@ -109,11 +125,14 @@ Exam.propTypes = {
isGated: PropTypes.bool.isRequired,
originalUserIsStaff: PropTypes.bool.isRequired,
isIntegritySignatureEnabled: PropTypes.bool,
canAccessProctoredExams: PropTypes.bool,
children: PropTypes.element.isRequired,
intl: intlShape.isRequired,
};

Exam.defaultProps = {
isIntegritySignatureEnabled: false,
canAccessProctoredExams: true,
};

export default Exam;
export default injectIntl(Exam);
4 changes: 4 additions & 0 deletions src/exam/ExamWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ExamWrapper = ({ children, ...props }) => {
isStaff,
originalUserIsStaff,
isIntegritySignatureEnabled,
canAccessProctoredExams,
} = props;
const { getExamAttemptsData, getAllowProctoringOptOut } = state;
const loadInitialData = async () => {
Expand All @@ -42,6 +43,7 @@ const ExamWrapper = ({ children, ...props }) => {
isTimeLimited={sequence.isTimeLimited}
originalUserIsStaff={originalUserIsStaff}
isIntegritySignatureEnabled={isIntegritySignatureEnabled}
canAccessProctoredExams={canAccessProctoredExams}
>
{children}
</Exam>
Expand All @@ -62,12 +64,14 @@ ExamWrapper.propTypes = {
isStaff: PropTypes.bool,
originalUserIsStaff: PropTypes.bool,
isIntegritySignatureEnabled: PropTypes.bool,
canAccessProctoredExams: PropTypes.bool,
};

ExamWrapper.defaultProps = {
isStaff: false,
originalUserIsStaff: false,
isIntegritySignatureEnabled: false,
canAccessProctoredExams: true,
};

export default ExamWrapper;
54 changes: 54 additions & 0 deletions src/exam/ExamWrapper.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,58 @@ describe('SequenceExamWrapper', () => {
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
expect(queryByTestId('masquerade-alert')).not.toBeInTheDocument();
});

it('shows access denied if learner is not accessible to proctoring exams', () => {
store.getState = () => ({
examState: Factory.build('examState', {
exam: Factory.build('exam', {
type: ExamType.PROCTORED,
attempt: null,
passed_due_date: false,
hide_after_due: false,
}),
}),
});
const { queryByTestId } = render(
<ExamStateProvider>
<SequenceExamWrapper
sequence={{ ...sequence, isTimeLimited: false }}
courseId={courseId}
canAccessProctoredExams={false}
>
<div data-testid="sequence-content">children</div>
</SequenceExamWrapper>
</ExamStateProvider>,
{ store },
);
expect(queryByTestId('no-access')).toHaveTextContent('You do not have access to proctored exams with your current enrollment.');
expect(queryByTestId('sequence-content')).toBeNull();
});

it('learner has access to timed exams', () => {
store.getState = () => ({
examState: Factory.build('examState', {
exam: Factory.build('exam', {
type: ExamType.TIMED,
attempt: null,
passed_due_date: false,
hide_after_due: false,
}),
}),
});
const { queryByTestId } = render(
<ExamStateProvider>
<SequenceExamWrapper
sequence={{ ...sequence, isTimeLimited: false }}
courseId={courseId}
canAccessProctoredExams={false}
>
<div data-testid="sequence-content">children</div>
</SequenceExamWrapper>
</ExamStateProvider>,
{ store },
);
expect(queryByTestId('no-access')).toBeNull();
expect(queryByTestId('sequence-content')).toHaveTextContent('children');
});
});
4 changes: 4 additions & 0 deletions src/exam/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const messages = defineMessages({
id: 'exam.apiError.supportText.withoutLink',
defaultMessage: 'If the issue persists, please reach out to support for assistance, and return to the exam once you receive further instructions.',
},
proctoredExamAccessDenied: {
id: 'exam.proctoredExamDenied',
defaultMessage: 'You do not have access to proctored exams with your current enrollment.',
},
});

export default messages;

0 comments on commit 6919561

Please sign in to comment.