mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
fix: handle invalid LiteLLM API keys during user migration (#12510)
This commit is contained in:
@@ -433,6 +433,14 @@ class LiteLlmManager:
|
||||
)
|
||||
|
||||
if not response.is_success:
|
||||
if response.status_code == 401:
|
||||
logger.warning(
|
||||
'invalid_litellm_key_during_update',
|
||||
extra={
|
||||
'user_id': keycloak_user_id,
|
||||
},
|
||||
)
|
||||
return
|
||||
logger.error(
|
||||
'error_updating_litellm_key',
|
||||
extra={
|
||||
|
||||
@@ -608,6 +608,73 @@ class TestLiteLlmManager:
|
||||
mock_http_client, 'test-user-id', 'test-team-id', 100.0
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_URL', 'http://test.com')
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_KEY', 'test-key')
|
||||
async def test_update_key_success(self, mock_http_client, mock_response):
|
||||
"""Test successful _update_key operation."""
|
||||
# Arrange
|
||||
mock_http_client.post.return_value = mock_response
|
||||
|
||||
# Act
|
||||
await LiteLlmManager._update_key(
|
||||
mock_http_client, 'test-user-id', 'test-api-key', team_id='test-team-id'
|
||||
)
|
||||
|
||||
# Assert
|
||||
mock_http_client.post.assert_called_once()
|
||||
call_args = mock_http_client.post.call_args
|
||||
assert 'http://test.com/key/update' in call_args[0]
|
||||
assert call_args[1]['json']['key'] == 'test-api-key'
|
||||
assert call_args[1]['json']['team_id'] == 'test-team-id'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('storage.lite_llm_manager.logger')
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_URL', 'http://test.com')
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_KEY', 'test-key')
|
||||
async def test_update_key_invalid_key_returns_gracefully(
|
||||
self, mock_logger, mock_http_client
|
||||
):
|
||||
"""Test _update_key handles 401 Unauthorized for invalid keys gracefully."""
|
||||
# Arrange
|
||||
error_response = MagicMock()
|
||||
error_response.is_success = False
|
||||
error_response.status_code = 401
|
||||
error_response.text = 'Unauthorized'
|
||||
mock_http_client.post.return_value = error_response
|
||||
|
||||
# Act
|
||||
await LiteLlmManager._update_key(
|
||||
mock_http_client, 'test-user-id', 'invalid-api-key', team_id='test-team-id'
|
||||
)
|
||||
|
||||
# Assert
|
||||
mock_logger.warning.assert_called_once_with(
|
||||
'invalid_litellm_key_during_update',
|
||||
extra={'user_id': 'test-user-id'},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_URL', 'http://test.com')
|
||||
@patch('storage.lite_llm_manager.LITE_LLM_API_KEY', 'test-key')
|
||||
async def test_update_key_other_error_raises_exception(self, mock_http_client):
|
||||
"""Test _update_key raises exception for non-401 errors."""
|
||||
# Arrange
|
||||
error_response = MagicMock()
|
||||
error_response.is_success = False
|
||||
error_response.status_code = 500
|
||||
error_response.text = 'Internal server error'
|
||||
error_response.raise_for_status.side_effect = httpx.HTTPStatusError(
|
||||
'Server error', request=MagicMock(), response=error_response
|
||||
)
|
||||
mock_http_client.post.return_value = error_response
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
await LiteLlmManager._update_key(
|
||||
mock_http_client, 'test-user-id', 'test-api-key', team_id='test-team-id'
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_key_success(self, mock_http_client, mock_response):
|
||||
"""Test successful _generate_key operation."""
|
||||
|
||||
@@ -88,13 +88,18 @@ class DbSessionInjector(BaseModel, Injector[async_sessionmaker]):
|
||||
)
|
||||
|
||||
async def _create_async_gcp_db_connection(self):
|
||||
gcp_connector = self._gcp_connector
|
||||
if gcp_connector is None:
|
||||
# Lazy import because lib does not import if user does not have posgres installed
|
||||
from google.cloud.sql.connector import Connector
|
||||
# Lazy import because lib does not import if user does not have postgres installed
|
||||
from google.cloud.sql.connector import Connector
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
gcp_connector = Connector(loop=loop)
|
||||
current_loop = asyncio.get_running_loop()
|
||||
gcp_connector = self._gcp_connector
|
||||
|
||||
# Create new connector if none exists or if event loop changed
|
||||
if (
|
||||
gcp_connector is None
|
||||
or getattr(gcp_connector, '_loop', None) != current_loop
|
||||
):
|
||||
gcp_connector = Connector(loop=current_loop)
|
||||
self._gcp_connector = gcp_connector
|
||||
|
||||
password = self.password
|
||||
|
||||
Reference in New Issue
Block a user