@@ -631,4 +631,149 @@ public async Task DownstreamApi_WithNonExistentApiName_ReturnsBadRequestWithProb
631631 // Just verify it returns 400 - that's sufficient for this test
632632 }
633633
634+ [ Fact ]
635+ public async Task DownstreamApi_WithExtraHeaderParametersOverride_PassesHeadersToOptionsAsync ( )
636+ {
637+ // Arrange
638+ var responseContent = "{\" result\" : \" success\" }" ;
639+ var mockResponse = new HttpResponseMessage ( HttpStatusCode . OK )
640+ {
641+ Content = new StringContent ( responseContent , Encoding . UTF8 , "application/json" )
642+ } ;
643+
644+ DownstreamApiOptions ? capturedOptions = null ;
645+ var mockDownstreamApi = new Mock < IDownstreamApi > ( ) ;
646+ mockDownstreamApi
647+ . Setup ( x => x . CallApiAsync ( It . IsAny < DownstreamApiOptions > ( ) , It . IsAny < System . Security . Claims . ClaimsPrincipal > ( ) , It . IsAny < HttpContent > ( ) , It . IsAny < CancellationToken > ( ) ) )
648+ . Callback < DownstreamApiOptions , System . Security . Claims . ClaimsPrincipal , HttpContent ? , CancellationToken > ( ( options , _ , _ , _ ) =>
649+ {
650+ capturedOptions = options ;
651+ } )
652+ . ReturnsAsync ( mockResponse ) ;
653+
654+ var client = _factory . WithWebHostBuilder ( builder =>
655+ {
656+ builder . ConfigureServices ( services =>
657+ {
658+ TestAuthenticationHandler . AddAlwaysSucceedTestAuthentication ( services ) ;
659+
660+ services . Configure < DownstreamApiOptions > ( "test-api" , options =>
661+ {
662+ options . BaseUrl = "https://api.example.com" ;
663+ options . Scopes = [ "user.read" ] ;
664+ } ) ;
665+
666+ services . AddSingleton ( mockDownstreamApi . Object ) ;
667+ } ) ;
668+ } ) . CreateClient ( ) ;
669+
670+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , "valid-token" ) ;
671+
672+ // Act
673+ var response = await client . PostAsync ( "/DownstreamApi/test-api?OptionsOverride.ExtraHeaderParameters.X-Custom-Header=test-value&OptionsOverride.ExtraHeaderParameters.OData-Version=4.0" , null ) ;
674+
675+ // Assert
676+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
677+ Assert . NotNull ( capturedOptions ? . ExtraHeaderParameters ) ;
678+ Assert . Equal ( "test-value" , capturedOptions . ExtraHeaderParameters [ "X-Custom-Header" ] ) ;
679+ Assert . Equal ( "4.0" , capturedOptions . ExtraHeaderParameters [ "OData-Version" ] ) ;
680+ }
681+
682+ [ Fact ]
683+ public async Task DownstreamApi_WithExtraQueryParametersOverride_PassesQueryParametersToOptionsAsync ( )
684+ {
685+ // Arrange
686+ var responseContent = "{\" result\" : \" success\" }" ;
687+ var mockResponse = new HttpResponseMessage ( HttpStatusCode . OK )
688+ {
689+ Content = new StringContent ( responseContent , Encoding . UTF8 , "application/json" )
690+ } ;
691+
692+ DownstreamApiOptions ? capturedOptions = null ;
693+ var mockDownstreamApi = new Mock < IDownstreamApi > ( ) ;
694+ mockDownstreamApi
695+ . Setup ( x => x . CallApiAsync ( It . IsAny < DownstreamApiOptions > ( ) , It . IsAny < System . Security . Claims . ClaimsPrincipal > ( ) , It . IsAny < HttpContent > ( ) , It . IsAny < CancellationToken > ( ) ) )
696+ . Callback < DownstreamApiOptions , System . Security . Claims . ClaimsPrincipal , HttpContent ? , CancellationToken > ( ( options , _ , _ , _ ) =>
697+ {
698+ capturedOptions = options ;
699+ } )
700+ . ReturnsAsync ( mockResponse ) ;
701+
702+ var client = _factory . WithWebHostBuilder ( builder =>
703+ {
704+ builder . ConfigureServices ( services =>
705+ {
706+ TestAuthenticationHandler . AddAlwaysSucceedTestAuthentication ( services ) ;
707+
708+ services . Configure < DownstreamApiOptions > ( "test-api" , options =>
709+ {
710+ options . BaseUrl = "https://api.example.com" ;
711+ options . Scopes = [ "user.read" ] ;
712+ } ) ;
713+
714+ services . AddSingleton ( mockDownstreamApi . Object ) ;
715+ } ) ;
716+ } ) . CreateClient ( ) ;
717+
718+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , "valid-token" ) ;
719+
720+ // Act
721+ var response = await client . PostAsync ( "/DownstreamApi/test-api?OptionsOverride.ExtraQueryParameters.param1=value1&OptionsOverride.ExtraQueryParameters.param2=value2" , null ) ;
722+
723+ // Assert
724+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
725+ Assert . NotNull ( capturedOptions ? . ExtraQueryParameters ) ;
726+ Assert . Equal ( "value1" , capturedOptions . ExtraQueryParameters [ "param1" ] ) ;
727+ Assert . Equal ( "value2" , capturedOptions . ExtraQueryParameters [ "param2" ] ) ;
728+ }
729+
730+ [ Fact ]
731+ public async Task DownstreamApi_WithBothExtraHeaderAndQueryParametersOverride_PassesBothToOptionsAsync ( )
732+ {
733+ // Arrange
734+ var responseContent = "{\" result\" : \" success\" }" ;
735+ var mockResponse = new HttpResponseMessage ( HttpStatusCode . OK )
736+ {
737+ Content = new StringContent ( responseContent , Encoding . UTF8 , "application/json" )
738+ } ;
739+
740+ DownstreamApiOptions ? capturedOptions = null ;
741+ var mockDownstreamApi = new Mock < IDownstreamApi > ( ) ;
742+ mockDownstreamApi
743+ . Setup ( x => x . CallApiAsync ( It . IsAny < DownstreamApiOptions > ( ) , It . IsAny < System . Security . Claims . ClaimsPrincipal > ( ) , It . IsAny < HttpContent > ( ) , It . IsAny < CancellationToken > ( ) ) )
744+ . Callback < DownstreamApiOptions , System . Security . Claims . ClaimsPrincipal , HttpContent ? , CancellationToken > ( ( options , _ , _ , _ ) =>
745+ {
746+ capturedOptions = options ;
747+ } )
748+ . ReturnsAsync ( mockResponse ) ;
749+
750+ var client = _factory . WithWebHostBuilder ( builder =>
751+ {
752+ builder . ConfigureServices ( services =>
753+ {
754+ TestAuthenticationHandler . AddAlwaysSucceedTestAuthentication ( services ) ;
755+
756+ services . Configure < DownstreamApiOptions > ( "test-api" , options =>
757+ {
758+ options . BaseUrl = "https://api.example.com" ;
759+ options . Scopes = [ "user.read" ] ;
760+ } ) ;
761+
762+ services . AddSingleton ( mockDownstreamApi . Object ) ;
763+ } ) ;
764+ } ) . CreateClient ( ) ;
765+
766+ client . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , "valid-token" ) ;
767+
768+ // Act
769+ var response = await client . PostAsync ( "/DownstreamApi/test-api?OptionsOverride.ExtraHeaderParameters.X-Test=header-val&OptionsOverride.ExtraQueryParameters.qparam=query-val" , null ) ;
770+
771+ // Assert
772+ Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
773+ Assert . NotNull ( capturedOptions ? . ExtraHeaderParameters ) ;
774+ Assert . NotNull ( capturedOptions ? . ExtraQueryParameters ) ;
775+ Assert . Equal ( "header-val" , capturedOptions . ExtraHeaderParameters [ "X-Test" ] ) ;
776+ Assert . Equal ( "query-val" , capturedOptions . ExtraQueryParameters [ "qparam" ] ) ;
777+ }
778+
634779}
0 commit comments