diff --git a/src/data/redux.test.jsx b/src/data/redux.test.jsx index 6a036a03..91e87b80 100644 --- a/src/data/redux.test.jsx +++ b/src/data/redux.test.jsx @@ -968,6 +968,27 @@ describe('Data layer integration tests', () => { }); }); + it('Should refresh exam state from backend if polled attempt has ended in error', async () => { + const errorAttempt = Factory.build('attempt', { attempt_status: ExamStatus.ERROR }); + const errorExam = Factory.build('exam', { attempt: errorAttempt }); + + await initWithExamAttempt(exam, attempt); + const attemptToPollURL = `${latestAttemptURL}?content_id=block-v1%3Atest%2Bspecial%2Bexam%2Btype%40sequential%2Bblock%40abc123`; + axiosMock.onGet(attemptToPollURL).reply(200, { + time_remaining_seconds: 0, + accessibility_time_string: 'you have 0 minutes remaining', + attempt_status: ExamStatus.ERROR, + }); + axiosMock.onGet(fetchExamAttemptsDataUrl).reply(200, { exam: errorExam, active_attempt: {} }); + + axiosMock.resetHistory(); + await executeThunk(thunks.pollAttempt(null, exam.content_id), store.dispatch, store.getState); + const state = store.getState(); + expect(axiosMock.history.get[0].url).toEqual(attemptToPollURL); + expect(axiosMock.history.get[1].url).toEqual(fetchExamAttemptsDataUrl); + expect(state.specialExams.exam.attempt.attempt_status).toBe(ExamStatus.ERROR); + }); + describe('pollAttempt api called directly', () => { // in the download view we call this function directly without invoking the wrapping thunk it('should call pollUrl if one is provided', async () => { diff --git a/src/data/thunks.js b/src/data/thunks.js index 31759531..76ebca13 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -284,8 +284,9 @@ export function pollAttempt(url) { dispatch(setActiveAttempt({ activeAttempt: updatedAttempt, })); - if (data.status === ExamStatus.SUBMITTED) { + if ([ExamStatus.SUBMITTED, ExamStatus.ERROR].includes(data.status)) { dispatch(expireExamAttempt()); + updateAttemptAfter(exam.course_id, exam.content_id)(dispatch); } } catch (error) { handleAPIError(error, dispatch); diff --git a/src/exam/ExamWrapper.jsx b/src/exam/ExamWrapper.jsx index 8b5ccde6..ac985463 100644 --- a/src/exam/ExamWrapper.jsx +++ b/src/exam/ExamWrapper.jsx @@ -28,8 +28,8 @@ const ExamWrapper = ({ children, ...props }) => { const loadInitialData = async () => { await dispatch(getExamAttemptsData(courseId, sequence.id)); - await getAllowProctoringOptOut(sequence.allowProctoringOptOut); - await checkExamEntry(); + await dispatch(getAllowProctoringOptOut(sequence.allowProctoringOptOut)); + await dispatch(checkExamEntry()); }; const isGated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;