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:
yuneng 2026-09-20 08:08:12 +00:00
parent 6ef7b86748
commit d47008129c
20 changed files with 16 additions and 117 deletions

View file

@ -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)

View file

@ -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",

View file

@ -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")

View file

@ -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:

View file

@ -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)