Merge pull request #32250 from openedx/MichaelRoytman/MST-1869-course-end-date

feat: send course end date as due date to exams service if exam has no due date
This commit is contained in:
Michael Roytman
2023-05-22 11:37:37 -04:00
committed by GitHub
2 changed files with 50 additions and 13 deletions

View File

@@ -70,12 +70,20 @@ def register_exams(course_key):
timed_exam.is_practice_exam,
timed_exam.is_onboarding_exam
)
exams_list.append({
'course_id': str(course_key),
'content_id': str(timed_exam.location),
'exam_name': timed_exam.display_name,
'time_limit_mins': timed_exam.default_time_limit_minutes,
'due_date': timed_exam.due.isoformat() if timed_exam.due and not course.self_paced else None,
# If the subsection has no due date, then infer a due date from the course end date. This behavior is a
# departure from the legacy register_exams function used by the edx-proctoring plugin because
# edx-proctoring makes a direct call to edx-when API when computing an exam's due date.
# By sending the course end date when registering exams, we can avoid calling to the platform from the
# exam service. Also note that we no longer consider the pacing type of the course - this applies to both
# self-paced and indstructor-paced courses. Therefore, this effectively opts out exams powered by edx-exams
# from personalized learner schedules/relative dates.
'due_date': timed_exam.due.isoformat() if timed_exam.due else course.end.isoformat(),
'exam_type': exam_type,
'is_active': True,
'hide_after_due': timed_exam.hide_after_due,

View File

@@ -1,7 +1,7 @@
"""
Test the exams service integration into Studio
"""
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from unittest.mock import patch, Mock
import ddt
@@ -147,8 +147,15 @@ class TestExamService(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
mock_patch_course_exams.assert_not_called()
def test_self_paced_no_due_dates(self, mock_patch_course_exams):
self.course.self_paced = True
@ddt.data(True, False)
def test_no_due_dates(self, is_self_paced, mock_patch_course_exams):
"""
Test that the coures end date is registered as the due date when the subsection does not have a due date for
both self-paced and instructor-paced exams.
"""
self.course.self_paced = is_self_paced
end_date = datetime(2035, 1, 1, 0, 0, tzinfo=timezone.utc)
self.course.end = end_date
self.course = self.update_course(self.course, 1)
BlockFactory.create(
parent=self.chapter,
@@ -159,18 +166,40 @@ class TestExamService(ModuleStoreTestCase):
default_time_limit_minutes=60,
is_proctored_enabled=False,
is_practice_exam=False,
due=datetime.now(UTC) + timedelta(minutes=60),
due=None,
hide_after_due=True,
is_onboarding_exam=False,
)
listen_for_course_publish(self, self.course.id)
called_exams, called_course = mock_patch_course_exams.call_args[0]
assert called_exams[0]['due_date'] is None
# now switch to instructor paced
# the exam will be updated with a due date
self.course.self_paced = False
self.course = self.update_course(self.course, 1)
listen_for_course_publish(self, self.course.id)
called_exams, called_course = mock_patch_course_exams.call_args[0]
assert called_exams[0]['due_date'] is not None
assert called_exams[0]['due_date'] == end_date.isoformat()
@ddt.data(True, False)
def test_subsection_due_date_prioritized(self, is_self_paced, mock_patch_course_exams):
"""
Test that the subsection due date is registered as the due date when both the subsection has a due date and the
course has an end date for both self-paced and instructor-paced exams.
"""
self.course.self_paced = is_self_paced
self.course.end = datetime(2035, 1, 1, 0, 0)
self.course = self.update_course(self.course, 1)
sequential_due_date = datetime.now(UTC) + timedelta(minutes=60)
BlockFactory.create(
parent=self.chapter,
category='sequential',
display_name='Test Proctored Exam',
graded=True,
is_time_limited=True,
default_time_limit_minutes=60,
is_proctored_enabled=False,
is_practice_exam=False,
due=sequential_due_date,
hide_after_due=True,
is_onboarding_exam=False,
)
listen_for_course_publish(self, self.course.id)
called_exams, called_course = mock_patch_course_exams.call_args[0]
assert called_exams[0]['due_date'] == sequential_due_date.isoformat()