mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(ci): clear the gate regressions the second merge introduced
The merge with the moved base pushed three budget gates over their ceilings. credential_hydration collected the per-credential field lookups with a list comprehension over an async call, which LIT002 reads as building a mutable value by accumulation. Replaced with asyncio.gather plus chain.from_iterable, so the lookups now also run concurrently instead of one after another. The credential form helper test carried an inline object literal that put no-large-inline-object-arg one over its 560 ceiling. Hoisted it to a named const. Two patch sites in the pass-through relay test were counted by TQ008. Both are genuine boundaries, the http client the test asserts bytes against and the logging hook that would otherwise want a database, so they carry suppressions naming the reason rather than being restructured.
This commit is contained in:
parent
eafb839394
commit
85763e7829
4 changed files with 35 additions and 24 deletions
|
|
@ -5,6 +5,7 @@ Memory first (``litellm.credential_list``, already decrypted -- matching
|
|||
in-memory list has not yet picked up a credential another pod just wrote or updated.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
from itertools import chain
|
||||
from types import MappingProxyType
|
||||
|
|
@ -118,11 +119,16 @@ async def effective_anthropic_wif_fields(
|
|||
"""
|
||||
from_stored: Final = () if stored is None else anthropic_wif_fields_present(stored)
|
||||
from_incoming: Final = () if incoming is None else anthropic_wif_fields_named(incoming.model_fields_set)
|
||||
per_credential: Final = [
|
||||
await named_credential_wif_fields(credential_name, prisma_client)
|
||||
for credential_name in _effective_credential_names(stored, incoming)
|
||||
]
|
||||
from_credential: Final = tuple(chain.from_iterable(per_credential))
|
||||
from_credential: Final = tuple(
|
||||
chain.from_iterable(
|
||||
await asyncio.gather(
|
||||
*(
|
||||
named_credential_wif_fields(credential_name, prisma_client)
|
||||
for credential_name in _effective_credential_names(stored, incoming)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
return tuple(dict.fromkeys(from_stored + from_incoming + from_credential))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -970,7 +970,7 @@ class TestManagementReadsTheStoredCredential:
|
|||
prisma = MagicMock()
|
||||
prisma.db.litellm_credentialstable.find_unique = AsyncMock(return_value=row)
|
||||
|
||||
with patch.object(litellm, "credential_list", [stale]):
|
||||
with patch.object(litellm, "credential_list", [stale]): # test-quality-ok: the stale copy under test
|
||||
served = await hydrate_named_credential("anthropic-wif", prisma)
|
||||
managed = await hydrate_named_credential_authoritative("anthropic-wif", prisma)
|
||||
|
||||
|
|
@ -991,7 +991,7 @@ class TestManagementReadsTheStoredCredential:
|
|||
prisma = MagicMock()
|
||||
prisma.db.litellm_credentialstable.find_unique = AsyncMock(return_value=None)
|
||||
|
||||
with patch.object(litellm, "credential_list", [only_in_memory]):
|
||||
with patch.object(litellm, "credential_list", [only_in_memory]): # test-quality-ok: the config.yaml fallback under test
|
||||
resolved = await hydrate_named_credential_authoritative("config-yaml-credential", prisma)
|
||||
|
||||
assert resolved is not None
|
||||
|
|
|
|||
|
|
@ -3253,14 +3253,9 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
"""
|
||||
|
||||
VKEY = "sk-litellm-victim-key"
|
||||
ENDPOINT = (
|
||||
"v1/projects/my-proj/locations/us-central1/publishers/google/models/"
|
||||
"gemini-2.5-flash:generateContent"
|
||||
)
|
||||
ENDPOINT = "v1/projects/my-proj/locations/us-central1/publishers/google/models/gemini-2.5-flash:generateContent"
|
||||
|
||||
async def _run(
|
||||
self, monkeypatch, headers: list[tuple[bytes, bytes]]
|
||||
) -> tuple[HTTPException | None, dict | None]:
|
||||
async def _run(self, monkeypatch, headers: list[tuple[bytes, bytes]]) -> tuple[HTTPException | None, dict | None]:
|
||||
from litellm.proxy.pass_through_endpoints.passthrough_endpoint_router import (
|
||||
PassthroughEndpointRouter,
|
||||
)
|
||||
|
|
@ -3378,7 +3373,9 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
(b"content-type", b"application/json"),
|
||||
],
|
||||
)
|
||||
assert forwarded is None, f"a virtual key echoed as '{scheme} <key>' in Authorization must be stripped, not forwarded"
|
||||
assert forwarded is None, (
|
||||
f"a virtual key echoed as '{scheme} <key>' in Authorization must be stripped, not forwarded"
|
||||
)
|
||||
assert raised is not None and raised.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -3424,8 +3421,7 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
@pytest.mark.parametrize(
|
||||
"credential_header",
|
||||
sorted(
|
||||
SpecialHeaders.litellm_credential_header_names()
|
||||
- {"authorization", "x-goog-api-key", "x-litellm-api-key"}
|
||||
SpecialHeaders.litellm_credential_header_names() - {"authorization", "x-goog-api-key", "x-litellm-api-key"}
|
||||
),
|
||||
)
|
||||
async def test_every_non_google_credential_header_is_dropped_by_name(self, monkeypatch, credential_header):
|
||||
|
|
@ -3500,7 +3496,9 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
assert raised is not None and raised.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_authenticated_authorization_is_stripped_over_a_lower_precedence_pass_through_header(self, monkeypatch):
|
||||
async def test_authenticated_authorization_is_stripped_over_a_lower_precedence_pass_through_header(
|
||||
self, monkeypatch
|
||||
):
|
||||
with mock.patch.dict( # test-quality-ok: general_settings is the real proxy config surface for pass_through_endpoints; no injection seam exists on this route
|
||||
"litellm.proxy.proxy_server.general_settings",
|
||||
{"pass_through_endpoints": [{"headers": {"litellm_user_api_key": "x-company-key"}}]},
|
||||
|
|
@ -3517,7 +3515,9 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
assert raised is None
|
||||
assert forwarded is not None
|
||||
assert forwarded.get("x-goog-api-key") == "AIza-real-google-api-key"
|
||||
assert "authorization" not in forwarded, "Authorization authenticated (higher precedence) so its key must be stripped"
|
||||
assert "authorization" not in forwarded, (
|
||||
"Authorization authenticated (higher precedence) so its key must be stripped"
|
||||
)
|
||||
assert "x-company-key" not in forwarded
|
||||
assert self.VKEY not in " ".join(f"{name}:{value}" for name, value in forwarded.items())
|
||||
|
||||
|
|
@ -3548,7 +3548,9 @@ class TestVertexCredentiallessPassthroughVirtualKeyLeak:
|
|||
(b"content-type", b"application/json"),
|
||||
],
|
||||
)
|
||||
assert forwarded is None, "a virtual key in the mapped-route litellm_user_api_key header must be dropped, not forwarded"
|
||||
assert forwarded is None, (
|
||||
"a virtual key in the mapped-route litellm_user_api_key header must be dropped, not forwarded"
|
||||
)
|
||||
assert raised is not None and raised.status_code == 401
|
||||
|
||||
|
||||
|
|
@ -4360,11 +4362,13 @@ class TestAnthropicProxyRouteCallerAuthHeaders:
|
|||
client_wrapper.client = httpx_client
|
||||
|
||||
with (
|
||||
patch(
|
||||
patch( # test-quality-ok: stubbing the http client IS the boundary; the test asserts on the bytes handed to it
|
||||
"litellm.proxy.pass_through_endpoints.pass_through_endpoints.get_async_httpx_client",
|
||||
return_value=client_wrapper,
|
||||
),
|
||||
patch("litellm.proxy.proxy_server.proxy_logging_obj") as mock_logging_obj,
|
||||
patch( # test-quality-ok: the relay calls these hooks, and they need a db this test has no use for
|
||||
"litellm.proxy.proxy_server.proxy_logging_obj"
|
||||
) as mock_logging_obj,
|
||||
):
|
||||
mock_logging_obj.pre_call_hook = AsyncMock(return_value={"model": "claude-sonnet-4-5", "messages": []})
|
||||
mock_logging_obj.post_call_success_hook = AsyncMock()
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ describe("resetCredentialFormOnProviderChange", () => {
|
|||
it("clears all fields when switching providers", () => {
|
||||
// Simulate the OpenAI->Google AI Studio leak: api_base picked up
|
||||
// OpenAI's default value and the user typed a custom URL.
|
||||
const { stub, fields, calls } = makeFormStub({
|
||||
const leakedOpenAiForm = {
|
||||
credential_name: "my-prod-key",
|
||||
custom_llm_provider: "OpenAI",
|
||||
api_base: "https://api.openai.com/v1",
|
||||
api_key: "sk-stale-openai-key",
|
||||
organization: "org-leak",
|
||||
});
|
||||
};
|
||||
const { stub, fields, calls } = makeFormStub(leakedOpenAiForm);
|
||||
const setSelectedProvider = vi.fn();
|
||||
|
||||
resetCredentialFormOnProviderChange(stub, Providers.Google_AI_Studio, setSelectedProvider);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue