99 initialize_telemetry_client ,
1010 get_telemetry_client ,
1111 _remove_telemetry_client ,
12+ TelemetryHelper ,
13+ BaseTelemetryClient
1214)
1315from databricks .sql .telemetry .models .enums import (
1416 AuthMech ,
1517 DatabricksClientType ,
18+ AuthFlow ,
1619)
1720from databricks .sql .telemetry .models .event import (
1821 DriverConnectionParameters ,
1922 HostDetails ,
2023)
2124from databricks .sql .auth .authenticators import (
2225 AccessTokenAuthProvider ,
26+ DatabricksOAuthProvider ,
27+ ExternalAuthProvider ,
2328)
2429
2530
@@ -257,6 +262,72 @@ def test_close(self, telemetry_client_setup):
257262
258263 client ._flush .assert_called_once ()
259264
265+ @patch ("requests.post" )
266+ def test_telemetry_request_callback_success (self , mock_post , telemetry_client_setup ):
267+ """Test successful telemetry request callback."""
268+ client = telemetry_client_setup ["client" ]
269+
270+ mock_response = MagicMock ()
271+ mock_response .status_code = 200
272+
273+ mock_future = MagicMock ()
274+ mock_future .result .return_value = mock_response
275+
276+ client ._telemetry_request_callback (mock_future )
277+
278+ mock_future .result .assert_called_once ()
279+
280+ @patch ("requests.post" )
281+ def test_telemetry_request_callback_failure (self , mock_post , telemetry_client_setup ):
282+ """Test telemetry request callback with failure"""
283+ client = telemetry_client_setup ["client" ]
284+
285+ # Test with non-200 status code
286+ mock_response = MagicMock ()
287+ mock_response .status_code = 500
288+ future = MagicMock ()
289+ future .result .return_value = mock_response
290+ client ._telemetry_request_callback (future )
291+
292+ # Test with exception
293+ future = MagicMock ()
294+ future .result .side_effect = Exception ("Test error" )
295+ client ._telemetry_request_callback (future )
296+
297+ def test_telemetry_client_exception_handling (self , telemetry_client_setup ):
298+ """Test exception handling in telemetry client methods."""
299+ client = telemetry_client_setup ["client" ]
300+
301+ # Test export_initial_telemetry_log with exception
302+ with patch .object (client , '_export_event' , side_effect = Exception ("Test error" )):
303+ # Should not raise exception
304+ client .export_initial_telemetry_log (MagicMock (), "test-agent" )
305+
306+ # Test export_failure_log with exception
307+ with patch .object (client , '_export_event' , side_effect = Exception ("Test error" )):
308+ # Should not raise exception
309+ client .export_failure_log ("TestError" , "Test error message" )
310+
311+ # Test _send_telemetry with exception
312+ with patch .object (client ._executor , 'submit' , side_effect = Exception ("Test error" )):
313+ # Should not raise exception
314+ client ._send_telemetry ([MagicMock ()])
315+
316+ def test_send_telemetry_thread_pool_failure (self , telemetry_client_setup ):
317+ """Test handling of thread pool submission failure"""
318+ client = telemetry_client_setup ["client" ]
319+ client ._executor .submit .side_effect = Exception ("Thread pool error" )
320+ event = MagicMock ()
321+ client ._send_telemetry ([event ])
322+
323+ def test_base_telemetry_client_abstract_methods (self ):
324+ """Test that BaseTelemetryClient cannot be instantiated without implementing all abstract methods"""
325+ class TestBaseClient (BaseTelemetryClient ):
326+ pass
327+
328+ with pytest .raises (TypeError ):
329+ TestBaseClient () # Can't instantiate abstract class
330+
260331
261332class TestTelemetrySystem :
262333 """Tests for the telemetry system functions."""
@@ -317,4 +388,87 @@ def test_close_telemetry_client(self, telemetry_system_reset):
317388 _remove_telemetry_client (session_id_hex )
318389
319390 client = get_telemetry_client (session_id_hex )
320- assert client is NOOP_TELEMETRY_CLIENT
391+ assert client is NOOP_TELEMETRY_CLIENT
392+
393+ @patch ("databricks.sql.telemetry.telemetry_client._handle_unhandled_exception" )
394+ def test_global_exception_hook (self , mock_handle_exception , telemetry_system_reset ):
395+ """Test that global exception hook is installed and handles exceptions."""
396+ from databricks .sql .telemetry .telemetry_client import _install_exception_hook , _handle_unhandled_exception
397+
398+ _install_exception_hook ()
399+
400+ test_exception = ValueError ("Test exception" )
401+ _handle_unhandled_exception (type (test_exception ), test_exception , None )
402+
403+ mock_handle_exception .assert_called_once_with (type (test_exception ), test_exception , None )
404+
405+
406+ class TestTelemetryHelper :
407+ """Tests for the TelemetryHelper class."""
408+
409+ def test_get_driver_system_configuration (self ):
410+ """Test getting driver system configuration."""
411+ config = TelemetryHelper .get_driver_system_configuration ()
412+
413+ assert isinstance (config .driver_name , str )
414+ assert isinstance (config .driver_version , str )
415+ assert isinstance (config .runtime_name , str )
416+ assert isinstance (config .runtime_vendor , str )
417+ assert isinstance (config .runtime_version , str )
418+ assert isinstance (config .os_name , str )
419+ assert isinstance (config .os_version , str )
420+ assert isinstance (config .os_arch , str )
421+ assert isinstance (config .locale_name , str )
422+ assert isinstance (config .char_set_encoding , str )
423+
424+ assert config .driver_name == "Databricks SQL Python Connector"
425+ assert "Python" in config .runtime_name
426+ assert config .runtime_vendor in ["CPython" , "PyPy" , "Jython" , "IronPython" ]
427+ assert config .os_name in ["Darwin" , "Linux" , "Windows" ]
428+
429+ # Verify caching behavior
430+ config2 = TelemetryHelper .get_driver_system_configuration ()
431+ assert config is config2 # Should return same instance
432+
433+ def test_get_auth_mechanism (self ):
434+ """Test getting auth mechanism for different auth providers."""
435+ # Test PAT auth
436+ pat_auth = AccessTokenAuthProvider ("test-token" )
437+ assert TelemetryHelper .get_auth_mechanism (pat_auth ) == AuthMech .PAT
438+
439+ # Test OAuth auth
440+ oauth_auth = MagicMock (spec = DatabricksOAuthProvider )
441+ assert TelemetryHelper .get_auth_mechanism (oauth_auth ) == AuthMech .DATABRICKS_OAUTH
442+
443+ # Test External auth
444+ external_auth = MagicMock (spec = ExternalAuthProvider )
445+ assert TelemetryHelper .get_auth_mechanism (external_auth ) == AuthMech .EXTERNAL_AUTH
446+
447+ # Test None auth provider
448+ assert TelemetryHelper .get_auth_mechanism (None ) is None
449+
450+ # Test unknown auth provider
451+ unknown_auth = MagicMock ()
452+ assert TelemetryHelper .get_auth_mechanism (unknown_auth ) == AuthMech .CLIENT_CERT
453+
454+ def test_get_auth_flow (self ):
455+ """Test getting auth flow for different OAuth providers."""
456+ # Test OAuth with existing tokens
457+ oauth_with_tokens = MagicMock (spec = DatabricksOAuthProvider )
458+ oauth_with_tokens ._access_token = "test-access-token"
459+ oauth_with_tokens ._refresh_token = "test-refresh-token"
460+ assert TelemetryHelper .get_auth_flow (oauth_with_tokens ) == AuthFlow .TOKEN_PASSTHROUGH
461+
462+ # Test OAuth with browser-based auth
463+ oauth_with_browser = MagicMock (spec = DatabricksOAuthProvider )
464+ oauth_with_browser ._access_token = None
465+ oauth_with_browser ._refresh_token = None
466+ oauth_with_browser .oauth_manager = MagicMock ()
467+ assert TelemetryHelper .get_auth_flow (oauth_with_browser ) == AuthFlow .BROWSER_BASED_AUTHENTICATION
468+
469+ # Test non-OAuth provider
470+ pat_auth = AccessTokenAuthProvider ("test-token" )
471+ assert TelemetryHelper .get_auth_flow (pat_auth ) is None
472+
473+ # Test None auth provider
474+ assert TelemetryHelper .get_auth_flow (None ) is None
0 commit comments