From 0023eb098293bf88230e3c9fa6833da17c5a3df0 Mon Sep 17 00:00:00 2001 From: Rohit Malhotra Date: Tue, 27 May 2025 18:26:36 -0400 Subject: [PATCH] (Hotfix): Handle cases where user secrets store doesn't exist (#8745) Co-authored-by: openhands --- openhands/server/routes/secrets.py | 44 ++++++++++++++---------------- tests/unit/test_secrets_api.py | 33 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/openhands/server/routes/secrets.py b/openhands/server/routes/secrets.py index 9535e4a6c4..41cb7183d3 100644 --- a/openhands/server/routes/secrets.py +++ b/openhands/server/routes/secrets.py @@ -188,10 +188,7 @@ async def load_custom_secrets_names( ) -> GETCustomSecrets | JSONResponse: try: if not user_secrets: - return JSONResponse( - status_code=status.HTTP_404_NOT_FOUND, - content={'error': 'User secrets not found'}, - ) + return GETCustomSecrets(custom_secrets=[]) custom_secrets: list[CustomSecretWithoutValueModel] = [] if user_secrets.custom_secrets: @@ -220,31 +217,30 @@ async def create_custom_secret( ) -> JSONResponse: try: existing_secrets = await secrets_store.load() - if existing_secrets: - custom_secrets = dict(existing_secrets.custom_secrets) + custom_secrets = dict(existing_secrets.custom_secrets) if existing_secrets else {} - secret_name = incoming_secret.name - secret_value = incoming_secret.value - secret_description = incoming_secret.description + secret_name = incoming_secret.name + secret_value = incoming_secret.value + secret_description = incoming_secret.description - if secret_name in custom_secrets: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={'message': f'Secret {secret_name} already exists'}, - ) - - custom_secrets[secret_name] = CustomSecret( - secret=secret_value, - description=secret_description or '', + if secret_name in custom_secrets: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={'message': f'Secret {secret_name} already exists'}, ) - # Create a new UserSecrets that preserves provider tokens - updated_user_secrets = UserSecrets( - custom_secrets=custom_secrets, - provider_tokens=existing_secrets.provider_tokens, - ) + custom_secrets[secret_name] = CustomSecret( + secret=secret_value, + description=secret_description or '', + ) - await secrets_store.store(updated_user_secrets) + # Create a new UserSecrets that preserves provider tokens + updated_user_secrets = UserSecrets( + custom_secrets=custom_secrets, + provider_tokens=existing_secrets.provider_tokens if existing_secrets else {}, + ) + + await secrets_store.store(updated_user_secrets) return JSONResponse( status_code=status.HTTP_201_CREATED, diff --git a/tests/unit/test_secrets_api.py b/tests/unit/test_secrets_api.py index dc210610d7..44443d9cfa 100644 --- a/tests/unit/test_secrets_api.py +++ b/tests/unit/test_secrets_api.py @@ -138,6 +138,39 @@ async def test_add_custom_secret(test_client, file_secrets_store): ) +@pytest.mark.asyncio +async def test_create_custom_secret_with_no_existing_secrets( + test_client, file_secrets_store +): + """Test creating a custom secret when there are no existing secrets at all.""" + + # Don't store any initial settings - this simulates a completely new user + # or a situation where the secrets store is empty + + # Make the POST request to add a custom secret + add_secret_data = { + 'name': 'NEW_API_KEY', + 'value': 'new-api-key-value', + 'description': 'Test API Key', + } + response = test_client.post('/api/secrets', json=add_secret_data) + assert response.status_code == 201 + + # Verify that the settings were stored with the new secret + stored_settings = await file_secrets_store.load() + + # Check that the secret was added + assert 'NEW_API_KEY' in stored_settings.custom_secrets + assert ( + stored_settings.custom_secrets['NEW_API_KEY'].secret.get_secret_value() + == 'new-api-key-value' + ) + assert stored_settings.custom_secrets['NEW_API_KEY'].description == 'Test API Key' + + # Check that provider_tokens is an empty dict, not None + assert stored_settings.provider_tokens == {} + + @pytest.mark.asyncio async def test_update_existing_custom_secret(test_client, file_secrets_store): """Test updating an existing custom secret's name and description (cannot change value once set)."""