@@ -933,32 +933,100 @@ def test_close(self):
933933 assert channel .close () is None
934934
935935
936- @pytest .mark .parametrize ("falsy_interceptors " , [None , [], ()])
937- def test_apply_interceptors_passthrough ( falsy_interceptors ):
938- """Verify that falsy or empty interceptor sequences return the channel unmodified."""
936+ @pytest .mark .parametrize ("falsy_wrappers " , [None , [], ()])
937+ def test_apply_channel_wrappers_passthrough ( falsy_wrappers ):
938+ """Verify that falsy or empty wrapper sequences return the channel unmodified."""
939939 mock_base_channel = mock .Mock (name = "base_channel" )
940- result = grpc_helpers .apply_interceptors (mock_base_channel , falsy_interceptors )
940+ result = grpc_helpers .apply_channel_wrappers (mock_base_channel , falsy_wrappers )
941941 assert result is mock_base_channel
942942
943943
944- @pytest .mark .parametrize ("count" , [1 , 2 , 3 ])
945- def test_apply_interceptors_wrapping (count ):
946- """Verify that interceptors are passed to grpc.intercept_channel unpacked in a single call.
944+ def test_apply_channel_wrappers_grpc_client_interceptors ():
945+ """Verify that standard gRPC ClientInterceptor instances are applied via grpc.intercept_channel."""
946+
947+ class DummyUnaryInterceptor (grpc .UnaryUnaryClientInterceptor ):
948+ def intercept_unary_unary (self , continuation , client_call_details , request ):
949+ return continuation (client_call_details , request )
950+
951+ class DummyStreamInterceptor (grpc .StreamStreamClientInterceptor ):
952+ def intercept_stream_stream (
953+ self , continuation , client_call_details , request_iterator
954+ ):
955+ return continuation (client_call_details , request_iterator )
956+
957+ interceptor1 = DummyUnaryInterceptor ()
958+ interceptor2 = DummyStreamInterceptor ()
947959
948- When given a sequence of N interceptors [i_0, i_1, ..., i_{N-1}], apply_interceptors
949- must pass the base channel and all interceptors unpacked (*interceptors) to
950- grpc.intercept_channel.
951- """
952960 mock_base_channel = mock .Mock (name = "base_channel" )
953- mock_wrapped_channel = mock .Mock (name = "wrapped_channel " )
954- mock_interceptors = [ mock .Mock (name = f"interceptor_ { i } " ) for i in range ( count )]
961+ mock_chan_after_i2 = mock .Mock (name = "chan_after_i2 " )
962+ mock_chan_after_i1 = mock .Mock (name = "chan_after_i1" )
955963
956964 with mock .patch (
957- "grpc.intercept_channel" , return_value = mock_wrapped_channel
958- ) as mock_intercept_channel :
959- result = grpc_helpers .apply_interceptors (mock_base_channel , mock_interceptors )
965+ "grpc.intercept_channel" ,
966+ side_effect = [mock_chan_after_i2 , mock_chan_after_i1 ],
967+ ) as mock_intercept :
968+ result = grpc_helpers .apply_channel_wrappers (
969+ mock_base_channel , [interceptor1 , interceptor2 ]
970+ )
960971
961- assert result is mock_wrapped_channel
962- mock_intercept_channel .assert_called_once_with (
963- mock_base_channel , * mock_interceptors
972+ assert result is mock_chan_after_i1
973+ assert mock_intercept .call_count == 2
974+ # Executed in reverse order so interceptor1 is outermost
975+ mock_intercept .assert_has_calls (
976+ [
977+ mock .call (mock_base_channel , interceptor2 ),
978+ mock .call (mock_chan_after_i2 , interceptor1 ),
979+ ]
964980 )
981+
982+
983+ def test_apply_channel_wrappers_callables ():
984+ """Verify that channel-wrapping callables Callable[[Channel], Channel] are invoked in sequence."""
985+ mock_base_channel = mock .Mock (name = "base_channel" )
986+ mock_chan_1 = mock .Mock (name = "chan_1" )
987+ mock_chan_2 = mock .Mock (name = "chan_2" )
988+
989+ wrapper1 = mock .Mock (side_effect = lambda ch : mock_chan_2 )
990+ wrapper2 = mock .Mock (side_effect = lambda ch : mock_chan_1 )
991+
992+ result = grpc_helpers .apply_channel_wrappers (
993+ mock_base_channel , [wrapper1 , wrapper2 ]
994+ )
995+
996+ assert result is mock_chan_2
997+ # Executed in reverse order: wrapper2 runs first on base channel, then wrapper1
998+ wrapper2 .assert_called_once_with (mock_base_channel )
999+ wrapper1 .assert_called_once_with (mock_chan_1 )
1000+
1001+
1002+ def test_apply_channel_wrappers_interspersed ():
1003+ """Verify that a mixed sequence of gRPC interceptors and channel wrapper callables are applied."""
1004+
1005+ class DummyUnaryInterceptor (grpc .UnaryUnaryClientInterceptor ):
1006+ def intercept_unary_unary (self , continuation , client_call_details , request ):
1007+ return continuation (client_call_details , request )
1008+
1009+ interceptor = DummyUnaryInterceptor ()
1010+ mock_base_channel = mock .Mock (name = "base_channel" )
1011+ mock_chan_after_wrapper = mock .Mock (name = "chan_after_wrapper" )
1012+ mock_chan_after_interceptor = mock .Mock (name = "chan_after_interceptor" )
1013+
1014+ wrapper = mock .Mock (return_value = mock_chan_after_wrapper )
1015+
1016+ with mock .patch (
1017+ "grpc.intercept_channel" , return_value = mock_chan_after_interceptor
1018+ ) as mock_intercept :
1019+ result = grpc_helpers .apply_channel_wrappers (
1020+ mock_base_channel , [interceptor , wrapper ]
1021+ )
1022+
1023+ assert result is mock_chan_after_interceptor
1024+ wrapper .assert_called_once_with (mock_base_channel )
1025+ mock_intercept .assert_called_once_with (mock_chan_after_wrapper , interceptor )
1026+
1027+
1028+ def test_apply_channel_wrappers_invalid_type_raises ():
1029+ """Verify that passing an invalid object that is neither an interceptor nor callable raises TypeError."""
1030+ mock_base_channel = mock .Mock (name = "base_channel" )
1031+ with pytest .raises (TypeError , match = "Expected ChannelWrapper" ):
1032+ grpc_helpers .apply_channel_wrappers (mock_base_channel , [12345 ])
0 commit comments