Skip to content

Commit

Permalink
feat: separate endpoint calls for fetchExamAttemptsData (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
alangsto authored Mar 17, 2023
1 parent feb32dd commit 71a6c1c
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 17 deletions.
4 changes: 2 additions & 2 deletions src/core/OuterExamTimer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const ExamTimer = ({ courseId }) => {
const {
activeAttempt, showTimer, stopExam, submitExam,
expireExam, pollAttempt, apiErrorMsg, pingAttempt,
getExamAttemptsData,
getLatestAttemptData,
} = state;

// if user is not authenticated they cannot have active exam, so no need for timer
Expand All @@ -22,7 +22,7 @@ const ExamTimer = ({ courseId }) => {
}

useEffect(() => {
getExamAttemptsData(courseId);
getLatestAttemptData(courseId);
}, [courseId]);

return (
Expand Down
6 changes: 3 additions & 3 deletions src/core/OuterExamTimer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import '@testing-library/jest-dom';
import { Factory } from 'rosie';
import React from 'react';
import OuterExamTimer from './OuterExamTimer';
import { store, getExamAttemptsData } from '../data';
import { store, getLatestAttemptData } from '../data';
import { render } from '../setupTest';
import { ExamStatus } from '../constants';

jest.mock('../data', () => ({
store: {},
getExamAttemptsData: jest.fn(),
getLatestAttemptData: jest.fn(),
Emitter: {
on: () => jest.fn(),
once: () => jest.fn(),
off: () => jest.fn(),
emit: () => jest.fn(),
},
}));
getExamAttemptsData.mockReturnValue(jest.fn());
getLatestAttemptData.mockReturnValue(jest.fn());
store.subscribe = jest.fn();
store.dispatch = jest.fn();

Expand Down
182 changes: 182 additions & 0 deletions src/data/__snapshots__/redux.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,140 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Data layer integration tests Test exams IDA url Should call the exams service to fetch attempt data 1`] = `
Object {
"examState": Object {
"activeAttempt": Object {
"accessibility_time_string": "you have 30 minutes remaining",
"attempt_code": "",
"attempt_id": 1,
"attempt_status": "started",
"course_id": "course-v1:test+special+exam",
"critically_low_threshold_sec": 90,
"desktop_application_js_url": "",
"exam_display_name": "timed",
"exam_started_poll_url": "/api/edx_proctoring/v1/proctored_exam/attempt/1",
"exam_type": "a timed exam",
"exam_url_path": "http://localhost:2000/course/course-v1:test+special+exam/block-v1:test+special+exam+type@sequential+block@abc123",
"in_timed_exam": true,
"low_threshold_sec": 360,
"software_download_url": "",
"taking_as_proctored": false,
"time_remaining_seconds": 1799.9,
"total_time": "30 minutes",
},
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": Object {
"attempt": Object {
"accessibility_time_string": "you have 30 minutes remaining",
"attempt_code": "",
"attempt_id": 1,
"attempt_status": "started",
"course_id": "course-v1:test+special+exam",
"critically_low_threshold_sec": 90,
"desktop_application_js_url": "",
"exam_display_name": "timed",
"exam_started_poll_url": "/api/edx_proctoring/v1/proctored_exam/attempt/1",
"exam_type": "a timed exam",
"exam_url_path": "http://localhost:2000/course/course-v1:test+special+exam/block-v1:test+special+exam+type@sequential+block@abc123",
"in_timed_exam": true,
"low_threshold_sec": 360,
"software_download_url": "",
"taking_as_proctored": false,
"time_remaining_seconds": 1799.9,
"total_time": "30 minutes",
},
"backend": "test",
"content_id": "block-v1:test+special+exam+type@sequential+block@abc123",
"course_id": "course-v1:test+special+exam",
"due_date": null,
"exam_name": "prock exam",
"external_id": null,
"hide_after_due": false,
"id": 1,
"is_active": true,
"is_practice_exam": false,
"is_proctored": false,
"prerequisite_status": Object {
"are_prerequisites_satisifed": true,
"declined_prerequisites": Array [],
"failed_prerequisites": Array [],
"pending_prerequisites": Array [],
"satisfied_prerequisites": Array [],
},
"time_limit_mins": 30,
"total_time": "30 minutes",
"type": "timed",
},
"isLoading": false,
"proctoringSettings": Object {
"contact_us": "",
"exam_proctoring_backend": Object {
"download_url": "",
"instructions": Array [],
"name": "",
"rules": Object {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"link_urls": null,
"platform_name": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
},
"timeIsOver": false,
},
}
`;

exports[`Data layer integration tests Test exams IDA url Should call the exams service to get latest attempt data 1`] = `
Object {
"examState": Object {
"activeAttempt": Object {
"accessibility_time_string": "you have 30 minutes remaining",
"attempt_code": "",
"attempt_id": 1,
"attempt_status": "started",
"course_id": "course-v1:test+special+exam",
"critically_low_threshold_sec": 90,
"desktop_application_js_url": "",
"exam_display_name": "timed",
"exam_started_poll_url": "/api/edx_proctoring/v1/proctored_exam/attempt/1",
"exam_type": "a timed exam",
"exam_url_path": "http://localhost:2000/course/course-v1:test+special+exam/block-v1:test+special+exam+type@sequential+block@abc123",
"in_timed_exam": true,
"low_threshold_sec": 360,
"software_download_url": "",
"taking_as_proctored": false,
"time_remaining_seconds": 1799.9,
"total_time": "30 minutes",
},
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": Object {},
"isLoading": false,
"proctoringSettings": Object {
"contact_us": "",
"exam_proctoring_backend": Object {
"download_url": "",
"instructions": Array [],
"name": "",
"rules": Object {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"link_urls": null,
"platform_name": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
},
"timeIsOver": false,
},
}
`;

exports[`Data layer integration tests Test getExamAttemptsData Should get, and save exam and attempt 1`] = `
Object {
"examState": Object {
Expand Down Expand Up @@ -88,6 +223,53 @@ Object {
}
`;

exports[`Data layer integration tests Test getLatestAttemptData Should get, and save latest attempt 1`] = `
Object {
"examState": Object {
"activeAttempt": Object {
"accessibility_time_string": "you have 30 minutes remaining",
"attempt_code": "",
"attempt_id": 1,
"attempt_status": "started",
"course_id": "course-v1:test+special+exam",
"critically_low_threshold_sec": 90,
"desktop_application_js_url": "",
"exam_display_name": "timed",
"exam_started_poll_url": "/api/edx_proctoring/v1/proctored_exam/attempt/1",
"exam_type": "a timed exam",
"exam_url_path": "http://localhost:2000/course/course-v1:test+special+exam/block-v1:test+special+exam+type@sequential+block@abc123",
"in_timed_exam": true,
"low_threshold_sec": 360,
"software_download_url": "",
"taking_as_proctored": false,
"time_remaining_seconds": 1799.9,
"total_time": "30 minutes",
},
"allowProctoringOptOut": false,
"apiErrorMsg": "",
"exam": Object {},
"isLoading": false,
"proctoringSettings": Object {
"contact_us": "",
"exam_proctoring_backend": Object {
"download_url": "",
"instructions": Array [],
"name": "",
"rules": Object {},
},
"integration_specific_email": "",
"learner_notification_from_email": "",
"link_urls": null,
"platform_name": "",
"provider_name": "",
"provider_tech_support_email": "",
"provider_tech_support_phone": "",
},
"timeIsOver": false,
},
}
`;

exports[`Data layer integration tests Test getProctoringSettings Should fail to fetch if error occurs 1`] = `
Object {
"contact_us": "",
Expand Down
51 changes: 43 additions & 8 deletions src/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,50 @@ import { ExamAction } from '../constants';

const BASE_API_URL = '/api/edx_proctoring/v1/proctored_exam/attempt';

async function fetchActiveAttempt() {
const activeAttemptUrl = new URL(`${getConfig().EXAMS_BASE_URL}/api/v1/exams/attempt/latest`);
const activeAttemptResponse = await getAuthenticatedHttpClient().get(activeAttemptUrl.href);
return activeAttemptResponse.data.attempt;
}

export async function fetchExamAttemptsData(courseId, sequenceId) {
const url = new URL(
`${getConfig().LMS_BASE_URL}${BASE_API_URL}/course_id/${courseId}`,
);
if (sequenceId) {
let data;
if (!getConfig().EXAMS_BASE_URL) {
const url = new URL(
`${getConfig().LMS_BASE_URL}${BASE_API_URL}/course_id/${courseId}`,
);
url.searchParams.append('content_id', sequenceId);
url.searchParams.append('is_learning_mfe', true);
const urlResponse = await getAuthenticatedHttpClient().get(url.href);
data = urlResponse.data;
} else {
const examUrl = new URL(`${getConfig().EXAMS_BASE_URL}/api/v1/student/exam/attempt/course_id/${courseId}/content_id/${sequenceId}`);
const examResponse = await getAuthenticatedHttpClient().get(examUrl.href);
data = examResponse.data;

const attemptData = await fetchActiveAttempt();
data.active_attempt = attemptData;
}
return data;
}

//
export async function fetchLatestAttempt(courseId) {
let data;
if (!getConfig().EXAMS_BASE_URL) {
const url = new URL(
`${getConfig().LMS_BASE_URL}${BASE_API_URL}/course_id/${courseId}`,
);
url.searchParams.append('is_learning_mfe', true);
const urlResponse = await getAuthenticatedHttpClient().get(url.href);
data = urlResponse.data;
} else {
// initialize data dictionary to be similar to what edx-proctoring returns
data = { exam: {} };

const attemptData = await fetchActiveAttempt();
data.active_attempt = attemptData;
}
url.searchParams.append('is_learning_mfe', true);
const { data } = await getAuthenticatedHttpClient().get(url.href);
return data;
}

Expand All @@ -26,7 +61,7 @@ export async function createExamAttempt(examId, startClock = true, attemptProcto
if (!getConfig().EXAMS_BASE_URL) {
urlString = `${getConfig().LMS_BASE_URL}${BASE_API_URL}`;
} else {
urlString = `${getConfig().EXAMS_BASE_URL}/exams/attempt`;
urlString = `${getConfig().EXAMS_BASE_URL}/api/v1/exams/attempt`;
}
const url = new URL(urlString);
const payload = {
Expand All @@ -43,7 +78,7 @@ export async function updateAttemptStatus(attemptId, action, detail = null) {
if (!getConfig().EXAMS_BASE_URL) {
urlString = `${getConfig().LMS_BASE_URL}${BASE_API_URL}/${attemptId}`;
} else {
urlString = `${getConfig().EXAMS_BASE_URL}/attempt/${attemptId}`;
urlString = `${getConfig().EXAMS_BASE_URL}/api/v1/attempt/${attemptId}`;
}
const url = new URL(urlString);
const payload = { action };
Expand Down
1 change: 1 addition & 0 deletions src/data/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
getExamAttemptsData,
getLatestAttemptData,
getProctoringSettings,
startTimedExam,
startProctoredExam,
Expand Down
Loading

0 comments on commit 71a6c1c

Please sign in to comment.