Skip to content

Commit 6c1822f

Browse files
GSVarshapvital
authored andcommitted
tests(httpx): Add tests for sync requests
Signed-off-by: Varsha GS <varsha.gs@ibm.com>
1 parent 4a4142b commit 6c1822f

1 file changed

Lines changed: 388 additions & 0 deletions

File tree

tests/clients/test_httpx.py

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
import pytest
4+
import httpx
5+
from typing import Generator
6+
7+
from instana.singletons import agent, tracer
8+
from instana.util.ids import hex_id
9+
import tests.apps.flask_app
10+
from tests.helpers import testenv
11+
12+
13+
class TestHttpx:
14+
@pytest.fixture(autouse=True)
15+
def _setup(self) -> Generator[None, None, None]:
16+
"""SetUp and TearDown"""
17+
# setup
18+
# Clear all spans before a test run
19+
self.host = "127.0.0.1"
20+
self.recorder = tracer.span_processor
21+
self.recorder.clear_spans()
22+
yield
23+
# teardown
24+
# Ensure that allow_exit_as_root has the default value
25+
agent.options.allow_exit_as_root = False
26+
27+
def test_get_request(self):
28+
with tracer.start_as_current_span("test"):
29+
res = httpx.get(testenv["flask_server"] + "/")
30+
31+
spans = self.recorder.queued_spans()
32+
assert len(spans) == 3
33+
34+
wsgi_span = spans[0]
35+
httpx_span = spans[1]
36+
test_span = spans[2]
37+
38+
assert res
39+
assert res.status_code == 200
40+
41+
assert "X-INSTANA-T" in res.headers
42+
assert int(res.headers["X-INSTANA-T"], 16)
43+
assert res.headers["X-INSTANA-T"] == hex_id(wsgi_span.t)
44+
45+
assert "X-INSTANA-S" in res.headers
46+
assert int(res.headers["X-INSTANA-S"], 16)
47+
assert res.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
48+
49+
assert "X-INSTANA-L" in res.headers
50+
assert res.headers["X-INSTANA-L"] == "1"
51+
52+
assert "Server-Timing" in res.headers
53+
server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
54+
assert res.headers["Server-Timing"] == server_timing_value
55+
56+
# Same traceId
57+
assert test_span.t == httpx_span.t
58+
assert httpx_span.t == wsgi_span.t
59+
60+
# Parent relationships
61+
assert httpx_span.p == test_span.s
62+
assert wsgi_span.p == httpx_span.s
63+
64+
# Error logging
65+
assert not test_span.ec
66+
assert not httpx_span.ec
67+
assert not wsgi_span.ec
68+
69+
# span names
70+
assert wsgi_span.n == "wsgi"
71+
assert test_span.data["sdk"]["name"] == "test"
72+
assert httpx_span.n == "http"
73+
74+
# httpx
75+
assert httpx_span.data["http"]["status"] == 200
76+
assert httpx_span.data["http"]["host"] == self.host
77+
assert httpx_span.data["http"]["path"] == "/"
78+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + "/"
79+
assert httpx_span.data["http"]["method"] == "GET"
80+
assert httpx_span.stack
81+
assert isinstance(httpx_span.stack, list)
82+
assert len(httpx_span.stack) > 1
83+
84+
def test_get_request_as_root_exit_span(self):
85+
agent.options.allow_exit_as_root = True
86+
res = httpx.get(testenv["flask_server"] + "/")
87+
88+
spans = self.recorder.queued_spans()
89+
assert len(spans) == 2
90+
91+
wsgi_span = spans[0]
92+
httpx_span = spans[1]
93+
94+
assert res
95+
assert res.status_code == 200
96+
97+
assert "X-INSTANA-T" in res.headers
98+
assert int(res.headers["X-INSTANA-T"], 16)
99+
assert res.headers["X-INSTANA-T"] == hex_id(wsgi_span.t)
100+
101+
assert "X-INSTANA-S" in res.headers
102+
assert int(res.headers["X-INSTANA-S"], 16)
103+
assert res.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
104+
105+
assert "X-INSTANA-L" in res.headers
106+
assert res.headers["X-INSTANA-L"] == "1"
107+
108+
assert "Server-Timing" in res.headers
109+
server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
110+
assert res.headers["Server-Timing"] == server_timing_value
111+
112+
# Same traceId
113+
assert httpx_span.t == wsgi_span.t
114+
115+
# Parent relationships
116+
assert not httpx_span.p
117+
assert wsgi_span.p == httpx_span.s
118+
119+
# Error logging
120+
assert not httpx_span.ec
121+
assert not wsgi_span.ec
122+
123+
# span names
124+
assert wsgi_span.n == "wsgi"
125+
assert httpx_span.n == "http"
126+
127+
# httpx
128+
assert httpx_span.data["http"]["status"] == 200
129+
assert httpx_span.data["http"]["host"] == self.host
130+
assert httpx_span.data["http"]["path"] == "/"
131+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + "/"
132+
assert httpx_span.data["http"]["method"] == "GET"
133+
assert httpx_span.stack
134+
assert isinstance(httpx_span.stack, list)
135+
assert len(httpx_span.stack) > 1
136+
137+
def test_get_request_with_query(self):
138+
with tracer.start_as_current_span("test"):
139+
res = httpx.get(testenv["flask_server"] + "/?user=instana&pass=itsasecret")
140+
141+
spans = self.recorder.queued_spans()
142+
assert len(spans) == 3
143+
144+
wsgi_span = spans[0]
145+
httpx_span = spans[1]
146+
test_span = spans[2]
147+
148+
assert res
149+
assert res.status_code == 200
150+
151+
# Same traceId
152+
assert test_span.t == httpx_span.t
153+
assert httpx_span.t == wsgi_span.t
154+
155+
# Parent relationships
156+
assert httpx_span.p == test_span.s
157+
assert wsgi_span.p == httpx_span.s
158+
159+
# Error logging
160+
assert not test_span.ec
161+
assert not httpx_span.ec
162+
assert not wsgi_span.ec
163+
164+
# span names
165+
assert wsgi_span.n == "wsgi"
166+
assert test_span.data["sdk"]["name"] == "test"
167+
assert httpx_span.n == "http"
168+
169+
# httpx
170+
assert httpx_span.data["http"]["status"] == 200
171+
assert httpx_span.data["http"]["host"] == self.host
172+
assert httpx_span.data["http"]["path"] == "/"
173+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + "/"
174+
assert httpx_span.data["http"]["params"] == "user=instana&pass=<redacted>"
175+
assert httpx_span.data["http"]["method"] == "GET"
176+
assert httpx_span.stack
177+
assert isinstance(httpx_span.stack, list)
178+
assert len(httpx_span.stack) > 1
179+
180+
def test_post_request(self):
181+
path = "/notfound"
182+
with tracer.start_as_current_span("test"):
183+
res = httpx.post(testenv["flask_server"] + "/notfound")
184+
185+
spans = self.recorder.queued_spans()
186+
assert len(spans) == 3
187+
188+
wsgi_span = spans[0]
189+
httpx_span = spans[1]
190+
test_span = spans[2]
191+
192+
assert res
193+
assert res.status_code == 404
194+
195+
# Same traceId
196+
assert test_span.t == httpx_span.t
197+
assert httpx_span.t == wsgi_span.t
198+
199+
# Parent relationships
200+
assert httpx_span.p == test_span.s
201+
assert wsgi_span.p == httpx_span.s
202+
203+
# Error logging
204+
assert not test_span.ec
205+
assert not httpx_span.ec
206+
assert not wsgi_span.ec
207+
208+
# span names
209+
assert wsgi_span.n == "wsgi"
210+
assert test_span.data["sdk"]["name"] == "test"
211+
assert httpx_span.n == "http"
212+
213+
# httpx
214+
assert httpx_span.data["http"]["status"] == 404
215+
assert httpx_span.data["http"]["host"] == self.host
216+
assert httpx_span.data["http"]["path"] == path
217+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + path
218+
assert httpx_span.data["http"]["method"] == "POST"
219+
assert httpx_span.stack
220+
assert isinstance(httpx_span.stack, list)
221+
assert len(httpx_span.stack) > 1
222+
223+
def test_5xx_request(self):
224+
path = "/500"
225+
with tracer.start_as_current_span("test"):
226+
res = httpx.get(testenv["flask_server"] + path)
227+
228+
spans = self.recorder.queued_spans()
229+
assert len(spans) == 3
230+
231+
wsgi_span = spans[0]
232+
httpx_span = spans[1]
233+
test_span = spans[2]
234+
235+
assert res
236+
assert res.status_code == 500
237+
238+
assert "X-INSTANA-T" in res.headers
239+
assert int(res.headers["X-INSTANA-T"], 16)
240+
assert res.headers["X-INSTANA-T"] == hex_id(wsgi_span.t)
241+
242+
assert "X-INSTANA-S" in res.headers
243+
assert int(res.headers["X-INSTANA-S"], 16)
244+
assert res.headers["X-INSTANA-S"] == hex_id(wsgi_span.s)
245+
246+
assert "X-INSTANA-L" in res.headers
247+
assert res.headers["X-INSTANA-L"] == "1"
248+
249+
assert "Server-Timing" in res.headers
250+
server_timing_value = f"intid;desc={hex_id(wsgi_span.t)}"
251+
assert res.headers["Server-Timing"] == server_timing_value
252+
253+
# Same traceId
254+
assert test_span.t == httpx_span.t
255+
assert httpx_span.t == wsgi_span.t
256+
257+
# Parent relationships
258+
assert httpx_span.p == test_span.s
259+
assert wsgi_span.p == httpx_span.s
260+
261+
# Error logging
262+
assert not test_span.ec
263+
assert httpx_span.ec == 1
264+
assert wsgi_span.ec == 1
265+
266+
# span names
267+
assert wsgi_span.n == "wsgi"
268+
assert test_span.data["sdk"]["name"] == "test"
269+
assert httpx_span.n == "http"
270+
271+
# httpx
272+
assert httpx_span.data["http"]["status"] == 500
273+
assert httpx_span.data["http"]["host"] == self.host
274+
assert httpx_span.data["http"]["path"] == path
275+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + path
276+
assert httpx_span.data["http"]["method"] == "GET"
277+
assert httpx_span.stack
278+
assert isinstance(httpx_span.stack, list)
279+
assert len(httpx_span.stack) > 1
280+
281+
def test_response_header_capture(self):
282+
original_extra_http_headers = agent.options.extra_http_headers
283+
agent.options.extra_http_headers = ["X-Capture-This", "X-Capture-That"]
284+
path = "/response_headers"
285+
286+
with tracer.start_as_current_span("test"):
287+
res = httpx.get(testenv["flask_server"] + path)
288+
289+
spans = self.recorder.queued_spans()
290+
assert len(spans) == 3
291+
292+
wsgi_span = spans[0]
293+
httpx_span = spans[1]
294+
test_span = spans[2]
295+
296+
assert res
297+
assert res.status_code == 200
298+
299+
# Same traceId
300+
assert test_span.t == httpx_span.t
301+
assert httpx_span.t == wsgi_span.t
302+
303+
# Parent relationships
304+
assert httpx_span.p == test_span.s
305+
assert wsgi_span.p == httpx_span.s
306+
307+
# Error logging
308+
assert not test_span.ec
309+
assert not httpx_span.ec
310+
assert not wsgi_span.ec
311+
312+
# span names
313+
assert wsgi_span.n == "wsgi"
314+
assert test_span.data["sdk"]["name"] == "test"
315+
assert httpx_span.n == "http"
316+
317+
# httpx
318+
assert httpx_span.data["http"]["status"] == 200
319+
assert httpx_span.data["http"]["host"] == self.host
320+
assert httpx_span.data["http"]["path"] == path
321+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + path
322+
assert httpx_span.data["http"]["method"] == "GET"
323+
assert httpx_span.stack
324+
assert isinstance(httpx_span.stack, list)
325+
assert len(httpx_span.stack) > 1
326+
327+
assert "X-Capture-This" in httpx_span.data["http"]["header"]
328+
assert httpx_span.data["http"]["header"]["X-Capture-This"] == "Ok"
329+
assert "X-Capture-That" in httpx_span.data["http"]["header"]
330+
assert httpx_span.data["http"]["header"]["X-Capture-That"] == "Ok too"
331+
332+
agent.options.extra_http_headers = original_extra_http_headers
333+
334+
def test_request_header_capture(self):
335+
original_extra_http_headers = agent.options.extra_http_headers
336+
agent.options.extra_http_headers = ["X-Capture-This-Too", "X-Capture-That-Too"]
337+
338+
request_headers = {
339+
"X-Capture-This-Too": "this too",
340+
"X-Capture-That-Too": "that too",
341+
}
342+
with tracer.start_as_current_span("test"):
343+
res = httpx.get(testenv["flask_server"] + "/", headers=request_headers)
344+
345+
spans = self.recorder.queued_spans()
346+
assert len(spans) == 3
347+
348+
wsgi_span = spans[0]
349+
httpx_span = spans[1]
350+
test_span = spans[2]
351+
352+
assert res
353+
assert res.status_code == 200
354+
355+
# Same traceId
356+
assert test_span.t == httpx_span.t
357+
assert httpx_span.t == wsgi_span.t
358+
359+
# Parent relationships
360+
assert httpx_span.p == test_span.s
361+
assert wsgi_span.p == httpx_span.s
362+
363+
# Error logging
364+
assert not test_span.ec
365+
assert not httpx_span.ec
366+
assert not wsgi_span.ec
367+
368+
# span names
369+
assert wsgi_span.n == "wsgi"
370+
assert test_span.data["sdk"]["name"] == "test"
371+
assert httpx_span.n == "http"
372+
373+
# httpx
374+
assert httpx_span.data["http"]["status"] == 200
375+
assert httpx_span.data["http"]["host"] == self.host
376+
assert httpx_span.data["http"]["path"] == "/"
377+
assert httpx_span.data["http"]["url"] == testenv["flask_server"] + "/"
378+
assert httpx_span.data["http"]["method"] == "GET"
379+
assert httpx_span.stack
380+
assert isinstance(httpx_span.stack, list)
381+
assert len(httpx_span.stack) > 1
382+
383+
assert "X-Capture-This-Too" in httpx_span.data["http"]["header"]
384+
assert httpx_span.data["http"]["header"]["X-Capture-This-Too"] == "this too"
385+
assert "X-Capture-That-Too" in httpx_span.data["http"]["header"]
386+
assert httpx_span.data["http"]["header"]["X-Capture-That-Too"] == "that too"
387+
388+
agent.options.extra_http_headers = original_extra_http_headers

0 commit comments

Comments
 (0)