PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/common/djangoapps/track/tests/test_shim.py

https://gitlab.com/unofficial-mirrors/edx-platform
Python | 320 lines | 303 code | 15 blank | 2 comment | 1 complexity | 1a037dddc486a83b542342a2f6fc440f MD5 | raw file
  1. """Ensure emitted events contain the fields legacy processors expect to find."""
  2. from collections import namedtuple
  3. import ddt
  4. from django.test.utils import override_settings
  5. from mock import sentinel
  6. from openedx.core.lib.tests.assertions.events import assert_events_equal
  7. from . import FROZEN_TIME, EventTrackingTestCase
  8. from .. import transformers
  9. from ..shim import PrefixedEventProcessor
  10. LEGACY_SHIM_PROCESSOR = [
  11. {
  12. 'ENGINE': 'track.shim.LegacyFieldMappingProcessor'
  13. }
  14. ]
  15. GOOGLE_ANALYTICS_PROCESSOR = [
  16. {
  17. 'ENGINE': 'track.shim.GoogleAnalyticsProcessor'
  18. }
  19. ]
  20. @override_settings(
  21. EVENT_TRACKING_PROCESSORS=LEGACY_SHIM_PROCESSOR,
  22. )
  23. class LegacyFieldMappingProcessorTestCase(EventTrackingTestCase):
  24. """Ensure emitted events contain the fields legacy processors expect to find."""
  25. def test_event_field_mapping(self):
  26. data = {sentinel.key: sentinel.value}
  27. context = {
  28. 'accept_language': sentinel.accept_language,
  29. 'referer': sentinel.referer,
  30. 'username': sentinel.username,
  31. 'session': sentinel.session,
  32. 'ip': sentinel.ip,
  33. 'host': sentinel.host,
  34. 'agent': sentinel.agent,
  35. 'path': sentinel.path,
  36. 'user_id': sentinel.user_id,
  37. 'course_id': sentinel.course_id,
  38. 'org_id': sentinel.org_id,
  39. 'client_id': sentinel.client_id,
  40. }
  41. with self.tracker.context('test', context):
  42. self.tracker.emit(sentinel.name, data)
  43. emitted_event = self.get_event()
  44. expected_event = {
  45. 'accept_language': sentinel.accept_language,
  46. 'referer': sentinel.referer,
  47. 'event_type': sentinel.name,
  48. 'name': sentinel.name,
  49. 'context': {
  50. 'user_id': sentinel.user_id,
  51. 'course_id': sentinel.course_id,
  52. 'org_id': sentinel.org_id,
  53. 'path': sentinel.path,
  54. },
  55. 'event': data,
  56. 'username': sentinel.username,
  57. 'event_source': 'server',
  58. 'time': FROZEN_TIME,
  59. 'agent': sentinel.agent,
  60. 'host': sentinel.host,
  61. 'ip': sentinel.ip,
  62. 'page': None,
  63. 'session': sentinel.session,
  64. }
  65. assert_events_equal(expected_event, emitted_event)
  66. def test_missing_fields(self):
  67. self.tracker.emit(sentinel.name)
  68. emitted_event = self.get_event()
  69. expected_event = {
  70. 'accept_language': '',
  71. 'referer': '',
  72. 'event_type': sentinel.name,
  73. 'name': sentinel.name,
  74. 'context': {},
  75. 'event': {},
  76. 'username': '',
  77. 'event_source': 'server',
  78. 'time': FROZEN_TIME,
  79. 'agent': '',
  80. 'host': '',
  81. 'ip': '',
  82. 'page': None,
  83. 'session': '',
  84. }
  85. assert_events_equal(expected_event, emitted_event)
  86. @override_settings(
  87. EVENT_TRACKING_PROCESSORS=GOOGLE_ANALYTICS_PROCESSOR,
  88. )
  89. class GoogleAnalyticsProcessorTestCase(EventTrackingTestCase):
  90. """Ensure emitted events contain the fields necessary for Google Analytics."""
  91. def test_event_fields(self):
  92. """ Test that course_id is added as the label if present, and nonInteraction is set. """
  93. data = {sentinel.key: sentinel.value}
  94. context = {
  95. 'path': sentinel.path,
  96. 'user_id': sentinel.user_id,
  97. 'course_id': sentinel.course_id,
  98. 'org_id': sentinel.org_id,
  99. 'client_id': sentinel.client_id,
  100. }
  101. with self.tracker.context('test', context):
  102. self.tracker.emit(sentinel.name, data)
  103. emitted_event = self.get_event()
  104. expected_event = {
  105. 'context': context,
  106. 'data': data,
  107. 'label': sentinel.course_id,
  108. 'name': sentinel.name,
  109. 'nonInteraction': 1,
  110. 'timestamp': FROZEN_TIME,
  111. }
  112. assert_events_equal(expected_event, emitted_event)
  113. def test_no_course_id(self):
  114. """ Test that a label is not added if course_id is not specified, but nonInteraction is still set. """
  115. data = {sentinel.key: sentinel.value}
  116. context = {
  117. 'path': sentinel.path,
  118. 'user_id': sentinel.user_id,
  119. 'client_id': sentinel.client_id,
  120. }
  121. with self.tracker.context('test', context):
  122. self.tracker.emit(sentinel.name, data)
  123. emitted_event = self.get_event()
  124. expected_event = {
  125. 'context': context,
  126. 'data': data,
  127. 'name': sentinel.name,
  128. 'nonInteraction': 1,
  129. 'timestamp': FROZEN_TIME,
  130. }
  131. assert_events_equal(expected_event, emitted_event)
  132. @override_settings(
  133. EVENT_TRACKING_BACKENDS={
  134. '0': {
  135. 'ENGINE': 'eventtracking.backends.routing.RoutingBackend',
  136. 'OPTIONS': {
  137. 'backends': {
  138. 'first': {'ENGINE': 'track.tests.InMemoryBackend'}
  139. },
  140. 'processors': [
  141. {
  142. 'ENGINE': 'track.shim.GoogleAnalyticsProcessor'
  143. }
  144. ]
  145. }
  146. },
  147. '1': {
  148. 'ENGINE': 'eventtracking.backends.routing.RoutingBackend',
  149. 'OPTIONS': {
  150. 'backends': {
  151. 'second': {
  152. 'ENGINE': 'track.tests.InMemoryBackend'
  153. }
  154. }
  155. }
  156. }
  157. }
  158. )
  159. class MultipleShimGoogleAnalyticsProcessorTestCase(EventTrackingTestCase):
  160. """Ensure changes don't impact other backends"""
  161. def test_multiple_backends(self):
  162. data = {
  163. sentinel.key: sentinel.value,
  164. }
  165. context = {
  166. 'path': sentinel.path,
  167. 'user_id': sentinel.user_id,
  168. 'course_id': sentinel.course_id,
  169. 'org_id': sentinel.org_id,
  170. 'client_id': sentinel.client_id,
  171. }
  172. with self.tracker.context('test', context):
  173. self.tracker.emit(sentinel.name, data)
  174. segment_emitted_event = self.tracker.backends['0'].backends['first'].events[0]
  175. log_emitted_event = self.tracker.backends['1'].backends['second'].events[0]
  176. expected_event = {
  177. 'context': context,
  178. 'data': data,
  179. 'label': sentinel.course_id,
  180. 'name': sentinel.name,
  181. 'nonInteraction': 1,
  182. 'timestamp': FROZEN_TIME,
  183. }
  184. assert_events_equal(expected_event, segment_emitted_event)
  185. expected_event = {
  186. 'context': context,
  187. 'data': data,
  188. 'name': sentinel.name,
  189. 'timestamp': FROZEN_TIME,
  190. }
  191. assert_events_equal(expected_event, log_emitted_event)
  192. SequenceDDT = namedtuple('SequenceDDT', ['action', 'tab_count', 'current_tab', 'legacy_event_type'])
  193. @ddt.ddt
  194. class EventTransformerRegistryTestCase(EventTrackingTestCase):
  195. """
  196. Test the behavior of the event registry
  197. """
  198. def setUp(self):
  199. super(EventTransformerRegistryTestCase, self).setUp()
  200. self.registry = transformers.EventTransformerRegistry()
  201. @ddt.data(
  202. ('edx.ui.lms.sequence.next_selected', transformers.NextSelectedEventTransformer),
  203. ('edx.ui.lms.sequence.previous_selected', transformers.PreviousSelectedEventTransformer),
  204. ('edx.ui.lms.sequence.tab_selected', transformers.SequenceTabSelectedEventTransformer),
  205. ('edx.video.foo.bar', transformers.VideoEventTransformer),
  206. )
  207. @ddt.unpack
  208. def test_event_registry_dispatch(self, event_name, expected_transformer):
  209. event = {'name': event_name}
  210. transformer = self.registry.create_transformer(event)
  211. self.assertIsInstance(transformer, expected_transformer)
  212. @ddt.data(
  213. 'edx.ui.lms.sequence.next_selected.what',
  214. 'edx',
  215. 'unregistered_event',
  216. )
  217. def test_dispatch_to_nonexistent_events(self, event_name):
  218. event = {'name': event_name}
  219. with self.assertRaises(KeyError):
  220. self.registry.create_transformer(event)
  221. @ddt.ddt
  222. class PrefixedEventProcessorTestCase(EventTrackingTestCase):
  223. """
  224. Test PrefixedEventProcessor
  225. """
  226. @ddt.data(
  227. SequenceDDT(action=u'next', tab_count=5, current_tab=3, legacy_event_type=u'seq_next'),
  228. SequenceDDT(action=u'next', tab_count=5, current_tab=5, legacy_event_type=None),
  229. SequenceDDT(action=u'previous', tab_count=5, current_tab=3, legacy_event_type=u'seq_prev'),
  230. SequenceDDT(action=u'previous', tab_count=5, current_tab=1, legacy_event_type=None),
  231. )
  232. def test_sequence_linear_navigation(self, sequence_ddt):
  233. event_name = u'edx.ui.lms.sequence.{}_selected'.format(sequence_ddt.action)
  234. event = {
  235. u'name': event_name,
  236. u'event': {
  237. u'current_tab': sequence_ddt.current_tab,
  238. u'tab_count': sequence_ddt.tab_count,
  239. u'id': u'ABCDEFG',
  240. }
  241. }
  242. process_event_shim = PrefixedEventProcessor()
  243. result = process_event_shim(event)
  244. # Legacy fields get added when needed
  245. if sequence_ddt.action == u'next':
  246. offset = 1
  247. else:
  248. offset = -1
  249. if sequence_ddt.legacy_event_type:
  250. self.assertEqual(result[u'event_type'], sequence_ddt.legacy_event_type)
  251. self.assertEqual(result[u'event'][u'old'], sequence_ddt.current_tab)
  252. self.assertEqual(result[u'event'][u'new'], sequence_ddt.current_tab + offset)
  253. else:
  254. self.assertNotIn(u'event_type', result)
  255. self.assertNotIn(u'old', result[u'event'])
  256. self.assertNotIn(u'new', result[u'event'])
  257. def test_sequence_tab_navigation(self):
  258. event_name = u'edx.ui.lms.sequence.tab_selected'
  259. event = {
  260. u'name': event_name,
  261. u'event': {
  262. u'current_tab': 2,
  263. u'target_tab': 5,
  264. u'tab_count': 9,
  265. u'id': u'block-v1:abc',
  266. u'widget_placement': u'top',
  267. }
  268. }
  269. process_event_shim = PrefixedEventProcessor()
  270. result = process_event_shim(event)
  271. self.assertEqual(result[u'event_type'], u'seq_goto')
  272. self.assertEqual(result[u'event'][u'old'], 2)
  273. self.assertEqual(result[u'event'][u'new'], 5)