@@ -387,4 +387,141 @@ public void parseGoogleGrpcConfig_unsupportedScheme() {
387387 assertThat (exception ).hasMessageThat ()
388388 .contains ("Target URI scheme is not resolvable" );
389389 }
390+
391+ static class RecordingMetadataApplier extends io .grpc .CallCredentials .MetadataApplier {
392+ boolean applied = false ;
393+ boolean failed = false ;
394+ io .grpc .Metadata appliedHeaders = null ;
395+
396+ @ Override
397+ public void apply (io .grpc .Metadata headers ) {
398+ applied = true ;
399+ appliedHeaders = headers ;
400+ }
401+
402+ @ Override
403+ public void fail (io .grpc .Status status ) {
404+ failed = true ;
405+ }
406+ }
407+
408+ static class FakeRequestInfo extends io .grpc .CallCredentials .RequestInfo {
409+ private final io .grpc .SecurityLevel securityLevel ;
410+ private final io .grpc .MethodDescriptor <?, ?> methodDescriptor ;
411+
412+ FakeRequestInfo (io .grpc .SecurityLevel securityLevel ) {
413+ this .securityLevel = securityLevel ;
414+ this .methodDescriptor = io .grpc .MethodDescriptor .<Void , Void >newBuilder ()
415+ .setType (io .grpc .MethodDescriptor .MethodType .UNARY )
416+ .setFullMethodName ("test_service/test_method" )
417+ .setRequestMarshaller (new NoopMarshaller <Void >())
418+ .setResponseMarshaller (new NoopMarshaller <Void >())
419+ .build ();
420+ }
421+
422+ private static class NoopMarshaller <T > implements io .grpc .MethodDescriptor .Marshaller <T > {
423+ @ Override
424+ public java .io .InputStream stream (T value ) {
425+ return null ;
426+ }
427+
428+ @ Override
429+ public T parse (java .io .InputStream stream ) {
430+ return null ;
431+ }
432+ }
433+
434+ @ Override
435+ public io .grpc .MethodDescriptor <?, ?> getMethodDescriptor () {
436+ return methodDescriptor ;
437+ }
438+
439+ @ Override
440+ public io .grpc .SecurityLevel getSecurityLevel () {
441+ return securityLevel ;
442+ }
443+
444+ @ Override
445+ public String getAuthority () {
446+ return "dummy-authority" ;
447+ }
448+
449+ @ Override
450+ public io .grpc .Attributes getTransportAttrs () {
451+ return io .grpc .Attributes .EMPTY ;
452+ }
453+ }
454+
455+
456+ @ Test
457+ public void securityAwareCredentials_secureConnection_appliesToken () throws Exception {
458+ Any insecureCreds = Any .pack (InsecureCredentials .getDefaultInstance ());
459+ Any accessTokenCreds =
460+ Any .pack (AccessTokenCredentials .newBuilder ().setToken ("test_token" ).build ());
461+ GrpcService .GoogleGrpc googleGrpc = GrpcService .GoogleGrpc .newBuilder ()
462+ .setTargetUri ("test_uri" )
463+ .addChannelCredentialsPlugin (insecureCreds )
464+ .addCallCredentialsPlugin (accessTokenCreds )
465+ .build ();
466+ GrpcService grpcService = GrpcService .newBuilder ().setGoogleGrpc (googleGrpc ).build ();
467+
468+ GrpcServiceConfig config = GrpcServiceConfigParser .parse (grpcService ,
469+ io .grpc .xds .internal .grpcservice .GrpcServiceXdsContextTestUtil .dummyProvider ());
470+
471+ io .grpc .CallCredentials creds = config .googleGrpc ().callCredentials ().get ();
472+ RecordingMetadataApplier applier = new RecordingMetadataApplier ();
473+ java .util .concurrent .CountDownLatch latch = new java .util .concurrent .CountDownLatch (1 );
474+
475+ creds .applyRequestMetadata (
476+ new FakeRequestInfo (io .grpc .SecurityLevel .PRIVACY_AND_INTEGRITY ),
477+ Runnable ::run , // Use direct executor to avoid async issues in test
478+ new io .grpc .CallCredentials .MetadataApplier () {
479+ @ Override
480+ public void apply (io .grpc .Metadata headers ) {
481+ applier .apply (headers );
482+ latch .countDown ();
483+ }
484+
485+ @ Override
486+ public void fail (io .grpc .Status status ) {
487+ applier .fail (status );
488+ latch .countDown ();
489+ }
490+ });
491+
492+ latch .await (5 , java .util .concurrent .TimeUnit .SECONDS );
493+ assertThat (applier .applied ).isTrue ();
494+ assertThat (applier .appliedHeaders .get (
495+ io .grpc .Metadata .Key .of ("Authorization" , io .grpc .Metadata .ASCII_STRING_MARSHALLER )))
496+ .isEqualTo ("Bearer test_token" );
497+ }
498+
499+ @ Test
500+ public void securityAwareCredentials_insecureConnection_appliesEmptyMetadata () throws Exception {
501+ Any insecureCreds = Any .pack (InsecureCredentials .getDefaultInstance ());
502+ Any accessTokenCreds =
503+ Any .pack (AccessTokenCredentials .newBuilder ().setToken ("test_token" ).build ());
504+ GrpcService .GoogleGrpc googleGrpc = GrpcService .GoogleGrpc .newBuilder ()
505+ .setTargetUri ("test_uri" )
506+ .addChannelCredentialsPlugin (insecureCreds )
507+ .addCallCredentialsPlugin (accessTokenCreds )
508+ .build ();
509+ GrpcService grpcService = GrpcService .newBuilder ().setGoogleGrpc (googleGrpc ).build ();
510+
511+ GrpcServiceConfig config = GrpcServiceConfigParser .parse (grpcService ,
512+ io .grpc .xds .internal .grpcservice .GrpcServiceXdsContextTestUtil .dummyProvider ());
513+
514+ io .grpc .CallCredentials creds = config .googleGrpc ().callCredentials ().get ();
515+ RecordingMetadataApplier applier = new RecordingMetadataApplier ();
516+
517+ creds .applyRequestMetadata (
518+ new FakeRequestInfo (io .grpc .SecurityLevel .NONE ),
519+ Runnable ::run ,
520+ applier );
521+
522+ assertThat (applier .applied ).isTrue ();
523+ assertThat (applier .appliedHeaders .get (
524+ io .grpc .Metadata .Key .of ("Authorization" , io .grpc .Metadata .ASCII_STRING_MARSHALLER )))
525+ .isNull ();
526+ }
390527}
0 commit comments