-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: receiver for invalidate certificate (#33319)
* feat: receiver for invalidate certificate - consumes event of exam attempt rejected - initial commit, need to make tests * temp: moving consumer from signals to handlers.py - Still need to make this work - Need to make tests work too * feat: refactored underlying code to api.py - tests still need to be tweaked * fix: commit history * fix: improve api func name + add source param
- Loading branch information
Showing
4 changed files
with
152 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
""" | ||
Handlers for credits | ||
""" | ||
import logging | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.dispatch import receiver | ||
from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED | ||
|
||
from lms.djangoapps.certificates.api import invalidate_certificate | ||
|
||
User = get_user_model() | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
@receiver(EXAM_ATTEMPT_REJECTED) | ||
def handle_exam_attempt_rejected_event(sender, signal, **kwargs): | ||
""" | ||
Consume `EXAM_ATTEMPT_REJECTED` events from the event bus. | ||
Pass the received data to invalidate_certificate in the services.py file in this folder. | ||
""" | ||
event_data = kwargs.get('exam_attempt') | ||
user_data = event_data.student_user | ||
course_key = event_data.course_key | ||
|
||
# Note that the course_key is the same as the course_key_or_id, and is being passed in as the course_key param | ||
invalidate_certificate(user_data.id, course_key, source='exam_event') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
""" | ||
Unit tests for certificates signals | ||
""" | ||
from datetime import datetime, timezone | ||
from unittest import mock | ||
from uuid import uuid4 | ||
|
||
from django.test import TestCase | ||
from opaque_keys.edx.keys import CourseKey, UsageKey | ||
from openedx_events.data import EventsMetadata | ||
from openedx_events.learning.data import ExamAttemptData, UserData, UserPersonalData | ||
from openedx_events.learning.signals import EXAM_ATTEMPT_REJECTED | ||
|
||
from common.djangoapps.student.tests.factories import UserFactory | ||
from lms.djangoapps.certificates.handlers import handle_exam_attempt_rejected_event | ||
|
||
|
||
class ExamCompletionEventBusTests(TestCase): | ||
""" | ||
Tests completion events from the event bus. | ||
""" | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.course_key = CourseKey.from_string('course-v1:edX+TestX+Test_Course') | ||
cls.subsection_id = 'block-v1:edX+TestX+Test_Course+type@sequential+block@subsection' | ||
cls.usage_key = UsageKey.from_string(cls.subsection_id) | ||
cls.student_user = UserFactory( | ||
username='student_user', | ||
) | ||
|
||
@staticmethod | ||
def _get_exam_event_data(student_user, course_key, usage_key, exam_type, requesting_user=None): | ||
""" create ExamAttemptData object for exam based event """ | ||
if requesting_user: | ||
requesting_user_data = UserData( | ||
id=requesting_user.id, | ||
is_active=True, | ||
pii=None | ||
) | ||
else: | ||
requesting_user_data = None | ||
|
||
return ExamAttemptData( | ||
student_user=UserData( | ||
id=student_user.id, | ||
is_active=True, | ||
pii=UserPersonalData( | ||
username=student_user.username, | ||
email=student_user.email, | ||
), | ||
), | ||
course_key=course_key, | ||
usage_key=usage_key, | ||
requesting_user=requesting_user_data, | ||
exam_type=exam_type, | ||
) | ||
|
||
@staticmethod | ||
def _get_exam_event_metadata(event_signal): | ||
""" create metadata object for event """ | ||
return EventsMetadata( | ||
event_type=event_signal.event_type, | ||
id=uuid4(), | ||
minorversion=0, | ||
source='openedx/lms/web', | ||
sourcehost='lms.test', | ||
time=datetime.now(timezone.utc) | ||
) | ||
|
||
@mock.patch('lms.djangoapps.certificates.handlers.invalidate_certificate') | ||
def test_exam_attempt_rejected_event(self, mock_api_function): | ||
""" | ||
Assert that CertificateService api's invalidate_certificate is called upon consuming the event | ||
""" | ||
exam_event_data = self._get_exam_event_data(self.student_user, | ||
self.course_key, | ||
self.usage_key, | ||
exam_type='proctored') | ||
event_metadata = self._get_exam_event_metadata(EXAM_ATTEMPT_REJECTED) | ||
|
||
event_kwargs = { | ||
'exam_attempt': exam_event_data, | ||
'metadata': event_metadata | ||
} | ||
handle_exam_attempt_rejected_event(None, EXAM_ATTEMPT_REJECTED, **event_kwargs) | ||
mock_api_function.assert_called_once_with(self.student_user.id, self.course_key, source='exam_event') |