diff --git a/common/djangoapps/track/views/segmentio.py b/common/djangoapps/track/views/segmentio.py index efdf8da96856..534323c83ea0 100644 --- a/common/djangoapps/track/views/segmentio.py +++ b/common/djangoapps/track/views/segmentio.py @@ -123,16 +123,16 @@ def track_segmentio_event(request): # pylint: disable=too-many-statements full_segment_event = request.json # We mostly care about the properties - segment_properties = full_segment_event.get('properties', {}) + segment_properties = _get_dict_value_with_default(full_segment_event, 'properties', {}) # Start with the context provided by Segment in the "client" field if it exists # We should tightly control which fields actually get included in the event emitted. - segment_context = full_segment_event.get('context', {}) + segment_context = _get_dict_value_with_default(full_segment_event, 'context', {}) # Build up the event context by parsing fields out of the event received from Segment context = {} - library_name = segment_context.get('library', {}).get('name') + library_name = _get_dict_value_with_default(segment_context, 'library', {}).get('name') source_map = getattr(settings, 'TRACKING_SEGMENTIO_SOURCE_MAP', {}) event_source = source_map.get(library_name) if not event_source: @@ -268,3 +268,12 @@ def _get_segmentio_event_name(event_properties): def parse_iso8601_timestamp(timestamp): """Parse a particular type of ISO8601 formatted timestamp""" return parser.parse(timestamp) + + +def _get_dict_value_with_default(dict_object, key, default): + """ + Returns default if the dict doesn't have the key or if the value is Falsey. + Otherwise, returns the dict's value for the key. + """ + value = dict_object.get(key, None) + return value if value else default diff --git a/common/djangoapps/track/views/tests/base.py b/common/djangoapps/track/views/tests/base.py index eda0fd6d37ac..5709b539352f 100644 --- a/common/djangoapps/track/views/tests/base.py +++ b/common/djangoapps/track/views/tests/base.py @@ -60,6 +60,14 @@ def post_segmentio_event(self, **kwargs): ) segmentio.track_segmentio_event(request) + def post_modified_segmentio_event(self, event): + """Post an externally-defined fake Segment event to the view that processes it""" + request = self.create_request( + data=json.dumps(event), + content_type='application/json' + ) + segmentio.track_segmentio_event(request) + def create_segmentio_event(self, **kwargs): """Populate a fake Segment event with data of interest""" action = kwargs.get('action', 'Track') diff --git a/common/djangoapps/track/views/tests/test_segmentio.py b/common/djangoapps/track/views/tests/test_segmentio.py index 8a6fd09fa4f3..640f55e73e60 100644 --- a/common/djangoapps/track/views/tests/test_segmentio.py +++ b/common/djangoapps/track/views/tests/test_segmentio.py @@ -68,10 +68,46 @@ def test_segmentio_ignore_actions(self, action): self.post_segmentio_event(action=action) self.assert_no_events_emitted() + def test_segmentio_ignore_missing_context_entry(self): + sample_event_raw = self.create_segmentio_event() + del sample_event_raw['context'] + self.post_modified_segmentio_event(sample_event_raw) + self.assert_no_events_emitted() + + def test_segmentio_ignore_null_context_entry(self): + sample_event_raw = self.create_segmentio_event() + sample_event_raw['context'] = None + self.post_modified_segmentio_event(sample_event_raw) + self.assert_no_events_emitted() + + def test_segmentio_ignore_missing_library_entry(self): + sample_event_raw = self.create_segmentio_event() + del sample_event_raw['context']['library'] + self.post_modified_segmentio_event(sample_event_raw) + self.assert_no_events_emitted() + + def test_segmentio_ignore_null_library_entry(self): + sample_event_raw = self.create_segmentio_event() + sample_event_raw['context']['library'] = None + self.post_modified_segmentio_event(sample_event_raw) + self.assert_no_events_emitted() + def test_segmentio_ignore_unknown_libraries(self): self.post_segmentio_event(library_name='foo') self.assert_no_events_emitted() + @expect_failure_with_message(segmentio.ERROR_MISSING_NAME) + def test_segmentio_ignore_missing_properties_entry(self): + sample_event_raw = self.create_segmentio_event() + del sample_event_raw['properties'] + self.post_modified_segmentio_event(sample_event_raw) + + @expect_failure_with_message(segmentio.ERROR_MISSING_NAME) + def test_segmentio_ignore_null_properties_entry(self): + sample_event_raw = self.create_segmentio_event() + sample_event_raw['properties'] = None + self.post_modified_segmentio_event(sample_event_raw) + @expect_failure_with_message(segmentio.ERROR_USER_NOT_EXIST) def test_no_user_for_user_id(self): self.post_segmentio_event(user_id=40) @@ -148,43 +184,23 @@ def test_invalid_course_id(self): def test_missing_name(self): sample_event_raw = self.create_segmentio_event() del sample_event_raw['properties']['name'] - request = self.create_request( - data=json.dumps(sample_event_raw), - content_type='application/json' - ) - - segmentio.track_segmentio_event(request) + self.post_modified_segmentio_event(sample_event_raw) @expect_failure_with_message(segmentio.ERROR_MISSING_DATA) def test_missing_data(self): sample_event_raw = self.create_segmentio_event() del sample_event_raw['properties']['data'] - request = self.create_request( - data=json.dumps(sample_event_raw), - content_type='application/json' - ) - - segmentio.track_segmentio_event(request) + self.post_modified_segmentio_event(sample_event_raw) @expect_failure_with_message(segmentio.ERROR_MISSING_TIMESTAMP) def test_missing_timestamp(self): sample_event_raw = self.create_event_without_fields('timestamp') - request = self.create_request( - data=json.dumps(sample_event_raw), - content_type='application/json' - ) - - segmentio.track_segmentio_event(request) + self.post_modified_segmentio_event(sample_event_raw) @expect_failure_with_message(segmentio.ERROR_MISSING_RECEIVED_AT) def test_missing_received_at(self): sample_event_raw = self.create_event_without_fields('receivedAt') - request = self.create_request( - data=json.dumps(sample_event_raw), - content_type='application/json' - ) - - segmentio.track_segmentio_event(request) + self.post_modified_segmentio_event(sample_event_raw) def create_event_without_fields(self, *fields): """Create a fake event and remove some fields from it"""