diff --git a/litellm/proxy/credential_endpoints/endpoints.py b/litellm/proxy/credential_endpoints/endpoints.py index 5b3507f9b6d..86e716f19e3 100644 --- a/litellm/proxy/credential_endpoints/endpoints.py +++ b/litellm/proxy/credential_endpoints/endpoints.py @@ -920,8 +920,6 @@ def update_db_credential( # update model info if encrypted_credential.credential_info: """Update credential info""" - if "credential_info" not in merged_credential.credential_info: - merged_credential.credential_info = {} merged_credential.credential_info.update(encrypted_credential.credential_info) return merged_credential diff --git a/tests/integration/management/test_credential_display_name.py b/tests/integration/management/test_credential_display_name.py index 0e72dcf8b2f..f605be1d059 100644 --- a/tests/integration/management/test_credential_display_name.py +++ b/tests/integration/management/test_credential_display_name.py @@ -122,6 +122,9 @@ def test_patch_rename_is_rejected_and_model_keeps_working(gateway: Gateway) -> N "PATCH", f"/credentials/{name}", {"credential_name": name, "credential_info": {"description": "y"}} ) assert same_name.status_code == 200, same_name.text + assert read_rows( + 'SELECT credential_info FROM "LiteLLM_CredentialsTable" WHERE credential_name = %s', (name,) + ) == [{"credential_info": {"custom_llm_provider": "openai", "description": "y"}}] assert _served_credential(gateway, name)["credential_info"] == { "custom_llm_provider": "openai", "description": "y", diff --git a/tests/unit/proxy/credential_endpoints/test_endpoints.py b/tests/unit/proxy/credential_endpoints/test_endpoints.py index 3d8e92988d9..cdd18fd4eef 100644 --- a/tests/unit/proxy/credential_endpoints/test_endpoints.py +++ b/tests/unit/proxy/credential_endpoints/test_endpoints.py @@ -3166,18 +3166,34 @@ class TestCredentialDisplayName: repository.update_by_name.assert_not_awaited() assert litellm.credential_list == [_labeled_credential()] - def test_patch_accepts_the_same_credential_name_echoed_back(self, restore_credential_list, monkeypatch): + @pytest.mark.parametrize( + ("patch_info", "expected_info"), + [ + ({}, {"custom_llm_provider": "openai"}), + ({"description": "y"}, {"custom_llm_provider": "openai", "description": "y"}), + ({"custom_llm_provider": "anthropic"}, {"custom_llm_provider": "anthropic"}), + ], + ) + def test_patch_accepts_the_same_credential_name_echoed_back( + self, restore_credential_list, monkeypatch, patch_info: dict[str, str], expected_info: dict[str, str] + ): monkeypatch.setattr(litellm, "credential_list", [_labeled_credential()]) with _repository_holding(_labeled_credential()) as repository: response = _patch_credential( "openai-prod", - {"credential_name": "openai-prod", "credential_values": {"api_key": "sk-new"}, "credential_info": {}}, + { + "credential_name": "openai-prod", + "credential_values": {"api_key": "sk-new"}, + "credential_info": patch_info, + }, ) assert response.status_code == 200, response.text assert [(c.credential_name, c.credential_values) for c in litellm.credential_list] == [ ("openai-prod", {"api_key": "sk-new"}) ] + assert json.loads(repository.update_by_name.await_args.kwargs["data"]["credential_info"]) == expected_info + assert litellm.credential_list[0].credential_info == expected_info def test_patch_on_a_config_credential_is_rejected_as_config_owned(self, restore_credential_list, monkeypatch): config_credential = _labeled_credential(display_name=None).model_copy(update={"source": "config"})