@@ -207,6 +207,63 @@ def test_get_python_sql_connector_default_auth(self, mock__initial_get_token):
207207
208208 self .assertEqual (auth_provider .external_provider ._client_id , PYSQL_OAUTH_CLIENT_ID )
209209
210+ @patch ("databricks.sql.auth.oauth.webbrowser.open_new" )
211+ @patch .object (OAuthManager , "_OAuthManager__fetch_well_known_config" )
212+ def test_get_tokens_does_not_hang_when_no_callback_received (
213+ self , mock_fetch_config , mock_open_new
214+ ):
215+ """When the U2M browser OAuth callback never arrives (e.g. a headless
216+ notebook/job with a null token), the local redirect server must not
217+ block forever. It should time out and surface a clear error rather than
218+ hang. See issue #458."""
219+ mock_fetch_config .return_value = {
220+ "authorization_endpoint" : "https://foo.cloud.databricks.com/oidc/oauth2/v2.0/authorize" ,
221+ "token_endpoint" : "https://foo.cloud.databricks.com/oidc/oauth2/v2.0/token" ,
222+ }
223+ # Do not actually launch a browser during the test.
224+ mock_open_new .return_value = True
225+
226+ oauth_manager = OAuthManager (
227+ port_range = [8030 ],
228+ client_id = "mock-id" ,
229+ idp_endpoint = InHouseOAuthEndpointCollection (),
230+ http_client = MagicMock (),
231+ )
232+ # Keep the test fast: shorten the callback wait. The production default
233+ # is minutes; the bug is that WITHOUT any bound the wait is infinite.
234+ oauth_manager .REDIRECT_CALLBACK_TIMEOUT_SECONDS = 2
235+
236+ # No callback is ever delivered to the redirect server. Run the flow in
237+ # a daemon thread and join with a wall-clock bound so a regression to
238+ # the infinite-block behaviour fails this test loudly instead of hanging
239+ # the whole suite.
240+ import threading
241+
242+ result = {}
243+
244+ def run ():
245+ try :
246+ oauth_manager .get_tokens (
247+ hostname = "foo.cloud.databricks.com" , scope = "offline_access sql"
248+ )
249+ result ["outcome" ] = "returned"
250+ except BaseException as e : # noqa: BLE001
251+ result ["outcome" ] = "raised"
252+ result ["error" ] = e
253+
254+ worker = threading .Thread (target = run , daemon = True )
255+ worker .start ()
256+ worker .join (timeout = 30 )
257+
258+ self .assertFalse (
259+ worker .is_alive (),
260+ "OAuth callback server blocked indefinitely waiting for a callback "
261+ "that never arrives (issue #458)" ,
262+ )
263+ self .assertEqual (result .get ("outcome" ), "raised" )
264+ self .assertIsInstance (result .get ("error" ), RuntimeError )
265+ self .assertIn ("No path parameters were returned" , str (result .get ("error" )))
266+
210267
211268class TestClientCredentialsTokenSource :
212269 @pytest .fixture
0 commit comments