Files
edx-platform/common/djangoapps/track/views/tests/test_views.py
Ken Clary 8c8450f6dc feat: add explicit courserun_key parameter to /event endpoint
We add 'courserun_key' (aka "course_id" though that's technically a
misnomer) as an optional parameter to the /event endpoint url. If it
is not present, it will still be parsed out of the url, if the url is
of the right format.

Additionally, Logger.log() in js adds this parameter to its /event
call, pulling it from the $$course_id global.

This provides opportunity for MFEs to (separately) provide the key
without concern about url parsing.

TNL-7752
2021-08-11 14:40:25 -04:00

372 lines
12 KiB
Python

# lint-amnesty, pylint: disable=missing-module-docstring
from unittest.mock import patch, sentinel
import ddt
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.test.client import RequestFactory
from django.test.utils import override_settings
from openedx.core.lib.tests.assertions.events import assert_event_matches
from common.djangoapps.track import views
from common.djangoapps.track.middleware import TrackMiddleware
from common.djangoapps.track.tests import FROZEN_TIME, EventTrackingTestCase
TEST_USERNAME = 'test-username'
TEST_USER_ID = 1000
@ddt.ddt
class TestTrackViews(EventTrackingTestCase): # lint-amnesty, pylint: disable=missing-class-docstring
@classmethod
def setUpTestData(cls):
super().setUpTestData()
User.objects.create(pk=TEST_USER_ID, username=TEST_USERNAME)
def setUp(self):
super().setUp()
self.request_factory = RequestFactory()
patcher = patch('common.djangoapps.track.views.tracker', autospec=True)
self.mock_tracker = patcher.start()
self.addCleanup(patcher.stop)
self.path_with_course = '/courses/foo/bar/baz/xmod/'
self.url_with_course = 'http://www.edx.org' + self.path_with_course
self.event = {
sentinel.key: sentinel.value
}
def test_user_track(self):
request = self.request_factory.get('/event', {
'page': self.url_with_course,
'event_type': sentinel.event_type,
'event': '{}',
'courserun_key': 'explicit/course/id'
})
views.user_track(request)
actual_event = self.get_event()
expected_event = {
'context': {
'course_id': 'explicit/course/id',
'org_id': 'explicit',
'event_source': 'browser',
'page': self.url_with_course,
'username': 'anonymous'
},
'data': {},
'timestamp': FROZEN_TIME,
'name': str(sentinel.event_type)
}
assert_event_matches(expected_event, actual_event)
def test_user_track_with_implicit_course_id(self):
request = self.request_factory.get('/event', {
'page': self.url_with_course,
'event_type': sentinel.event_type,
'event': '{}'
})
views.user_track(request)
actual_event = self.get_event()
expected_event = {
'context': {
'course_id': 'foo/bar/baz',
'org_id': 'foo',
'event_source': 'browser',
'page': self.url_with_course,
'username': 'anonymous'
},
'data': {},
'timestamp': FROZEN_TIME,
'name': str(sentinel.event_type)
}
assert_event_matches(expected_event, actual_event)
def test_user_track_with_missing_values(self):
request = self.request_factory.get('/event')
views.user_track(request)
actual_event = self.get_event()
expected_event = {
'context': {
'course_id': '',
'org_id': '',
'event_source': 'browser',
'page': '',
'username': 'anonymous'
},
'data': {},
'timestamp': FROZEN_TIME,
'name': 'unknown'
}
assert_event_matches(expected_event, actual_event)
def test_user_track_with_empty_event(self):
request = self.request_factory.get('/event', {
'page': self.url_with_course,
'event_type': sentinel.event_type,
'event': ''
})
views.user_track(request)
actual_event = self.get_event()
expected_event = {
'context': {
'course_id': 'foo/bar/baz',
'org_id': 'foo',
'event_source': 'browser',
'page': self.url_with_course,
'username': 'anonymous'
},
'data': {},
'timestamp': FROZEN_TIME,
'name': str(sentinel.event_type)
}
assert_event_matches(expected_event, actual_event)
@ddt.data(
{
'event_data': f'{{"username": "{TEST_USERNAME}"}}',
'expected_event_data': {"username": TEST_USERNAME, "user_id": TEST_USER_ID}
},
{
'event_data': '{"username": "unknown-user"}',
'expected_event_data': {"username": "unknown-user"},
}
)
@ddt.unpack
def test_user_track_with_username_in_data(self, event_data, expected_event_data):
request = self.request_factory.get('/event', {
'event': event_data,
})
views.user_track(request)
actual_event = self.get_event()
expected_event = {
'context': {
'course_id': '',
'org_id': '',
'event_source': 'browser',
'page': '',
'username': 'anonymous'
},
'data': expected_event_data,
'timestamp': FROZEN_TIME,
'name': 'unknown'
}
assert_event_matches(expected_event, actual_event)
@override_settings(
EVENT_TRACKING_PROCESSORS=[{'ENGINE': 'common.djangoapps.track.shim.LegacyFieldMappingProcessor'}],
)
def test_user_track_with_middleware_and_processors(self):
self.recreate_tracker()
middleware = TrackMiddleware()
payload = '{"foo": "bar"}'
user_id = 1
request = self.request_factory.get('/event', {
'page': self.url_with_course,
'event_type': sentinel.event_type,
'event': payload
})
request.user = User.objects.create(pk=user_id, username=str(sentinel.username))
request.META['REMOTE_ADDR'] = '10.0.0.1'
request.META['HTTP_REFERER'] = str(sentinel.referer)
request.META['HTTP_ACCEPT_LANGUAGE'] = str(sentinel.accept_language)
request.META['HTTP_USER_AGENT'] = str(sentinel.user_agent)
request.META['SERVER_NAME'] = 'testserver2'
middleware.process_request(request)
try:
views.user_track(request)
expected_event = {
'accept_language': str(sentinel.accept_language),
'referer': str(sentinel.referer),
'username': str(sentinel.username),
'session': '',
'ip': '10.0.0.1',
'event_source': 'browser',
'event_type': str(sentinel.event_type),
'name': str(sentinel.event_type),
'event': payload,
'agent': str(sentinel.user_agent),
'page': self.url_with_course,
'time': FROZEN_TIME,
'host': 'testserver2',
'context': {
'course_id': 'foo/bar/baz',
'org_id': 'foo',
'user_id': user_id,
'path': '/event'
},
}
finally:
middleware.process_response(request, None)
actual_event = self.get_event()
assert_event_matches(expected_event, actual_event)
def test_server_track(self):
request = self.request_factory.get(self.path_with_course)
views.server_track(request, str(sentinel.event_type), '{}')
expected_event = {
'timestamp': FROZEN_TIME,
'data': '{}',
'name': str(sentinel.event_type),
'context':
{
'username': 'anonymous',
'page': None,
'event_source': 'server'
}
}
actual_event = self.get_event()
assert_event_matches(expected_event, actual_event)
def test_server_track_with_middleware(self):
middleware = TrackMiddleware()
request = self.request_factory.get(self.path_with_course)
middleware.process_request(request)
# The middleware emits an event, reset the mock to ignore it since we aren't testing that feature.
self.mock_tracker.reset_mock()
try:
views.server_track(request, str(sentinel.event_type), '{}')
expected_event = {
'timestamp': FROZEN_TIME,
'data': '{"GET": {}, "POST": {}}',
'name': self.path_with_course,
'context': {
'username': 'anonymous',
'user_id': '',
'accept_language': '',
'ip': '127.0.0.1',
'org_id': 'foo',
'agent': '',
'event_source': 'server',
'host': 'testserver',
'session': '',
'referer': '',
'client_id': None,
'course_id': 'foo/bar/baz',
'enterprise_uuid': '',
'path': self.path_with_course,
'page': None
}
}
finally:
middleware.process_response(request, None)
actual_event = self.get_event()
assert_event_matches(
expected_event,
actual_event,
tolerate={'string_payload'}
)
def test_server_track_with_middleware_and_google_analytics_cookie(self):
middleware = TrackMiddleware()
request = self.request_factory.get(self.path_with_course)
request.COOKIES['_ga'] = 'GA1.2.1033501218.1368477899'
middleware.process_request(request)
# The middleware emits an event, reset the mock to ignore it since we aren't testing that feature.
self.mock_tracker.reset_mock()
try:
views.server_track(request, str(sentinel.event_type), '{}')
expected_event = {
'timestamp': FROZEN_TIME,
'data': '{"GET": {}, "POST": {}}',
'name': self.path_with_course,
'context': {
'username': 'anonymous',
'user_id': '',
'accept_language': '',
'ip': '127.0.0.1',
'org_id': 'foo',
'agent': '',
'event_source': 'server',
'host': 'testserver',
'session': '',
'referer': '',
'client_id': '1033501218.1368477899',
'course_id': 'foo/bar/baz',
'enterprise_uuid': '',
'path': self.path_with_course,
'page': None
}
}
finally:
middleware.process_response(request, None)
actual_event = self.get_event()
assert_event_matches(expected_event, actual_event, tolerate={'string_payload', })
def test_server_track_with_no_request(self):
request = None
views.server_track(request, str(sentinel.event_type), '{}')
expected_event = {
'timestamp': FROZEN_TIME,
'data': '{}',
'name': str(sentinel.event_type),
'context': {
'username': 'anonymous',
'page': None,
'event_source':
'server'
}
}
actual_event = self.get_event()
assert_event_matches(expected_event, actual_event)
def test_task_track(self):
request_info = {
'accept_language': '',
'referer': '',
'username': 'anonymous',
'ip': '127.0.0.1',
'agent': 'agent',
'host': 'testserver',
}
task_info = {
str(sentinel.task_key): sentinel.task_value
}
expected_event_data = dict(task_info)
expected_event_data.update(self.event)
views.task_track(request_info, task_info, str(sentinel.event_type), self.event)
expected_event = {
'timestamp': FROZEN_TIME,
'data': expected_event_data,
'name': str(sentinel.event_type),
'context': {
'username': 'anonymous',
'ip': '127.0.0.1',
'agent': 'agent',
'host': 'testserver',
'page': None,
'event_source': 'task'
}
}
actual_event = self.get_event()
assert_event_matches(expected_event, actual_event)