diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 980c19397e4..2b520563bf4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,9 @@ Change Log Unreleased ~~~~~~~~~~ +[3.8.3] - 2021-04-05 +~~~~~~~~~~~~~~~~~~~~~ +* Use exam due_date or course end date to evaluate the visibility of the onboarding status panel [3.8.2] - 2021-04-02 ~~~~~~~~~~~~~~~~~~~~~ diff --git a/edx_proctoring/__init__.py b/edx_proctoring/__init__.py index 79b7a818827..a1102f45889 100644 --- a/edx_proctoring/__init__.py +++ b/edx_proctoring/__init__.py @@ -3,6 +3,6 @@ """ # Be sure to update the version number in edx_proctoring/package.json -__version__ = '3.8.2' +__version__ = '3.8.3' default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name diff --git a/edx_proctoring/tests/test_services.py b/edx_proctoring/tests/test_services.py index fa1b27ca7f2..9433d7d6824 100644 --- a/edx_proctoring/tests/test_services.py +++ b/edx_proctoring/tests/test_services.py @@ -350,14 +350,16 @@ def __init__(self, accessible_sequences): class MockScheduleData: """Mock Outline Schedule""" - def __init__(self, schedule_items): + def __init__(self, schedule_items, course_end=None): self.sequences = schedule_items + self.course_end = course_end class MockScheduleItemData: """Mock Schedule Item""" - def __init__(self, effective_start): + def __init__(self, effective_start, due_date=None): self.effective_start = effective_start + self.due = due_date class MockLearningSequencesService: @@ -367,8 +369,12 @@ def __init__(self, accessible_sequences, schedule_items): self.schedule_items = schedule_items def get_user_course_outline_details(self, course_key, user, at_time): - """ Return mock CourseOutlineDetailsData """ + """ Return mock UserCourseOutlineDetailsData """ return MockUserCourseOutlineDetailsData( MockUserCourseOutlineData(self.accessible_sequences), MockScheduleData(self.schedule_items), ) + + def get_user_course_outline(self, course_key, user, at_time): + """ Return mock UserCourseOutlineData """ + return MockUserCourseOutlineData(self.accessible_sequences) diff --git a/edx_proctoring/tests/test_utils.py b/edx_proctoring/tests/test_utils.py index 6030288a510..a1405fd2c2f 100644 --- a/edx_proctoring/tests/test_utils.py +++ b/edx_proctoring/tests/test_utils.py @@ -3,7 +3,7 @@ """ import unittest -from datetime import datetime +from datetime import datetime, timedelta from itertools import product import ddt @@ -11,9 +11,11 @@ from freezegun import freeze_time from edx_proctoring.statuses import ProctoredExamStudentAttemptStatus +from edx_proctoring.tests.test_services import MockScheduleData, MockScheduleItemData from edx_proctoring.utils import ( _emit_event, get_time_remaining_for_attempt, + get_visibility_check_date, humanized_time, is_reattempting_exam, obscured_user_id @@ -155,3 +157,30 @@ def test_obscured_user_id(self): user_id = 32432455 expected_obscured_user_id = '9b82efd5d28f1a170b23b8f648c3093e75a0a0ca' self.assertEqual(expected_obscured_user_id, obscured_user_id(user_id)) + + @ddt.data( + (1, 3, 1), + (None, 4, 4), + (2, None, 2), + (-1, -3, -1), + (None, -2, -2), + (-3, None, -3) + ) + @ddt.unpack + def test_get_visibility_check_date(self, mock_due_offset, mock_end_offset, expected_offset): + cur_time = pytz.utc.localize(datetime.now()) + expected_datetime = cur_time + timedelta(days=expected_offset) + mock_due = None + if mock_due_offset: + mock_due = cur_time + timedelta(days=mock_due_offset) + mock_course_end = None + if mock_end_offset: + mock_course_end = cur_time + timedelta(days=mock_end_offset) + mock_usage_key = '32fjkle2jf3' + mock_sequences = { + mock_usage_key: MockScheduleItemData(cur_time, mock_due) + } + mock_schedule = MockScheduleData(mock_sequences, mock_course_end) + with freeze_time(cur_time): + due_date = get_visibility_check_date(mock_schedule, mock_usage_key) + self.assertEqual(due_date, expected_datetime) diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 464c30a869d..fd3939e8b0e 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -439,6 +439,7 @@ def test_get_exam_insufficient_args(self): self.assertEqual(response_data['time_limit_mins'], proctored_exam.time_limit_mins) +@ddt.ddt class TestStudentOnboardingStatusView(ProctoredExamTestCase): """ Tests for StudentOnboardingStatusView @@ -823,14 +824,15 @@ def test_no_accessible_onboarding(self): message = 'There is no onboarding exam accessible to this user.' self.assertEqual(response_data['detail'], message) - def test_onboarding_not_yet_released(self): + @ddt.data(None, timezone.now() + timezone.timedelta(days=3)) + def test_onboarding_not_yet_released(self, due_date): """ If the onboarding section has not been released the release date is returned """ tomorrow = timezone.now() + timezone.timedelta(days=1) self.course_scheduled_sections[ BlockUsageLocator.from_string(self.onboarding_exam.content_id) - ] = MockScheduleItemData(tomorrow) + ] = MockScheduleItemData(tomorrow, due_date=due_date) set_runtime_service('learning_sequences', MockLearningSequencesService( list(self.course_scheduled_sections.keys()), diff --git a/edx_proctoring/utils.py b/edx_proctoring/utils.py index dae2dfd7184..29ebf1f9e93 100644 --- a/edx_proctoring/utils.py +++ b/edx_proctoring/utils.py @@ -282,3 +282,21 @@ def is_reattempting_exam(from_status, to_status): ProctoredExamStudentAttemptStatus.is_in_progress_status(from_status) and ProctoredExamStudentAttemptStatus.is_pre_started_status(to_status) ) + + +def get_visibility_check_date(course_schedule, usage_key): + """ + Utility function to return the date, of which + we should use to test the learner's visibility to the exam + + Returns one of the following: + * The due date of the course structure usage_key + * The course end date + * The max datetime if no course_end date specified + """ + visibility_check_date = course_schedule.course_end or pytz.utc.localize(datetime.max) + exam_schedule_item = course_schedule.sequences.get(usage_key) + if exam_schedule_item and exam_schedule_item.due: + visibility_check_date = exam_schedule_item.due + + return visibility_check_date diff --git a/edx_proctoring/views.py b/edx_proctoring/views.py index 00762d1805f..c7d16e8622e 100644 --- a/edx_proctoring/views.py +++ b/edx_proctoring/views.py @@ -80,6 +80,7 @@ from edx_proctoring.utils import ( AuthenticatedAPIView, get_time_remaining_for_attempt, + get_visibility_check_date, humanized_time, locate_attempt_by_attempt_code, obscured_user_id @@ -369,7 +370,12 @@ def get(self, request): for onboarding_exam in onboarding_exams: usage_key = BlockUsageLocator.from_string(onboarding_exam.content_id) - if usage_key not in details.outline.accessible_sequences: + visibility_check_date = get_visibility_check_date(details.schedule, usage_key) + user_outline = learning_sequences_service.get_user_course_outline( + course_key, user, visibility_check_date + ) + + if usage_key not in user_outline.accessible_sequences: onboarding_exams.remove(onboarding_exam) if not onboarding_exams: diff --git a/package.json b/package.json index 46d5beb7862..8fe2d4dce44 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@edx/edx-proctoring", "//": "Be sure to update the version number in edx_proctoring/__init__.py", "//": "Note that the version format is slightly different than that of the Python version when using prereleases.", - "version": "3.8.2", + "version": "3.8.3", "main": "edx_proctoring/static/index.js", "scripts":{ "test":"gulp test"