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:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user