Skip to content

Commit e432ea4

Browse files
committed
added NotImplementedError to abstract class, added unit tests
Signed-off-by: Sai Shree Pradhan <saishree.pradhan@databricks.com>
1 parent 57845f5 commit e432ea4

File tree

2 files changed

+158
-4
lines changed

2 files changed

+158
-4
lines changed

src/databricks/sql/telemetry/telemetry_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ class BaseTelemetryClient(ABC):
104104

105105
@abstractmethod
106106
def export_initial_telemetry_log(self, driver_connection_params, user_agent):
107-
pass
107+
raise NotImplementedError("Subclasses must implement export_initial_telemetry_log")
108108

109109
@abstractmethod
110110
def export_failure_log(self, error_name, error_message):
111-
pass
111+
raise NotImplementedError("Subclasses must implement export_failure_log")
112112

113113
@abstractmethod
114114
def close(self):
115-
pass
115+
raise NotImplementedError("Subclasses must implement close")
116116

117117

118118
# A single instance of the no-op client that can be reused

tests/unit/test_telemetry.py

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@
99
initialize_telemetry_client,
1010
get_telemetry_client,
1111
_remove_telemetry_client,
12+
TelemetryHelper,
13+
BaseTelemetryClient
1214
)
1315
from databricks.sql.telemetry.models.enums import (
1416
AuthMech,
1517
DatabricksClientType,
18+
AuthFlow,
1619
)
1720
from databricks.sql.telemetry.models.event import (
1821
DriverConnectionParameters,
1922
HostDetails,
2023
)
2124
from 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

261332
class 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

Comments
 (0)