"""Unit tests for core telemetry functionality. This file tests ONLY telemetry logic, following SRP. All external dependencies (PostHog, file system) are mocked. """ import os from pathlib import Path from unittest.mock import MagicMock, Mock, mock_open, patch import pytest class TestTelemetryEnabled: """Test telemetry enable/disable logic (SRP: Only tests enable/disable).""" def test_telemetry_enabled_by_default(self, monkeypatch): """Test that telemetry is enabled by default.""" # Remove any environment variables that might affect the test monkeypatch.delenv("CUA_TELEMETRY", raising=False) monkeypatch.delenv("CUA_TELEMETRY_ENABLED", raising=False) monkeypatch.delenv("CUA_TELEMETRY_DISABLED", raising=False) from cua_core.telemetry import is_telemetry_enabled assert is_telemetry_enabled() is True def test_telemetry_disabled_with_flag(self, monkeypatch): """Test that telemetry can be disabled with CUA_TELEMETRY_ENABLED=false.""" monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "false") from cua_core.telemetry import is_telemetry_enabled assert is_telemetry_enabled() is False @pytest.mark.parametrize("value", ["0", "false", "no", "off"]) def test_telemetry_disabled_with_various_values(self, monkeypatch, value): """Test that telemetry respects various disable values.""" monkeypatch.setenv("CUA_TELEMETRY_ENABLED", value) from cua_core.telemetry import is_telemetry_enabled assert is_telemetry_enabled() is False @pytest.mark.parametrize("value", ["1", "true", "yes", "on"]) def test_telemetry_enabled_with_various_values(self, monkeypatch, value): """Test that telemetry respects various enable values.""" monkeypatch.delenv("CUA_TELEMETRY_DISABLED", raising=False) monkeypatch.setenv("CUA_TELEMETRY_ENABLED", value) from cua_core.telemetry import is_telemetry_enabled assert is_telemetry_enabled() is True class TestPostHogTelemetryClient: """Test PostHogTelemetryClient class (SRP: Only tests client logic).""" @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_client_initialization(self, mock_path, mock_posthog, disable_telemetry): """Test that client initializes correctly.""" from cua_core.telemetry.posthog import PostHogTelemetryClient # Mock the storage directory mock_storage_dir = MagicMock() mock_storage_dir.exists.return_value = False mock_path.return_value.parent.parent = MagicMock() mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir # Reset singleton PostHogTelemetryClient.destroy_client() client = PostHogTelemetryClient() assert client is not None assert hasattr(client, "installation_id") assert hasattr(client, "initialized") assert hasattr(client, "queued_events") @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_installation_id_generation(self, mock_path, mock_posthog, disable_telemetry): """Test that installation ID is generated if not exists.""" from cua_core.telemetry.posthog import PostHogTelemetryClient # Mock file system: Path.home() / ".config" / "cua" / "installation_id" mock_id_file = MagicMock() mock_id_file.exists.return_value = False mock_config_dir = MagicMock() mock_config_dir.__truediv__.return_value = mock_id_file mock_path.home.return_value.__truediv__.return_value.__truediv__.return_value = ( mock_config_dir ) # Reset singleton PostHogTelemetryClient.destroy_client() client = PostHogTelemetryClient() # Should have generated a new UUID assert client.installation_id is not None assert len(client.installation_id) == 36 # UUID format @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_installation_id_persistence(self, mock_path, mock_posthog, disable_telemetry): """Test that installation ID is read from file if exists.""" from cua_core.telemetry.posthog import PostHogTelemetryClient existing_id = "test-installation-id-123" # Mock file system: Path.home() / ".config" / "cua" / "installation_id" mock_id_file = MagicMock() mock_id_file.exists.return_value = True mock_id_file.read_text.return_value.strip.return_value = existing_id mock_config_dir = MagicMock() mock_config_dir.__truediv__.return_value = mock_id_file mock_path.home.return_value.__truediv__.return_value.__truediv__.return_value = ( mock_config_dir ) # Reset singleton PostHogTelemetryClient.destroy_client() client = PostHogTelemetryClient() assert client.installation_id == existing_id @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_record_event_when_disabled(self, mock_path, mock_posthog, monkeypatch): """Test that events are not recorded when telemetry is disabled.""" from cua_core.telemetry.posthog import PostHogTelemetryClient # Disable telemetry explicitly using the correct environment variable monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "false") # Mock file system mock_storage_dir = MagicMock() mock_storage_dir.exists.return_value = False mock_path.return_value.parent.parent = MagicMock() mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir # Reset singleton PostHogTelemetryClient.destroy_client() client = PostHogTelemetryClient() client.record_event("test_event", {"key": "value"}) # PostHog capture should not be called at all when telemetry is disabled mock_posthog.capture.assert_not_called() @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_record_event_when_enabled(self, mock_path, mock_posthog, monkeypatch): """Test that events are recorded when telemetry is enabled.""" from cua_core.telemetry.posthog import PostHogTelemetryClient # Enable telemetry monkeypatch.delenv("CUA_TELEMETRY_DISABLED", raising=False) monkeypatch.setenv("CUA_TELEMETRY_ENABLED", "true") # Mock file system mock_storage_dir = MagicMock() mock_storage_dir.exists.return_value = False mock_path.return_value.parent.parent = MagicMock() mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir # Reset singleton PostHogTelemetryClient.destroy_client() client = PostHogTelemetryClient() client.initialized = True # Pretend it's initialized event_name = "test_event" event_props = {"key": "value"} client.record_event(event_name, event_props) # PostHog capture should be called assert mock_posthog.capture.call_count >= 1 @patch("cua_core.telemetry.posthog.posthog") @patch("cua_core.telemetry.posthog.Path") def test_singleton_pattern(self, mock_path, mock_posthog, disable_telemetry): """Test that get_client returns the same instance.""" from cua_core.telemetry.posthog import PostHogTelemetryClient # Mock file system mock_storage_dir = MagicMock() mock_storage_dir.exists.return_value = False mock_path.return_value.parent.parent = MagicMock() mock_path.return_value.parent.parent.__truediv__.return_value = mock_storage_dir # Reset singleton PostHogTelemetryClient.destroy_client() client1 = PostHogTelemetryClient.get_client() client2 = PostHogTelemetryClient.get_client() assert client1 is client2 class TestRecordEvent: """Test the public record_event function (SRP: Only tests public API).""" @patch("cua_core.telemetry.posthog.PostHogTelemetryClient") def test_record_event_calls_client(self, mock_client_class, disable_telemetry): """Test that record_event delegates to the client.""" from cua_core.telemetry import record_event mock_client_instance = Mock() mock_client_class.get_client.return_value = mock_client_instance event_name = "test_event" event_props = {"key": "value"} record_event(event_name, event_props) mock_client_instance.record_event.assert_called_once_with(event_name, event_props) @patch("cua_core.telemetry.posthog.PostHogTelemetryClient") def test_record_event_without_properties(self, mock_client_class, disable_telemetry): """Test that record_event works without properties.""" from cua_core.telemetry import record_event mock_client_instance = Mock() mock_client_class.get_client.return_value = mock_client_instance event_name = "test_event" record_event(event_name) mock_client_instance.record_event.assert_called_once_with(event_name, {}) class TestDestroyTelemetryClient: """Test client destruction (SRP: Only tests cleanup).""" @patch("cua_core.telemetry.posthog.PostHogTelemetryClient") def test_destroy_client_calls_class_method(self, mock_client_class): """Test that destroy_telemetry_client delegates correctly.""" from cua_core.telemetry import destroy_telemetry_client destroy_telemetry_client() mock_client_class.destroy_client.assert_called_once()