From 284eb8d577545d569c68937844aec739da701877 Mon Sep 17 00:00:00 2001 From: alangsto <46360176+alangsto@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:38:32 -0400 Subject: [PATCH] feat: emit rejected exam signal (#181) --- edx_exams/apps/core/api.py | 14 +++++++++++- edx_exams/apps/core/signals/handlers.py | 16 ++++++++++++- edx_exams/apps/core/signals/signals.py | 30 ++++++++++++++++++++++--- edx_exams/apps/core/tests/test_api.py | 28 +++++++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/edx_exams/apps/core/api.py b/edx_exams/apps/core/api.py index db201ecc..911eac6e 100644 --- a/edx_exams/apps/core/api.py +++ b/edx_exams/apps/core/api.py @@ -17,7 +17,11 @@ ExamIllegalStatusTransition ) from edx_exams.apps.core.models import Exam, ExamAttempt -from edx_exams.apps.core.signals.signals import emit_exam_attempt_submitted_event, emit_exam_attempt_verified_event +from edx_exams.apps.core.signals.signals import ( + emit_exam_attempt_rejected_event, + emit_exam_attempt_submitted_event, + emit_exam_attempt_verified_event +) from edx_exams.apps.core.statuses import ExamAttemptStatus log = logging.getLogger(__name__) @@ -115,6 +119,14 @@ def update_attempt_status(attempt_id, to_status): attempt_obj.exam.exam_type ) + if to_status == ExamAttemptStatus.rejected: + emit_exam_attempt_rejected_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 9510c256..fe39bc13 100644 --- a/edx_exams/apps/core/signals/handlers.py +++ b/edx_exams/apps/core/signals/handlers.py @@ -3,7 +3,7 @@ """ from django.dispatch import receiver from openedx_events.event_bus import get_producer -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED @receiver(EXAM_ATTEMPT_SUBMITTED) @@ -32,3 +32,17 @@ def listen_for_exam_attempt_verified(sender, signal, **kwargs): # pylint: disab event_data={'exam_attempt': kwargs['exam_attempt']}, event_metadata=kwargs['metadata'], ) + + +@receiver(EXAM_ATTEMPT_REJECTED) +def listen_for_exam_attempt_rejected(sender, signal, **kwargs): # pylint: disable=unused-argument + """ + Publish EXAM_ATTEMPT_REJECTED signal onto the event bus + """ + get_producer().send( + signal=EXAM_ATTEMPT_REJECTED, + topic='exam-attempt-rejected', + 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 09c5c7ce..d5d7b540 100644 --- a/edx_exams/apps/core/signals/signals.py +++ b/edx_exams/apps/core/signals/signals.py @@ -3,7 +3,7 @@ """ from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData -from openedx_events.learning.signals import EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED +from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type): @@ -36,14 +36,13 @@ 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 + name=user.full_name ) ) @@ -56,3 +55,28 @@ def emit_exam_attempt_verified_event(user, course_key, usage_key, exam_type): exam_type=exam_type ) ) + + +def emit_exam_attempt_rejected_event(user, course_key, usage_key, exam_type): + """ + Emit the EXAM_ATTEMPT_REJECTED Open edX event. + """ + user_data = UserData( + id=user.id, + is_active=user.is_active, + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.full_name + ) + ) + + # .. event_implemented_name: EXAM_ATTEMPT_REJECTED + EXAM_ATTEMPT_REJECTED.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 f6073c2f..4cddd5a4 100644 --- a/edx_exams/apps/core/tests/test_api.py +++ b/edx_exams/apps/core/tests/test_api.py @@ -355,6 +355,34 @@ def test_verified_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_REJECTED.send_event') + def test_reject_attempt_event_emitted(self, mock_event_send): + """ + Test that when an exam is rejected, the EXAM_ATTEMPT_REJECTED Open edX event is emitted. + """ + update_attempt_status(self.exam_attempt.id, ExamAttemptStatus.rejected) + self.assertEqual(mock_event_send.call_count, 1) + + user_data = 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 = CourseKey.from_string(self.exam.course_id) + usage_key = UsageKey.from_string(self.exam.content_id) + + expected_data = ExamAttemptData( + student_user=user_data, + 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