Skip to content

Commit 6822ed8

Browse files
feat: add necessary tooling for Open edX events
- Add signal class used to create events - Add metadata generator to class - Add argument validator to class - Override signal methods to recommend using the custom send_event
1 parent e7bc02d commit 6822ed8

File tree

10 files changed

+495
-2
lines changed

10 files changed

+495
-2
lines changed

openedx_events/exceptions.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Custom exceptions thrown by Open edX events tooling.
3+
"""
4+
5+
6+
class OpenEdxEventException(Exception):
7+
"""
8+
Base class for Open edX Events exceptions.
9+
"""
10+
11+
def __init__(self, message=""):
12+
"""
13+
Init method for OpenEdxEventException base class.
14+
15+
Arguments:
16+
message (str): message describing why the exception was raised.
17+
"""
18+
super().__init__()
19+
self.message = message
20+
21+
def __str__(self):
22+
"""
23+
Show string representation of OpenEdxEventException using its message.
24+
"""
25+
return self.message
26+
27+
28+
class InstantiationError(OpenEdxEventException):
29+
"""
30+
Describes errors that occur while instantiating events.
31+
32+
This exception is raised when there's an error instantiating an Open edX
33+
event, it can be that a required argument for the event definition is
34+
missing.
35+
"""
36+
37+
def __init__(self, event_type="", message=""):
38+
"""
39+
Init method for InstantiationError custom exception class.
40+
41+
Arguments:
42+
event_type (str): name of the event raising the exception.
43+
message (str): message describing why the exception was raised.
44+
"""
45+
super().__init__(
46+
message="InstantiationError {event_type}: {message}".format(
47+
event_type=event_type, message=message
48+
)
49+
)
50+
51+
52+
class SenderValidationError(OpenEdxEventException):
53+
"""
54+
Describes errors that occur while validating arguments of send methods.
55+
"""
56+
57+
def __init__(self, event_type="", message=""):
58+
"""
59+
Init method for SenderValidationError custom exception class.
60+
61+
Arguments:
62+
event_type (str): name of the event raising the exception.
63+
message (str): message describing why the exception was raised.
64+
"""
65+
super().__init__(
66+
message="SenderValidationError {event_type}: {message}".format(
67+
event_type=event_type, message=message
68+
)
69+
)
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"""This file contains all test for the tooling.py file.
2+
3+
Classes:
4+
EventsToolingTest: Test events tooling.
5+
"""
6+
from unittest.mock import Mock, patch
7+
8+
import ddt
9+
from django.test import TestCase, override_settings
10+
11+
from openedx_events.exceptions import InstantiationError, SenderValidationError
12+
from openedx_events.tooling import OpenEdxPublicSignal
13+
14+
15+
@ddt.ddt
16+
class OpenEdxPublicSignalTest(TestCase):
17+
"""
18+
Test cases for Open edX events base class.
19+
"""
20+
21+
def setUp(self):
22+
"""
23+
Setup common conditions for every test case.
24+
"""
25+
super().setUp()
26+
self.event_type = "org.openedx.learning.session.login.completed.v1"
27+
self.user_mock = Mock()
28+
self.data_attr = {
29+
"user": Mock,
30+
}
31+
self.public_signal = OpenEdxPublicSignal(
32+
event_type=self.event_type, data=self.data_attr,
33+
)
34+
35+
def test_string_representation(self):
36+
"""
37+
This methods checks the string representation for events base class.
38+
39+
Expected behavior:
40+
The representation contains the event_type.
41+
"""
42+
self.assertIn(self.event_type, str(self.public_signal))
43+
44+
@override_settings(SERVICE_VARIANT="lms")
45+
@patch("openedx_events.tooling.openedx_events")
46+
@patch("openedx_events.tooling.crum")
47+
def test_get_signal_metadata(self, crum_mock, events_package_mock):
48+
"""
49+
This methods tests getting the generated metadata for an event.
50+
51+
Expected behavior:
52+
Returns the metadata containing information about the event.
53+
"""
54+
events_package_mock.__version__ = "0.1.0"
55+
crum_mock.get_current_request.return_value.get_host.return_value = "edx.devstack.lms"
56+
expected_metadata = {
57+
"event_type": self.event_type,
58+
"minorversion": "0.0",
59+
"source": "openedx/lms/web",
60+
"sourcehost": "edx.devstack.lms",
61+
"specversion": "1.0",
62+
"events_version": "0.1.0",
63+
}
64+
65+
metadata = self.public_signal.get_signal_metadata()
66+
67+
self.assertDictContainsSubset(expected_metadata, metadata)
68+
69+
@override_settings(SERVICE_VARIANT="lms")
70+
@patch("openedx_events.tooling.openedx_events")
71+
def test_get_signal_metadata_unknown_host(self, events_package_mock):
72+
"""
73+
This methods tests getting the generated metadata without a host.
74+
75+
Expected behavior:
76+
Returns the metadata containing information about the event.
77+
"""
78+
events_package_mock.__version__ = "0.1.0"
79+
expected_metadata = {
80+
"event_type": self.event_type,
81+
"minorversion": "0.0",
82+
"source": "openedx/lms/web",
83+
"sourcehost": "Unknown host",
84+
"specversion": "1.0",
85+
"events_version": "0.1.0",
86+
}
87+
88+
metadata = self.public_signal.get_signal_metadata()
89+
90+
self.assertDictContainsSubset(expected_metadata, metadata)
91+
92+
@ddt.data(
93+
("", {"user": Mock()}, "event_type"),
94+
("org.openedx.learning.session.login.completed.v1", None, "data"),
95+
)
96+
@ddt.unpack
97+
def test_event_instantiation_exception(
98+
self, event_type, event_data, missing_argument
99+
):
100+
"""
101+
This method tests when an event is instantiated without event_type or
102+
event data.
103+
104+
Expected behavior:
105+
An InstantiationError exception is raised.
106+
"""
107+
exception_message = "InstantiationError {event_type}: Missing required argument '{missing_argument}'".format(
108+
event_type=event_type,
109+
missing_argument=missing_argument
110+
)
111+
112+
with self.assertRaisesMessage(InstantiationError, exception_message):
113+
OpenEdxPublicSignal(event_type=event_type, data=event_data)
114+
115+
@patch("openedx_events.tooling.OpenEdxPublicSignal.get_signal_metadata")
116+
@patch("openedx_events.tooling.Signal.send")
117+
def test_send_event_successfully(self, send_mock, fake_metadata):
118+
"""
119+
This method tests the process of sending an event.
120+
121+
Expected behavior:
122+
The event is sent as a django signal.
123+
"""
124+
expected_metadata = {
125+
"some_data": "data",
126+
"raise_exception": True,
127+
}
128+
fake_metadata.return_value = expected_metadata
129+
130+
self.public_signal.send_event(user=self.user_mock)
131+
132+
send_mock.assert_called_once_with(
133+
sender=None,
134+
user=self.user_mock,
135+
metadata=expected_metadata,
136+
)
137+
138+
@patch("openedx_events.tooling.OpenEdxPublicSignal.get_signal_metadata")
139+
@patch("openedx_events.tooling.Signal.send_robust")
140+
def test_send_robust_event_successfully(self, send_robust_mock, fake_metadata):
141+
"""
142+
This method tests the process of sending an event.
143+
144+
Expected behavior:
145+
The event is sent as a django signal.
146+
"""
147+
expected_metadata = {
148+
"some_data": "data",
149+
"raise_exception": True,
150+
}
151+
fake_metadata.return_value = expected_metadata
152+
153+
self.public_signal.send_event(user=self.user_mock, send_robust=True)
154+
155+
send_robust_mock.assert_called_once_with(
156+
sender=None,
157+
user=self.user_mock,
158+
metadata=expected_metadata,
159+
)
160+
161+
@ddt.data(
162+
(
163+
{"student": Mock()},
164+
"SenderValidationError org.openedx.learning.session.login.completed.v1: "
165+
"Missing required argument 'user'",
166+
),
167+
(
168+
{"user": {"student": Mock()}},
169+
"SenderValidationError org.openedx.learning.session.login.completed.v1: "
170+
"The argument 'user' is not instance of the Class Attribute 'type'",
171+
),
172+
(
173+
{"student": Mock(), "user": Mock()},
174+
"SenderValidationError org.openedx.learning.session.login.completed.v1: "
175+
"There's a mismatch between initialization data and send_event arguments",
176+
),
177+
)
178+
@ddt.unpack
179+
def test_invalid_sender(self, send_arguments, exception_message):
180+
"""
181+
This method tests sending an event with invalid setup on the sender
182+
side.
183+
184+
Expected behavior:
185+
A SenderValidationError exception is raised.
186+
"""
187+
with self.assertRaisesMessage(SenderValidationError, exception_message):
188+
self.public_signal.send_event(**send_arguments)
189+
190+
def test_send_event_with_django(self):
191+
"""
192+
This method tests sending an event using the `send` built-in Django
193+
method.
194+
195+
Expected behavior:
196+
A warning is showed advicing to use Open edX events custom
197+
send_signal method.
198+
"""
199+
message = "Please, use 'send_event' when triggering an Open edX event."
200+
201+
with self.assertWarns(Warning, msg=message):
202+
self.public_signal.send(sender=Mock())
203+
204+
def test_send_robust_event_with_django(self):
205+
"""
206+
This method tests sending an event using the `send` built-in Django
207+
method.
208+
209+
Expected behavior:
210+
A warning is showed advicing to use Open edX events custom
211+
send_signal method.
212+
"""
213+
message = "Please, use 'send_event' with send_robust equals to True when triggering an Open edX event."
214+
215+
with self.assertWarns(Warning, msg=message):
216+
self.public_signal.send_robust(sender=Mock())

0 commit comments

Comments
 (0)