Skip to content

Commit 8dc53de

Browse files
committed
Migrate Prometheus and OpenMetrics tests to RequestWrapperMock
Replace requests.Session.get patches with get_http_handler + RequestWrapperMock/HTTPResponseMock so tests are implementation-agnostic and do not depend on requests. Add ignore_tls_warning to RequestWrapperMock so scraper code that checks handler.ignore_tls_warning works when the handler is the mock.
1 parent d8f8d3f commit 8dc53de

File tree

3 files changed

+152
-142
lines changed

3 files changed

+152
-142
lines changed

datadog_checks_base/datadog_checks/base/utils/http_mock.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55
Test helpers for HTTP client and response that implement the HTTP protocols.
66
Use these to mock self.http or inject responses without referencing requests or httpx,
77
keeping tests implementation-independent.
8+
9+
When to use which:
10+
- **RequestWrapperMock / HTTPResponseMock**: Use in tests where the *subject under test*
11+
is a *check* (or any code) that calls ``check.http``. Inject RequestWrapperMock so the
12+
test does not depend on requests/httpx and works with either wrapper. Example:
13+
``with RequestWrapperMock(check, get=lambda url, **kw: HTTPResponseMock(200, content=b'...')):``
14+
- **Legacy base tests:** Tests that target RequestsWrapper itself (e.g. test_api.py,
15+
test_proxy.py, test_tls_and_certs.py) currently use ``requests.Session.get`` or similar
16+
patches. They are legacy and will be removed when the requests library is removed; use
17+
RequestWrapperMock for all new check-level tests.
818
"""
919

1020
from __future__ import annotations
@@ -146,6 +156,7 @@ def __init__(
146156
delete: _RequestHandler | None = None,
147157
options_method: _RequestHandler | None = None,
148158
default_response: HTTPResponseProtocol | None = None,
159+
ignore_tls_warning: bool = False,
149160
) -> None:
150161
self._check = check
151162
self._handlers = {
@@ -161,6 +172,7 @@ def __init__(
161172
self._saved_http: Any = None
162173
self.options = {}
163174
self.session = _MockSession()
175+
self.ignore_tls_warning = ignore_tls_warning
164176

165177
def _response(self, method: str, url: str, **options: Any) -> HTTPResponseProtocol:
166178
handler = self._handlers.get(method)

datadog_checks_base/tests/base/checks/openmetrics/test_legacy/test_openmetrics.py

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from prometheus_client.samples import Sample
2020

2121
from datadog_checks.base import ensure_bytes
22+
from datadog_checks.base.utils.http_mock import HTTPResponseMock, RequestWrapperMock
2223
from datadog_checks.checks.openmetrics import OpenMetricsBaseCheck
2324
from datadog_checks.dev import get_here
2425
from datadog_checks.dev.http import MockResponse
@@ -95,10 +96,19 @@ def text_data():
9596

9697

9798
@pytest.fixture
98-
def mock_get(mock_http_response):
99-
yield mock_http_response(
100-
file_path=os.path.join(FIXTURE_PATH, 'ksm.txt'), headers={'Content-Type': text_content_type}
101-
).return_value.text
99+
def mock_get():
100+
f_name = os.path.join(FIXTURE_PATH, 'ksm.txt')
101+
with open(f_name, 'r') as f:
102+
text_data = f.read()
103+
content = text_data.encode('utf-8')
104+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
105+
mock_wrapper = RequestWrapperMock(get=lambda url, **kw: mock_resp)
106+
patcher = mock.patch.object(OpenMetricsBaseCheck, 'get_http_handler', return_value=mock_wrapper)
107+
patcher.start()
108+
try:
109+
yield text_data
110+
finally:
111+
patcher.stop()
102112

103113

104114
def test_config_instance(mocked_prometheus_check):
@@ -154,10 +164,11 @@ def test_process_metric_filtered(aggregator, mocked_prometheus_check, mocked_pro
154164
def test_poll_text_plain(mocked_prometheus_check, mocked_prometheus_scraper_config, text_data):
155165
"""Tests poll using the text format"""
156166
check = mocked_prometheus_check
157-
mock_response = mock.MagicMock(
158-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
159-
)
160-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
167+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
168+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
169+
with mock.patch.object(
170+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
171+
):
161172
response = check.poll(mocked_prometheus_scraper_config)
162173
messages = list(check.parse_metric_family(response, mocked_prometheus_scraper_config))
163174
messages.sort(key=lambda x: x.name)
@@ -168,13 +179,11 @@ def test_poll_text_plain(mocked_prometheus_check, mocked_prometheus_scraper_conf
168179
def test_poll_octet_stream(mocked_prometheus_check, mocked_prometheus_scraper_config, text_data):
169180
"""Tests poll using the text format"""
170181
check = mocked_prometheus_check
171-
172-
mock_response = requests.Response()
173-
mock_response.raw = io.BytesIO(ensure_bytes(text_data))
174-
mock_response.status_code = 200
175-
mock_response.headers = {'Content-Type': 'application/octet-stream'}
176-
177-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
182+
content = ensure_bytes(text_data)
183+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': 'application/octet-stream'})
184+
with mock.patch.object(
185+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
186+
):
178187
response = check.poll(mocked_prometheus_scraper_config)
179188
messages = list(check.parse_metric_family(response, mocked_prometheus_scraper_config))
180189
assert len(messages) == 40
@@ -1692,10 +1701,11 @@ def test_ignore_metrics_multiple_wildcards(
16921701

16931702
config = check.create_scraper_configuration(instance)
16941703

1695-
mock_response = mock.MagicMock(
1696-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
1697-
)
1698-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
1704+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
1705+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
1706+
with mock.patch.object(
1707+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
1708+
):
16991709
check.process(config)
17001710

17011711
# Make sure metrics are ignored
@@ -1800,10 +1810,11 @@ def test_metrics_with_ignore_label_values(
18001810
instance['ignore_metrics_by_labels'] = {'system': ['auth', 'recursive'], 'cache': ['*']}
18011811
config = check.create_scraper_configuration(instance)
18021812
expected_tags = ['cause:nxdomain']
1803-
mock_response = mock.MagicMock(
1804-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
1805-
)
1806-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
1813+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
1814+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
1815+
with mock.patch.object(
1816+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
1817+
):
18071818
check.process(config)
18081819

18091820
# Make sure metrics are ignored
@@ -1851,10 +1862,11 @@ def test_match_metrics_multiple_wildcards(
18511862

18521863
config = check.create_scraper_configuration(instance)
18531864

1854-
mock_response = mock.MagicMock(
1855-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
1856-
)
1857-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
1865+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
1866+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
1867+
with mock.patch.object(
1868+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
1869+
):
18581870
check.process(config)
18591871

18601872
aggregator.assert_metric('prometheus.go_memstats_mcache_inuse_bytes', count=1)
@@ -2301,10 +2313,11 @@ def test_label_joins_gc(aggregator, mocked_prometheus_check, mocked_prometheus_s
23012313
pvc_replace = re.compile(r'^kube_persistentvolumeclaim_.*\n', re.MULTILINE)
23022314
text_data = pvc_replace.sub('', text_data)
23032315

2304-
mock_response = mock.MagicMock(
2305-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
2306-
)
2307-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
2316+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
2317+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
2318+
with mock.patch.object(
2319+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
2320+
):
23082321
check.process(mocked_prometheus_scraper_config)
23092322
assert 'dd-agent-1337' in mocked_prometheus_scraper_config['_label_mapping']['pod']
23102323
assert 'dd-agent-62bgh' not in mocked_prometheus_scraper_config['_label_mapping']['pod']
@@ -2461,10 +2474,11 @@ def test_label_join_state_change(aggregator, mocked_prometheus_check, mocked_pro
24612474
'kube_pod_status_phase{namespace="default",phase="Test",pod="dd-agent-62bgh"} 1',
24622475
)
24632476

2464-
mock_response = mock.MagicMock(
2465-
status_code=200, iter_lines=lambda **kwargs: text_data.split("\n"), headers={'Content-Type': text_content_type}
2466-
)
2467-
with mock.patch('requests.Session.get', return_value=mock_response, __name__="get"):
2477+
content = text_data.encode('utf-8') if isinstance(text_data, str) else text_data
2478+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
2479+
with mock.patch.object(
2480+
check, 'get_http_handler', return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp)
2481+
):
24682482
check.process(mocked_prometheus_scraper_config)
24692483
assert 15 == len(mocked_prometheus_scraper_config['_label_mapping']['pod'])
24702484
assert mocked_prometheus_scraper_config['_label_mapping']['pod']['dd-agent-62bgh']['phase'] == 'Test'
@@ -2577,19 +2591,18 @@ def test_text_filter_input(mocked_prometheus_check, mocked_prometheus_scraper_co
25772591

25782592
@pytest.fixture()
25792593
def mock_filter_get():
2580-
text_data = None
25812594
f_name = os.path.join(FIXTURE_PATH, 'deprecated.txt')
25822595
with open(f_name, 'r') as f:
25832596
text_data = f.read()
2584-
with mock.patch(
2585-
'requests.Session.get',
2586-
return_value=mock.MagicMock(
2587-
status_code=200,
2588-
iter_lines=lambda **kwargs: text_data.split("\n"),
2589-
headers={'Content-Type': text_content_type},
2590-
),
2591-
):
2597+
content = text_data.encode('utf-8')
2598+
mock_resp = HTTPResponseMock(200, content=content, headers={'Content-Type': text_content_type})
2599+
mock_wrapper = RequestWrapperMock(get=lambda url, **kw: mock_resp)
2600+
patcher = mock.patch.object(OpenMetricsBaseCheck, 'get_http_handler', return_value=mock_wrapper)
2601+
patcher.start()
2602+
try:
25922603
yield text_data
2604+
finally:
2605+
patcher.stop()
25932606

25942607

25952608
class FilterOpenMetricsCheck(OpenMetricsBaseCheck):
@@ -2671,8 +2684,12 @@ def test_ssl_verify_not_raise_warning(caplog, mocked_openmetrics_check_factory,
26712684
}
26722685
check = mocked_openmetrics_check_factory(instance)
26732686
scraper_config = check.get_scraper_config(instance)
2674-
2675-
with caplog.at_level(logging.DEBUG), mock.patch('requests.Session.get', return_value=MockResponse('httpbin.org')):
2687+
mock_resp = HTTPResponseMock(200, content=b'httpbin.org')
2688+
with caplog.at_level(logging.DEBUG), mock.patch.object(
2689+
check,
2690+
'get_http_handler',
2691+
return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp, ignore_tls_warning=True),
2692+
):
26762693
resp = check.send_request('https://httpbin.org/get', scraper_config)
26772694

26782695
assert "httpbin.org" in resp.content.decode('utf-8')
@@ -2696,7 +2713,12 @@ def test_send_request_with_dynamic_prometheus_url(caplog, mocked_openmetrics_che
26962713
# `prometheus_url` changed just before calling `send_request`
26972714
scraper_config['prometheus_url'] = 'https://www.example.com/foo/bar'
26982715

2699-
with caplog.at_level(logging.DEBUG), mock.patch('requests.Session.get', return_value=MockResponse('httpbin.org')):
2716+
mock_resp = HTTPResponseMock(200, content=b'httpbin.org')
2717+
with caplog.at_level(logging.DEBUG), mock.patch.object(
2718+
check,
2719+
'get_http_handler',
2720+
return_value=RequestWrapperMock(get=lambda url, **kw: mock_resp, ignore_tls_warning=True),
2721+
):
27002722
resp = check.send_request('https://httpbin.org/get', scraper_config)
27012723

27022724
assert "httpbin.org" in resp.content.decode('utf-8')

0 commit comments

Comments
 (0)