From 776f4bf94e7f51542403f2122894ffdc051b817c Mon Sep 17 00:00:00 2001 From: Kira Miller <31229189+kiram15@users.noreply.github.com> Date: Mon, 28 Aug 2023 09:08:49 -0600 Subject: [PATCH] feat: adding unenrollments to event bus (#33085) * feat: adding unenrollments to event bus * fix: quality fixes * fix: tweaks to pass tests * fix: more tweaks for testing --------- Co-authored-by: John Nagro --- cms/envs/devstack.py | 2 + common/djangoapps/student/handlers.py | 25 +++++++ .../djangoapps/student/tests/test_handlers.py | 67 +++++++++++++++++++ .../core/djangoapps/notifications/handlers.py | 2 +- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 common/djangoapps/student/handlers.py create mode 100644 common/djangoapps/student/tests/test_handlers.py diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index 4170af3d39..3364d42b97 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -303,11 +303,13 @@ CREDENTIALS_PUBLIC_SERVICE_URL = 'http://localhost:18150' # in the LMS and CMS. # .. toggle_tickets: 'https://github.com/openedx/edx-platform/pull/31813' FEATURES['ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS'] = True +FEATURES['ENABLE_SEND_ENROLLMENT_EVENTS_OVER_BUS'] = True EVENT_BUS_PRODUCER = 'edx_event_bus_redis.create_producer' EVENT_BUS_REDIS_CONNECTION_URL = 'redis://:password@edx.devstack.redis:6379/' EVENT_BUS_TOPIC_PREFIX = 'dev' EVENT_BUS_CONSUMER = 'edx_event_bus_redis.RedisEventConsumer' EVENT_BUS_XBLOCK_LIFECYCLE_TOPIC = 'course-authoring-xblock-lifecycle' +EVENT_BUS_ENROLLMENT_LIFECYCLE_TOPIC = 'course-authoring-enrollment-lifecycle' ################# New settings must go ABOVE this line ################# ######################################################################## diff --git a/common/djangoapps/student/handlers.py b/common/djangoapps/student/handlers.py new file mode 100644 index 0000000000..6dc841f98f --- /dev/null +++ b/common/djangoapps/student/handlers.py @@ -0,0 +1,25 @@ +""" +Handlers for student +""" +from django.conf import settings +from django.dispatch import receiver + +from openedx_events.event_bus import get_producer +from openedx_events.learning.signals import ( + COURSE_UNENROLLMENT_COMPLETED, +) + + +@receiver(COURSE_UNENROLLMENT_COMPLETED) +def course_unenrollment_receiver(sender, signal, **kwargs): + """ + Removes user notification preference when user un-enrolls from the course + """ + if settings.FEATURES.get("ENABLE_SEND_ENROLLMENT_EVENTS_OVER_BUS"): + get_producer().send( + signal=COURSE_UNENROLLMENT_COMPLETED, + topic=getattr(settings, "EVENT_BUS_ENROLLMENT_LIFECYCLE_TOPIC", "course-unenrollment-lifecycle"), + event_key_field='enrollment.course.course_key', + event_data={'enrollment': kwargs.get('enrollment')}, + event_metadata=kwargs.get('metadata') + ) diff --git a/common/djangoapps/student/tests/test_handlers.py b/common/djangoapps/student/tests/test_handlers.py new file mode 100644 index 0000000000..bd8d6a9baf --- /dev/null +++ b/common/djangoapps/student/tests/test_handlers.py @@ -0,0 +1,67 @@ +""" +Unit tests for event bus tests for course unenrollments +""" + +import unittest +from datetime import datetime, timezone +from unittest import mock +from uuid import uuid4 + +from django.test.utils import override_settings +from common.djangoapps.student.handlers import course_unenrollment_receiver +from common.djangoapps.student.tests.factories import ( + UserFactory, + CourseEnrollmentFactory, +) + +from openedx_events.data import EventsMetadata +from openedx_events.learning.signals import COURSE_UNENROLLMENT_COMPLETED +from pytest import mark + + +@mark.django_db +class UnenrollmentEventBusTests(unittest.TestCase): + """ + Tests for unenrollment events that interact with the event bus. + """ + @override_settings(ENABLE_SEND_ENROLLMENT_EVENTS_OVER_BUS=False) + @mock.patch('common.djangoapps.student.handlers.get_producer', autospec=True) + def test_event_disabled(self, mock_producer): + """ + Test to verify that we do not push `CERTIFICATE_CREATED` events to the event bus if the + `SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled. + """ + course_unenrollment_receiver(None, None) + mock_producer.assert_not_called() + + @override_settings(FEATURES={'ENABLE_SEND_ENROLLMENT_EVENTS_OVER_BUS': True}) + @mock.patch('common.djangoapps.student.handlers.get_producer', autospec=True) + def test_event_enabled(self, mock_producer): + """ + Test to verify that we push `COURSE_UNENROLLMENT_COMPLETED` events to the event bus. + """ + user = UserFactory() + enrollment = CourseEnrollmentFactory(user=user) + + event_metadata = EventsMetadata( + event_type=COURSE_UNENROLLMENT_COMPLETED.event_type, + id=uuid4(), + minorversion=0, + source='openedx/lms/web', + sourcehost='lms.test', + time=datetime.now(timezone.utc) + ) + + event_kwargs = { + 'enrollment': enrollment, + 'metadata': event_metadata + } + course_unenrollment_receiver(None, COURSE_UNENROLLMENT_COMPLETED, **event_kwargs) + + # verify that the data sent to the event bus matches what we expect + print(mock_producer.return_value) + print(mock_producer.return_value.send.call_args) + data = mock_producer.return_value.send.call_args.kwargs + assert data['event_data']['enrollment'] == enrollment + assert data['topic'] == 'course-unenrollment-lifecycle' + assert data['event_key_field'] == 'enrollment.course.course_key' diff --git a/openedx/core/djangoapps/notifications/handlers.py b/openedx/core/djangoapps/notifications/handlers.py index 8d39c63025..7d37bbe7d6 100644 --- a/openedx/core/djangoapps/notifications/handlers.py +++ b/openedx/core/djangoapps/notifications/handlers.py @@ -46,7 +46,7 @@ def on_user_course_unenrollment(enrollment, **kwargs): preference = CourseNotificationPreference.objects.get(user__id=user_id, course_id=course_key) preference.delete() except ObjectDoesNotExist: - log.info(f'Notification Preference doesnot exist for {enrollment.user.pii.username} in {course_key}') + log.info(f'Notification Preference does not exist for {enrollment.user.pii.username} in {course_key}') @receiver(USER_NOTIFICATION_REQUESTED)