mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
Merge pull request #21637 from BerriAI/litellm_fix_get_image_cache_bypass
[Fix] /get_image Ignores UI_LOGO_PATH When cached_logo.jpg Exists
This commit is contained in:
commit
8f92e448ae
2 changed files with 171 additions and 4 deletions
|
|
@ -10694,13 +10694,23 @@ async def get_image():
|
|||
cache_dir = assets_dir if os.access(assets_dir, os.W_OK) else current_dir
|
||||
cache_path = os.path.join(cache_dir, "cached_logo.jpg")
|
||||
|
||||
# [OPTIMIZATION] Check if the cached image exists first
|
||||
if os.path.exists(cache_path):
|
||||
return FileResponse(cache_path, media_type="image/jpeg")
|
||||
|
||||
logo_path = os.getenv("UI_LOGO_PATH", default_logo)
|
||||
verbose_proxy_logger.debug("Reading logo from path: %s", logo_path)
|
||||
|
||||
# If UI_LOGO_PATH points to a local file, serve it directly (skip cache)
|
||||
if logo_path != default_logo and not logo_path.startswith(("http://", "https://")):
|
||||
if os.path.exists(logo_path):
|
||||
return FileResponse(logo_path, media_type="image/jpeg")
|
||||
# Custom path doesn't exist — fall back to default
|
||||
verbose_proxy_logger.warning(
|
||||
f"UI_LOGO_PATH '{logo_path}' does not exist, falling back to default logo"
|
||||
)
|
||||
logo_path = default_logo
|
||||
|
||||
# [OPTIMIZATION] For HTTP URLs and default logo, check if the cached image exists
|
||||
if os.path.exists(cache_path):
|
||||
return FileResponse(cache_path, media_type="image/jpeg")
|
||||
|
||||
# Check if the logo path is an HTTP/HTTPS URL
|
||||
if logo_path.startswith(("http://", "https://")):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -3154,6 +3154,163 @@ async def test_get_image_root_case_uses_current_dir(monkeypatch):
|
|||
assert mock_file_response.called, "FileResponse should be called"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_image_custom_local_logo_bypasses_cache(monkeypatch):
|
||||
"""
|
||||
Test that when UI_LOGO_PATH is set to a local file, get_image serves it
|
||||
directly and does not return a stale cached_logo.jpg.
|
||||
|
||||
Regression test: previously the cache check ran before reading UI_LOGO_PATH,
|
||||
so a pre-existing cached_logo.jpg (e.g. from the base Docker image) would
|
||||
always be returned, ignoring the user's custom logo.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from litellm.proxy.proxy_server import get_image
|
||||
|
||||
monkeypatch.setenv("UI_LOGO_PATH", "/app/custom_logo.jpg")
|
||||
monkeypatch.delenv("LITELLM_NON_ROOT", raising=False)
|
||||
monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False)
|
||||
|
||||
calls_to_file_response = []
|
||||
|
||||
def fake_file_response(path, **kwargs):
|
||||
calls_to_file_response.append(path)
|
||||
return MagicMock()
|
||||
|
||||
with patch("litellm.proxy.proxy_server.os.path.exists", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.os.access", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response):
|
||||
|
||||
await get_image()
|
||||
|
||||
assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once"
|
||||
assert calls_to_file_response[0] == "/app/custom_logo.jpg", (
|
||||
f"Expected custom logo path, got {calls_to_file_response[0]}. "
|
||||
"A stale cached_logo.jpg may have been returned instead."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_image_default_logo_still_uses_cache(monkeypatch):
|
||||
"""
|
||||
Test that when UI_LOGO_PATH is NOT set (default logo), the cache
|
||||
optimization still works — cached_logo.jpg is returned if it exists.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from litellm.proxy.proxy_server import get_image
|
||||
|
||||
monkeypatch.delenv("UI_LOGO_PATH", raising=False)
|
||||
monkeypatch.delenv("LITELLM_NON_ROOT", raising=False)
|
||||
monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False)
|
||||
|
||||
calls_to_file_response = []
|
||||
|
||||
def fake_file_response(path, **kwargs):
|
||||
calls_to_file_response.append(path)
|
||||
return MagicMock()
|
||||
|
||||
with patch("litellm.proxy.proxy_server.os.path.exists", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.os.access", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response):
|
||||
|
||||
await get_image()
|
||||
|
||||
assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once"
|
||||
served_path = calls_to_file_response[0]
|
||||
assert served_path.endswith("cached_logo.jpg"), (
|
||||
f"Expected cached_logo.jpg for default logo, got {served_path}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_image_custom_logo_missing_falls_through_to_default(monkeypatch):
|
||||
"""
|
||||
Test that when UI_LOGO_PATH points to a non-existent local file,
|
||||
get_image falls through to the cache/default logo instead of failing.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from litellm.proxy.proxy_server import get_image
|
||||
|
||||
monkeypatch.setenv("UI_LOGO_PATH", "/app/nonexistent_logo.jpg")
|
||||
monkeypatch.delenv("LITELLM_NON_ROOT", raising=False)
|
||||
monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False)
|
||||
|
||||
calls_to_file_response = []
|
||||
|
||||
def fake_file_response(path, **kwargs):
|
||||
calls_to_file_response.append(path)
|
||||
return MagicMock()
|
||||
|
||||
def exists_side_effect(path):
|
||||
# The custom logo does NOT exist; cache and default DO exist
|
||||
if path == "/app/nonexistent_logo.jpg":
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch("litellm.proxy.proxy_server.os.path.exists", side_effect=exists_side_effect), \
|
||||
patch("litellm.proxy.proxy_server.os.access", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response):
|
||||
|
||||
await get_image()
|
||||
|
||||
assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once"
|
||||
served_path = calls_to_file_response[0]
|
||||
assert served_path != "/app/nonexistent_logo.jpg", (
|
||||
"Should not attempt to serve a non-existent custom logo"
|
||||
)
|
||||
assert served_path.endswith("cached_logo.jpg"), (
|
||||
f"Expected fallback to cached_logo.jpg, got {served_path}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_image_custom_logo_missing_no_cache_serves_default(monkeypatch):
|
||||
"""
|
||||
Test that when UI_LOGO_PATH points to a non-existent file AND there is no
|
||||
cached_logo.jpg, get_image serves the default logo instead of the
|
||||
non-existent custom path.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from litellm.proxy.proxy_server import get_image
|
||||
|
||||
monkeypatch.setenv("UI_LOGO_PATH", "/app/nonexistent_logo.jpg")
|
||||
monkeypatch.delenv("LITELLM_NON_ROOT", raising=False)
|
||||
monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False)
|
||||
|
||||
calls_to_file_response = []
|
||||
|
||||
def fake_file_response(path, **kwargs):
|
||||
calls_to_file_response.append(path)
|
||||
return MagicMock()
|
||||
|
||||
def exists_side_effect(path):
|
||||
# Neither the custom logo nor the cache exist
|
||||
if path == "/app/nonexistent_logo.jpg":
|
||||
return False
|
||||
if "cached_logo.jpg" in path:
|
||||
return False
|
||||
return True
|
||||
|
||||
with patch("litellm.proxy.proxy_server.os.path.exists", side_effect=exists_side_effect), \
|
||||
patch("litellm.proxy.proxy_server.os.access", return_value=True), \
|
||||
patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response):
|
||||
|
||||
await get_image()
|
||||
|
||||
assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once"
|
||||
served_path = calls_to_file_response[0]
|
||||
assert served_path != "/app/nonexistent_logo.jpg", (
|
||||
"Should not attempt to serve a non-existent custom logo"
|
||||
)
|
||||
assert served_path.endswith("logo.jpg"), (
|
||||
f"Expected fallback to default logo.jpg, got {served_path}"
|
||||
)
|
||||
|
||||
|
||||
def test_get_config_normalizes_string_callbacks(monkeypatch):
|
||||
"""
|
||||
Test that /get/config/callbacks normalizes string callbacks to lists.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue