From 8656325440e2580b5bb6ee785a15c7ce3af3cefc Mon Sep 17 00:00:00 2001 From: Alie Langston Date: Fri, 29 Sep 2023 08:22:56 -0400 Subject: [PATCH] feat: emit verified event for attempt --- edx_exams/apps/core/api.py | 15 +++++++++--- edx_exams/apps/core/signals/handlers.py | 26 ++++++++++++-------- edx_exams/apps/core/signals/signals.py | 32 +++++++++++++++++++++---- edx_exams/apps/core/tests/test_api.py | 29 +++++++++++++++++++++- 4 files changed, 83 insertions(+), 19 deletions(-) diff --git a/edx_exams/apps/core/api.py b/edx_exams/apps/core/api.py index b6173fef..9fc672a3 100644 --- a/edx_exams/apps/core/api.py +++ b/edx_exams/apps/core/api.py @@ -17,8 +17,8 @@ ExamIllegalStatusTransition ) from edx_exams.apps.core.models import Exam, ExamAttempt -from edx_exams.apps.core.signals.signals import emit_exam_attempt_submitted_event from edx_exams.apps.core.statuses import ExamAttemptStatus +from edx_exams.apps.core.signals.signals import emit_exam_attempt_submitted_event, emit_exam_attempt_verified_event log = logging.getLogger(__name__) @@ -94,11 +94,12 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.start_time = datetime.now(pytz.UTC) attempt_obj.allowed_time_limit_mins = _calculate_allowed_mins(attempt_obj.exam) + course_key = CourseKey.from_string(attempt_obj.exam.course_id) + usage_key = UsageKey.from_string(attempt_obj.exam.content_id) + if to_status == ExamAttemptStatus.submitted: attempt_obj.end_time = datetime.now(pytz.UTC) - course_key = CourseKey.from_string(attempt_obj.exam.course_id) - usage_key = UsageKey.from_string(attempt_obj.exam.content_id) emit_exam_attempt_submitted_event( attempt_obj.user, course_key, @@ -106,6 +107,14 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.exam.exam_type ) + if to_status == ExamAttemptStatus.verified: + emit_exam_attempt_verified_event( + attempt_obj.user, + course_key, + usage_key, + attempt_obj.exam.exam_type + ) + attempt_obj.status = to_status attempt_obj.save() diff --git a/edx_exams/apps/core/signals/handlers.py b/edx_exams/apps/core/signals/handlers.py index a540f21f..3f99b39a 100644 --- a/edx_exams/apps/core/signals/handlers.py +++ b/edx_exams/apps/core/signals/handlers.py @@ -2,21 +2,13 @@ Signal handlers for the edx-exams application. """ from django.dispatch import receiver -<<<<<<< HEAD + from openedx_events.event_bus import get_producer -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED +from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED @receiver(EXAM_ATTEMPT_SUBMITTED) def listen_for_exam_attempt_submitted(sender, signal, **kwargs): # pylint: disable=unused-argument -======= - -from openedx_events.event_bus import get_producer -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED - -@receiver(EXAM_ATTEMPT_SUBMITTED) -def listen_for_exam_attempt_submitted(sender, signal, **kwargs): ->>>>>>> d324b9d (feat: incorporate changes in 8.7.0 and add signal hander) """ Publish EXAM_ATTEMPT_SUBMITTED signals onto the event bus. """ @@ -27,3 +19,17 @@ def listen_for_exam_attempt_submitted(sender, signal, **kwargs): event_data={'exam_attempt': kwargs['exam_attempt']}, event_metadata=kwargs['metadata'], ) + + +@receiver(EXAM_ATTEMPT_VERIFIED) +def listen_for_exam_attempt_verified(sender, signal, **kwargs): # pylint: disable=unused-argument + """ + Publish EXAM_ATTEMPT_VERIFIED signal onto the event bus + """ + get_producer().send( + signal=EXAM_ATTEMPT_VERIFIED, + topic='exam-attempt-verified', + event_key_field='exam_attempt.course_key', + event_data={'exam_attempt': kwargs['exam_attempt']}, + event_metadata=kwargs['metadata'], + ) diff --git a/edx_exams/apps/core/signals/signals.py b/edx_exams/apps/core/signals/signals.py index cde898c6..e2fd4923 100644 --- a/edx_exams/apps/core/signals/signals.py +++ b/edx_exams/apps/core/signals/signals.py @@ -3,18 +3,14 @@ """ from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED +from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type): -<<<<<<< HEAD """ Emit the EXAM_ATTEMPT_SUBMITTED Open edX event. """ name = user.full_name or '' -======= - name=user.full_name or '' ->>>>>>> d324b9d (feat: incorporate changes in 8.7.0 and add signal hander) user_data = UserData( id=user.id, is_active=user.is_active, @@ -35,3 +31,29 @@ def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type): requesting_user=user_data ) ) + + +def emit_exam_attempt_verified_event(user, course_key, usage_key, exam_type): + """ + Emit the EXAM_ATTEMPT_VERIFIED Open edX event. + """ + name = user.full_name or '' + user_data = UserData( + id=user.id, + is_active=user.is_active, + pii=UserPersonalData( + username=user.username, + email=user.email, + name=name + ) + ) + + # .. event_implemented_name: EXAM_ATTEMPT_VERIFIED + EXAM_ATTEMPT_VERIFIED.send_event( + exam_attempt=ExamAttemptData( + student_user=user_data, + course_key=course_key, + usage_key=usage_key, + exam_type=exam_type + ) + ) diff --git a/edx_exams/apps/core/tests/test_api.py b/edx_exams/apps/core/tests/test_api.py index 49553449..f6073c2f 100644 --- a/edx_exams/apps/core/tests/test_api.py +++ b/edx_exams/apps/core/tests/test_api.py @@ -268,7 +268,7 @@ def test_start_attempt_with_due_date(self, is_due_during_exam): resource_id=str(uuid.uuid4()), course_id=self.course_id, provider=self.test_provider, - content_id='abcd1234', + content_id='block-v1:edX+test+2023+type@sequential+block@1111111111', exam_name='test_exam', exam_type='proctored', time_limit_mins=30, @@ -328,6 +328,33 @@ def test_submit_attempt_event_emitted(self, mock_event_send): ) mock_event_send.assert_called_with(exam_attempt=expected_data) + @patch('edx_exams.apps.core.signals.signals.EXAM_ATTEMPT_VERIFIED.send_event') + def test_verified_attempt_event_emitted(self, mock_event_send): + """ + Test that when an exam is verified, the EXAM_ATTEMPT_VERIFIED Open edX event is emitted. + """ + update_attempt_status(self.exam_attempt.id, ExamAttemptStatus.verified) + self.assertEqual(mock_event_send.call_count, 1) + + usage_key = UsageKey.from_string(self.exam.content_id) + course_key = CourseKey.from_string(self.exam.course_id) + + expected_data = ExamAttemptData( + student_user=UserData( + id=self.user.id, + is_active=self.user.is_active, + pii=UserPersonalData( + username=self.user.username, + email=self.user.email, + name=self.user.full_name + ) + ), + course_key=course_key, + usage_key=usage_key, + exam_type=self.exam.exam_type, + ) + mock_event_send.assert_called_with(exam_attempt=expected_data) + def test_illegal_start(self): """ Test that an already started exam cannot be started