@@ -58,6 +58,13 @@ def setup_optin(mock_os_environ):
5858 importlib .reload (_opentelemetry_tracing )
5959
6060
61+ @pytest .fixture ()
62+ def setup_optout (mock_os_environ ):
63+ """Mock envar to opt-in tracing for storage client."""
64+ mock_os_environ ["ENABLE_GCS_PYTHON_CLIENT_OTEL_TRACES" ] = "False"
65+ importlib .reload (_opentelemetry_tracing )
66+
67+
6168def test_opentelemetry_not_installed (setup , monkeypatch ):
6269 monkeypatch .setitem (sys .modules , "opentelemetry" , None )
6370 importlib .reload (_opentelemetry_tracing )
@@ -83,6 +90,13 @@ def test_enable_trace_yield_span(setup, setup_optin):
8390 assert span is not None
8491
8592
93+ def test_disable_traces (setup , setup_optout ):
94+ assert _opentelemetry_tracing .HAS_OPENTELEMETRY
95+ assert not _opentelemetry_tracing .enable_otel_traces
96+ with _opentelemetry_tracing .create_trace_span ("No-ops for opentelemetry" ) as span :
97+ assert span is None
98+
99+
86100def test_enable_trace_call (setup , setup_optin ):
87101 from opentelemetry import trace as trace_api
88102
@@ -136,7 +150,7 @@ def test_get_final_attributes(setup, setup_optin):
136150 }
137151 api_request = {
138152 "method" : "GET" ,
139- "path" : "/foo/bar/baz" ,
153+ "path" : "/foo/bar/baz?sensitive=true " ,
140154 "timeout" : (100 , 100 ),
141155 }
142156 retry_obj = api_retry .Retry ()
@@ -147,15 +161,19 @@ def test_get_final_attributes(setup, setup_optin):
147161 "rpc.system" : "http" ,
148162 "user_agent.original" : f"gcloud-python/{ __version__ } " ,
149163 "http.request.method" : "GET" ,
150- "url.full" : "https://testOtel.org/foo/bar/baz" ,
151- "connect_timeout,read_timeout" : (100 , 100 ),
164+ "server.address" : "testOtel.org" ,
165+ "url.path" : "/foo/bar/baz" ,
166+ "url.scheme" : "https" ,
167+ "connect_timeout,read_timeout" : str ((100 , 100 )),
152168 "retry" : f"multiplier{ retry_obj ._multiplier } /deadline{ retry_obj ._deadline } /max{ retry_obj ._maximum } /initial{ retry_obj ._initial } /predicate{ retry_obj ._predicate } " ,
153169 }
154170 expected_attributes .update (_opentelemetry_tracing ._cloud_trace_adoption_attrs )
155171
156172 with mock .patch ("google.cloud.storage.client.Client" ) as test_client :
157173 test_client .project = "test_project"
158- test_client ._connection .API_BASE_URL = "https://testOtel.org"
174+ test_client ._connection .build_api_url .return_value = (
175+ "https://testOtel.org/foo/bar/baz?sensitive=true"
176+ )
159177 with _opentelemetry_tracing .create_trace_span (
160178 test_span_name ,
161179 attributes = test_span_attributes ,
@@ -165,6 +183,7 @@ def test_get_final_attributes(setup, setup_optin):
165183 ) as span :
166184 assert span is not None
167185 assert span .name == test_span_name
186+ assert "url.query" not in span .attributes
168187 assert span .attributes == expected_attributes
169188
170189
@@ -196,23 +215,108 @@ def test_set_conditional_retry_attr(setup, setup_optin):
196215 assert span .attributes == expected_attributes
197216
198217
199- def test_set_api_request_attr ():
200- from google .cloud .storage import Client
218+ def test__get_opentelemetry_attributes_from_url ():
219+ url = "https://example.com:8080/path?query=true"
220+ expected = {
221+ "server.address" : "example.com" ,
222+ "server.port" : 8080 ,
223+ "url.scheme" : "https" ,
224+ "url.path" : "/path" ,
225+ }
226+ # Test stripping query
227+ attrs = _opentelemetry_tracing ._get_opentelemetry_attributes_from_url (
228+ url , strip_query = True
229+ )
230+ assert attrs == expected
231+ assert "url.query" not in attrs
232+
233+ # Test not stripping query
234+ expected ["url.query" ] = "query=true"
235+ attrs = _opentelemetry_tracing ._get_opentelemetry_attributes_from_url (
236+ url , strip_query = False
237+ )
238+ assert attrs == expected
201239
202- test_client = Client ()
203- args_method = {"method" : "GET" }
204- expected_attributes = {"http.request.method" : "GET" }
205- attr = _opentelemetry_tracing ._set_api_request_attr (args_method , test_client )
206- assert attr == expected_attributes
207240
208- args_path = {"path" : "/foo/bar/baz" }
209- expected_attributes = {"url.full" : "https://storage.googleapis.com/foo/bar/baz" }
210- attr = _opentelemetry_tracing ._set_api_request_attr (args_path , test_client )
211- assert attr == expected_attributes
241+ def test__get_opentelemetry_attributes_from_url_with_query ():
242+ url = "https://example.com/path?query=true&another=false"
243+ expected = {
244+ "server.address" : "example.com" ,
245+ "server.port" : None ,
246+ "url.scheme" : "https" ,
247+ "url.path" : "/path" ,
248+ "url.query" : "query=true&another=false" ,
249+ }
250+ # Test not stripping query
251+ attrs = _opentelemetry_tracing ._get_opentelemetry_attributes_from_url (
252+ url , strip_query = False
253+ )
254+ assert attrs == expected
212255
213- args_timeout = {"timeout" : (100 , 100 )}
256+
257+ def test_set_api_request_attr_with_pii_in_query ():
258+ client = mock .Mock ()
259+ client ._connection .build_api_url .return_value = (
260+ "https://example.com/path?sensitive=true&token=secret"
261+ )
262+
263+ request = {
264+ "method" : "GET" ,
265+ "path" : "/path?sensitive=true&token=secret" ,
266+ "timeout" : 60 ,
267+ }
214268 expected_attributes = {
215- "connect_timeout,read_timeout" : (100 , 100 ),
269+ "http.request.method" : "GET" ,
270+ "server.address" : "example.com" ,
271+ "server.port" : None ,
272+ "url.scheme" : "https" ,
273+ "url.path" : "/path" ,
274+ "connect_timeout,read_timeout" : "60" ,
216275 }
217- attr = _opentelemetry_tracing ._set_api_request_attr (args_timeout , test_client )
276+ attr = _opentelemetry_tracing ._set_api_request_attr (request , client )
218277 assert attr == expected_attributes
278+ assert "url.query" not in attr # Ensure query with PII is not captured
279+
280+
281+ def test_set_api_request_attr_no_timeout ():
282+ client = mock .Mock ()
283+ client ._connection .build_api_url .return_value = "https://example.com/path"
284+
285+ request = {"method" : "GET" , "path" : "/path" }
286+ attr = _opentelemetry_tracing ._set_api_request_attr (request , client )
287+ assert "connect_timeout,read_timeout" not in attr
288+
289+
290+ @pytest .mark .parametrize (
291+ "env_value, default, expected" ,
292+ [
293+ # Test default values when env var is not set
294+ (None , False , False ),
295+ (None , True , True ),
296+ # Test truthy values
297+ ("1" , False , True ),
298+ ("true" , False , True ),
299+ ("yes" , False , True ),
300+ ("on" , False , True ),
301+ ("TRUE" , False , True ),
302+ (" Yes " , False , True ),
303+ # Test falsy values
304+ ("0" , False , False ),
305+ ("false" , False , False ),
306+ ("no" , False , False ),
307+ ("off" , False , False ),
308+ ("any_other_string" , False , False ),
309+ ("" , False , False ),
310+ # Test with default=True and falsy values
311+ ("false" , True , False ),
312+ ("0" , True , False ),
313+ ],
314+ )
315+ def test__parse_bool_env (monkeypatch , env_value , default , expected ):
316+ env_var_name = "TEST_ENV_VAR"
317+ monkeypatch .setenv (
318+ env_var_name , str (env_value )
319+ ) if env_value is not None else monkeypatch .delenv (env_var_name , raising = False )
320+
321+ result = _opentelemetry_tracing ._parse_bool_env (env_var_name , default )
322+ assert result is expected
0 commit comments