Skip to content

Commit 7542410

Browse files
authored
tests: add showcase tests for reading grpc/rest response metadata (#2300)
1 parent c8b7229 commit 7542410

File tree

3 files changed

+280
-15
lines changed

3 files changed

+280
-15
lines changed

tests/system/conftest.py

Lines changed: 165 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import collections
15+
1616
import grpc
1717
from unittest import mock
1818
import os
1919
import pytest
2020

21+
from typing import Sequence, Tuple
22+
2123
from google.api_core.client_options import ClientOptions # type: ignore
24+
from google.showcase_v1beta1.services.echo.transports import EchoRestInterceptor
2225

2326
try:
2427
from google.auth.aio import credentials as ga_credentials_async
@@ -42,6 +45,7 @@
4245
try:
4346
from google.showcase_v1beta1.services.echo.transports import (
4447
AsyncEchoRestTransport,
48+
AsyncEchoRestInterceptor,
4549
)
4650

4751
HAS_ASYNC_REST_ECHO_TRANSPORT = True
@@ -248,7 +252,51 @@ def messaging(use_mtls, request):
248252
return construct_client(MessagingClient, use_mtls, transport_name=request.param)
249253

250254

251-
class MetadataClientInterceptor(
255+
class EchoMetadataClientRestInterceptor(EchoRestInterceptor):
256+
request_metadata: Sequence[Tuple[str, str]] = []
257+
response_metadata: Sequence[Tuple[str, str]] = []
258+
259+
def pre_echo(self, request, metadata):
260+
self.request_metadata = metadata
261+
return request, metadata
262+
263+
def post_echo_with_metadata(self, request, metadata):
264+
self.response_metadata = metadata
265+
return request, metadata
266+
267+
def pre_expand(self, request, metadata):
268+
self.request_metadata = metadata
269+
return request, metadata
270+
271+
def post_expand_with_metadata(self, request, metadata):
272+
self.response_metadata = metadata
273+
return request, metadata
274+
275+
276+
if HAS_ASYNC_REST_ECHO_TRANSPORT:
277+
278+
class EchoMetadataClientRestAsyncInterceptor(AsyncEchoRestInterceptor):
279+
request_metadata: Sequence[Tuple[str, str]] = []
280+
response_metadata: Sequence[Tuple[str, str]] = []
281+
282+
async def pre_echo(self, request, metadata):
283+
self.request_metadata = metadata
284+
return request, metadata
285+
286+
async def post_echo_with_metadata(self, request, metadata):
287+
self.response_metadata = metadata
288+
return request, metadata
289+
290+
async def pre_expand(self, request, metadata):
291+
self.request_metadata = metadata
292+
return request, metadata
293+
294+
async def post_expand_with_metadata(self, request, metadata):
295+
self.response_metadata = metadata
296+
return request, metadata
297+
298+
299+
class EchoMetadataClientGrpcInterceptor(
252300
grpc.UnaryUnaryClientInterceptor,
253301
grpc.UnaryStreamClientInterceptor,
254302
grpc.StreamUnaryClientInterceptor,
@@ -257,42 +305,94 @@ class MetadataClientInterceptor(
257305
def __init__(self, key, value):
258306
self._key = key
259307
self._value = value
308+
self.request_metadata = []
309+
self.response_metadata = []
260310

261-
def _add_metadata(self, client_call_details):
311+
def _add_request_metadata(self, client_call_details):
262312
if client_call_details.metadata is not None:
263313
client_call_details.metadata.append((self._key, self._value))
314+
self.request_metadata = client_call_details.metadata
264315

265316
def intercept_unary_unary(self, continuation, client_call_details, request):
266-
self._add_metadata(client_call_details)
317+
self._add_request_metadata(client_call_details)
267318
response = continuation(client_call_details, request)
319+
metadata = [(k, str(v)) for k, v in response.trailing_metadata()]
320+
self.response_metadata = metadata
268321
return response
269322

270323
def intercept_unary_stream(self, continuation, client_call_details, request):
271-
self._add_metadata(client_call_details)
324+
self._add_request_metadata(client_call_details)
272325
response_it = continuation(client_call_details, request)
273326
return response_it
274327

275328
def intercept_stream_unary(
276329
self, continuation, client_call_details, request_iterator
277330
):
278-
self._add_metadata(client_call_details)
331+
self._add_request_metadata(client_call_details)
279332
response = continuation(client_call_details, request_iterator)
280333
return response
281334

282335
def intercept_stream_stream(
283336
self, continuation, client_call_details, request_iterator
284337
):
285-
self._add_metadata(client_call_details)
338+
self._add_request_metadata(client_call_details)
339+
response_it = continuation(client_call_details, request_iterator)
340+
return response_it
341+
342+
343+
class EchoMetadataClientGrpcAsyncInterceptor(
344+
grpc.aio.UnaryUnaryClientInterceptor,
345+
grpc.aio.UnaryStreamClientInterceptor,
346+
grpc.aio.StreamUnaryClientInterceptor,
347+
grpc.aio.StreamStreamClientInterceptor,
348+
):
349+
def __init__(self, key, value):
350+
self._key = key
351+
self._value = value
352+
self.request_metadata = []
353+
self.response_metadata = []
354+
355+
async def _add_request_metadata(self, client_call_details):
356+
if client_call_details.metadata is not None:
357+
client_call_details.metadata.append((self._key, self._value))
358+
self.request_metadata = client_call_details.metadata
359+
360+
async def intercept_unary_unary(self, continuation, client_call_details, request):
361+
await self._add_request_metadata(client_call_details)
362+
response = await continuation(client_call_details, request)
363+
metadata = [(k, str(v)) for k, v in await response.trailing_metadata()]
364+
self.response_metadata = metadata
365+
return response
366+
367+
async def intercept_unary_stream(self, continuation, client_call_details, request):
368+
self._add_request_metadata(client_call_details)
369+
response_it = continuation(client_call_details, request)
370+
return response_it
371+
372+
async def intercept_stream_unary(
373+
self, continuation, client_call_details, request_iterator
374+
):
375+
self._add_request_metadata(client_call_details)
376+
response = continuation(client_call_details, request_iterator)
377+
return response
378+
379+
async def intercept_stream_stream(
380+
self, continuation, client_call_details, request_iterator
381+
):
382+
self._add_request_metadata(client_call_details)
286383
response_it = continuation(client_call_details, request_iterator)
287384
return response_it
288385

289386

290387
@pytest.fixture
291-
def intercepted_echo(use_mtls):
388+
def intercepted_echo_grpc(use_mtls):
292389
# The interceptor adds 'showcase-trailer' client metadata. Showcase server
293-
# echos any metadata with key 'showcase-trailer', so the same metadata
390+
# echoes any metadata with key 'showcase-trailer', so the same metadata
294391
# should appear as trailing metadata in the response.
295-
interceptor = MetadataClientInterceptor("showcase-trailer", "intercepted")
392+
interceptor = EchoMetadataClientGrpcInterceptor(
393+
"showcase-trailer",
394+
"intercepted",
395+
)
296396
host = "localhost:7469"
297397
channel = (
298398
grpc.secure_channel(host, ssl_credentials)
@@ -304,4 +404,58 @@ def intercepted_echo(use_mtls):
304404
credentials=ga_credentials.AnonymousCredentials(),
305405
channel=intercept_channel,
306406
)
307-
return EchoClient(transport=transport)
407+
return EchoClient(transport=transport), interceptor
408+
409+
410+
@pytest.fixture
411+
def intercepted_echo_grpc_async():
412+
# The interceptor adds 'showcase-trailer' client metadata. Showcase server
413+
# echoes any metadata with key 'showcase-trailer', so the same metadata
414+
# should appear as trailing metadata in the response.
415+
interceptor = EchoMetadataClientGrpcAsyncInterceptor(
416+
"showcase-trailer",
417+
"intercepted",
418+
)
419+
host = "localhost:7469"
420+
channel = grpc.aio.insecure_channel(host, interceptors=[interceptor])
421+
# intercept_channel = grpc.aio.intercept_channel(channel, interceptor)
422+
transport = EchoAsyncClient.get_transport_class("grpc_asyncio")(
423+
credentials=ga_credentials.AnonymousCredentials(),
424+
channel=channel,
425+
)
426+
return EchoAsyncClient(transport=transport), interceptor
427+
428+
429+
@pytest.fixture
430+
def intercepted_echo_rest():
431+
transport_name = "rest"
432+
transport_cls = EchoClient.get_transport_class(transport_name)
433+
interceptor = EchoMetadataClientRestInterceptor()
434+
435+
# The custom host explicitly bypasses https.
436+
transport = transport_cls(
437+
credentials=ga_credentials.AnonymousCredentials(),
438+
host="localhost:7469",
439+
url_scheme="http",
440+
interceptor=interceptor,
441+
)
442+
return EchoClient(transport=transport), interceptor
443+
444+
445+
@pytest.fixture
446+
def intercepted_echo_rest_async():
447+
if not HAS_ASYNC_REST_ECHO_TRANSPORT:
448+
pytest.skip("Skipping test with async rest.")
449+
450+
transport_name = "rest_asyncio"
451+
transport_cls = EchoAsyncClient.get_transport_class(transport_name)
452+
interceptor = EchoMetadataClientRestAsyncInterceptor()
453+
454+
# The custom host explicitly bypasses https.
455+
transport = transport_cls(
456+
credentials=async_anonymous_credentials(),
457+
host="localhost:7469",
458+
url_scheme="http",
459+
interceptor=interceptor,
460+
)
461+
return EchoAsyncClient(transport=transport), interceptor

tests/system/test_grpc_interceptor_streams.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
intercepted_metadata = (("showcase-trailer", "intercepted"),)
2222

2323

24-
def test_unary_stream(intercepted_echo):
24+
def test_unary_stream(intercepted_echo_grpc):
25+
client, interceptor = intercepted_echo_grpc
2526
content = "The hail in Wales falls mainly on the snails."
26-
responses = intercepted_echo.expand(
27+
responses = client.expand(
2728
{
2829
"content": content,
2930
}
@@ -37,13 +38,15 @@ def test_unary_stream(intercepted_echo):
3738
(metadata.key, metadata.value) for metadata in responses.trailing_metadata()
3839
]
3940
assert intercepted_metadata[0] in response_metadata
41+
interceptor.response_metadata = response_metadata
4042

4143

42-
def test_stream_stream(intercepted_echo):
44+
def test_stream_stream(intercepted_echo_grpc):
45+
client, interceptor = intercepted_echo_grpc
4346
requests = []
4447
requests.append(showcase.EchoRequest(content="hello"))
4548
requests.append(showcase.EchoRequest(content="world!"))
46-
responses = intercepted_echo.chat(iter(requests))
49+
responses = client.chat(iter(requests))
4750

4851
contents = [response.content for response in responses]
4952
assert contents == ["hello", "world!"]
@@ -52,3 +55,4 @@ def test_stream_stream(intercepted_echo):
5255
(metadata.key, metadata.value) for metadata in responses.trailing_metadata()
5356
]
5457
assert intercepted_metadata[0] in response_metadata
58+
interceptor.response_metadata = response_metadata
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2025 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+
# https://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+
import os
16+
import pytest
17+
18+
from google import showcase
19+
20+
21+
@pytest.mark.parametrize(
22+
"transport,response_metadata",
23+
[
24+
("grpc", ("something1", "something_value1")),
25+
("rest", ("X-Showcase-Request-Something1", "something_value1")),
26+
],
27+
)
28+
def test_metadata_response_unary(
29+
intercepted_echo_rest, intercepted_echo_grpc, transport, response_metadata
30+
):
31+
request_content = "The hail in Wales falls mainly on the snails."
32+
request_metadata = ("something1", "something_value1")
33+
if transport == "grpc":
34+
client, interceptor = intercepted_echo_grpc
35+
else:
36+
client, interceptor = intercepted_echo_rest
37+
response = client.echo(
38+
request=showcase.EchoRequest(content=request_content),
39+
metadata=(request_metadata,),
40+
)
41+
assert response.content == request_content
42+
assert request_metadata in interceptor.request_metadata
43+
assert response_metadata in interceptor.response_metadata
44+
45+
46+
def test_metadata_response_rest_streams(intercepted_echo_rest):
47+
request_content = "The hail in Wales falls mainly on the snails."
48+
request_metadata = ("something2", "something_value2")
49+
response_metadata = ("X-Showcase-Request-Something2", "something_value2")
50+
client, interceptor = intercepted_echo_rest
51+
client.expand(
52+
{
53+
"content": request_content,
54+
},
55+
metadata=(request_metadata,),
56+
)
57+
58+
assert request_metadata in interceptor.request_metadata
59+
assert response_metadata in interceptor.response_metadata
60+
61+
62+
if os.environ.get("GAPIC_PYTHON_ASYNC", "true") == "true":
63+
64+
@pytest.mark.asyncio
65+
async def test_metadata_response_rest_streams_async(intercepted_echo_rest_async):
66+
request_content = "The hail in Wales falls mainly on the snails."
67+
request_metadata = ("something2", "something_value2")
68+
response_metadata = ("X-Showcase-Request-Something2", "something_value2")
69+
client, interceptor = intercepted_echo_rest_async
70+
await client.expand(
71+
{
72+
"content": request_content,
73+
},
74+
metadata=(request_metadata,),
75+
)
76+
77+
assert request_metadata in interceptor.request_metadata
78+
assert response_metadata in interceptor.response_metadata
79+
80+
@pytest.mark.parametrize(
81+
"transport,response_metadata",
82+
[
83+
("grpc_asyncio", ("something3", "something_value3")),
84+
("rest_asyncio", ("X-Showcase-Request-Something3", "something_value3")),
85+
],
86+
)
87+
@pytest.mark.asyncio
88+
async def test_metadata_response_unary_async(
89+
intercepted_echo_grpc_async,
90+
intercepted_echo_rest_async,
91+
transport,
92+
response_metadata,
93+
):
94+
request_content = "The hail in Wales falls mainly on the snails."
95+
request_metadata = ("something3", "something_value3")
96+
97+
if transport == "grpc_asyncio":
98+
client, interceptor = intercepted_echo_grpc_async
99+
else:
100+
client, interceptor = intercepted_echo_rest_async
101+
response = await client.echo(
102+
request=showcase.EchoRequest(content=request_content),
103+
metadata=(request_metadata,),
104+
)
105+
assert response.content == request_content
106+
assert request_metadata in interceptor.request_metadata
107+
assert response_metadata in interceptor.response_metadata

0 commit comments

Comments
 (0)