From 83e4fa2005735ab7d4fa4046b7d372910a965835 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 | 25 +++++++++------- edx_exams/apps/core/signals/signals.py | 28 +++++++++++++++++- edx_exams/apps/core/tests/test_api.py | 29 ++++++++++++++++++- .../apps/lti/signals/tests/test_handlers.py | 2 +- edx_exams/apps/lti/tests/test_views.py | 2 +- 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/edx_exams/apps/core/api.py b/edx_exams/apps/core/api.py index 73af36e1..b0b0978f 100644 --- a/edx_exams/apps/core/api.py +++ b/edx_exams/apps/core/api.py @@ -17,7 +17,7 @@ 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.signals.signals import emit_exam_attempt_submitted_event, emit_exam_attempt_verified_event from edx_exams.apps.core.statuses import ExamAttemptStatus from edx_exams.apps.core.signals.signals import emit_exam_attempt_submitted_event from edx_exams.apps.core.statuses import ExamAttemptStatus @@ -96,11 +96,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, @@ -108,6 +109,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..9510c256 100644 --- a/edx_exams/apps/core/signals/handlers.py +++ b/edx_exams/apps/core/signals/handlers.py @@ -2,21 +2,12 @@ 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 +18,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 2a1e4204..b36e423e 100644 --- a/edx_exams/apps/core/signals/signals.py +++ b/edx_exams/apps/core/signals/signals.py @@ -2,7 +2,7 @@ Signal definitions and functions to send those signals for the edx-exams application. """ 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): @@ -29,3 +29,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 diff --git a/edx_exams/apps/lti/signals/tests/test_handlers.py b/edx_exams/apps/lti/signals/tests/test_handlers.py index c24a6d8c..3f1437eb 100644 --- a/edx_exams/apps/lti/signals/tests/test_handlers.py +++ b/edx_exams/apps/lti/signals/tests/test_handlers.py @@ -36,7 +36,7 @@ def setUp(self): ) self.course_id = 'course-v1:edx+test+f19' - self.content_id = '11111111' + self.content_id = 'block-v1:edX+test+2023+type@sequential+block@1111111111' self.exam = Exam.objects.create( resource_id=str(uuid.uuid4()), diff --git a/edx_exams/apps/lti/tests/test_views.py b/edx_exams/apps/lti/tests/test_views.py index 0817a8cb..7cbf675d 100644 --- a/edx_exams/apps/lti/tests/test_views.py +++ b/edx_exams/apps/lti/tests/test_views.py @@ -365,7 +365,7 @@ def setUp(self): super().setUp() self.course_id = 'course-v1:edx+test+f19' - self.content_id = '11111111' + self.content_id = 'block-v1:edX+test+2023+type@sequential+block@1111111111' self.exam = ExamFactory( course_id=self.course_id,