From f8c0ce88b09cdb459bcc7cf819f498068fc9c08f Mon Sep 17 00:00:00 2001 From: shivam Date: Wed, 29 Jul 2026 22:31:44 +0000 Subject: [PATCH] test(proxy): cover reload model_cost_map no-op and updated success response The reload endpoint now short-circuits to a no_op response when LITELLM_LOCAL_MODEL_COST_MAP forces the bundled backup, and the success response carries source/is_env_forced. Update the pinned happy-path test and add a no-op case. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 35 ++++---- .../test_routes_model_cost_map.py | 81 ++++++++++++++----- 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index e884e4649f5..1069e4feaa8 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15666,9 +15666,26 @@ async def reload_model_cost_map( get_model_cost_map_source_info, ) + current_time = datetime.utcnow() + source_info = get_model_cost_map_source_info() + + if source_info["is_env_forced"]: + no_op_message = ( + "Reload was a no-op: LITELLM_LOCAL_MODEL_COST_MAP=true forces the bundled backup " + "cost map, so no fresh pricing data was fetched. Unset that env var to reload live data." + ) + verbose_proxy_logger.warning(no_op_message) + return { + "message": no_op_message, + "status": "no_op", + "models_count": len(litellm.model_cost) if litellm.model_cost else 0, + "source": source_info["source"], + "is_env_forced": True, + "timestamp": current_time.isoformat(), + } + model_cost_map_url = litellm.model_cost_map_url new_model_cost_map = get_model_cost_map(url=model_cost_map_url) - source_info = get_model_cost_map_source_info() litellm.model_cost = new_model_cost_map # Invalidate case-insensitive lookup map since model_cost was replaced _invalidate_model_cost_lowercase_map() @@ -15678,7 +15695,6 @@ async def reload_model_cost_map( # Update pod's in-memory last reload time global last_model_cost_map_reload - current_time = datetime.utcnow() last_model_cost_map_reload = current_time.isoformat() # Set force reload flag in database for other pods, preserving existing interval_hours @@ -15703,21 +15719,6 @@ async def reload_model_cost_map( models_count = len(new_model_cost_map) if new_model_cost_map else 0 - if source_info["is_env_forced"]: - no_op_message = ( - "Reload was a no-op: LITELLM_LOCAL_MODEL_COST_MAP=true forces the bundled backup " - "cost map, so no fresh pricing data was fetched. Unset that env var to reload live data." - ) - verbose_proxy_logger.warning(no_op_message) - return { - "message": no_op_message, - "status": "no_op", - "models_count": models_count, - "source": source_info["source"], - "is_env_forced": True, - "timestamp": current_time.isoformat(), - } - verbose_proxy_logger.info(f"Model cost map reloaded successfully in current pod. Models count: {models_count}") return { diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py index 16e410f1b1e..d2648001875 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_cost_map.py @@ -12,7 +12,6 @@ from __future__ import annotations from unittest.mock import AsyncMock, MagicMock -import pytest from .conftest import VOLATILE_KEYS, normalize @@ -66,6 +65,15 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): lambda: None, raising=False, ) + monkeypatch.setattr( + "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_source_info", + lambda: { + "source": "remote", + "url": "https://example.invalid/cost_map.json", + "is_env_forced": False, + "fallback_reason": None, + }, + ) async def _fake_invalidate(name): return None @@ -80,11 +88,58 @@ def test_reload_model_cost_map_happy(client, auth_as, monkeypatch, mock_prisma): "message": "Price data reloaded successfully! 2 models updated.", "status": "success", "models_count": 2, + "source": "remote", + "is_env_forced": False, "timestamp": "", } assert table.upsert.await_count == 1 +def test_reload_model_cost_map_no_op_when_env_forced(client, auth_as, monkeypatch, mock_prisma): + """When LITELLM_LOCAL_MODEL_COST_MAP forces the backup, reload is a no-op.""" + from litellm.proxy import proxy_server as ps + from litellm.proxy._types import LitellmUserRoles + + table = _attach_litellm_config(mock_prisma) + monkeypatch.setattr(ps, "prisma_client", mock_prisma) + + monkeypatch.setattr("litellm.model_cost", {"gpt-4": {}, "gpt-3.5": {}}, raising=False) + monkeypatch.setattr( + "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map_source_info", + lambda: { + "source": "local", + "url": None, + "is_env_forced": True, + "fallback_reason": None, + }, + ) + + def _fail_fetch(url=None): + raise AssertionError("get_model_cost_map should not run when env forces backup") + + monkeypatch.setattr( + "litellm.litellm_core_utils.get_model_cost_map.get_model_cost_map", + _fail_fetch, + ) + + with auth_as(LitellmUserRoles.PROXY_ADMIN): + response = client.post("/reload/model_cost_map") + assert response.status_code == 200 + body = normalize(response.json(), volatile=_VOLATILE) + assert body == { + "message": ( + "Reload was a no-op: LITELLM_LOCAL_MODEL_COST_MAP=true forces the bundled backup " + "cost map, so no fresh pricing data was fetched. Unset that env var to reload live data." + ), + "status": "no_op", + "models_count": 2, + "source": "local", + "is_env_forced": True, + "timestamp": "", + } + assert table.upsert.await_count == 0 + + def test_reload_model_cost_map_not_admin_forbidden(client, auth_as): """Non-admin caller gets 403 with a role-specific detail.""" from litellm.proxy._types import LitellmUserRoles @@ -112,9 +167,7 @@ def test_reload_model_cost_map_no_db_500(client, auth_as, monkeypatch): # --------------------------------------------------------------------------- -def test_schedule_model_cost_map_reload_happy( - client, auth_as, monkeypatch, mock_prisma -): +def test_schedule_model_cost_map_reload_happy(client, auth_as, monkeypatch, mock_prisma): """Admin schedules a reload — handler upserts config and echoes interval.""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles @@ -140,9 +193,7 @@ def test_schedule_model_cost_map_reload_happy( assert table.upsert.await_count == 1 -def test_schedule_model_cost_map_reload_invalid_hours( - client, auth_as, monkeypatch, mock_prisma -): +def test_schedule_model_cost_map_reload_invalid_hours(client, auth_as, monkeypatch, mock_prisma): """hours <= 0 is rejected with 400.""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles @@ -221,9 +272,7 @@ def test_cancel_model_cost_map_reload_no_db_500(client, auth_as, monkeypatch): # --------------------------------------------------------------------------- -def test_get_model_cost_map_reload_status_no_db_not_scheduled( - client, auth_as, monkeypatch -): +def test_get_model_cost_map_reload_status_no_db_not_scheduled(client, auth_as, monkeypatch): """No prisma client → returns the not-scheduled shape (4 keys, all-null).""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles @@ -240,9 +289,7 @@ def test_get_model_cost_map_reload_status_no_db_not_scheduled( } -def test_get_model_cost_map_reload_status_scheduled( - client, auth_as, monkeypatch, mock_prisma -): +def test_get_model_cost_map_reload_status_scheduled(client, auth_as, monkeypatch, mock_prisma): """A valid config row → scheduled=True and the interval is echoed.""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles @@ -265,9 +312,7 @@ def test_get_model_cost_map_reload_status_scheduled( } -def test_get_model_cost_map_reload_status_no_config_not_scheduled( - client, auth_as, monkeypatch, mock_prisma -): +def test_get_model_cost_map_reload_status_no_config_not_scheduled(client, auth_as, monkeypatch, mock_prisma): """Config row exists but interval_hours=None → not scheduled.""" from litellm.proxy import proxy_server as ps from litellm.proxy._types import LitellmUserRoles @@ -332,9 +377,7 @@ def test_get_model_cost_map_source_happy(client, auth_as, monkeypatch): } -def test_get_model_cost_map_source_admin_view_only_allowed( - client, auth_as, monkeypatch -): +def test_get_model_cost_map_source_admin_view_only_allowed(client, auth_as, monkeypatch): """PROXY_ADMIN_VIEW_ONLY can read source info — pins the read-only ACL.""" from litellm.proxy._types import LitellmUserRoles