@@ -493,12 +493,18 @@ private enum CredentialType
493493 private ( ConfidentialClientApplication app , MockHttpMessageHandler handler ) CreateConfidentialClient (
494494 MockHttpManager httpManager ,
495495 X509Certificate2 cert ,
496- CredentialType credentialType = CredentialType . Certificate )
496+ CredentialType credentialType = CredentialType . Certificate ,
497+ bool withClientCapability = false )
497498 {
498499 var builder = ConfidentialClientApplicationBuilder . Create ( TestConstants . ClientId )
499500 . WithRedirectUri ( TestConstants . RedirectUri )
500501 . WithHttpManager ( httpManager ) ;
501502
503+ if ( withClientCapability )
504+ {
505+ builder . WithClientCapabilities ( TestConstants . ClientCapabilities ) ;
506+ }
507+
502508 ConfidentialClientApplication app ;
503509
504510 switch ( credentialType )
@@ -526,15 +532,38 @@ private enum CredentialType
526532 Assert . IsNull ( app . Certificate ) ;
527533 break ;
528534 case CredentialType . SignedAssertionWithAssertionRequestOptionsAsyncDelegate :
529- builder = builder . WithClientAssertion ( ( options ) =>
530535 {
531- Assert . IsNotNull ( options . ClientID ) ;
532- Assert . IsNotNull ( options . TokenEndpoint ) ;
533- return Task . FromResult ( TestConstants . DefaultClientAssertion ) ;
534- } ) ;
535- app = builder . BuildConcrete ( ) ;
536- Assert . IsNull ( app . Certificate ) ;
537- break ;
536+ bool localWithClientCapability = withClientCapability ;
537+
538+ builder = builder . WithClientAssertion ( ( options ) =>
539+ {
540+ // Basic checks
541+ Assert . IsNotNull ( options . ClientID ) ;
542+ Assert . IsNotNull ( options . TokenEndpoint ) ;
543+
544+ // Conditionally check ClientCapabilities
545+ if ( localWithClientCapability )
546+ {
547+ Assert . IsNotNull ( options . ClientCapabilities , "Expected ClientCapabilities to be set." ) ;
548+ CollectionAssert . AreEqual (
549+ TestConstants . ClientCapabilities ,
550+ options . ClientCapabilities . ToList ( ) ,
551+ "ClientCapabilities should match what was configured."
552+ ) ;
553+ }
554+ else
555+ {
556+ Assert . IsNull ( options . ClientCapabilities , "ClientCapabilities should not be set if not requested." ) ;
557+ }
558+
559+ return Task . FromResult ( TestConstants . DefaultClientAssertion ) ;
560+ } ) ;
561+
562+ app = builder . BuildConcrete ( ) ;
563+ Assert . IsNull ( app . Certificate ) ;
564+
565+ break ;
566+ }
538567 case CredentialType . Certificate :
539568 builder = builder . WithCertificate ( cert ) ;
540569 app = builder . BuildConcrete ( ) ;
@@ -768,6 +797,35 @@ public async Task ConfidentialClientUsingSignedClientAssertion_AsyncDelegateWith
768797 }
769798 }
770799
800+ [ TestMethod ]
801+ public async Task SignedAssertionWithClientCapabilitiesTestAsync ( )
802+ {
803+ using ( var httpManager = new MockHttpManager ( ) )
804+ {
805+ httpManager . AddInstanceDiscoveryMockHandler ( ) ;
806+
807+ ( ConfidentialClientApplication App , MockHttpMessageHandler Handler ) setup =
808+ CreateConfidentialClient ( httpManager ,
809+ null ,
810+ CredentialType . SignedAssertionWithAssertionRequestOptionsAsyncDelegate ,
811+ true ) ;
812+
813+ var result = await setup . App . AcquireTokenForClient ( TestConstants . s_scope . ToArray ( ) )
814+ . ExecuteAsync ( CancellationToken . None ) . ConfigureAwait ( false ) ;
815+ Assert . IsNotNull ( result ) ;
816+ Assert . IsNotNull ( "header.payload.signature" , result . AccessToken ) ;
817+ Assert . AreEqual ( TestConstants . s_scope . AsSingleString ( ) , result . Scopes . AsSingleString ( ) ) ;
818+
819+ Assert . AreEqual (
820+ TestConstants . DefaultClientAssertion ,
821+ setup . Handler . ActualRequestPostData [ "client_assertion" ] ) ;
822+
823+ Assert . AreEqual (
824+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ,
825+ setup . Handler . ActualRequestPostData [ "client_assertion_type" ] ) ;
826+ }
827+ }
828+
771829 [ TestMethod ]
772830 public async Task ConfidentialClientUsingSignedClientAssertion_AsyncDelegate_CancellationTestAsync ( )
773831 {
@@ -799,6 +857,68 @@ await AssertException.TaskThrowsAsync<OperationCanceledException>(
799857 }
800858 }
801859
860+ [ TestMethod ]
861+ public async Task SignedAssertionWithSingleClientCapabilityTestAsync ( )
862+ {
863+ using ( var httpManager = new MockHttpManager ( ) )
864+ {
865+ // 1. Instance discovery
866+ httpManager . AddInstanceDiscoveryMockHandler ( ) ;
867+
868+ // 2. Mock the token endpoint response
869+ httpManager . AddMockHandlerSuccessfulClientCredentialTokenResponseMessage ( ) ;
870+
871+ var builder = ConfidentialClientApplicationBuilder . Create ( TestConstants . ClientId )
872+ . WithHttpManager ( httpManager )
873+ . WithClientCapabilities ( new [ ] { "cp1" } ) // Single capability
874+ . WithClientAssertion ( ( options ) =>
875+ {
876+ // Assert - only one capability
877+ Assert . IsNotNull ( options . ClientCapabilities ) ;
878+ CollectionAssert . AreEquivalent (
879+ new [ ] { "cp1" } ,
880+ options . ClientCapabilities . ToList ( ) ) ;
881+
882+ return Task . FromResult ( TestConstants . DefaultClientAssertion ) ;
883+ } ) ;
884+
885+ var app = builder . BuildConcrete ( ) ;
886+
887+ // Act - calls the token endpoint
888+ var result = await app . AcquireTokenForClient ( TestConstants . s_scope . ToArray ( ) )
889+ . ExecuteAsync ( )
890+ . ConfigureAwait ( false ) ;
891+
892+ // Basic validations
893+ Assert . IsNotNull ( result ) ;
894+ Assert . AreEqual ( TestConstants . s_scope . AsSingleString ( ) , result . Scopes . AsSingleString ( ) ) ;
895+ }
896+ }
897+
898+ [ TestMethod ]
899+ public void Constructor_NullDelegate_ThrowsArgumentNullException ( )
900+ {
901+ // Arrange
902+ Func < AssertionRequestOptions , Task < string > > nullDelegate = null ;
903+
904+ // Act & Assert
905+ Assert . ThrowsException < ArgumentNullException > ( ( ) =>
906+ new SignedAssertionDelegateClientCredential ( nullDelegate ) ) ;
907+ }
908+
909+ [ TestMethod ]
910+ public void Constructor_ValidDelegate_DoesNotThrow ( )
911+ {
912+ // Arrange
913+ Func < AssertionRequestOptions , Task < string > > validDelegate =
914+ ( options ) => Task . FromResult ( "fake_assertion" ) ;
915+
916+ // Act & Assert
917+ // Should not throw
918+ var credential = new SignedAssertionDelegateClientCredential ( validDelegate ) ;
919+ Assert . IsNotNull ( credential ) ;
920+ }
921+
802922 [ TestMethod ]
803923 public async Task GetAuthorizationRequestUrlNoRedirectUriTestAsync ( )
804924 {
@@ -1858,5 +1978,80 @@ public void AssertionInputIsMutable()
18581978 options . CancellationToken = CancellationToken . None ;
18591979 options . Claims = TestConstants . Claims ;
18601980 }
1981+
1982+ [ TestMethod ]
1983+ public void ConfidentialClient_WithEmptyClientSecret_ThrowsException ( )
1984+ {
1985+ Assert . ThrowsException < ArgumentNullException > ( ( ) =>
1986+ {
1987+ ConfidentialClientApplicationBuilder . Create ( TestConstants . ClientId )
1988+ . WithClientSecret ( string . Empty ) // or null
1989+ . Build ( ) ;
1990+ } ) ;
1991+ }
1992+
1993+ [ TestMethod ]
1994+ public async Task ConfidentialClient_WithClaims_TestAsync ( )
1995+ {
1996+ using ( var httpManager = new MockHttpManager ( ) )
1997+ {
1998+ httpManager . AddInstanceDiscoveryMockHandler ( ) ;
1999+
2000+ // Mock success with verifying we got the extra claims in the request
2001+ var handler = httpManager . AddMockHandlerSuccessfulClientCredentialTokenResponseMessage ( ) ;
2002+ handler . ExpectedPostData = new Dictionary < string , string > ( )
2003+ {
2004+ { "claims" , "{\" extra_claim\" :\" value\" }" }
2005+ } ;
2006+
2007+ var app = ConfidentialClientApplicationBuilder
2008+ . Create ( TestConstants . ClientId )
2009+ . WithClientSecret ( TestConstants . ClientSecret )
2010+ . WithHttpManager ( httpManager )
2011+ . BuildConcrete ( ) ;
2012+
2013+ var result = await app . AcquireTokenForClient ( TestConstants . s_scope )
2014+ . WithClaims ( "{\" extra_claim\" :\" value\" }" )
2015+ . ExecuteAsync ( )
2016+ . ConfigureAwait ( false ) ;
2017+
2018+ Assert . IsNotNull ( result ) ;
2019+ }
2020+ }
2021+
2022+ [ TestMethod ]
2023+ public async Task AcquireTokenByAuthorizationCode_NullOrEmptyCode_ThrowsAsync ( )
2024+ {
2025+ using ( var httpManager = new MockHttpManager ( ) )
2026+ {
2027+ // Arrange
2028+ var app = ConfidentialClientApplicationBuilder
2029+ . Create ( TestConstants . ClientId )
2030+ . WithClientSecret ( TestConstants . ClientSecret )
2031+ . WithHttpManager ( httpManager )
2032+ . BuildConcrete ( ) ;
2033+
2034+ // Act & Assert
2035+ await AssertException . TaskThrowsAsync < ArgumentException > (
2036+ ( ) => app . AcquireTokenByAuthorizationCode ( TestConstants . s_scope , null ) . ExecuteAsync ( )
2037+ ) . ConfigureAwait ( false ) ;
2038+
2039+ await AssertException . TaskThrowsAsync < ArgumentException > (
2040+ ( ) => app . AcquireTokenByAuthorizationCode ( TestConstants . s_scope , string . Empty ) . ExecuteAsync ( )
2041+ ) . ConfigureAwait ( false ) ;
2042+ }
2043+ }
2044+
2045+ [ TestMethod ]
2046+ public void ConfidentialClient_WithInvalidAuthority_ThrowsArgumentException ( )
2047+ {
2048+ Assert . ThrowsException < ArgumentException > ( ( ) =>
2049+ {
2050+ ConfidentialClientApplicationBuilder
2051+ . Create ( TestConstants . ClientId )
2052+ . WithAuthority ( "NotAValidAuthority" )
2053+ . Build ( ) ;
2054+ } ) ;
2055+ }
18612056 }
18622057}
0 commit comments