1414
1515class DatabricksCliScopeValidationTest {
1616
17- private static final String HOST = "https://my-workspace.cloud.databricks.com" ;
17+
1818 private static final ObjectMapper MAPPER = new ObjectMapper ();
1919
2020 /** Builds a fake JWT (header.payload.signature) with the given claims. */
@@ -62,16 +62,24 @@ static List<Arguments> scopeValidationCases() {
6262 Arrays .asList ("all-apis" , "offline_access" ),
6363 false ,
6464 "offline_access_in_config_only" ),
65- // Scope claim as list instead of string.
65+ // Order should not matter.
66+ Arguments .of (
67+ Collections .singletonMap ("scope" , "clusters sql" ),
68+ Arrays .asList ("sql" , "clusters" ),
69+ false ,
70+ "multiple_scopes_order_independent" ),
71+ // Partial overlap is still a mismatch.
72+ Arguments .of (
73+ Collections .singletonMap ("scope" , "sql clusters" ),
74+ Arrays .asList ("sql" , "compute" ),
75+ true ,
76+ "multiple_scopes_partial_overlap_mismatch" ),
77+ // No scope claim in token — validation is skipped.
6678 Arguments .of (
67- new HashMap <String , Object >() {
68- {
69- put ("scope" , Arrays .asList ("sql" , "offline_access" ));
70- }
71- },
79+ Collections .singletonMap ("sub" , "user@example.com" ),
7280 Collections .singletonList ("sql" ),
7381 false ,
74- "scope_as_list " ));
82+ "no_scope_claim_skips_validation " ));
7583 }
7684
7785 @ ParameterizedTest (name = "{3}" )
@@ -87,30 +95,21 @@ void testScopeValidation(
8795 assertThrows (
8896 DatabricksCliCredentialsProvider .ScopeMismatchException .class ,
8997 () ->
90- DatabricksCliCredentialsProvider .validateTokenScopes (token , configuredScopes , HOST ));
98+ DatabricksCliCredentialsProvider .validateTokenScopes (token , configuredScopes ));
9199 } else {
92100 assertDoesNotThrow (
93101 () ->
94- DatabricksCliCredentialsProvider .validateTokenScopes (token , configuredScopes , HOST ));
102+ DatabricksCliCredentialsProvider .validateTokenScopes (token , configuredScopes ));
95103 }
96104 }
97105
98- @ Test
99- void testNoScopeClaimSkipsValidation () {
100- Token token = makeToken (Collections .singletonMap ("sub" , "user@example.com" ));
101- assertDoesNotThrow (
102- () ->
103- DatabricksCliCredentialsProvider .validateTokenScopes (
104- token , Collections .singletonList ("sql" ), HOST ));
105- }
106-
107106 @ Test
108107 void testNonJwtTokenSkipsValidation () {
109108 Token token = new Token ("opaque-token-string" , "Bearer" , Instant .now ().plusSeconds (3600 ));
110109 assertDoesNotThrow (
111110 () ->
112111 DatabricksCliCredentialsProvider .validateTokenScopes (
113- token , Collections .singletonList ("sql" ), HOST ));
112+ token , Collections .singletonList ("sql" )));
114113 }
115114
116115 @ Test
@@ -121,12 +120,27 @@ void testErrorMessageContainsReauthCommand() {
121120 DatabricksCliCredentialsProvider .ScopeMismatchException .class ,
122121 () ->
123122 DatabricksCliCredentialsProvider .validateTokenScopes (
124- token , Arrays .asList ("sql" , "offline_access" ), HOST ));
123+ token , Arrays .asList ("sql" , "offline_access" )));
125124 assertTrue (
126125 e .getMessage ().contains ("databricks auth login" ),
127126 "Expected re-auth command in error message, got: " + e .getMessage ());
128127 assertTrue (
129128 e .getMessage ().contains ("do not match the configured scopes" ),
130129 "Expected scope mismatch details in error message, got: " + e .getMessage ());
131130 }
131+
132+ @ Test
133+ void testScopesExplicitlySetFlag () {
134+ DatabricksConfig config = new DatabricksConfig ();
135+ assertFalse (config .isScopesExplicitlySet ());
136+
137+ config .setScopes (Arrays .asList ("sql" , "clusters" ));
138+ assertTrue (config .isScopesExplicitlySet ());
139+
140+ config .setScopes (Collections .emptyList ());
141+ assertFalse (config .isScopesExplicitlySet (), "Empty list should not count as explicitly set" );
142+
143+ config .setScopes (null );
144+ assertFalse (config .isScopesExplicitlySet (), "null should not count as explicitly set" );
145+ }
132146}
0 commit comments