@@ -303,7 +303,8 @@ void cacheWithValidRefreshableTokenTest() throws IOException {
303303 any (DatabricksConfig .class ),
304304 any (String .class ),
305305 any (String .class ),
306- any (TokenCache .class ));
306+ any (TokenCache .class ),
307+ any (OpenIDConnectEndpoints .class ));
307308
308309 // Verify token was NOT saved back to cache (we're using the cached one as-is).
309310 Mockito .verify (mockTokenCache , Mockito .never ()).save (any (Token .class ));
@@ -363,7 +364,12 @@ void cacheWithValidNonRefreshableTokenTest() throws IOException {
363364
364365 // Verify performBrowserAuth was NOT called.
365366 Mockito .verify (provider , Mockito .never ())
366- .performBrowserAuth (any (DatabricksConfig .class ), any (), any (), any (TokenCache .class ));
367+ .performBrowserAuth (
368+ any (DatabricksConfig .class ),
369+ any (),
370+ any (),
371+ any (TokenCache .class ),
372+ any (OpenIDConnectEndpoints .class ));
367373
368374 // Verify no token was saved (we're using the cached one as-is).
369375 Mockito .verify (mockTokenCache , Mockito .never ()).save (any (Token .class ));
@@ -430,7 +436,8 @@ void cacheWithInvalidAccessTokenValidRefreshTest() throws IOException {
430436 any (DatabricksConfig .class ),
431437 any (String .class ),
432438 any (String .class ),
433- any (TokenCache .class ));
439+ any (TokenCache .class ),
440+ any (OpenIDConnectEndpoints .class ));
434441
435442 // Verify token was saved back to cache
436443 Mockito .verify (mockTokenCache , Mockito .times (1 )).save (any (Token .class ));
@@ -508,7 +515,12 @@ void cacheWithInvalidAccessTokenRefreshFailingTest() throws IOException {
508515 Mockito .spy (new ExternalBrowserCredentialsProvider (mockTokenCache ));
509516 Mockito .doReturn (cachedTokenSource )
510517 .when (provider )
511- .performBrowserAuth (any (DatabricksConfig .class ), any (), any (), any (TokenCache .class ));
518+ .performBrowserAuth (
519+ any (DatabricksConfig .class ),
520+ any (),
521+ any (),
522+ any (TokenCache .class ),
523+ any (OpenIDConnectEndpoints .class ));
512524
513525 // Spy on the config to inject the endpoints
514526 DatabricksConfig spyConfig = Mockito .spy (config );
@@ -527,7 +539,12 @@ void cacheWithInvalidAccessTokenRefreshFailingTest() throws IOException {
527539
528540 // Verify performBrowserAuth was called since refresh failed
529541 Mockito .verify (provider , Mockito .times (1 ))
530- .performBrowserAuth (any (DatabricksConfig .class ), any (), any (), any (TokenCache .class ));
542+ .performBrowserAuth (
543+ any (DatabricksConfig .class ),
544+ any (),
545+ any (),
546+ any (TokenCache .class ),
547+ any (OpenIDConnectEndpoints .class ));
531548
532549 // Verify token was saved after browser auth (for the new token)
533550 Mockito .verify (mockTokenCache , Mockito .times (1 )).save (any (Token .class ));
@@ -572,17 +589,31 @@ void cacheWithInvalidTokensTest() throws IOException {
572589 new DatabricksConfig ()
573590 .setAuthType ("external-browser" )
574591 .setHost ("https://test.databricks.com" )
575- .setClientId ("test-client-id" );
592+ .setClientId ("test-client-id" )
593+ .setHttpClient (mockHttpClient );
576594
577595 // Create our provider and mock the browser auth method
578596 ExternalBrowserCredentialsProvider provider =
579597 Mockito .spy (new ExternalBrowserCredentialsProvider (mockTokenCache ));
580598 Mockito .doReturn (cachedTokenSource )
581599 .when (provider )
582- .performBrowserAuth (any (DatabricksConfig .class ), any (), any (), any (TokenCache .class ));
600+ .performBrowserAuth (
601+ any (DatabricksConfig .class ),
602+ any (),
603+ any (),
604+ any (TokenCache .class ),
605+ any (OpenIDConnectEndpoints .class ));
606+
607+ // Spy on the config to inject the endpoints
608+ OpenIDConnectEndpoints endpoints =
609+ new OpenIDConnectEndpoints (
610+ "https://test.databricks.com/oidc/v1/token" ,
611+ "https://test.databricks.com/oidc/v1/authorize" );
612+ DatabricksConfig spyConfig = Mockito .spy (config );
613+ Mockito .doReturn (endpoints ).when (spyConfig ).getOidcEndpoints ();
583614
584615 // Configure provider
585- HeaderFactory headerFactory = provider .configure (config );
616+ HeaderFactory headerFactory = provider .configure (spyConfig );
586617 assertNotNull (headerFactory );
587618 // Verify headers contain the browser auth token (fallback)
588619 Map <String , String > headers = headerFactory .headers ();
@@ -593,7 +624,12 @@ void cacheWithInvalidTokensTest() throws IOException {
593624
594625 // Verify performBrowserAuth was called since we had an invalid token
595626 Mockito .verify (provider , Mockito .times (1 ))
596- .performBrowserAuth (any (DatabricksConfig .class ), any (), any (), any (TokenCache .class ));
627+ .performBrowserAuth (
628+ any (DatabricksConfig .class ),
629+ any (),
630+ any (),
631+ any (TokenCache .class ),
632+ any (OpenIDConnectEndpoints .class ));
597633
598634 // Verify token was saved after browser auth (for the new token)
599635 Mockito .verify (mockTokenCache , Mockito .times (1 )).save (any (Token .class ));
@@ -609,7 +645,7 @@ void doNotAddOfflineAccessScopeWhenDisableOauthRefreshTokenIsTrue() {
609645 .setScopes (Arrays .asList ("my-test-scope" ));
610646
611647 ExternalBrowserCredentialsProvider provider = new ExternalBrowserCredentialsProvider ();
612- List <String > scopes = provider .getScopes (config );
648+ List <String > scopes = provider .getScopes (config , null );
613649
614650 assertEquals (1 , scopes .size ());
615651 assertTrue (scopes .contains ("my-test-scope" ));
@@ -625,7 +661,7 @@ void doNotRemoveUserProvidedScopesWhenDisableOauthRefreshTokenIsTrue() {
625661 .setScopes (Arrays .asList ("my-test-scope" , "offline_access" ));
626662
627663 ExternalBrowserCredentialsProvider provider = new ExternalBrowserCredentialsProvider ();
628- List <String > scopes = provider .getScopes (config );
664+ List <String > scopes = provider .getScopes (config , null );
629665
630666 assertEquals (2 , scopes .size ());
631667 assertTrue (scopes .contains ("offline_access" ));
@@ -641,10 +677,98 @@ void addOfflineAccessScopeWhenDisableOauthRefreshTokenIsFalse() {
641677 .setScopes (Arrays .asList ("my-test-scope" ));
642678
643679 ExternalBrowserCredentialsProvider provider = new ExternalBrowserCredentialsProvider ();
644- List <String > scopes = provider .getScopes (config );
680+ List <String > scopes = provider .getScopes (config , null );
645681
646682 assertEquals (2 , scopes .size ());
647683 assertTrue (scopes .contains ("offline_access" ));
648684 assertTrue (scopes .contains ("my-test-scope" ));
649685 }
686+
687+ @ Test
688+ void externalBrowserAuthWithAzureClientIdTest () throws IOException {
689+ // Create mock HTTP client
690+ HttpClient mockHttpClient = Mockito .mock (HttpClient .class );
691+
692+ // Mock token cache
693+ TokenCache mockTokenCache = Mockito .mock (TokenCache .class );
694+ Mockito .doReturn (null ).when (mockTokenCache ).load ();
695+
696+ // Create valid token for browser auth
697+ Token browserAuthToken =
698+ new Token (
699+ "azure_access_token" , "Bearer" , "azure_refresh_token" , Instant .now ().plusSeconds (3600 ));
700+
701+ // Create token source
702+ SessionCredentialsTokenSource browserAuthTokenSource =
703+ new SessionCredentialsTokenSource (
704+ browserAuthToken ,
705+ mockHttpClient ,
706+ "https://test.azuredatabricks.net/oidc/v1/token" ,
707+ "test-azure-client-id" ,
708+ null ,
709+ Optional .empty (),
710+ Optional .empty ());
711+
712+ CachedTokenSource cachedTokenSource =
713+ new CachedTokenSource .Builder (browserAuthTokenSource ).setToken (browserAuthToken ).build ();
714+
715+ // Create Azure config with Azure client ID
716+ DatabricksConfig config =
717+ new DatabricksConfig ()
718+ .setAuthType ("external-browser" )
719+ .setHost ("https://test.azuredatabricks.net" )
720+ .setAzureClientId ("test-azure-client-id" )
721+ .setHttpClient (mockHttpClient );
722+
723+ // Create provider and mock browser auth
724+ ExternalBrowserCredentialsProvider provider =
725+ Mockito .spy (new ExternalBrowserCredentialsProvider (mockTokenCache ));
726+ Mockito .doReturn (cachedTokenSource )
727+ .when (provider )
728+ .performBrowserAuth (
729+ any (DatabricksConfig .class ),
730+ any (),
731+ any (),
732+ any (TokenCache .class ),
733+ any (OpenIDConnectEndpoints .class ));
734+
735+ // Spy on config to inject OIDC endpoints
736+ OpenIDConnectEndpoints endpoints =
737+ new OpenIDConnectEndpoints (
738+ "https://test.azuredatabricks.net/oidc/v1/token" ,
739+ "https://test.azuredatabricks.net/oidc/v1/authorize" );
740+ DatabricksConfig spyConfig = Mockito .spy (config );
741+ Mockito .doReturn (endpoints ).when (spyConfig ).getOidcEndpoints ();
742+
743+ // Configure provider
744+ HeaderFactory headerFactory = provider .configure (spyConfig );
745+ assertNotNull (headerFactory );
746+
747+ // Verify headers contain the Azure token
748+ Map <String , String > headers = headerFactory .headers ();
749+ assertEquals ("Bearer azure_access_token" , headers .get ("Authorization" ));
750+
751+ // Capture and verify the OpenIDConnectEndpoints passed to performBrowserAuth
752+ ArgumentCaptor <OpenIDConnectEndpoints > endpointsCaptor =
753+ ArgumentCaptor .forClass (OpenIDConnectEndpoints .class );
754+ Mockito .verify (provider , Mockito .times (1 ))
755+ .performBrowserAuth (
756+ any (DatabricksConfig .class ),
757+ any (),
758+ any (),
759+ any (TokenCache .class ),
760+ endpointsCaptor .capture ());
761+
762+ // Verify the captured endpoints match what we expect for Azure
763+ OpenIDConnectEndpoints capturedEndpoints = endpointsCaptor .getValue ();
764+ assertNotNull (capturedEndpoints );
765+ assertEquals (
766+ "https://test.azuredatabricks.net/oidc/v1/token" , capturedEndpoints .getTokenEndpoint ());
767+ assertEquals (
768+ "https://test.azuredatabricks.net/oidc/v1/authorize" ,
769+ capturedEndpoints .getAuthorizationEndpoint ());
770+
771+ // Verify token was saved
772+ Mockito .verify (mockTokenCache , Mockito .times (1 )).save (any (Token .class ));
773+ }
650774}
0 commit comments