9
9
initialize_telemetry_client ,
10
10
get_telemetry_client ,
11
11
_remove_telemetry_client ,
12
+ TelemetryHelper ,
13
+ BaseTelemetryClient
12
14
)
13
15
from databricks .sql .telemetry .models .enums import (
14
16
AuthMech ,
15
17
DatabricksClientType ,
18
+ AuthFlow ,
16
19
)
17
20
from databricks .sql .telemetry .models .event import (
18
21
DriverConnectionParameters ,
19
22
HostDetails ,
20
23
)
21
24
from databricks .sql .auth .authenticators import (
22
25
AccessTokenAuthProvider ,
26
+ DatabricksOAuthProvider ,
27
+ ExternalAuthProvider ,
23
28
)
24
29
25
30
@@ -257,6 +262,72 @@ def test_close(self, telemetry_client_setup):
257
262
258
263
client ._flush .assert_called_once ()
259
264
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
+
260
331
261
332
class TestTelemetrySystem :
262
333
"""Tests for the telemetry system functions."""
@@ -317,4 +388,87 @@ def test_close_telemetry_client(self, telemetry_system_reset):
317
388
_remove_telemetry_client (session_id_hex )
318
389
319
390
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