1616
1717package io .grpc .internal ;
1818
19+ import static com .google .common .truth .Truth .assertThat ;
1920import static org .junit .Assert .assertEquals ;
2021import static org .junit .Assert .assertFalse ;
2122import static org .junit .Assert .assertNotNull ;
2728import static org .mockito .Mockito .mock ;
2829import static org .mockito .Mockito .verify ;
2930
31+ import com .google .common .collect .Lists ;
3032import io .grpc .CallOptions ;
3133import io .grpc .ClientStreamTracer ;
3234import io .grpc .LoadBalancer .PickResult ;
3335import io .grpc .Metadata ;
3436import io .grpc .Status ;
37+ import io .grpc .Status .Code ;
3538import io .grpc .internal .ClientStreamListener .RpcProgress ;
3639import io .grpc .internal .GrpcUtil .Http2Error ;
3740import io .grpc .testing .TestMethodDescriptors ;
41+ import java .util .ArrayList ;
3842import org .junit .Rule ;
3943import org .junit .Test ;
4044import org .junit .rules .ExpectedException ;
4145import org .junit .runner .RunWith ;
4246import org .junit .runners .JUnit4 ;
47+ import org .mockito .ArgumentCaptor ;
48+ import org .mockito .Captor ;
49+ import org .mockito .junit .MockitoJUnit ;
50+ import org .mockito .junit .MockitoRule ;
4351
4452/** Unit tests for {@link GrpcUtil}. */
4553@ RunWith (JUnit4 .class )
@@ -51,6 +59,11 @@ public class GrpcUtilTest {
5159
5260 @ SuppressWarnings ("deprecation" ) // https://github.com/grpc/grpc-java/issues/7467
5361 @ Rule public final ExpectedException thrown = ExpectedException .none ();
62+ @ Rule public final MockitoRule mocks = MockitoJUnit .rule ();
63+
64+ @ Captor
65+ private ArgumentCaptor <Status > statusCaptor ;
66+
5467
5568 @ Test
5669 public void http2ErrorForCode () {
@@ -258,6 +271,69 @@ public void getTransportFromPickResult_errorPickResult_failFast() {
258271 verify (listener ).closed (eq (status ), eq (RpcProgress .PROCESSED ), any (Metadata .class ));
259272 }
260273
274+ /* Status codes that a control plane should not be returned get replaced by INTERNAL. */
275+ @ Test
276+ public void getTransportFromPickResult_errorPickResult_noInappropriateControlPlaneStatus () {
277+
278+ // These are NOT appropriate for a control plane to return.
279+ ArrayList <Status > inappropriateStatus = Lists .newArrayList (
280+ Status .INVALID_ARGUMENT .withDescription ("bad one" ).withCause (new RuntimeException ()),
281+ Status .NOT_FOUND .withDescription ("not here" ).withCause (new RuntimeException ()),
282+ Status .ALREADY_EXISTS .withDescription ("not again" ).withCause (new RuntimeException ()),
283+ Status .FAILED_PRECONDITION .withDescription ("naah" ).withCause (new RuntimeException ()),
284+ Status .ABORTED .withDescription ("nope" ).withCause (new RuntimeException ()),
285+ Status .OUT_OF_RANGE .withDescription ("outta range" ).withCause (new RuntimeException ()),
286+ Status .DATA_LOSS .withDescription ("lost" ).withCause (new RuntimeException ()));
287+
288+ for (Status status : inappropriateStatus ) {
289+ PickResult pickResult = PickResult .withError (status );
290+ ClientTransport transport = GrpcUtil .getTransportFromPickResult (pickResult , false );
291+
292+ ClientStream stream = transport .newStream (
293+ TestMethodDescriptors .voidMethod (), new Metadata (), CallOptions .DEFAULT ,
294+ tracers );
295+ ClientStreamListener listener = mock (ClientStreamListener .class );
296+ stream .start (listener );
297+
298+ verify (listener ).closed (statusCaptor .capture (), eq (RpcProgress .PROCESSED ),
299+ any (Metadata .class ));
300+ Status usedStatus = statusCaptor .getValue ();
301+ assertThat (usedStatus .getCode ()).isEqualTo (Code .INTERNAL );
302+ assertThat (usedStatus .getDescription ()).contains ("Inappropriate status" );
303+ assertThat (usedStatus .getCause ()).isInstanceOf (RuntimeException .class );
304+ }
305+ }
306+
307+ /* Status codes a control plane can return are not replaced. */
308+ @ Test
309+ public void getTransportFromPickResult_errorPickResult_appropriateControlPlaneStatus () {
310+
311+ // These ARE appropriate for a control plane to return.
312+ ArrayList <Status > inappropriateStatus = Lists .newArrayList (
313+ Status .CANCELLED ,
314+ Status .UNKNOWN ,
315+ Status .DEADLINE_EXCEEDED ,
316+ Status .PERMISSION_DENIED ,
317+ Status .RESOURCE_EXHAUSTED ,
318+ Status .UNIMPLEMENTED ,
319+ Status .INTERNAL ,
320+ Status .UNAVAILABLE ,
321+ Status .UNAUTHENTICATED );
322+
323+ for (Status status : inappropriateStatus ) {
324+ PickResult pickResult = PickResult .withError (status );
325+ ClientTransport transport = GrpcUtil .getTransportFromPickResult (pickResult , false );
326+
327+ ClientStream stream = transport .newStream (
328+ TestMethodDescriptors .voidMethod (), new Metadata (), CallOptions .DEFAULT ,
329+ tracers );
330+ ClientStreamListener listener = mock (ClientStreamListener .class );
331+ stream .start (listener );
332+
333+ verify (listener ).closed (eq (status ), eq (RpcProgress .PROCESSED ), any (Metadata .class ));
334+ }
335+ }
336+
261337 @ Test
262338 public void getTransportFromPickResult_dropPickResult_waitForReady () {
263339 Status status = Status .UNAVAILABLE ;
0 commit comments