Skip to content

Commit 98b4f27

Browse files
committed
test(api-core): achieve 100% coverage for observability package
Added tests for __init__.py import failures and expanded metadata parsing tests in test_tracing.py to cover all branches and error handling.
1 parent 91e5ce2 commit 98b4f27

2 files changed

Lines changed: 99 additions & 14 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for google.api_core.observability.__init__."""
16+
17+
import importlib
18+
import sys
19+
20+
21+
def test_init_exports():
22+
import google.api_core.observability
23+
24+
# Check if dependencies are available
25+
try:
26+
import grpc # noqa: F401
27+
import opentelemetry.trace # noqa: F401
28+
29+
has_deps = True
30+
except ImportError:
31+
has_deps = False
32+
33+
if has_deps:
34+
assert "OtelUnaryClientInterceptor" in google.api_core.observability.__all__
35+
else:
36+
assert google.api_core.observability.__all__ == []
37+
38+
39+
def test_init_import_error_forced(monkeypatch):
40+
"""Verifies behavior when tracing module fails to import, even if deps are present."""
41+
import google.api_core.observability
42+
43+
# Poison the tracing module
44+
monkeypatch.setitem(sys.modules, "google.api_core.observability.tracing", None)
45+
46+
# Reload observability, it should fail to import tracing and trigger except block
47+
importlib.reload(google.api_core.observability)
48+
49+
assert google.api_core.observability.__all__ == []
50+
51+
# Clean up
52+
monkeypatch.undo()
53+
importlib.reload(google.api_core.observability)

packages/google-api-core/tests/unit/observability/test_tracing.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,41 @@ def test_interceptor_non_recording_span(mock_tracer, monkeypatch):
157157
mock_span_obj.set_attribute.assert_not_called()
158158

159159

160-
def test_interceptor_extracts_destination_id(mock_tracer, monkeypatch):
161-
"""F1.7 (Partial): Verifies that the interceptor extracts gcp.resource.destination.id from metadata."""
160+
@pytest.mark.parametrize(
161+
"metadata,expected_destination_id",
162+
[
163+
# Case A: Success with 'name'
164+
(
165+
[
166+
(
167+
"x-goog-request-params",
168+
"name=projects/my-project/secrets/my-secret/versions/1&other=val",
169+
)
170+
],
171+
"projects/my-project/secrets/my-secret/versions/1",
172+
),
173+
# Case B: Success with 'parent'
174+
(
175+
[
176+
(
177+
"x-goog-request-params",
178+
"parent=projects/my-project/locations/us-central1&other=val",
179+
)
180+
],
181+
"projects/my-project/locations/us-central1",
182+
),
183+
# Case C: Other metadata keys (Loop continues, no destination id)
184+
([("some-other-header", "value")], None),
185+
# Case D: x-goog-request-params exists but no name/parent
186+
([("x-goog-request-params", "other=val")], None),
187+
# Case E: Malformed x-goog-request-params (Exception caught, fails open)
188+
([("x-goog-request-params", "name=foo=bar")], None),
189+
],
190+
)
191+
def test_interceptor_metadata_parsing(
192+
mock_tracer, monkeypatch, metadata, expected_destination_id
193+
):
194+
"""F1.7 (Partial): Verifies metadata parsing scenarios, including success, missing keys, and malformed data."""
162195
from google.api_core.observability.tracing import OtelUnaryClientInterceptor
163196

164197
monkeypatch.setenv("GOOGLE_CLOUD_PYTHON_TRACING_ENABLED", "true")
@@ -170,19 +203,18 @@ def test_interceptor_extracts_destination_id(mock_tracer, monkeypatch):
170203

171204
continuation = Mock(return_value="response")
172205
details = MockClientCallDetails()
173-
# Simulate standard routing metadata
174-
details.metadata = [
175-
(
176-
"x-goog-request-params",
177-
"name=projects/my-project/secrets/my-secret/versions/1&other=val",
178-
)
179-
]
206+
details.metadata = metadata
180207
request = "request_payload"
181208

182209
interceptor.intercept_unary_unary(continuation, details, request)
183210

184-
# Verify attribute was extracted and set
185-
mock_span_obj.set_attribute.assert_any_call(
186-
"gcp.resource.destination.id",
187-
"projects/my-project/secrets/my-secret/versions/1",
188-
)
211+
if expected_destination_id:
212+
mock_span_obj.set_attribute.assert_any_call(
213+
"gcp.resource.destination.id", expected_destination_id
214+
)
215+
else:
216+
# Verify gcp.resource.destination.id was NOT called
217+
called_keys = [
218+
call[0][0] for call in mock_span_obj.set_attribute.call_args_list
219+
]
220+
assert "gcp.resource.destination.id" not in called_keys

0 commit comments

Comments
 (0)