Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: emit error event #189

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ omit =
edx_exams/settings/*
edx_exams/conf*
edx_exams/docker_gunicorn_configuration.py
edx_exams/apps/core/signals/handlers.py
*wsgi.py
*migrations*
*admin.py
Expand Down
9 changes: 9 additions & 0 deletions edx_exams/apps/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)
from edx_exams.apps.core.models import Exam, ExamAttempt
from edx_exams.apps.core.signals.signals import (
emit_exam_attempt_errored_event,
emit_exam_attempt_rejected_event,
emit_exam_attempt_submitted_event,
emit_exam_attempt_verified_event
Expand Down Expand Up @@ -127,6 +128,14 @@ def update_attempt_status(attempt_id, to_status):
attempt_obj.exam.exam_type
)

if to_status == ExamAttemptStatus.error:
emit_exam_attempt_errored_event(
attempt_obj.user,
course_key,
usage_key,
attempt_obj.exam.exam_type
)

attempt_obj.status = to_status
attempt_obj.save()

Expand Down
21 changes: 20 additions & 1 deletion edx_exams/apps/core/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
"""
from django.dispatch import receiver
from openedx_events.event_bus import get_producer
from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED
from openedx_events.learning.signals import (
EXAM_ATTEMPT_ERRORED,
EXAM_ATTEMPT_REJECTED,
EXAM_ATTEMPT_SUBMITTED,
EXAM_ATTEMPT_VERIFIED
)


@receiver(EXAM_ATTEMPT_SUBMITTED)
Expand Down Expand Up @@ -46,3 +51,17 @@ def listen_for_exam_attempt_rejected(sender, signal, **kwargs): # pylint: disab
event_data={'exam_attempt': kwargs['exam_attempt']},
event_metadata=kwargs['metadata'],
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest just marking this entire file as pragma: no-cover since this pattern of manual sends is temporary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excluded this file from coverage by modifying .coveragerc


@receiver(EXAM_ATTEMPT_ERRORED)
def listen_for_exam_attempt_errored(sender, signal, **kwargs): # pylint: disable=unused-argument
"""
Publish EXAM_ATTEMPT_ERRORED signal onto the event bus
"""
get_producer().send(
signal=EXAM_ATTEMPT_ERRORED,
topic='exam-attempt-errored',
event_key_field='exam_attempt.course_key',
event_data={'exam_attempt': kwargs['exam_attempt']},
event_metadata=kwargs['metadata'],
)
57 changes: 36 additions & 21 deletions edx_exams/apps/core/signals/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
"""

from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData
from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED, EXAM_ATTEMPT_SUBMITTED, EXAM_ATTEMPT_VERIFIED
from openedx_events.learning.signals import (
EXAM_ATTEMPT_ERRORED,
EXAM_ATTEMPT_REJECTED,
EXAM_ATTEMPT_SUBMITTED,
EXAM_ATTEMPT_VERIFIED
)


def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type):
def _create_user_data(user):
"""
Emit the EXAM_ATTEMPT_SUBMITTED Open edX event.
Helper function to create a UserData object.
"""
user_data = UserData(
id=user.id,
Expand All @@ -20,6 +25,15 @@ def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type):
)
)

return user_data


def emit_exam_attempt_submitted_event(user, course_key, usage_key, exam_type):
"""
Emit the EXAM_ATTEMPT_SUBMITTED Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: EXAM_ATTEMPT_SUBMITTED
EXAM_ATTEMPT_SUBMITTED.send_event(
exam_attempt=ExamAttemptData(
Expand All @@ -36,15 +50,7 @@ def emit_exam_attempt_verified_event(user, course_key, usage_key, exam_type):
"""
Emit the EXAM_ATTEMPT_VERIFIED 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
)
)
user_data = _create_user_data(user)

# .. event_implemented_name: EXAM_ATTEMPT_VERIFIED
EXAM_ATTEMPT_VERIFIED.send_event(
Expand All @@ -61,15 +67,7 @@ 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
)
)
user_data = _create_user_data(user)

# .. event_implemented_name: EXAM_ATTEMPT_REJECTED
EXAM_ATTEMPT_REJECTED.send_event(
Expand All @@ -80,3 +78,20 @@ def emit_exam_attempt_rejected_event(user, course_key, usage_key, exam_type):
exam_type=exam_type
)
)


def emit_exam_attempt_errored_event(user, course_key, usage_key, exam_type):
"""
Emit the EXAM_ATTEMPT_ERRORED Open edX event.
"""
user_data = _create_user_data(user)

# .. event_implemented_name: EXAM_ATTEMPT_ERRORED
EXAM_ATTEMPT_ERRORED.send_event(
exam_attempt=ExamAttemptData(
student_user=user_data,
course_key=course_key,
usage_key=usage_key,
exam_type=exam_type
)
)
97 changes: 25 additions & 72 deletions edx_exams/apps/core/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,89 +299,42 @@ def test_submit_attempt(self):
self.assertEqual(updated_attempt.status, ExamAttemptStatus.submitted)
self.assertEqual(updated_attempt.end_time, timezone.now())

@patch('edx_exams.apps.core.signals.signals.EXAM_ATTEMPT_SUBMITTED.send_event')
def test_submit_attempt_event_emitted(self, mock_event_send):
"""
Test that when an exam is submitted, the EXAM_ATTEMPT_SUBMITED Open edX event is emitted.
"""
update_attempt_status(self.exam_attempt.id, ExamAttemptStatus.submitted)
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,
requesting_user=user_data,
)
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):
@ddt.data(
('EXAM_ATTEMPT_SUBMITTED', ExamAttemptStatus.submitted, True),
('EXAM_ATTEMPT_VERIFIED', ExamAttemptStatus.verified, False),
('EXAM_ATTEMPT_REJECTED', ExamAttemptStatus.rejected, False),
('EXAM_ATTEMPT_ERRORED', ExamAttemptStatus.error, False)
)
@ddt.unpack
def test_attempt_event_emitted(self, event_name, status, expect_requesting_user):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff for this is not displaying that well, but I combined all of the producer tests into one test. Each event is mocked, and then the status is updated.

"""
Test that when an exam is verified, the EXAM_ATTEMPT_VERIFIED Open edX event is emitted.
Test that when an exam status is updated, the corresponding Open edX event is emitted.
"""
update_attempt_status(self.exam_attempt.id, ExamAttemptStatus.verified)
self.assertEqual(mock_event_send.call_count, 1)
patch_event = 'edx_exams.apps.core.signals.signals.{event_name}.send_event'.format(event_name=event_name)
with patch(patch_event) as mock_event_send:
update_attempt_status(self.exam_attempt.id, status)
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(
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=course_key,
usage_key=usage_key,
exam_type=self.exam.exam_type,
)
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)
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,
requesting_user=user_data if expect_requesting_user else None,
)
mock_event_send.assert_called_with(exam_attempt=expected_data)

def test_illegal_start(self):
"""
Expand Down
Loading