litellm/tests/unit/proxy/common_utils/test_credential_hydration.py
DucLong06 0b5376db36 fix(proxy): strip credential names and URL-encode credential routes in UI
Credential names created with surrounding whitespace could not be read,
updated or deleted by the trimmed name the UI shows. Create now strips the
name and rejects a blank one, lookups fall back to the single stored row
whose name only differs by surrounding whitespace, and the dashboard
percent-encodes the name in credential routes.

Fixes #46598
2026-10-20 11:03:31 +07:00

98 lines
3.9 KiB
Python

from typing import Final
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import litellm
from litellm.litellm_core_utils.credential_accessor import CredentialAccessor
from litellm.proxy.common_utils.credential_hydration import (
effective_server_owned_wif_fields,
hydrate_named_credential_authoritative,
named_credential_wif_fields,
)
from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper
from litellm.types.utils import CredentialItem
@pytest.mark.asyncio
async def test_authoritative_hydrate_returns_an_encrypted_empty_value_as_empty(monkeypatch):
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-hydration-test-salt")
row = {
"credential_name": "openai-wif",
"credential_values": {
"api_base": encrypt_value_helper(""),
"openai_service_account_id": encrypt_value_helper("user-1"),
},
"credential_info": {"custom_llm_provider": "openai"},
}
prisma = MagicMock()
prisma.db.litellm_credentialstable.find_unique = AsyncMock(return_value=row)
with patch.object(litellm, "credential_list", []): # test-quality-ok: the row under test must win over memory
resolved = await hydrate_named_credential_authoritative("openai-wif", prisma)
assert resolved is not None
assert resolved.credential_values == {"api_base": "", "openai_service_account_id": "user-1"}
@pytest.mark.asyncio
async def test_wif_gate_sees_the_padded_credential_a_trimmed_name_resolves_to():
"""Resolution maps ``corp-wif`` onto a credential stored as ``corp-wif ``, so the federation
gate must see that credential's server-owned fields too, or a team admin could swap them out."""
padded = CredentialItem(
credential_name="corp-wif ",
credential_values={"anthropic_keycloak_token_url": "https://keycloak.internal/token"},
credential_info={},
source="config",
)
with patch.object(
litellm, "credential_list", [padded]
): # test-quality-ok: the in-memory config credential under test
resolved = CredentialAccessor.find_credential("corp-wif")
gated = await named_credential_wif_fields("corp-wif", None)
assert resolved is padded
assert "anthropic_keycloak_token_url" in gated
@pytest.mark.asyncio
@pytest.mark.parametrize(
"credential_api_key, expected",
[
("oidc/file//var/run/secrets/kubernetes.io/serviceaccount/token", ("api_key",)),
("sk-static", ()),
],
ids=["oidc_reference", "static_key"],
)
async def test_attaching_a_credential_is_server_owned_only_when_its_api_key_is_an_oidc_reference(
credential_api_key: str, expected: tuple[str, ...]
):
credential: Final = CredentialItem(
credential_name="admin-oidc", credential_values={"api_key": credential_api_key}, credential_info={}
)
with patch.object(litellm, "credential_list", [credential]): # test-quality-ok: the in-memory list is the input
fields = await effective_server_owned_wif_fields(
{"model": "mistral/devstral-latest"}, {"litellm_credential_name": "admin-oidc"}, None
)
assert fields == expected
@pytest.mark.asyncio
async def test_a_stored_credential_whose_encrypted_api_key_is_an_oidc_reference_is_server_owned(monkeypatch):
monkeypatch.setenv("LITELLM_SALT_KEY", "sk-hydration-test-salt")
row = {
"credential_name": "admin-oidc",
"credential_values": {
"api_key": encrypt_value_helper("oidc/file//var/run/secrets/kubernetes.io/serviceaccount/token")
},
"credential_info": {"custom_llm_provider": "mistral"},
}
prisma = MagicMock()
prisma.db.litellm_credentialstable.find_unique = AsyncMock(return_value=row)
with patch.object(litellm, "credential_list", []): # test-quality-ok: a pod that has not loaded the row yet
fields = await named_credential_wif_fields("admin-oidc", prisma)
assert fields == ("api_key",)