fix(telemetry): Fix enterprise linting errors

- Fix mypy type error: collector_results should store int not str
- Remove unused variables in test files (F841 errors)
- All 99 tests still passing after fixes

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
openhands 2025-12-19 15:46:18 +00:00
parent be4fa60970
commit 92a86624b0
3 changed files with 65 additions and 57 deletions

View File

@ -15,6 +15,7 @@ from telemetry.registry import CollectorRegistry
# Optional import for Replicated SDK (to be implemented in M4) # Optional import for Replicated SDK (to be implemented in M4)
try: try:
from replicated import InstanceStatus, ReplicatedClient from replicated import InstanceStatus, ReplicatedClient
REPLICATED_AVAILABLE = True REPLICATED_AVAILABLE = True
except ImportError: except ImportError:
REPLICATED_AVAILABLE = False REPLICATED_AVAILABLE = False
@ -233,7 +234,9 @@ class TelemetryService:
# If identity was just established, it will be created during upload # If identity was just established, it will be created during upload
# Continue with short interval for one more cycle to ensure upload succeeds # Continue with short interval for one more cycle to ensure upload succeeds
if not was_established_before and self._is_identity_established(): if not was_established_before and self._is_identity_established():
logger.info('Identity just established - first upload completed') logger.info(
'Identity just established - first upload completed'
)
logger.info('Metrics upload completed') logger.info('Metrics upload completed')
@ -268,9 +271,11 @@ class TelemetryService:
""" """
try: try:
with session_maker() as session: with session_maker() as session:
identity = session.query(TelemetryIdentity).filter( identity = (
TelemetryIdentity.id == 1 session.query(TelemetryIdentity)
).first() .filter(TelemetryIdentity.id == 1)
.first()
)
# Identity is established if we have both customer_id and instance_id # Identity is established if we have both customer_id and instance_id
return ( return (
@ -316,9 +321,11 @@ class TelemetryService:
if not last_uploaded: if not last_uploaded:
# Check if we have any pending metrics to upload # Check if we have any pending metrics to upload
pending_count = session.query(TelemetryMetrics).filter( pending_count = (
TelemetryMetrics.uploaded_at.is_(None) session.query(TelemetryMetrics)
).count() .filter(TelemetryMetrics.uploaded_at.is_(None))
.count()
)
return pending_count > 0 return pending_count > 0
hours_since = ( hours_since = (
@ -355,7 +362,7 @@ class TelemetryService:
f'Collector {collector.collector_name} failed: {e}', f'Collector {collector.collector_name} failed: {e}',
exc_info=True, exc_info=True,
) )
collector_results[collector.collector_name] = f'error: {str(e)}' collector_results[collector.collector_name] = 0
# Store metrics in database # Store metrics in database
with session_maker() as session: with session_maker() as session:
@ -483,13 +490,11 @@ class TelemetryService:
return None return None
def _get_or_create_identity( def _get_or_create_identity(self, session, admin_email: str) -> TelemetryIdentity:
self, session, admin_email: str
) -> TelemetryIdentity:
"""Get or create telemetry identity with customer and instance IDs.""" """Get or create telemetry identity with customer and instance IDs."""
identity = session.query(TelemetryIdentity).filter( identity = (
TelemetryIdentity.id == 1 session.query(TelemetryIdentity).filter(TelemetryIdentity.id == 1).first()
).first() )
if not identity: if not identity:
identity = TelemetryIdentity(id=1) identity = TelemetryIdentity(id=1)

View File

@ -5,10 +5,7 @@ from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
from server.telemetry.service import TelemetryService from server.telemetry.service import TelemetryService
from storage.telemetry_identity import TelemetryIdentity
from storage.telemetry_metrics import TelemetryMetrics
@pytest.fixture @pytest.fixture
@ -40,10 +37,10 @@ class TestTelemetryServiceLifecycle:
"""Test that service starts and stops without errors.""" """Test that service starts and stops without errors."""
with patch.object( with patch.object(
fresh_telemetry_service, '_collection_loop', new_callable=AsyncMock fresh_telemetry_service, '_collection_loop', new_callable=AsyncMock
) as mock_collection: ):
with patch.object( with patch.object(
fresh_telemetry_service, '_upload_loop', new_callable=AsyncMock fresh_telemetry_service, '_upload_loop', new_callable=AsyncMock
) as mock_upload: ):
with patch.object( with patch.object(
fresh_telemetry_service, fresh_telemetry_service,
'_initial_collection_check', '_initial_collection_check',
@ -78,7 +75,7 @@ class TestTelemetryServiceLifecycle:
fresh_telemetry_service, fresh_telemetry_service,
'_initial_collection_check', '_initial_collection_check',
new_callable=AsyncMock, new_callable=AsyncMock,
) as mock_initial: ):
await fresh_telemetry_service.start() await fresh_telemetry_service.start()
# Wait for async task to be created # Wait for async task to be created
@ -114,7 +111,8 @@ class TestTelemetryServiceLifecycle:
# Verify tasks are the same (not recreated) # Verify tasks are the same (not recreated)
assert ( assert (
fresh_telemetry_service._collection_task == first_collection_task fresh_telemetry_service._collection_task
== first_collection_task
) )
# Clean up # Clean up
@ -286,16 +284,22 @@ class TestCollectionLoop:
pass pass
with patch.object( with patch.object(
fresh_telemetry_service, '_collect_metrics', side_effect=mock_collect_with_error fresh_telemetry_service,
'_collect_metrics',
side_effect=mock_collect_with_error,
): ):
with patch.object( with patch.object(
fresh_telemetry_service, '_should_collect', return_value=True fresh_telemetry_service, '_should_collect', return_value=True
): ):
with patch.object( with patch.object(
fresh_telemetry_service, '_is_identity_established', return_value=True fresh_telemetry_service,
'_is_identity_established',
return_value=True,
): ):
# Start collection loop # Start collection loop
task = asyncio.create_task(fresh_telemetry_service._collection_loop()) task = asyncio.create_task(
fresh_telemetry_service._collection_loop()
)
# Wait for multiple iterations # Wait for multiple iterations
await asyncio.sleep(0.3) await asyncio.sleep(0.3)
@ -354,13 +358,17 @@ class TestUploadLoop:
pass pass
with patch.object( with patch.object(
fresh_telemetry_service, '_upload_pending_metrics', side_effect=mock_upload_with_error fresh_telemetry_service,
'_upload_pending_metrics',
side_effect=mock_upload_with_error,
): ):
with patch.object( with patch.object(
fresh_telemetry_service, '_should_upload', return_value=True fresh_telemetry_service, '_should_upload', return_value=True
): ):
with patch.object( with patch.object(
fresh_telemetry_service, '_is_identity_established', return_value=True fresh_telemetry_service,
'_is_identity_established',
return_value=True,
): ):
# Start upload loop # Start upload loop
task = asyncio.create_task(fresh_telemetry_service._upload_loop()) task = asyncio.create_task(fresh_telemetry_service._upload_loop())
@ -392,7 +400,9 @@ class TestMetricsCollection:
mock_session.__exit__ = MagicMock(return_value=None) mock_session.__exit__ = MagicMock(return_value=None)
mock_session_maker.return_value = mock_session mock_session_maker.return_value = mock_session
with patch('server.telemetry.service.CollectorRegistry') as mock_registry_class: with patch(
'server.telemetry.service.CollectorRegistry'
) as mock_registry_class:
mock_registry = MagicMock() mock_registry = MagicMock()
mock_registry_class.return_value = mock_registry mock_registry_class.return_value = mock_registry
@ -464,14 +474,18 @@ class TestReplicatedIntegration:
with patch('server.telemetry.service.REPLICATED_AVAILABLE', True): with patch('server.telemetry.service.REPLICATED_AVAILABLE', True):
with patch('server.telemetry.service.InstanceStatus') as mock_status: with patch('server.telemetry.service.InstanceStatus') as mock_status:
mock_status.RUNNING = 'RUNNING' mock_status.RUNNING = 'RUNNING'
with patch('server.telemetry.service.ReplicatedClient') as mock_client_class: with patch(
'server.telemetry.service.ReplicatedClient'
) as mock_client_class:
mock_client = MagicMock() mock_client = MagicMock()
mock_customer = MagicMock() mock_customer = MagicMock()
mock_customer.customer_id = 'cust-123' mock_customer.customer_id = 'cust-123'
mock_instance = MagicMock() mock_instance = MagicMock()
mock_instance.instance_id = 'inst-456' mock_instance.instance_id = 'inst-456'
mock_customer.get_or_create_instance.return_value = mock_instance mock_customer.get_or_create_instance.return_value = (
mock_instance
)
mock_client.customer.get_or_create.return_value = mock_customer mock_client.customer.get_or_create.return_value = mock_customer
mock_client_class.return_value = mock_client mock_client_class.return_value = mock_client

View File

@ -1,11 +1,9 @@
"""Unit tests for the TelemetryService.""" """Unit tests for the TelemetryService."""
import asyncio
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
from server.telemetry.service import TelemetryService from server.telemetry.service import TelemetryService
@ -39,7 +37,6 @@ class TestTelemetryServiceInitialization:
def test_initialization_once(self, telemetry_service): def test_initialization_once(self, telemetry_service):
"""Test that __init__ only runs once.""" """Test that __init__ only runs once."""
initial_collection_interval = telemetry_service.collection_interval_days
telemetry_service.collection_interval_days = 999 telemetry_service.collection_interval_days = 999
# Create another "instance" (should be same singleton) # Create another "instance" (should be same singleton)
@ -132,9 +129,7 @@ class TestIdentityEstablishment:
assert not telemetry_service._is_identity_established() assert not telemetry_service._is_identity_established()
@patch('server.telemetry.service.session_maker') @patch('server.telemetry.service.session_maker')
def test_identity_established_complete( def test_identity_established_complete(self, mock_session_maker, telemetry_service):
self, mock_session_maker, telemetry_service
):
"""Test identity established when both customer_id and instance_id exist.""" """Test identity established when both customer_id and instance_id exist."""
mock_session = MagicMock() mock_session = MagicMock()
mock_session.__enter__ = MagicMock(return_value=mock_session) mock_session.__enter__ = MagicMock(return_value=mock_session)
@ -239,9 +234,7 @@ class TestUploadLogic:
assert telemetry_service._should_upload() assert telemetry_service._should_upload()
@patch('server.telemetry.service.session_maker') @patch('server.telemetry.service.session_maker')
def test_should_not_upload_no_pending( def test_should_not_upload_no_pending(self, mock_session_maker, telemetry_service):
self, mock_session_maker, telemetry_service
):
"""Test should not upload when no pending metrics exist.""" """Test should not upload when no pending metrics exist."""
mock_session = MagicMock() mock_session = MagicMock()
mock_session.__enter__ = MagicMock(return_value=mock_session) mock_session.__enter__ = MagicMock(return_value=mock_session)
@ -355,9 +348,7 @@ class TestGetAdminEmail:
"""Test when no admin email is available.""" """Test when no admin email is available."""
mock_getenv.return_value = None mock_getenv.return_value = None
mock_session.query.return_value.filter.return_value.filter.return_value.order_by.return_value.first.return_value = ( mock_session.query.return_value.filter.return_value.filter.return_value.order_by.return_value.first.return_value = None
None
)
email = telemetry_service._get_admin_email(mock_session) email = telemetry_service._get_admin_email(mock_session)
@ -388,7 +379,7 @@ class TestGetOrCreateIdentity:
mock_customer mock_customer
) )
identity = telemetry_service._get_or_create_identity( telemetry_service._get_or_create_identity(
mock_session, 'test@example.com' mock_session, 'test@example.com'
) )
@ -405,9 +396,7 @@ class TestGetOrCreateIdentity:
mock_identity mock_identity
) )
identity = telemetry_service._get_or_create_identity( telemetry_service._get_or_create_identity(mock_session, 'test@example.com')
mock_session, 'test@example.com'
)
# Should not create new instance since both IDs exist # Should not create new instance since both IDs exist
assert mock_session.add.call_count == 0 assert mock_session.add.call_count == 0
@ -425,9 +414,7 @@ class TestLicenseWarningStatus:
mock_session.__exit__ = MagicMock(return_value=None) mock_session.__exit__ = MagicMock(return_value=None)
mock_session_maker.return_value = mock_session mock_session_maker.return_value = mock_session
mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = ( mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = None
None
)
status = telemetry_service.get_license_warning_status() status = telemetry_service.get_license_warning_status()
@ -446,9 +433,7 @@ class TestLicenseWarningStatus:
mock_metric = MagicMock() mock_metric = MagicMock()
mock_metric.uploaded_at = datetime.now(timezone.utc) - timedelta(days=2) mock_metric.uploaded_at = datetime.now(timezone.utc) - timedelta(days=2)
mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = ( mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = mock_metric
mock_metric
)
status = telemetry_service.get_license_warning_status() status = telemetry_service.get_license_warning_status()
@ -466,9 +451,7 @@ class TestLicenseWarningStatus:
mock_metric = MagicMock() mock_metric = MagicMock()
mock_metric.uploaded_at = datetime.now(timezone.utc) - timedelta(days=5) mock_metric.uploaded_at = datetime.now(timezone.utc) - timedelta(days=5)
mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = ( mock_session.query.return_value.filter.return_value.order_by.return_value.first.return_value = mock_metric
mock_metric
)
status = telemetry_service.get_license_warning_status() status = telemetry_service.get_license_warning_status()
@ -489,7 +472,9 @@ class TestLifecycleManagement:
telemetry_service, '_upload_loop', new_callable=AsyncMock telemetry_service, '_upload_loop', new_callable=AsyncMock
): ):
with patch.object( with patch.object(
telemetry_service, '_initial_collection_check', new_callable=AsyncMock telemetry_service,
'_initial_collection_check',
new_callable=AsyncMock,
): ):
await telemetry_service.start() await telemetry_service.start()
@ -509,7 +494,9 @@ class TestLifecycleManagement:
telemetry_service, '_upload_loop', new_callable=AsyncMock telemetry_service, '_upload_loop', new_callable=AsyncMock
): ):
with patch.object( with patch.object(
telemetry_service, '_initial_collection_check', new_callable=AsyncMock telemetry_service,
'_initial_collection_check',
new_callable=AsyncMock,
): ):
await telemetry_service.start() await telemetry_service.start()
first_collection_task = telemetry_service._collection_task first_collection_task = telemetry_service._collection_task
@ -535,7 +522,9 @@ class TestLifecycleManagement:
telemetry_service, '_upload_loop', new_callable=AsyncMock telemetry_service, '_upload_loop', new_callable=AsyncMock
): ):
with patch.object( with patch.object(
telemetry_service, '_initial_collection_check', new_callable=AsyncMock telemetry_service,
'_initial_collection_check',
new_callable=AsyncMock,
): ):
await telemetry_service.start() await telemetry_service.start()
await telemetry_service.stop() await telemetry_service.stop()