mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
test(llms): migrate phase 7 provider unit tests to tests/unit
Move the wave 1 phase 7 batch (fireworks_ai, gemini, gigachat, github_copilot; 20 files) from tests/test_litellm to tests/unit after judging every test function under a behaviour mutation. Seven wiring or mock-echo tests that stayed green are deleted. The fireworks cost calculator tests get a local model_cost save/restore fixture since the tests/unit tree has no shared conftest for it Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
6ef7b86748
commit
d47008129c
20 changed files with 16 additions and 117 deletions
|
|
@ -406,21 +406,6 @@ def test_responses_call_sends_session_affinity_for_caller_session_id() -> None:
|
|||
assert headers["x-session-affinity"] == "sess-42"
|
||||
|
||||
|
||||
def test_responses_call_keeps_caller_supplied_session_affinity_header() -> None:
|
||||
client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3"))
|
||||
pinned: Final[Mapping[str, str]] = MappingProxyType({"x-session-affinity": "explicit-node"})
|
||||
with patch(HTTPX_CLIENT_FACTORY, return_value=client):
|
||||
litellm.responses(
|
||||
model="fireworks_ai/kimi-k3",
|
||||
input="hi",
|
||||
api_key="fw-test-key",
|
||||
litellm_session_id="sess-42",
|
||||
extra_headers=pinned,
|
||||
)
|
||||
_, headers, _ = _sent_request(client)
|
||||
assert headers["x-session-affinity"] == "explicit-node"
|
||||
|
||||
|
||||
def test_responses_call_maps_provider_errors_to_fireworks_ai() -> None:
|
||||
client: Final = MagicMock()
|
||||
request: Final = httpx.Request("POST", FIREWORKS_RESPONSES_URL)
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import math
|
||||
from collections.abc import Generator
|
||||
from datetime import datetime, timezone
|
||||
from typing import Final
|
||||
|
||||
|
|
@ -24,6 +25,15 @@ CACHE_READ_COST = litellm.get_model_info(model=MODEL, custom_llm_provider="firew
|
|||
OUTPUT_COST = 4.4e-06
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def restore_model_cost() -> Generator[None, None, None]:
|
||||
original: Final = litellm.model_cost
|
||||
litellm.get_model_info.cache_clear()
|
||||
yield
|
||||
litellm.model_cost = original
|
||||
litellm.get_model_info.cache_clear()
|
||||
|
||||
|
||||
def _usage(prompt_tokens: int, cached_tokens: int, completion_tokens: int) -> Usage:
|
||||
return Usage(
|
||||
prompt_tokens=prompt_tokens,
|
||||
|
|
@ -57,7 +67,7 @@ def _register_off_peak_model(
|
|||
cache_read_cost: float | None = STANDARD_CACHE_READ_COST,
|
||||
model: str = OFF_PEAK_MODEL,
|
||||
) -> None:
|
||||
litellm.model_cost = { # test-quality-ok: conftest restores litellm.model_cost after each test
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test
|
||||
**litellm.model_cost,
|
||||
f"fireworks_ai/{model}": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -151,7 +161,7 @@ def test_an_entry_without_a_cache_read_rate_bills_cached_tokens_at_the_documente
|
|||
"""Fireworks documents a default 50% cached-token discount for serverless models:
|
||||
https://docs.fireworks.ai/guides/prompt-caching, accessed 2026-09-19."""
|
||||
model = "accounts/fireworks/models/default-cache-read-test"
|
||||
litellm.model_cost = { # test-quality-ok: conftest restores litellm.model_cost after each test
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test
|
||||
**litellm.model_cost,
|
||||
f"fireworks_ai/{model}": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -171,7 +181,7 @@ def test_an_entry_without_a_cache_read_rate_bills_cached_tokens_at_the_documente
|
|||
|
||||
def test_fireworks_cache_read_rates_match_breakdown_and_caching_savings():
|
||||
model = "accounts/fireworks/models/breakdown-cache-read-test"
|
||||
litellm.model_cost = { # test-quality-ok: the save/restore conftest returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
**litellm.model_cost,
|
||||
f"fireworks_ai/{model}": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -204,7 +214,7 @@ def test_fireworks_cache_read_rates_match_breakdown_and_caching_savings():
|
|||
|
||||
def test_generic_cost_per_token_applies_fireworks_cache_read_default_with_or_without_model_info():
|
||||
model = "accounts/fireworks/models/generic-cache-read-test"
|
||||
litellm.model_cost = { # test-quality-ok: conftest restores litellm.model_cost after each test
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test
|
||||
**litellm.model_cost,
|
||||
f"fireworks_ai/{model}": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -257,7 +267,7 @@ COMPONENT_AUDIO_OUT_COST = 6e-06
|
|||
|
||||
|
||||
def test_cache_write_reasoning_and_audio_tokens_are_billed_at_their_component_rates():
|
||||
litellm.model_cost = { # test-quality-ok: the save/restore conftest returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
**litellm.model_cost,
|
||||
f"fireworks_ai/{COMPONENT_MODEL}": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -302,7 +312,7 @@ def test_cache_write_reasoning_and_audio_tokens_are_billed_at_their_component_ra
|
|||
|
||||
|
||||
def test_an_entry_without_an_input_rate_gets_no_cache_read_fallback():
|
||||
litellm.model_cost = { # test-quality-ok: the save/restore conftest returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
litellm.model_cost = { # test-quality-ok: the restore_model_cost fixture returns litellm.model_cost to the original object after each test, so replacing the map for this entry leaks nothing
|
||||
**litellm.model_cost, # pyright: ignore[reportUnknownMemberType] # the SDK types model_cost as dict[Unknown, Unknown]
|
||||
"fireworks_ai/accounts/fireworks/models/no-input-rate-test": {
|
||||
"litellm_provider": "fireworks_ai",
|
||||
|
|
@ -141,22 +141,6 @@ class TestValidateEnvironment:
|
|||
assert self.config._current_credentials == "my-creds"
|
||||
assert self.config._current_api_base == "https://my-api.example.com"
|
||||
|
||||
@patch(f"{TRANSFORM_MODULE}.get_access_token", return_value="token")
|
||||
@patch(f"{TRANSFORM_MODULE}.get_secret_str")
|
||||
def test_falls_back_to_env_for_credentials( # test-quality-ok: mock-echo of internal wiring
|
||||
self, mock_get_secret, mock_get_token
|
||||
):
|
||||
mock_get_secret.return_value = "env-creds"
|
||||
self.config.validate_environment(
|
||||
headers={},
|
||||
model="GigaChat",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
mock_get_secret.assert_any_call("GIGACHAT_CREDENTIALS") # test-quality-ok: mock-echo of internal wiring
|
||||
|
||||
|
||||
class TestGetSupportedOpenAiParams:
|
||||
|
|
@ -865,18 +849,6 @@ class TestUploadImage:
|
|||
def setup_method(self):
|
||||
self.config = GigaChatConfig()
|
||||
|
||||
@patch(f"{TRANSFORM_MODULE}.upload_file_sync", return_value="file-uploaded")
|
||||
def test_upload_image_success(self, mock_upload):
|
||||
self.config._current_credentials = "creds"
|
||||
self.config._current_api_base = "https://api.example.com"
|
||||
result = self.config._upload_image("https://example.com/img.jpg")
|
||||
assert result == "file-uploaded"
|
||||
mock_upload.assert_called_once_with(
|
||||
image_url="https://example.com/img.jpg",
|
||||
credentials="creds",
|
||||
api_base="https://api.example.com",
|
||||
)
|
||||
|
||||
@patch(f"{TRANSFORM_MODULE}.upload_file_sync", side_effect=Exception("fail"))
|
||||
def test_upload_image_failure_returns_none(self, mock_upload):
|
||||
result = self.config._upload_image("https://example.com/img.jpg")
|
||||
|
|
@ -37,17 +37,6 @@ def _make_httpx_response(body: dict, status_code: int = 200) -> httpx.Response:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetConfig:
|
||||
def setup_method(self):
|
||||
self.config = GigaChatEmbeddingConfig()
|
||||
|
||||
def test_contains_only_abc_impl(self):
|
||||
"""get_config returns ABC internal data due to inheritance."""
|
||||
result = self.config.get_config()
|
||||
# The only key should be _abc_impl from ABC base class
|
||||
assert set(result.keys()) == {"_abc_impl"}
|
||||
|
||||
|
||||
class TestGetSupportedOpenAiParams:
|
||||
def setup_method(self):
|
||||
self.config = GigaChatEmbeddingConfig()
|
||||
|
|
@ -287,25 +276,6 @@ class TestTransformEmbeddingResponse:
|
|||
)
|
||||
assert result.model == "Embeddings"
|
||||
|
||||
def test_calls_logging_post_call(self):
|
||||
raw = self._make_gigachat_response([
|
||||
{"object": "embedding", "embedding": [0.1], "index": 0},
|
||||
])
|
||||
model_response = EmbeddingResponse()
|
||||
self.config.transform_embedding_response(
|
||||
model="gigachat/Embeddings",
|
||||
raw_response=raw,
|
||||
model_response=model_response,
|
||||
logging_obj=self.logging_obj,
|
||||
api_key="test-api-key",
|
||||
request_data={"input": ["hello"]},
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
self.logging_obj.post_call.assert_called_once()
|
||||
args = self.logging_obj.post_call.call_args.kwargs
|
||||
assert args["api_key"] == "test-api-key"
|
||||
assert args["input"] == ["hello"]
|
||||
|
||||
|
||||
class TestValidateEnvironment:
|
||||
|
|
@ -344,25 +344,6 @@ class TestUploadFileSync:
|
|||
|
||||
assert result is None
|
||||
|
||||
@patch(f"{FILE_MODULE}.get_api_base", return_value="https://api.example.com")
|
||||
@patch(f"{FILE_MODULE}.get_access_token", return_value="test-token")
|
||||
@patch(f"{FILE_MODULE}._get_httpx_client")
|
||||
def test_uploads_without_optional_args(
|
||||
self, mock_http_handler_cls, mock_get_token, mock_get_api_base
|
||||
):
|
||||
"""Verify that credentials, api_base, and litellm_params are optional."""
|
||||
mock_client = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"id": "file-no-args"}
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
mock_client.post.return_value = mock_response
|
||||
mock_http_handler_cls.return_value = mock_client
|
||||
|
||||
result = upload_file_sync(image_url=_RED_PNG_DATA_URL)
|
||||
|
||||
assert result == "file-no-args"
|
||||
# Should still have called get_access_token without args
|
||||
mock_get_token.assert_called_once_with(credentials=None, litellm_params=None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -483,22 +464,3 @@ class TestUploadFileAsync:
|
|||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch(f"{FILE_MODULE}.get_api_base", return_value="https://api.example.com")
|
||||
@patch(f"{FILE_MODULE}.get_access_token_async", return_value="test-token-async")
|
||||
@patch(f"{FILE_MODULE}.get_async_httpx_client")
|
||||
async def test_uploads_without_optional_args(
|
||||
self, mock_get_client, mock_get_token, mock_get_api_base
|
||||
):
|
||||
mock_client = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.json = MagicMock(return_value={"id": "async-no-args"})
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
mock_client.post = AsyncMock(return_value=mock_response)
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
result = await upload_file_async(image_url=_RED_PNG_DATA_URL)
|
||||
|
||||
assert result == "async-no-args"
|
||||
mock_get_token.assert_called_once_with(credentials=None, litellm_params=None)
|
||||
Loading…
Add table
Reference in a new issue