Revert "chore(backend): Add better PostHog tracking" (#11749)

This commit is contained in:
sp.wack 2025-12-01 17:58:03 +04:00 committed by GitHub
parent d9731b6850
commit 96f13b15e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 0 additions and 931 deletions

View File

@ -31,7 +31,6 @@ from openhands.server.services.conversation_service import create_provider_token
from openhands.server.shared import config
from openhands.server.user_auth import get_access_token
from openhands.server.user_auth.user_auth import get_user_auth
from openhands.utils.posthog_tracker import track_user_signup_completed
with warnings.catch_warnings():
warnings.simplefilter('ignore')
@ -370,12 +369,6 @@ async def accept_tos(request: Request):
logger.info(f'User {user_id} accepted TOS')
# Track user signup completion in PostHog
track_user_signup_completed(
user_id=user_id,
signup_timestamp=user_settings.accepted_tos.isoformat(),
)
response = JSONResponse(
status_code=status.HTTP_200_OK, content={'redirect_url': redirect_url}
)

View File

@ -28,7 +28,6 @@ from storage.subscription_access import SubscriptionAccess
from openhands.server.user_auth import get_user_id
from openhands.utils.http_session import httpx_verify_option
from openhands.utils.posthog_tracker import track_credits_purchased
stripe.api_key = STRIPE_API_KEY
billing_router = APIRouter(prefix='/api/billing')
@ -458,20 +457,6 @@ async def success_callback(session_id: str, request: Request):
)
session.commit()
# Track credits purchased in PostHog
try:
track_credits_purchased(
user_id=billing_session.user_id,
amount_usd=amount_subtotal / 100, # Convert cents to dollars
credits_added=add_credits,
stripe_session_id=session_id,
)
except Exception as e:
logger.warning(
f'Failed to track credits purchase: {e}',
extra={'user_id': billing_session.user_id, 'error': str(e)},
)
return RedirectResponse(
f'{request.base_url}settings/billing?checkout=success', status_code=302
)

View File

