mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(keenable): run the Keenable tests in the llms-providers shard and pass the LIT gate
- Name tests/unit/llms/keenable in the llms-providers shard, which the new shard-coverage check requires (it also left the tests out of coverage). - Give the logging_obj keyless-flag write a `# rebind-ok` reason, as other providers do for the same cost-routing write. - Restore upstream formatting in the shared base search test; keep only the Keenable import and PROVIDERS row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2a7dda9331
commit
2c0ca4fcd3
3 changed files with 33 additions and 12 deletions
1
.github/workflows/test-unit.yml
vendored
1
.github/workflows/test-unit.yml
vendored
|
|
@ -284,6 +284,7 @@ jobs:
|
|||
tests/unit/llms/inception
|
||||
tests/unit/llms/infinity
|
||||
tests/unit/llms/jina_ai
|
||||
tests/unit/llms/keenable
|
||||
tests/unit/llms/lambda_ai
|
||||
tests/unit/llms/langflow
|
||||
tests/unit/llms/langgraph
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ class KeenableSearchConfig(BaseSearchConfig):
|
|||
headers=dict(raw_response.headers),
|
||||
)
|
||||
|
||||
logging_obj.optional_params = {
|
||||
logging_obj.optional_params = { # rebind-ok: hands the keyless flag to the search cost calculator
|
||||
**logging_obj.optional_params,
|
||||
KEENABLE_KEYLESS_PARAM: _answered_keyless(raw_response),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ from litellm.llms.brave.search.transformation import BraveSearchConfig
|
|||
from litellm.llms.dataforseo.search.transformation import DataForSEOSearchConfig
|
||||
from litellm.llms.exa_ai.search.transformation import ExaAISearchConfig
|
||||
from litellm.llms.fastcrw.search.transformation import FastCRWSearchConfig
|
||||
from litellm.llms.keenable.search.transformation import KeenableSearchConfig
|
||||
from litellm.llms.firecrawl.search.transformation import FirecrawlSearchConfig
|
||||
from litellm.llms.google_pse.search.transformation import GooglePSESearchConfig
|
||||
from litellm.llms.keenable.search.transformation import KeenableSearchConfig
|
||||
from litellm.llms.linkup.search.transformation import LinkupSearchConfig
|
||||
from litellm.llms.nimble.search.transformation import NimbleSearchConfig
|
||||
from litellm.llms.parallel_ai.search.transformation import ParallelAISearchConfig
|
||||
|
|
@ -109,7 +109,9 @@ PROVIDERS: Tuple[ProviderSpec, ...] = (
|
|||
_IDS = tuple(spec[0].__name__ for spec in PROVIDERS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS)
|
||||
@pytest.mark.parametrize(
|
||||
"config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS
|
||||
)
|
||||
def test_server_secret_refused_for_caller_api_base(
|
||||
config_cls: Type[BaseSearchConfig],
|
||||
server_env: Dict[str, str],
|
||||
|
|
@ -124,7 +126,9 @@ def test_server_secret_refused_for_caller_api_base(
|
|||
config_cls().validate_environment(headers={}, api_base=ATTACKER_BASE)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS)
|
||||
@pytest.mark.parametrize(
|
||||
"config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS
|
||||
)
|
||||
def test_caller_supplied_key_is_honored_for_custom_api_base(
|
||||
config_cls: Type[BaseSearchConfig],
|
||||
server_env: Dict[str, str],
|
||||
|
|
@ -137,10 +141,14 @@ def test_caller_supplied_key_is_honored_for_custom_api_base(
|
|||
|
||||
# An explicit caller key is the caller's own credential, so pointing it at
|
||||
# the caller's own host must be allowed.
|
||||
config_cls().validate_environment(headers={}, api_key=caller_key, api_base=ATTACKER_BASE)
|
||||
config_cls().validate_environment(
|
||||
headers={}, api_key=caller_key, api_base=ATTACKER_BASE
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS)
|
||||
@pytest.mark.parametrize(
|
||||
"config_cls, server_env, caller_key, extra_env", PROVIDERS, ids=_IDS
|
||||
)
|
||||
def test_server_secret_used_without_caller_api_base(
|
||||
config_cls: Type[BaseSearchConfig],
|
||||
server_env: Dict[str, str],
|
||||
|
|
@ -161,7 +169,9 @@ def test_keyless_provider_allows_caller_api_base(
|
|||
) -> None:
|
||||
monkeypatch.delenv("SEARXNG_API_KEY", raising=False)
|
||||
|
||||
headers = SearXNGSearchConfig().validate_environment(headers={}, api_base="https://my-searxng.internal")
|
||||
headers = SearXNGSearchConfig().validate_environment(
|
||||
headers={}, api_base="https://my-searxng.internal"
|
||||
)
|
||||
|
||||
assert "Authorization" not in headers
|
||||
|
||||
|
|
@ -174,7 +184,9 @@ def test_operator_env_base_override_is_trusted(
|
|||
|
||||
# Mirrors the second validate_environment call in the search handler, which
|
||||
# receives the already-resolved operator base as api_base.
|
||||
headers = SerperSearchConfig().validate_environment(headers={}, api_base="https://serper.internal.corp/search")
|
||||
headers = SerperSearchConfig().validate_environment(
|
||||
headers={}, api_base="https://serper.internal.corp/search"
|
||||
)
|
||||
|
||||
assert headers["X-API-KEY"] == "srv"
|
||||
|
||||
|
|
@ -190,7 +202,9 @@ class TestResolveServerApiKey:
|
|||
)
|
||||
assert result == "mine"
|
||||
|
||||
def test_returns_none_when_no_server_secret(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_returns_none_when_no_server_secret(
|
||||
self, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
monkeypatch.delenv("SEARXNG_API_KEY", raising=False)
|
||||
result = BaseSearchConfig().resolve_server_api_key(
|
||||
caller_api_key=None,
|
||||
|
|
@ -216,10 +230,14 @@ class TestResolveServerApiKey:
|
|||
|
||||
class TestIsTrustedSearchApiBase:
|
||||
def test_matches_default_host(self) -> None:
|
||||
assert _is_trusted_search_api_base("https://google.serper.dev/search", "https://google.serper.dev", None)
|
||||
assert _is_trusted_search_api_base(
|
||||
"https://google.serper.dev/search", "https://google.serper.dev", None
|
||||
)
|
||||
|
||||
def test_foreign_host_untrusted(self) -> None:
|
||||
assert not _is_trusted_search_api_base(ATTACKER_BASE, "https://google.serper.dev", None)
|
||||
assert not _is_trusted_search_api_base(
|
||||
ATTACKER_BASE, "https://google.serper.dev", None
|
||||
)
|
||||
|
||||
def test_env_override_host_trusted(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("SERPER_API_BASE", "https://serper.internal.corp")
|
||||
|
|
@ -232,7 +250,9 @@ class TestIsTrustedSearchApiBase:
|
|||
def test_schemeless_candidate_untrusted(self) -> None:
|
||||
# Without a scheme urlsplit puts the value in the path, leaving an empty
|
||||
# netloc; an unparseable host must never be treated as trusted.
|
||||
assert not _is_trusted_search_api_base("attacker.example.com", "https://google.serper.dev", None)
|
||||
assert not _is_trusted_search_api_base(
|
||||
"attacker.example.com", "https://google.serper.dev", None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue