Skip to content

Commit a267869

Browse files
feat: add signal isolation class and methods
1 parent 88c0ee9 commit a267869

File tree

4 files changed

+134
-1
lines changed

4 files changed

+134
-1
lines changed

openedx_events/tests/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Test package for openedx-events implementations.
3+
"""

openedx_events/tests/test_tooling.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,18 @@ def test_send_robust_event_with_django(self):
185185

186186
with self.assertWarns(Warning, msg=message):
187187
self.public_signal.send_robust(sender=Mock())
188+
189+
@patch("openedx_events.tooling.Signal.send")
190+
def test_send_event_disabled(self, send_mock):
191+
"""
192+
This method tests sending an event that has been disabled.
193+
194+
Expected behavior:
195+
The Django Signal associated to the event is not sent.
196+
"""
197+
self.public_signal.disable()
198+
199+
result = self.public_signal.send_event(sender=Mock())
200+
201+
send_mock.assert_not_called()
202+
self.assertListEqual([], result)

openedx_events/tests/utils.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
Utils used by Open edX events.
3+
"""
4+
from django.test import TestCase
5+
6+
from openedx_events.tooling import OpenEdxPublicSignal
7+
8+
9+
class EventsIsolationMixin:
10+
"""
11+
A mixin to be used by TestCases that want to isolate their use of Open edX Events.
12+
"""
13+
14+
@classmethod
15+
def disable_all_events(cls):
16+
"""
17+
Disable all events Open edX Events from all subdomains.
18+
"""
19+
for event in OpenEdxPublicSignal.all_events():
20+
event.disable()
21+
22+
@classmethod
23+
def enable_all_events(cls):
24+
"""
25+
Enable all events Open edX Events from all subdomains.
26+
"""
27+
for event in OpenEdxPublicSignal.all_events():
28+
event.enable()
29+
30+
@classmethod
31+
def enable_events_by_type(cls, *event_types):
32+
"""
33+
Enable specific Open edX Events given their type.
34+
35+
Arguments:
36+
event_types (list of `str`): types of events to enable.
37+
"""
38+
for event_type in event_types:
39+
try:
40+
event = OpenEdxPublicSignal.get_signal_by_type(event_type)
41+
except KeyError:
42+
all_event_types = sorted(s.event_type for s in OpenEdxPublicSignal.all_events())
43+
err_msg = (
44+
"You tried to enable event '{}', but I don't recognize that "
45+
"signal type. Did you mean one of these?: {}"
46+
)
47+
raise ValueError(err_msg.format(event_type, all_event_types)) # pylint: disable=raise-missing-from
48+
event.enable()
49+
50+
51+
class OpenEdxEventsTestCase(EventsIsolationMixin, TestCase):
52+
"""
53+
A mixin to be used by TestCases that want to isolate their use of Open edX Events.
54+
55+
Example usage:
56+
57+
class MyTestCase(OpenEdxEventsTestCase):
58+
59+
ENABLED_OPENEDX_EVENTS = ['org.openedx.learning.student.registration.completed.v1']
60+
"""
61+
62+
ENABLED_OPENEDX_EVENTS = []
63+
64+
@classmethod
65+
def setUpClass(cls):
66+
"""
67+
Start events isolation for class.
68+
"""
69+
super().setUpClass()
70+
cls().start_events_isolation()
71+
72+
@classmethod
73+
def start_events_isolation(cls):
74+
"""
75+
Start Open edX Events isolation and then enable events by type.
76+
"""
77+
cls().disable_all_events()
78+
cls().enable_events_by_type(*cls.ENABLED_OPENEDX_EVENTS)

openedx_events/tooling.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class OpenEdxPublicSignal(Signal):
1414
Custom class used to create Open edX events.
1515
"""
1616

17+
_mapping = {}
18+
instances = []
19+
1720
def __init__(self, event_type, data, minor_version=0):
1821
"""
1922
Init method for OpenEdxPublicSignal definition class.
@@ -34,6 +37,9 @@ def __init__(self, event_type, data, minor_version=0):
3437
self.init_data = data
3538
self.event_type = event_type
3639
self.minor_version = minor_version
40+
self._allow_events = True
41+
self.__class__.instances.append(self)
42+
self.__class__._mapping[self.event_type] = self
3743
super().__init__()
3844

3945
def __repr__(self):
@@ -42,6 +48,20 @@ def __repr__(self):
4248
"""
4349
return "<OpenEdxPublicSignal: {event_type}>".format(event_type=self.event_type)
4450

51+
@classmethod
52+
def all_events(cls):
53+
"""
54+
Get all current events.
55+
"""
56+
return cls.instances
57+
58+
@classmethod
59+
def get_signal_by_type(cls, event_type):
60+
"""
61+
Get event identified by type.
62+
"""
63+
return cls._mapping[event_type]
64+
4565
def generate_signal_metadata(self):
4666
"""
4767
Generate signal metadata when an event is sent.
@@ -76,6 +96,8 @@ def send_event(self, send_robust=False, **kwargs):
7696
some validations are run on the arguments, and then relevant metadata
7797
that can be used for logging or debugging purposes is generated.
7898
Besides this behavior, send_event behaves just like the send method.
99+
If the event is disabled (i.e _allow_events is False), then this method
100+
won't have any effect. Meaning, the Django Signal won't be sent.
79101
80102
Example usage:
81103
>>> STUDENT_REGISTRATION_COMPLETED.send_event(
@@ -89,7 +111,7 @@ def send_event(self, send_robust=False, **kwargs):
89111
90112
Returns:
91113
list: response of each receiver following the format
92-
[(receiver, response), ... ]
114+
[(receiver, response), ... ]. Empty list if the event is disabled.
93115
94116
Exceptions raised:
95117
SenderValidationError: raised when there's a mismatch between
@@ -126,6 +148,9 @@ def validate_sender():
126148
),
127149
)
128150

151+
if not self._allow_events:
152+
return []
153+
129154
validate_sender()
130155

131156
kwargs["metadata"] = self.generate_signal_metadata()
@@ -147,3 +172,15 @@ def send_robust(self, sender, **kwargs): # pylint: disable=unused-argument
147172
warnings.warn(
148173
"Please, use 'send_event' with send_robust equals to True when triggering an Open edX event."
149174
)
175+
176+
def enable(self):
177+
"""
178+
Enable all events. Meaning, send_event will send a Django signal.
179+
"""
180+
self._allow_events = True
181+
182+
def disable(self):
183+
"""
184+
Disable all events. Meaning, send_event will have no effect.
185+
"""
186+
self._allow_events = False

0 commit comments

Comments
 (0)