import pytest from ddtrace import tracer, Span from ddtrace.http import store_request_headers, store_response_headers from ddtrace.settings import Config, IntegrationConfig class TestHeaders(object): @pytest.fixture() def span(self): yield Span(tracer, 'some_span') @pytest.fixture() def config(self): yield Config() @pytest.fixture() def integration_config(self, config): yield IntegrationConfig(config, 'test') def test_it_does_not_break_if_no_headers(self, span, integration_config): store_request_headers(None, span, integration_config) store_response_headers(None, span, integration_config) def test_it_does_not_break_if_headers_are_not_a_dict(self, span, integration_config): store_request_headers(list(), span, integration_config) store_response_headers(list(), span, integration_config) def test_it_accept_headers_as_list_of_tuples(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers(['Content-Type', 'Max-Age']) store_request_headers([('Content-Type', 'some;value;content-type')], span, integration_config) assert span.get_tag('http.request.headers.content-type') == 'some;value;content-type' assert None is span.get_tag('http.request.headers.other') def test_store_multiple_request_headers_as_dict(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers(['Content-Type', 'Max-Age']) store_request_headers({ 'Content-Type': 'some;value;content-type', 'Max-Age': 'some;value;max_age', 'Other': 'some;value;other', }, span, integration_config) assert span.get_tag('http.request.headers.content-type') == 'some;value;content-type' assert span.get_tag('http.request.headers.max-age') == 'some;value;max_age' assert None is span.get_tag('http.request.headers.other') def test_store_multiple_response_headers_as_dict(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers(['Content-Type', 'Max-Age']) store_response_headers({ 'Content-Type': 'some;value;content-type', 'Max-Age': 'some;value;max_age', 'Other': 'some;value;other', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') == 'some;value;content-type' assert span.get_tag('http.response.headers.max-age') == 'some;value;max_age' assert None is span.get_tag('http.response.headers.other') def test_numbers_in_headers_names_are_allowed(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('Content-Type123') store_response_headers({ 'Content-Type123': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type123') == 'some;value' def test_allowed_chars_not_replaced_in_tag_name(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # See: https://docs.datadoghq.com/tagging/#defining-tags integration_config.http.trace_headers('C0n_t:e/nt-Type') store_response_headers({ 'C0n_t:e/nt-Type': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.c0n_t:e/nt-type') == 'some;value' def test_period_is_replaced_by_underscore(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # Deviation from https://docs.datadoghq.com/tagging/#defining-tags in order to allow # consistent representation of headers having the period in the name. integration_config.http.trace_headers('api.token') store_response_headers({ 'api.token': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.api_token') == 'some;value' def test_non_allowed_chars_replaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # See: https://docs.datadoghq.com/tagging/#defining-tags integration_config.http.trace_headers('C!#ontent-Type') store_response_headers({ 'C!#ontent-Type': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.c__ontent-type') == 'some;value' def test_key_trim_leading_trailing_spaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('Content-Type') store_response_headers({ ' Content-Type ': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') == 'some;value' def test_value_not_trim_leading_trailing_spaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('Content-Type') store_response_headers({ 'Content-Type': ' some;value ', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') == ' some;value ' def test_no_allowlist(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ store_response_headers({ 'Content-Type': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') is None def test_allowlist_exact(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('content-type') store_response_headers({ 'Content-Type': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') == 'some;value' def test_allowlist_case_insensitive(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('CoNtEnT-tYpE') store_response_headers({ 'cOnTeNt-TyPe': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') == 'some;value'