1818import os
1919import pytest
2020import pytest_asyncio
21+ from requests .adapters import HTTPAdapter
2122
2223from typing import Sequence , Tuple
2324
@@ -113,15 +114,18 @@ def async_identity(use_mtls, request, event_loop):
113114 )
114115
115116
116- dir = os .path .dirname (__file__ )
117- with open (os .path .join (dir , "../cert/mtls.crt" ), "rb" ) as fh :
117+ base_dir = os .path .dirname (__file__ )
118+ CERT_PATH = os .path .join (base_dir , "../cert/mtls.crt" )
119+ KEY_PATH = os .path .join (base_dir , "../cert/mtls.key" )
120+ with open (CERT_PATH , "rb" ) as fh :
118121 cert = fh .read ()
119- with open (os . path . join ( dir , "../cert/mtls.key" ) , "rb" ) as fh :
122+ with open (KEY_PATH , "rb" ) as fh :
120123 key = fh .read ()
121124
122125ssl_credentials = grpc .ssl_channel_credentials (
123126 root_certificates = cert , certificate_chain = cert , private_key = key
124127)
128+ tls_credentials = grpc .ssl_channel_credentials (root_certificates = cert )
125129
126130
127131def callback ():
@@ -136,6 +140,9 @@ def pytest_addoption(parser):
136140 parser .addoption (
137141 "--mtls" , action = "store_true" , help = "Run system test with mutual TLS channel"
138142 )
143+ parser .addoption (
144+ "--tls" , action = "store_true" , help = "Run system test with standard one-way TLS channel"
145+ )
139146
140147
141148# TODO: Need to test without passing in a transport class
@@ -189,6 +196,11 @@ def use_mtls(request):
189196 return request .config .getoption ("--mtls" )
190197
191198
199+ @pytest .fixture
200+ def use_tls (request ):
201+ return request .config .getoption ("--tls" ) or request .config .getoption ("--mtls" )
202+
203+
192204@pytest .fixture
193205def parametrized_echo (
194206 use_mtls ,
@@ -328,7 +340,7 @@ def _read_response_metadata_stream(self):
328340 def intercept_unary_unary (self , continuation , client_call_details , request ):
329341 self ._add_request_metadata (client_call_details )
330342 response = continuation (client_call_details , request )
331- metadata = [(k , str (v )) for k , v in response .trailing_metadata ()]
343+ metadata = [(k , str (v )) for k , v in response .initial_metadata ()] + [( k , str ( v )) for k , v in response . trailing_metadata ()]
332344 self .response_metadata = metadata
333345 return response
334346
@@ -387,7 +399,7 @@ async def _add_request_metadata(self, client_call_details):
387399 async def intercept_unary_unary (self , continuation , client_call_details , request ):
388400 await self ._add_request_metadata (client_call_details )
389401 response = await continuation (client_call_details , request )
390- metadata = [(k , str (v )) for k , v in await response .trailing_metadata ()]
402+ metadata = [(k , str (v )) for k , v in await response .initial_metadata ()] + [( k , str ( v )) for k , v in await response . trailing_metadata ()]
391403 self .response_metadata = metadata
392404 return response
393405
@@ -412,7 +424,7 @@ async def intercept_stream_stream(
412424
413425
414426@pytest .fixture
415- def intercepted_echo_grpc (use_mtls ):
427+ def intercepted_echo_grpc (use_mtls , use_tls ):
416428 # The interceptor adds 'showcase-trailer' client metadata. Showcase server
417429 # echoes any metadata with key 'showcase-trailer', so the same metadata
418430 # should appear as trailing metadata in the response.
@@ -421,11 +433,12 @@ def intercepted_echo_grpc(use_mtls):
421433 "intercepted" ,
422434 )
423435 host = "localhost:7469"
424- channel = (
425- grpc .secure_channel (host , ssl_credentials )
426- if use_mtls
427- else grpc .insecure_channel (host )
428- )
436+ if use_mtls :
437+ channel = grpc .secure_channel (host , ssl_credentials )
438+ elif use_tls :
439+ channel = grpc .secure_channel (host , tls_credentials )
440+ else :
441+ channel = grpc .insecure_channel (host )
429442 intercept_channel = grpc .intercept_channel (channel , interceptor )
430443 transport = EchoClient .get_transport_class ("grpc" )(
431444 credentials = ga_credentials .AnonymousCredentials (),
@@ -435,7 +448,7 @@ def intercepted_echo_grpc(use_mtls):
435448
436449
437450@pytest_asyncio .fixture
438- async def intercepted_echo_grpc_async ():
451+ async def intercepted_echo_grpc_async (use_mtls , use_tls ):
439452 # The interceptor adds 'showcase-trailer' client metadata. Showcase server
440453 # echoes any metadata with key 'showcase-trailer', so the same metadata
441454 # should appear as trailing metadata in the response.
@@ -444,28 +457,45 @@ async def intercepted_echo_grpc_async():
444457 "intercepted" ,
445458 )
446459 host = "localhost:7469"
447- channel = grpc .aio .insecure_channel (host , interceptors = [interceptor ])
448- # intercept_channel = grpc.aio.intercept_channel(channel, interceptor)
460+ if use_mtls :
461+ channel = grpc .aio .secure_channel (host , ssl_credentials , interceptors = [interceptor ])
462+ elif use_tls :
463+ channel = grpc .aio .secure_channel (host , tls_credentials , interceptors = [interceptor ])
464+ else :
465+ channel = grpc .aio .insecure_channel (host , interceptors = [interceptor ])
449466 transport = EchoAsyncClient .get_transport_class ("grpc_asyncio" )(
450467 credentials = ga_credentials .AnonymousCredentials (),
451468 channel = channel ,
452469 )
453470 return EchoAsyncClient (transport = transport ), interceptor
454471
455472
473+ class HostNameIgnoringAdapter (HTTPAdapter ):
474+ """Custom HTTPAdapter that disables hostname verification for local self-signed certs."""
475+ def cert_verify (self , conn , url , verify , cert ):
476+ super ().cert_verify (conn , url , verify , cert )
477+ conn .assert_hostname = False
478+
479+
456480@pytest .fixture
457- def intercepted_echo_rest ():
481+ def intercepted_echo_rest (use_mtls , use_tls ):
458482 transport_name = "rest"
459483 transport_cls = EchoClient .get_transport_class (transport_name )
460484 interceptor = EchoMetadataClientRestInterceptor ()
461485
462- # The custom host explicitly bypasses https.
486+ url_scheme = "https" if ( use_mtls or use_tls ) else "http"
463487 transport = transport_cls (
464488 credentials = ga_credentials .AnonymousCredentials (),
465489 host = "localhost:7469" ,
466- url_scheme = "http" ,
490+ url_scheme = url_scheme ,
467491 interceptor = interceptor ,
468492 )
493+ if use_mtls or use_tls :
494+ transport ._session .verify = CERT_PATH
495+ transport ._session .mount ("https://" , HostNameIgnoringAdapter ())
496+ if use_mtls :
497+ transport ._session .cert = (CERT_PATH , KEY_PATH )
498+
469499 return EchoClient (transport = transport ), interceptor
470500
471501
@@ -478,11 +508,11 @@ def intercepted_echo_rest_async():
478508 transport_cls = EchoAsyncClient .get_transport_class (transport_name )
479509 interceptor = EchoMetadataClientRestAsyncInterceptor ()
480510
481- # The custom host explicitly bypasses https.
482511 transport = transport_cls (
483512 credentials = async_anonymous_credentials (),
484513 host = "localhost:7469" ,
485514 url_scheme = "http" ,
486515 interceptor = interceptor ,
487516 )
517+
488518 return EchoAsyncClient (transport = transport ), interceptor
0 commit comments