feat: remove manual sends of events (#33642)
This commit is contained in:
@@ -13,14 +13,7 @@ from django.dispatch import receiver
|
||||
from edx_toggles.toggles import SettingToggle
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx_events.content_authoring.data import CourseCatalogData, CourseScheduleData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
COURSE_CATALOG_INFO_CHANGED,
|
||||
XBLOCK_DELETED,
|
||||
XBLOCK_DUPLICATED,
|
||||
XBLOCK_PUBLISHED,
|
||||
)
|
||||
from openedx.core.lib.events import determine_producer_config_for_signal_and_topic
|
||||
from openedx_events.event_bus import get_producer
|
||||
from openedx_events.content_authoring.signals import COURSE_CATALOG_INFO_CHANGED
|
||||
from pytz import UTC
|
||||
|
||||
from cms.djangoapps.contentstore.courseware_index import (
|
||||
@@ -160,85 +153,6 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
|
||||
transaction.on_commit(lambda: emit_catalog_info_changed_signal(course_key))
|
||||
|
||||
|
||||
@receiver(COURSE_CATALOG_INFO_CHANGED)
|
||||
def listen_for_course_catalog_info_changed(sender, signal, **kwargs):
|
||||
"""
|
||||
Publish COURSE_CATALOG_INFO_CHANGED signals onto the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(COURSE_CATALOG_INFO_CHANGED,
|
||||
'course-catalog-info-changed')
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing course-catalog-info-changed event via config")
|
||||
return
|
||||
log.info("Producing course-catalog-info-changed event via manual send")
|
||||
get_producer().send(
|
||||
signal=COURSE_CATALOG_INFO_CHANGED, topic='course-catalog-info-changed',
|
||||
event_key_field='catalog_info.course_key', event_data={'catalog_info': kwargs['catalog_info']},
|
||||
event_metadata=kwargs['metadata'],
|
||||
)
|
||||
|
||||
|
||||
@receiver(XBLOCK_PUBLISHED)
|
||||
def listen_for_xblock_published(sender, signal, **kwargs):
|
||||
"""
|
||||
Publish XBLOCK_PUBLISHED signals onto the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
topic = getattr(settings, "EVENT_BUS_XBLOCK_LIFECYCLE_TOPIC", "course-authoring-xblock-lifecycle")
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(XBLOCK_PUBLISHED, topic)
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing xblock-published event via config")
|
||||
return
|
||||
if settings.FEATURES.get("ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS"):
|
||||
log.info("Producing xblock-published event via manual send")
|
||||
get_producer().send(
|
||||
signal=XBLOCK_PUBLISHED, topic=topic,
|
||||
event_key_field='xblock_info.usage_key', event_data={'xblock_info': kwargs['xblock_info']},
|
||||
event_metadata=kwargs['metadata'],
|
||||
)
|
||||
|
||||
|
||||
@receiver(XBLOCK_DELETED)
|
||||
def listen_for_xblock_deleted(sender, signal, **kwargs):
|
||||
"""
|
||||
Publish XBLOCK_DELETED signals onto the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
topic = getattr(settings, "EVENT_BUS_XBLOCK_LIFECYCLE_TOPIC", "course-authoring-xblock-lifecycle")
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(XBLOCK_DELETED, topic)
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing xblock-deleted event via config")
|
||||
return
|
||||
if settings.FEATURES.get("ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS"):
|
||||
log.info("Producing xblock-deleted event via manual send")
|
||||
get_producer().send(
|
||||
signal=XBLOCK_DELETED, topic=topic,
|
||||
event_key_field='xblock_info.usage_key', event_data={'xblock_info': kwargs['xblock_info']},
|
||||
event_metadata=kwargs['metadata'],
|
||||
)
|
||||
|
||||
|
||||
@receiver(XBLOCK_DUPLICATED)
|
||||
def listen_for_xblock_duplicated(sender, signal, **kwargs):
|
||||
"""
|
||||
Publish XBLOCK_DUPLICATED signals onto the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
topic = getattr(settings, "EVENT_BUS_XBLOCK_LIFECYCLE_TOPIC", "course-authoring-xblock-lifecycle")
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(XBLOCK_DUPLICATED, topic)
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing xblock-duplicated event via config")
|
||||
return
|
||||
if settings.FEATURES.get("ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS"):
|
||||
log.info("Producing xblock-duplicated event via manual send")
|
||||
get_producer().send(
|
||||
signal=XBLOCK_DUPLICATED, topic=topic,
|
||||
event_key_field='xblock_info.usage_key', event_data={'xblock_info': kwargs['xblock_info']},
|
||||
event_metadata=kwargs['metadata'],
|
||||
)
|
||||
|
||||
|
||||
@receiver(SignalHandler.course_deleted)
|
||||
def listen_for_course_delete(sender, course_key, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
|
||||
@@ -301,24 +301,22 @@ CREDENTIALS_INTERNAL_SERVICE_URL = 'http://localhost:18150'
|
||||
CREDENTIALS_PUBLIC_SERVICE_URL = 'http://localhost:18150'
|
||||
|
||||
#################### Event bus backend ########################
|
||||
# .. toggle_name: FEATURES['ENABLE_SEND_XBLOCK_EVENTS_OVER_BUS']
|
||||
# .. toggle_implementation: DjangoSetting
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: Temporary configuration which enables sending xblock events over the event bus.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2023-02-21
|
||||
# .. toggle_warning: For consistency in user experience, keep the value in sync with the setting of the same name
|
||||
# in the LMS and CMS.
|
||||
# This will be deprecated in favor of ENABLE_SEND_XBLOCK_LIFECYCLE_EVENTS_OVER_BUS
|
||||
# .. 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'
|
||||
|
||||
course_catalog_event_setting = EVENT_BUS_PRODUCER_CONFIG['org.openedx.content_authoring.course.catalog_info.changed.v1']
|
||||
course_catalog_event_setting['course-catalog-info-changed']['enabled'] = True
|
||||
|
||||
xblock_published_event_setting = EVENT_BUS_PRODUCER_CONFIG['org.openedx.content_authoring.xblock.published.v1']
|
||||
xblock_published_event_setting['course-authoring-xblock-lifecycle']['enabled'] = True
|
||||
xblock_deleted_event_setting = EVENT_BUS_PRODUCER_CONFIG['org.openedx.content_authoring.xblock.deleted.v1']
|
||||
xblock_deleted_event_setting['course-authoring-xblock-lifecycle']['enabled'] = True
|
||||
xblock_duplicated_event_setting = EVENT_BUS_PRODUCER_CONFIG['org.openedx.content_authoring.xblock.duplicated.v1']
|
||||
xblock_duplicated_event_setting['course-authoring-xblock-lifecycle']['enabled'] = True
|
||||
|
||||
|
||||
################# New settings must go ABOVE this line #################
|
||||
########################################################################
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
from openedx.core.lib.events import determine_producer_config_for_signal_and_topic
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@receiver(COURSE_UNENROLLMENT_COMPLETED)
|
||||
def course_unenrollment_receiver(sender, signal, **kwargs):
|
||||
"""
|
||||
Removes user notification preference when user un-enrolls from the course
|
||||
"""
|
||||
topic = getattr(settings, "EVENT_BUS_ENROLLMENT_LIFECYCLE_TOPIC", "course-unenrollment-lifecycle")
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(COURSE_UNENROLLMENT_COMPLETED, topic)
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing unenrollment-event event via config")
|
||||
return
|
||||
if settings.FEATURES.get("ENABLE_SEND_ENROLLMENT_EVENTS_OVER_BUS"):
|
||||
log.info("Producing unenrollment-event event via manual send")
|
||||
get_producer().send(
|
||||
signal=COURSE_UNENROLLMENT_COMPLETED,
|
||||
topic=topic,
|
||||
event_key_field='enrollment.course.course_key',
|
||||
event_data={'enrollment': kwargs.get('enrollment')},
|
||||
event_metadata=kwargs.get('metadata')
|
||||
)
|
||||
@@ -1,67 +0,0 @@
|
||||
"""
|
||||
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'
|
||||
@@ -2,8 +2,7 @@
|
||||
This module contains various configuration settings via
|
||||
waffle switches for the Certificates app.
|
||||
"""
|
||||
|
||||
from edx_toggles.toggles import SettingToggle, WaffleSwitch
|
||||
from edx_toggles.toggles import WaffleSwitch
|
||||
|
||||
# Namespace
|
||||
WAFFLE_NAMESPACE = 'certificates'
|
||||
@@ -15,31 +14,3 @@ WAFFLE_NAMESPACE = 'certificates'
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2017-09-14
|
||||
AUTO_CERTIFICATE_GENERATION = WaffleSwitch(f"{WAFFLE_NAMESPACE}.auto_certificate_generation", __name__)
|
||||
|
||||
|
||||
# .. toggle_name: SEND_CERTIFICATE_CREATED_SIGNAL
|
||||
# .. toggle_implementation: SettingToggle
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: When True, the system will publish `CERTIFICATE_CREATED` signals to the event bus. The
|
||||
# `CERTIFICATE_CREATED` signal is emit when a certificate has been awarded to a learner and the creation process has
|
||||
# completed.
|
||||
# .. toggle_warning: Will be deprecated in favor of SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS
|
||||
# .. toggle_use_cases: temporary
|
||||
# .. toggle_creation_date: 2023-04-11
|
||||
# .. toggle_target_removal_date: 2023-07-31
|
||||
# .. toggle_tickets: TODO
|
||||
SEND_CERTIFICATE_CREATED_SIGNAL = SettingToggle('SEND_CERTIFICATE_CREATED_SIGNAL', default=False, module_name=__name__)
|
||||
|
||||
|
||||
# .. toggle_name: SEND_CERTIFICATE_REVOKED_SIGNAL
|
||||
# .. toggle_implementation: SettingToggle
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: When True, the system will publish `CERTIFICATE_REVOKED` signals to the event bus. The
|
||||
# `CERTIFICATE_REVOKED` signal is emit when a certificate has been revoked from a learner and the revocation process
|
||||
# has completed.
|
||||
# .. toggle_warning: Will be deprecated in favor of SEND_LEARNING_CERTIFICATE_LIFECYCLE_EVENTS_TO_BUS
|
||||
# .. toggle_use_cases: temporary
|
||||
# .. toggle_creation_date: 2023-09-15
|
||||
# .. toggle_target_removal_date: 2024-01-01
|
||||
# .. toggle_tickets: TODO
|
||||
SEND_CERTIFICATE_REVOKED_SIGNAL = SettingToggle('SEND_CERTIFICATE_REVOKED_SIGNAL', default=False, module_name=__name__)
|
||||
|
||||
@@ -3,17 +3,13 @@ Signal handler for enabling/disabling self-generated certificates based on the c
|
||||
"""
|
||||
|
||||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from openedx_events.event_bus import get_producer
|
||||
from edx_django_utils.monitoring import set_custom_attribute
|
||||
|
||||
from common.djangoapps.course_modes import api as modes_api
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.student.signals import ENROLLMENT_TRACK_UPDATED
|
||||
from lms.djangoapps.certificates.config import SEND_CERTIFICATE_CREATED_SIGNAL, SEND_CERTIFICATE_REVOKED_SIGNAL
|
||||
from lms.djangoapps.certificates.generation_handler import (
|
||||
CertificateGenerationNotAllowed,
|
||||
generate_allowlist_certificate_task,
|
||||
@@ -29,13 +25,11 @@ from lms.djangoapps.certificates.models import (
|
||||
from lms.djangoapps.certificates.api import auto_certificate_generation_enabled
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from openedx.core.djangoapps.content.course_overviews.signals import COURSE_PACING_CHANGED
|
||||
from openedx.core.lib.events import determine_producer_config_for_signal_and_topic
|
||||
from openedx.core.djangoapps.signals.signals import (
|
||||
COURSE_GRADE_NOW_FAILED,
|
||||
COURSE_GRADE_NOW_PASSED,
|
||||
LEARNER_NOW_VERIFIED
|
||||
)
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -162,71 +156,3 @@ def _listen_for_enrollment_mode_change(sender, user, course_key, mode, **kwargs)
|
||||
course_key,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _determine_producer_config_for_signal_and_topic(signal, topic):
|
||||
"""
|
||||
Utility method to determine the setting for the given signal and topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
|
||||
Records to New Relic for later analysis.
|
||||
|
||||
Parameters
|
||||
signal (OpenEdxPublicSignal): The signal being sent to the event bus
|
||||
topic (string): The topic to which the signal is being sent (without environment prefix)
|
||||
|
||||
Returns
|
||||
True if the signal is enabled for that topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
False if the signal is explicitly disabled for that topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
None if the signal/topic pair is not present in EVENT_BUS_PRODUCER_CONFIG
|
||||
"""
|
||||
event_type_producer_configs = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG",
|
||||
{}).get(signal.event_type, {})
|
||||
topic_config = event_type_producer_configs.get(topic, {})
|
||||
topic_setting = topic_config.get('enabled', None)
|
||||
set_custom_attribute(f'producer_config_setting_{topic}_{signal.event_type}',
|
||||
topic_setting if topic_setting is not None else 'Unset')
|
||||
return topic_setting
|
||||
|
||||
|
||||
@receiver(CERTIFICATE_CREATED)
|
||||
def listen_for_certificate_created_event(sender, signal, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Publish `CERTIFICATE_CREATED` events to the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(CERTIFICATE_CREATED,
|
||||
'learning-certificate-lifecycle')
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing certificate-created event via config")
|
||||
return
|
||||
if SEND_CERTIFICATE_CREATED_SIGNAL.is_enabled():
|
||||
log.info("Producing certificate-created event via manual send")
|
||||
get_producer().send(
|
||||
signal=CERTIFICATE_CREATED,
|
||||
topic='learning-certificate-lifecycle',
|
||||
event_key_field='certificate.course.course_key',
|
||||
event_data={'certificate': kwargs['certificate']},
|
||||
event_metadata=kwargs['metadata']
|
||||
)
|
||||
|
||||
|
||||
@receiver(CERTIFICATE_REVOKED)
|
||||
def listen_for_certificate_revoked_event(sender, signal, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Publish `CERTIFICATE_REVOKED` events to the event bus.
|
||||
"""
|
||||
# temporary: defer to EVENT_BUS_PRODUCER_CONFIG if present
|
||||
producer_config_setting = determine_producer_config_for_signal_and_topic(CERTIFICATE_REVOKED,
|
||||
'learning-certificate-lifecycle')
|
||||
if producer_config_setting is True:
|
||||
log.info("Producing certificate-revoked event via config")
|
||||
return
|
||||
if SEND_CERTIFICATE_REVOKED_SIGNAL.is_enabled():
|
||||
log.info("Producing certificate-revoked event via manual send")
|
||||
get_producer().send(
|
||||
signal=CERTIFICATE_REVOKED,
|
||||
topic='learning-certificate-lifecycle',
|
||||
event_key_field='certificate.course.course_key',
|
||||
event_data={'certificate': kwargs['certificate']},
|
||||
event_metadata=kwargs['metadata']
|
||||
)
|
||||
|
||||
@@ -3,12 +3,9 @@ Unit tests for enabling self-generated certificates for self-paced courses
|
||||
and disabling for instructor-paced courses.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
from django.test.utils import override_settings
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -21,17 +18,10 @@ from lms.djangoapps.certificates.models import (
|
||||
CertificateGenerationConfiguration,
|
||||
GeneratedCertificate
|
||||
)
|
||||
from lms.djangoapps.certificates.signals import (
|
||||
listen_for_certificate_created_event,
|
||||
listen_for_certificate_revoked_event
|
||||
)
|
||||
from lms.djangoapps.certificates.tests.factories import CertificateAllowlistFactory, GeneratedCertificateFactory
|
||||
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
|
||||
from lms.djangoapps.grades.tests.utils import mock_passing_grade
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from openedx_events.data import EventsMetadata
|
||||
from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED
|
||||
from openedx_events.learning.data import CourseData, UserData, UserPersonalData, CertificateData
|
||||
|
||||
|
||||
class SelfGeneratedCertsSignalTest(ModuleStoreTestCase):
|
||||
@@ -443,109 +433,3 @@ class EnrollmentModeChangeCertsTest(ModuleStoreTestCase):
|
||||
) as mock_allowlist_task:
|
||||
self.verified_enrollment.change_mode('audit')
|
||||
mock_allowlist_task.assert_not_called()
|
||||
|
||||
|
||||
class CertificateEventBusTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for Certificate events that interact with the event bus.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory.create()
|
||||
self.name = f'{self.user.first_name} {self.user.last_name}'
|
||||
self.course = CourseFactory.create(self_paced=True)
|
||||
self.enrollment = CourseEnrollmentFactory(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
is_active=True,
|
||||
mode='verified',
|
||||
)
|
||||
|
||||
def _create_event_data(self, event_type, certificate_status):
|
||||
"""
|
||||
Utility function to create test data for unit tests.
|
||||
"""
|
||||
expected_course_data = CourseData(course_key=self.course.id)
|
||||
expected_user_data = UserData(
|
||||
pii=UserPersonalData(
|
||||
username=self.user.username,
|
||||
email=self.user.email,
|
||||
name=self.name,
|
||||
),
|
||||
id=self.user.id,
|
||||
is_active=self.user.is_active
|
||||
)
|
||||
expected_certificate_data = CertificateData(
|
||||
user=expected_user_data,
|
||||
course=expected_course_data,
|
||||
mode='verified',
|
||||
grade='',
|
||||
current_status=certificate_status,
|
||||
download_url='',
|
||||
name='',
|
||||
)
|
||||
expected_event_metadata = EventsMetadata(
|
||||
event_type=event_type.event_type,
|
||||
id=uuid4(),
|
||||
minorversion=0,
|
||||
source='openedx/lms/web',
|
||||
sourcehost='lms.test',
|
||||
time=datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
return {
|
||||
'certificate': expected_certificate_data,
|
||||
'metadata': expected_event_metadata,
|
||||
}
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=False)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_created_event_disabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we do not publish `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled.
|
||||
"""
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED)
|
||||
mock_producer.assert_not_called()
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=False)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_revoked_event_disabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we do not publish `CERTIFICATE_REVOKED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_REVOKED_SIGNAL` setting is disabled.
|
||||
"""
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_REVOKED)
|
||||
mock_producer.assert_not_called()
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=True)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_created_event_enabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we push `CERTIFICATE_CREATED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_CREATED_SIGNAL` setting is enabled.
|
||||
"""
|
||||
event_data = self._create_event_data(CERTIFICATE_CREATED, CertificateStatuses.downloadable)
|
||||
listen_for_certificate_created_event(None, CERTIFICATE_CREATED, **event_data)
|
||||
# verify that the data sent to the event bus matches what we expect
|
||||
data = mock_producer.return_value.send.call_args.kwargs
|
||||
assert data['signal'].event_type == CERTIFICATE_CREATED.event_type
|
||||
assert data['event_data']['certificate'] == event_data['certificate']
|
||||
assert data['topic'] == 'learning-certificate-lifecycle'
|
||||
assert data['event_key_field'] == 'certificate.course.course_key'
|
||||
|
||||
@override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=True)
|
||||
@mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True)
|
||||
def test_certificate_revoked_event_enabled(self, mock_producer):
|
||||
"""
|
||||
Test to verify that we push `CERTIFICATE_REVOKED` events to the event bus if the
|
||||
`SEND_CERTIFICATE_REVOKED_SIGNAL` setting is enabled.
|
||||
"""
|
||||
event_data = self._create_event_data(CERTIFICATE_REVOKED, CertificateStatuses.notpassing)
|
||||
listen_for_certificate_revoked_event(None, CERTIFICATE_REVOKED, **event_data)
|
||||
# verify that the data sent to the event bus matches what we expect
|
||||
data = mock_producer.return_value.send.call_args.kwargs
|
||||
assert data['signal'].event_type == CERTIFICATE_REVOKED.event_type
|
||||
assert data['event_data']['certificate'] == event_data['certificate']
|
||||
assert data['topic'] == 'learning-certificate-lifecycle'
|
||||
assert data['event_key_field'] == 'certificate.course.course_key'
|
||||
|
||||
@@ -509,6 +509,11 @@ EVENT_BUS_REDIS_CONNECTION_URL = 'redis://:password@edx.devstack.redis:6379/'
|
||||
EVENT_BUS_TOPIC_PREFIX = 'dev'
|
||||
EVENT_BUS_CONSUMER = 'edx_event_bus_redis.RedisEventConsumer'
|
||||
|
||||
certificate_revoked_event_config = EVENT_BUS_PRODUCER_CONFIG['org.openedx.learning.certificate.revoked.v1']
|
||||
certificate_revoked_event_config['learning-certificate-lifecycle']['enabled'] = True
|
||||
certificate_created_event_config = EVENT_BUS_PRODUCER_CONFIG['org.openedx.learning.certificate.created.v1']
|
||||
certificate_created_event_config['learning-certificate-lifecycle']['enabled'] = True
|
||||
|
||||
######################## Subscriptions API SETTINGS ########################
|
||||
SUBSCRIPTIONS_ROOT_URL = "http://host.docker.internal:18750"
|
||||
SUBSCRIPTIONS_API_PATH = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscription/"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
"""Temporary method for use in rolling out a new event producer configuration."""
|
||||
|
||||
from django.conf import settings
|
||||
from edx_django_utils.monitoring import set_custom_attribute
|
||||
|
||||
|
||||
def determine_producer_config_for_signal_and_topic(signal, topic):
|
||||
"""
|
||||
Utility method to determine the setting for the given signal and topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
|
||||
Records to New Relic for later analysis.
|
||||
|
||||
Parameters
|
||||
signal (OpenEdxPublicSignal): The signal being sent to the event bus
|
||||
topic (string): The topic to which the signal is being sent (without environment prefix)
|
||||
|
||||
Returns
|
||||
True if the signal is enabled for that topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
False if the signal is explicitly disabled for that topic in EVENT_BUS_PRODUCER_CONFIG
|
||||
None if the signal/topic pair is not present in EVENT_BUS_PRODUCER_CONFIG
|
||||
"""
|
||||
event_type_producer_configs = getattr(settings, "EVENT_BUS_PRODUCER_CONFIG",
|
||||
{}).get(signal.event_type, {})
|
||||
topic_config = event_type_producer_configs.get(topic, {})
|
||||
topic_setting = topic_config.get('enabled', None)
|
||||
set_custom_attribute(f'producer_config_setting_{topic}_{signal.event_type}',
|
||||
topic_setting if topic_setting is not None else 'Unset')
|
||||
return topic_setting
|
||||
Reference in New Issue
Block a user