@ -42,10 +42,6 @@ from openhands.core.exceptions import (
from openhands.core.logger import LOG_ALL_EVENTS
from openhands.core.logger import openhands_logger as logger
from openhands.core.schema import AgentState
from openhands.utils.posthog_tracker import (
track_agent_task_completed,
track_credit_limit_reached,
)
from openhands.events import (
EventSource,
EventStream,
@ -713,20 +709,6 @@ class AgentController:
EventSource.ENVIRONMENT,
)
# Track agent task completion in PostHog
if new_state == AgentState.FINISHED:
try:
# Get app_mode from environment, default to 'oss'
app_mode = os.environ.get('APP_MODE', 'oss')
track_agent_task_completed(
conversation_id=self.id,
user_id=self.user_id,
app_mode=app_mode,
)
except Exception as e:
# Don't let tracking errors interrupt the agent
self.log('warning', f'Failed to track agent completion: {e}')
# Save state whenever agent state changes to ensure we don't lose state
# in case of crashes or unexpected circumstances
self.save_state()
@ -905,18 +887,6 @@ class AgentController:
self.state_tracker.run_control_flags()
except Exception as e:
logger.warning('Control flag limits hit')
# Track credit limit reached if it's a budget exception
if 'budget' in str(e).lower() and self.state.budget_flag:
try:
track_credit_limit_reached(
conversation_id=self.id,
user_id=self.user_id,
current_budget=self.state.budget_flag.current_value,
max_budget=self.state.budget_flag.max_value,
)
except Exception as track_error:
# Don't let tracking errors interrupt the agent
self.log('warning', f'Failed to track credit limit: {track_error}')
await self._react_to_exception(e)
return

View File

@ -26,13 +26,11 @@ from openhands.microagent.types import (
)
from openhands.server.dependencies import get_dependencies
from openhands.server.shared import server_config
from openhands.server.types import AppMode
from openhands.server.user_auth import (
get_access_token,
get_provider_tokens,
get_user_id,
)
from openhands.utils.posthog_tracker import alias_user_identities
app = APIRouter(prefix='/api/user', dependencies=get_dependencies())
@ -119,14 +117,6 @@ async def get_user(
try:
user: User = await client.get_user()
# Alias git provider login with Keycloak user ID in PostHog (SaaS mode only)
if user_id and user.login and server_config.app_mode == AppMode.SAAS:
alias_user_identities(
keycloak_user_id=user_id,
git_login=user.login,
)
return user
except UnknownException as e:

View File

@ -1,270 +0,0 @@
"""PostHog tracking utilities for OpenHands events."""
import os
from openhands.core.logger import openhands_logger as logger
# Lazy import posthog to avoid import errors in environments where it's not installed
posthog = None
def _init_posthog():
"""Initialize PostHog client lazily."""
global posthog
if posthog is None:
try:
import posthog as ph
posthog = ph
posthog.api_key = os.environ.get(
'POSTHOG_CLIENT_KEY', 'phc_3ESMmY9SgqEAGBB6sMGK5ayYHkeUuknH2vP6FmWH9RA'
)
posthog.host = os.environ.get('POSTHOG_HOST', 'https://us.i.posthog.com')
except ImportError:
logger.warning(
'PostHog not installed. Analytics tracking will be disabled.'
)
posthog = None
def track_agent_task_completed(
conversation_id: str,
user_id: str | None = None,
app_mode: str | None = None,
) -> None:
"""Track when an agent completes a task.
Args:
conversation_id: The ID of the conversation/session
user_id: The ID of the user (optional, may be None for unauthenticated users)
app_mode: The application mode (saas/oss), optional
"""
_init_posthog()
if posthog is None:
return
# Use conversation_id as distinct_id if user_id is not available
# This ensures we can track completions even for anonymous users
distinct_id = user_id if user_id else f'conversation_{conversation_id}'
try:
posthog.capture(
distinct_id=distinct_id,
event='agent_task_completed',
properties={
'conversation_id': conversation_id,
'user_id': user_id,
'app_mode': app_mode or 'unknown',
},
)
logger.debug(
'posthog_track',
extra={
'event': 'agent_task_completed',
'conversation_id': conversation_id,
'user_id': user_id,
},
)
except Exception as e:
logger.warning(
f'Failed to track agent_task_completed to PostHog: {e}',
extra={
'conversation_id': conversation_id,
'error': str(e),
},
)
def track_user_signup_completed(
user_id: str,
signup_timestamp: str,
) -> None:
"""Track when a user completes signup by accepting TOS.
Args:
user_id: The ID of the user (Keycloak user ID)
signup_timestamp: ISO format timestamp of when TOS was accepted
"""
_init_posthog()
if posthog is None:
return
try:
posthog.capture(
distinct_id=user_id,
event='user_signup_completed',
properties={
'user_id': user_id,
'signup_timestamp': signup_timestamp,
},
)
logger.debug(
'posthog_track',
extra={
'event': 'user_signup_completed',
'user_id': user_id,
},
)
except Exception as e:
logger.warning(
f'Failed to track user_signup_completed to PostHog: {e}',
extra={
'user_id': user_id,
'error': str(e),
},
)
def track_credit_limit_reached(
conversation_id: str,
user_id: str | None = None,
current_budget: float = 0.0,
max_budget: float = 0.0,
) -> None:
"""Track when a user reaches their credit limit during a conversation.
Args:
conversation_id: The ID of the conversation/session
user_id: The ID of the user (optional, may be None for unauthenticated users)
current_budget: The current budget spent
max_budget: The maximum budget allowed
"""
_init_posthog()
if posthog is None:
return
distinct_id = user_id if user_id else f'conversation_{conversation_id}'
try:
posthog.capture(
distinct_id=distinct_id,
event='credit_limit_reached',
properties={
'conversation_id': conversation_id,
'user_id': user_id,
'current_budget': current_budget,
'max_budget': max_budget,
},
)
logger.debug(
'posthog_track',
extra={
'event': 'credit_limit_reached',
'conversation_id': conversation_id,
'user_id': user_id,
'current_budget': current_budget,
'max_budget': max_budget,
},
)
except Exception as e:
logger.warning(
f'Failed to track credit_limit_reached to PostHog: {e}',
extra={
'conversation_id': conversation_id,
'error': str(e),
},
)
def track_credits_purchased(
user_id: str,
amount_usd: float,
credits_added: float,
stripe_session_id: str,
) -> None:
"""Track when a user successfully purchases credits.
Args:
user_id: The ID of the user (Keycloak user ID)
amount_usd: The amount paid in USD (cents converted to dollars)
credits_added: The number of credits added to the user's account
stripe_session_id: The Stripe checkout session ID
"""
_init_posthog()
if posthog is None:
return
try:
posthog.capture(
distinct_id=user_id,
event='credits_purchased',
properties={
'user_id': user_id,
'amount_usd': amount_usd,
'credits_added': credits_added,
'stripe_session_id': stripe_session_id,
},
)
logger.debug(
'posthog_track',
extra={
'event': 'credits_purchased',
'user_id': user_id,
'amount_usd': amount_usd,
'credits_added': credits_added,
},
)
except Exception as e:
logger.warning(
f'Failed to track credits_purchased to PostHog: {e}',
extra={
'user_id': user_id,
'error': str(e),
},
)
def alias_user_identities(
keycloak_user_id: str,
git_login: str,
) -> None:
"""Alias a user's Keycloak ID with their git provider login for unified tracking.
This allows PostHog to link events tracked from the frontend (using git provider login)
with events tracked from the backend (using Keycloak user ID).
PostHog Python alias syntax: alias(previous_id, distinct_id)
- previous_id: The old/previous distinct ID that will be merged
- distinct_id: The new/canonical distinct ID to merge into
For our use case:
- Git provider login is the previous_id (first used in frontend, before backend auth)
- Keycloak user ID is the distinct_id (canonical backend ID)
- Result: All events with git login will be merged into Keycloak user ID
Args:
keycloak_user_id: The Keycloak user ID (canonical distinct_id)
git_login: The git provider username (GitHub/GitLab/Bitbucket) to merge
Reference:
https://github.com/PostHog/posthog-python/blob/master/posthog/client.py
"""
_init_posthog()
if posthog is None:
return
try:
# Merge git provider login into Keycloak user ID
# posthog.alias(previous_id, distinct_id) - official Python SDK signature
posthog.alias(git_login, keycloak_user_id)
logger.debug(
'posthog_alias',
extra={
'previous_id': git_login,
'distinct_id': keycloak_user_id,
},
)
except Exception as e:
logger.warning(
f'Failed to alias user identities in PostHog: {e}',
extra={
'keycloak_user_id': keycloak_user_id,
'git_login': git_login,
'error': str(e),
},
)

View File

@ -1,243 +0,0 @@
"""Integration tests for PostHog tracking in AgentController."""
import asyncio
from unittest.mock import MagicMock, patch
import pytest
from openhands.controller.agent import Agent
from openhands.controller.agent_controller import AgentController
from openhands.core.config import OpenHandsConfig
from openhands.core.config.agent_config import AgentConfig
from openhands.core.config.llm_config import LLMConfig
from openhands.core.schema import AgentState
from openhands.events import EventSource, EventStream
from openhands.events.action.message import SystemMessageAction
from openhands.llm.llm_registry import LLMRegistry
from openhands.server.services.conversation_stats import ConversationStats
from openhands.storage.memory import InMemoryFileStore
@pytest.fixture(scope='function')
def event_loop():
"""Create event loop for async tests."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture
def mock_agent_with_stats():
"""Create a mock agent with properly connected LLM registry and conversation stats."""
import uuid
# Create LLM registry
config = OpenHandsConfig()
llm_registry = LLMRegistry(config=config)
# Create conversation stats
file_store = InMemoryFileStore({})
conversation_id = f'test-conversation-{uuid.uuid4()}'
conversation_stats = ConversationStats(
file_store=file_store, conversation_id=conversation_id, user_id='test-user'
)
# Connect registry to stats
llm_registry.subscribe(conversation_stats.register_llm)
# Create mock agent
agent = MagicMock(spec=Agent)
agent_config = MagicMock(spec=AgentConfig)
llm_config = LLMConfig(
model='gpt-4o',
api_key='test_key',
num_retries=2,
retry_min_wait=1,
retry_max_wait=2,
)
agent_config.disabled_microagents = []
agent_config.enable_mcp = True
agent_config.enable_stuck_detection = True
llm_registry.service_to_llm.clear()
mock_llm = llm_registry.get_llm('agent_llm', llm_config)
agent.llm = mock_llm
agent.name = 'test-agent'
agent.sandbox_plugins = []
agent.config = agent_config
agent.llm_registry = llm_registry
agent.prompt_manager = MagicMock()
# Add a proper system message mock
system_message = SystemMessageAction(
content='Test system message', tools=['test_tool']
)
system_message._source = EventSource.AGENT
system_message._id = -1 # Set invalid ID to avoid the ID check
agent.get_system_message.return_value = system_message
return agent, conversation_stats, llm_registry
@pytest.fixture
def mock_event_stream():
"""Create a mock event stream."""
mock = MagicMock(
spec=EventStream,
event_stream=EventStream(sid='test', file_store=InMemoryFileStore({})),
)
mock.get_latest_event_id.return_value = 0
return mock
@pytest.mark.asyncio
async def test_agent_finish_triggers_posthog_tracking(
mock_agent_with_stats, mock_event_stream
):
"""Test that setting agent state to FINISHED triggers PostHog tracking."""
mock_agent, conversation_stats, llm_registry = mock_agent_with_stats
controller = AgentController(
agent=mock_agent,
event_stream=mock_event_stream,
conversation_stats=conversation_stats,
iteration_delta=10,
sid='test-conversation-123',
user_id='test-user-456',
confirmation_mode=False,
headless_mode=True,
)
with (
patch('openhands.utils.posthog_tracker.posthog') as mock_posthog,
patch('os.environ.get') as mock_env_get,
):
# Setup mocks
mock_posthog.capture = MagicMock()
mock_env_get.return_value = 'saas'
# Initialize posthog in the tracker module
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
# Set agent state to FINISHED
await controller.set_agent_state_to(AgentState.FINISHED)
# Verify PostHog tracking was called
mock_posthog.capture.assert_called_once()
call_args = mock_posthog.capture.call_args
assert call_args[1]['distinct_id'] == 'test-user-456'
assert call_args[1]['event'] == 'agent_task_completed'
assert 'conversation_id' in call_args[1]['properties']
assert call_args[1]['properties']['user_id'] == 'test-user-456'
assert call_args[1]['properties']['app_mode'] == 'saas'
await controller.close()
@pytest.mark.asyncio
async def test_agent_finish_without_user_id(mock_agent_with_stats, mock_event_stream):
"""Test tracking when user_id is None."""
mock_agent, conversation_stats, llm_registry = mock_agent_with_stats
controller = AgentController(
agent=mock_agent,
event_stream=mock_event_stream,
conversation_stats=conversation_stats,
iteration_delta=10,
sid='test-conversation-789',
user_id=None,
confirmation_mode=False,
headless_mode=True,
)
with (
patch('openhands.utils.posthog_tracker.posthog') as mock_posthog,
patch('os.environ.get') as mock_env_get,
):
mock_posthog.capture = MagicMock()
mock_env_get.return_value = 'oss'
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
await controller.set_agent_state_to(AgentState.FINISHED)
mock_posthog.capture.assert_called_once()
call_args = mock_posthog.capture.call_args
# When user_id is None, distinct_id should be conversation_id
assert call_args[1]['distinct_id'].startswith('conversation_')
assert call_args[1]['properties']['user_id'] is None
await controller.close()
@pytest.mark.asyncio
async def test_other_states_dont_trigger_tracking(
mock_agent_with_stats, mock_event_stream
):
"""Test that non-FINISHED states don't trigger tracking."""
mock_agent, conversation_stats, llm_registry = mock_agent_with_stats
controller = AgentController(
agent=mock_agent,
event_stream=mock_event_stream,
conversation_stats=conversation_stats,
iteration_delta=10,
sid='test-conversation-999',
confirmation_mode=False,
headless_mode=True,
)
with patch('openhands.utils.posthog_tracker.posthog') as mock_posthog:
mock_posthog.capture = MagicMock()
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
# Try different states
await controller.set_agent_state_to(AgentState.RUNNING)
await controller.set_agent_state_to(AgentState.PAUSED)
await controller.set_agent_state_to(AgentState.STOPPED)
# PostHog should not be called for non-FINISHED states
mock_posthog.capture.assert_not_called()
await controller.close()
@pytest.mark.asyncio
async def test_tracking_error_doesnt_break_agent(
mock_agent_with_stats, mock_event_stream
):
"""Test that tracking errors don't interrupt agent operation."""
mock_agent, conversation_stats, llm_registry = mock_agent_with_stats
controller = AgentController(
agent=mock_agent,
event_stream=mock_event_stream,
conversation_stats=conversation_stats,
iteration_delta=10,
sid='test-conversation-error',
confirmation_mode=False,
headless_mode=True,
)
with patch('openhands.utils.posthog_tracker.posthog') as mock_posthog:
mock_posthog.capture = MagicMock(side_effect=Exception('PostHog error'))
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
# Should not raise an exception
await controller.set_agent_state_to(AgentState.FINISHED)
# Agent state should still be FINISHED despite tracking error
assert controller.state.agent_state == AgentState.FINISHED
await controller.close()

View File

@ -1,356 +0,0 @@
"""Unit tests for PostHog tracking utilities."""
from unittest.mock import MagicMock, patch
import pytest
from openhands.utils.posthog_tracker import (
alias_user_identities,
track_agent_task_completed,
track_credit_limit_reached,
track_credits_purchased,
track_user_signup_completed,
)
@pytest.fixture
def mock_posthog():
"""Mock the posthog module."""
with patch('openhands.utils.posthog_tracker.posthog') as mock_ph:
mock_ph.capture = MagicMock()
yield mock_ph
def test_track_agent_task_completed_with_user_id(mock_posthog):
"""Test tracking agent task completion with user ID."""
# Initialize posthog manually in the test
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_agent_task_completed(
conversation_id='test-conversation-123',
user_id='user-456',
app_mode='saas',
)
mock_posthog.capture.assert_called_once_with(
distinct_id='user-456',
event='agent_task_completed',
properties={
'conversation_id': 'test-conversation-123',
'user_id': 'user-456',
'app_mode': 'saas',
},
)
def test_track_agent_task_completed_without_user_id(mock_posthog):
"""Test tracking agent task completion without user ID (anonymous)."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_agent_task_completed(
conversation_id='test-conversation-789',
user_id=None,
app_mode='oss',
)
mock_posthog.capture.assert_called_once_with(
distinct_id='conversation_test-conversation-789',
event='agent_task_completed',
properties={
'conversation_id': 'test-conversation-789',
'user_id': None,
'app_mode': 'oss',
},
)
def test_track_agent_task_completed_default_app_mode(mock_posthog):
"""Test tracking with default app_mode."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_agent_task_completed(
conversation_id='test-conversation-999',
user_id='user-111',
)
mock_posthog.capture.assert_called_once_with(
distinct_id='user-111',
event='agent_task_completed',
properties={
'conversation_id': 'test-conversation-999',
'user_id': 'user-111',
'app_mode': 'unknown',
},
)
def test_track_agent_task_completed_handles_errors(mock_posthog):
"""Test that tracking errors are handled gracefully."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.capture.side_effect = Exception('PostHog API error')
# Should not raise an exception
track_agent_task_completed(
conversation_id='test-conversation-error',
user_id='user-error',
app_mode='saas',
)
def test_track_agent_task_completed_when_posthog_not_installed():
"""Test tracking when posthog is not installed."""
import openhands.utils.posthog_tracker as tracker
# Simulate posthog not being installed
tracker.posthog = None
# Should not raise an exception
track_agent_task_completed(
conversation_id='test-conversation-no-ph',
user_id='user-no-ph',
app_mode='oss',
)
def test_track_user_signup_completed(mock_posthog):
"""Test tracking user signup completion."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_user_signup_completed(
user_id='test-user-123',
signup_timestamp='2025-01-15T10:30:00Z',
)
mock_posthog.capture.assert_called_once_with(
distinct_id='test-user-123',
event='user_signup_completed',
properties={
'user_id': 'test-user-123',
'signup_timestamp': '2025-01-15T10:30:00Z',
},
)
def test_track_user_signup_completed_handles_errors(mock_posthog):
"""Test that user signup tracking errors are handled gracefully."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.capture.side_effect = Exception('PostHog API error')
# Should not raise an exception
track_user_signup_completed(
user_id='test-user-error',
signup_timestamp='2025-01-15T12:00:00Z',
)
def test_track_user_signup_completed_when_posthog_not_installed():
"""Test user signup tracking when posthog is not installed."""
import openhands.utils.posthog_tracker as tracker
# Simulate posthog not being installed
tracker.posthog = None
# Should not raise an exception
track_user_signup_completed(
user_id='test-user-no-ph',
signup_timestamp='2025-01-15T13:00:00Z',
)
def test_track_credit_limit_reached_with_user_id(mock_posthog):
"""Test tracking credit limit reached with user ID."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_credit_limit_reached(
conversation_id='test-conversation-456',
user_id='user-789',
current_budget=10.50,
max_budget=10.00,
)
mock_posthog.capture.assert_called_once_with(
distinct_id='user-789',
event='credit_limit_reached',
properties={
'conversation_id': 'test-conversation-456',
'user_id': 'user-789',
'current_budget': 10.50,
'max_budget': 10.00,
},
)
def test_track_credit_limit_reached_without_user_id(mock_posthog):
"""Test tracking credit limit reached without user ID (anonymous)."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_credit_limit_reached(
conversation_id='test-conversation-999',
user_id=None,
current_budget=5.25,
max_budget=5.00,
)
mock_posthog.capture.assert_called_once_with(
distinct_id='conversation_test-conversation-999',
event='credit_limit_reached',
properties={
'conversation_id': 'test-conversation-999',
'user_id': None,
'current_budget': 5.25,
'max_budget': 5.00,
},
)
def test_track_credit_limit_reached_handles_errors(mock_posthog):
"""Test that credit limit tracking errors are handled gracefully."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.capture.side_effect = Exception('PostHog API error')
# Should not raise an exception
track_credit_limit_reached(
conversation_id='test-conversation-error',
user_id='user-error',
current_budget=15.00,
max_budget=10.00,
)
def test_track_credit_limit_reached_when_posthog_not_installed():
"""Test credit limit tracking when posthog is not installed."""
import openhands.utils.posthog_tracker as tracker
# Simulate posthog not being installed
tracker.posthog = None
# Should not raise an exception
track_credit_limit_reached(
conversation_id='test-conversation-no-ph',
user_id='user-no-ph',
current_budget=8.00,
max_budget=5.00,
)
def test_track_credits_purchased(mock_posthog):
"""Test tracking credits purchased."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
track_credits_purchased(
user_id='test-user-999',
amount_usd=50.00,
credits_added=50.00,
stripe_session_id='cs_test_abc123',
)
mock_posthog.capture.assert_called_once_with(
distinct_id='test-user-999',
event='credits_purchased',
properties={
'user_id': 'test-user-999',
'amount_usd': 50.00,
'credits_added': 50.00,
'stripe_session_id': 'cs_test_abc123',
},
)
def test_track_credits_purchased_handles_errors(mock_posthog):
"""Test that credits purchased tracking errors are handled gracefully."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.capture.side_effect = Exception('PostHog API error')
# Should not raise an exception
track_credits_purchased(
user_id='test-user-error',
amount_usd=100.00,
credits_added=100.00,
stripe_session_id='cs_test_error',
)
def test_track_credits_purchased_when_posthog_not_installed():
"""Test credits purchased tracking when posthog is not installed."""
import openhands.utils.posthog_tracker as tracker
# Simulate posthog not being installed
tracker.posthog = None
# Should not raise an exception
track_credits_purchased(
user_id='test-user-no-ph',
amount_usd=25.00,
credits_added=25.00,
stripe_session_id='cs_test_no_ph',
)
def test_alias_user_identities(mock_posthog):
"""Test aliasing user identities.
Verifies that posthog.alias(previous_id, distinct_id) is called correctly
where git_login is the previous_id and keycloak_user_id is the distinct_id.
"""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.alias = MagicMock()
alias_user_identities(
keycloak_user_id='keycloak-123',
git_login='git-user',
)
# Verify: posthog.alias(previous_id='git-user', distinct_id='keycloak-123')
mock_posthog.alias.assert_called_once_with('git-user', 'keycloak-123')
def test_alias_user_identities_handles_errors(mock_posthog):
"""Test that aliasing errors are handled gracefully."""
import openhands.utils.posthog_tracker as tracker
tracker.posthog = mock_posthog
mock_posthog.alias = MagicMock(side_effect=Exception('PostHog API error'))
# Should not raise an exception
alias_user_identities(
keycloak_user_id='keycloak-error',
git_login='git-error',
)
def test_alias_user_identities_when_posthog_not_installed():
"""Test aliasing when posthog is not installed."""
import openhands.utils.posthog_tracker as tracker
# Simulate posthog not being installed
tracker.posthog = None
# Should not raise an exception
alias_user_identities(
keycloak_user_id='keycloak-no-ph',
git_login='git-no-ph',
)