2222import com .databricks .sdk .core .commons .CommonsHttpClient ;
2323import com .databricks .sdk .core .utils .Cloud ;
2424import java .io .IOException ;
25+ import java .net .ServerSocket ;
2526import java .util .List ;
2627import java .util .Properties ;
2728import org .junit .jupiter .api .Test ;
@@ -169,6 +170,7 @@ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_AuthenticatesCorrect
169170 when (mockContext .getClientSecret ()).thenReturn ("browser-client-secret" );
170171 when (mockContext .getOAuthScopesForU2M ()).thenReturn (List .of (new String [] {"scope1" , "scope2" }));
171172 when (mockContext .getHttpConnectionPoolSize ()).thenReturn (100 );
173+ when (mockContext .getOAuth2RedirectUrlPorts ()).thenReturn (List .of (8020 ));
172174 configurator = new ClientConfigurator (mockContext );
173175 WorkspaceClient client = configurator .getWorkspaceClient ();
174176 assertNotNull (client );
@@ -195,6 +197,7 @@ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_AuthenticatesCorrect
195197 when (mockContext .isOAuthDiscoveryModeEnabled ()).thenReturn (true );
196198 when (mockContext .getOAuthDiscoveryURL ()).thenReturn (TEST_DISCOVERY_URL );
197199 when (mockContext .getHttpConnectionPoolSize ()).thenReturn (100 );
200+ when (mockContext .getOAuth2RedirectUrlPorts ()).thenReturn (List .of (8020 ));
198201 configurator = new ClientConfigurator (mockContext );
199202 WorkspaceClient client = configurator .getWorkspaceClient ();
200203 assertNotNull (client );
@@ -315,4 +318,120 @@ void setupM2MConfig_WithAzureTenantIdButNonAzureCloud_ThrowsException()
315318 verify (mockContext ).getAzureTenantId ();
316319 verify (mockContext , times (2 )).getCloud ();
317320 }
321+
322+ @ Test
323+ void testFindAvailablePort () throws Exception {
324+ // Create a mockContext for the ClientConfigurator constructor
325+ when (mockContext .getAuthMech ()).thenReturn (AuthMech .PAT );
326+ when (mockContext .getHostUrl ()).thenReturn ("https://test.databricks.com" );
327+ when (mockContext .getToken ()).thenReturn ("test-token" );
328+ when (mockContext .getHttpConnectionPoolSize ()).thenReturn (100 );
329+ configurator = new ClientConfigurator (mockContext );
330+
331+ // Test with a single available port
332+ int availablePort = findFreePort ();
333+ List <Integer > ports = List .of (availablePort );
334+ int result = configurator .findAvailablePort (ports );
335+ assertEquals (availablePort , result );
336+
337+ // Test with multiple ports, first unavailable
338+ int secondAvailablePort = findFreePort ();
339+ try (ServerSocket serverSocket = new ServerSocket (availablePort )) {
340+ serverSocket .setReuseAddress (true );
341+ ports = List .of (availablePort , secondAvailablePort );
342+ result = configurator .findAvailablePort (ports );
343+ assertEquals (secondAvailablePort , result );
344+ }
345+
346+ // Test incremental search - first port unavailable, second available
347+ try (ServerSocket serverSocket = new ServerSocket (availablePort )) {
348+ serverSocket .setReuseAddress (true );
349+ ports = List .of (availablePort );
350+ result = configurator .findAvailablePort (ports );
351+ assertEquals (availablePort + 1 , result );
352+ }
353+ }
354+
355+ @ Test
356+ void testFindAvailablePortThrowsExceptionWhenNoPortsAvailable () throws Exception {
357+ // Create a mockContext for the ClientConfigurator constructor
358+ when (mockContext .getAuthMech ()).thenReturn (AuthMech .PAT );
359+ when (mockContext .getHostUrl ()).thenReturn ("https://test.databricks.com" );
360+ when (mockContext .getToken ()).thenReturn ("test-token" );
361+ when (mockContext .getHttpConnectionPoolSize ()).thenReturn (100 );
362+ configurator = new ClientConfigurator (mockContext );
363+
364+ // Use a port that is likely to be available
365+ int port1 = findFreePort ();
366+ int port2 = findFreePort ();
367+ if (port1 == port2 ) {
368+ port2 = port1 + 1 ;
369+ }
370+
371+ // Occupy the ports to make them unavailable
372+ try (ServerSocket socket1 = new ServerSocket (port1 );
373+ ServerSocket socket2 = new ServerSocket (port2 )) {
374+ socket1 .setReuseAddress (true );
375+ socket2 .setReuseAddress (true );
376+
377+ // First test with multiple specified ports
378+ List <Integer > unavailablePorts = List .of (port1 , port2 );
379+ DatabricksException exception =
380+ assertThrows (
381+ DatabricksException .class , () -> configurator .findAvailablePort (unavailablePorts ));
382+ assertTrue (exception .getMessage ().contains ("No available port found" ));
383+
384+ // Now test with single port and verify it tries incremental ports
385+ // We need to create a subclass to control isPortAvailable behavior
386+ ClientConfigurator testConfigurator =
387+ new ClientConfigurator (mockContext ) {
388+ @ Override
389+ protected boolean isPortAvailable (int port ) {
390+ return false ; // All ports are unavailable
391+ }
392+ };
393+
394+ exception =
395+ assertThrows (
396+ DatabricksException .class , () -> testConfigurator .findAvailablePort (List .of (port1 )));
397+ assertTrue (exception .getMessage ().contains ("No available port found" ));
398+ }
399+ }
400+
401+ /** Utility method to find a free port */
402+ private int findFreePort () {
403+ try (ServerSocket socket = new ServerSocket (0 )) {
404+ socket .setReuseAddress (true );
405+ return socket .getLocalPort ();
406+ } catch (IOException e ) {
407+ throw new RuntimeException ("Failed to find free port" , e );
408+ }
409+ }
410+
411+ @ Test
412+ void getWorkspaceClient_OAuthWithBrowserBasedAuthentication_SetsCustomRedirectUrl ()
413+ throws Exception {
414+ // We'll mock getOAuth2RedirectUrlPorts to return a predefined list
415+ int testPort = findFreePort ();
416+ when (mockContext .getAuthMech ()).thenReturn (AuthMech .OAUTH );
417+ when (mockContext .getAuthFlow ()).thenReturn (AuthFlow .BROWSER_BASED_AUTHENTICATION );
418+ when (mockContext .getHostForOAuth ()).thenReturn ("https://oauth-browser.databricks.com" );
419+ when (mockContext .getClientId ()).thenReturn ("browser-client-id" );
420+ when (mockContext .getClientSecret ()).thenReturn ("browser-client-secret" );
421+ when (mockContext .getOAuthScopesForU2M ()).thenReturn (List .of (new String [] {"scope1" , "scope2" }));
422+ when (mockContext .getOAuth2RedirectUrlPorts ()).thenReturn (List .of (testPort ));
423+ when (mockContext .getHttpConnectionPoolSize ()).thenReturn (100 );
424+
425+ configurator = new ClientConfigurator (mockContext );
426+ WorkspaceClient client = configurator .getWorkspaceClient ();
427+ assertNotNull (client );
428+ DatabricksConfig config = client .config ();
429+
430+ assertEquals ("https://oauth-browser.databricks.com" , config .getHost ());
431+ assertEquals ("browser-client-id" , config .getClientId ());
432+ assertEquals ("browser-client-secret" , config .getClientSecret ());
433+ assertEquals (List .of (new String [] {"scope1" , "scope2" }), config .getScopes ());
434+ assertEquals ("http://localhost:" + testPort , config .getOAuthRedirectUrl ());
435+ assertEquals (DatabricksJdbcConstants .U2M_AUTH_TYPE , config .getAuthType ());
436+ }
318437}
0 commit comments