test(proxy/files): update stubs to handle IO[bytes] file content

This commit is contained in:
Ishaan Jaffer 2026-03-23 09:50:38 -07:00
parent 3bde9e1827
commit 8477facb95

View file

@ -1,6 +1,8 @@
import io
import json
import os
import sys
from typing import Union
from unittest.mock import ANY
import pytest
@ -21,6 +23,19 @@ from litellm.proxy.proxy_server import app
from litellm.types.llms.openai import OpenAIFileObject
client = TestClient(app)
def _get_file_size(file_content: Union[bytes, io.IOBase]) -> int:
"""Return file size without loading IO objects fully into memory."""
if isinstance(file_content, io.IOBase):
pos = file_content.tell()
file_content.seek(0, 2)
size = file_content.tell()
file_content.seek(pos)
return size
return len(file_content) # type: ignore[arg-type]
from litellm.caching.caching import DualCache
from litellm.proxy.proxy_server import hash_token
from litellm.proxy.utils import ProxyLogging
@ -106,16 +121,18 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router:
Asserts 'create_file' is called with the correct arguments
"""
import litellm
import litellm.proxy.proxy_server as ps
from litellm import Router
from litellm.proxy._types import LitellmUserRoles
import litellm.proxy.proxy_server as ps
from litellm.proxy.utils import ProxyLogging
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
# Mock create_file as an async function
mock_create_file = mocker.patch("litellm.files.main.create_file", new=mocker.AsyncMock())
mock_create_file = mocker.patch(
"litellm.files.main.create_file", new=mocker.AsyncMock()
)
proxy_logging_obj = ProxyLogging(
user_api_key_cache=DualCache(default_in_memory_ttl=1)
@ -127,7 +144,14 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router:
from litellm.llms.base_llm.files.transformation import BaseFileEndpoints
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Handle both dict and object forms of create_file_request
if isinstance(create_file_request, dict):
file_data = create_file_request.get("file")
@ -135,7 +159,7 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router:
else:
file_data = create_file_request.file
purpose_data = create_file_request.purpose
# Call the mocked litellm.files.main.create_file to ensure asserts work
await litellm.files.main.create_file(
custom_llm_provider="azure",
@ -153,26 +177,31 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router:
)
# Return a dummy response object as needed by the test
from litellm.types.llms.openai import OpenAIFileObject
return OpenAIFileObject(
id="dummy-id",
object="file",
bytes=len(file_data[1]) if file_data else 0,
bytes=_get_file_size(file_data[1]) if file_data else 0,
created_at=1234567890,
filename=file_data[0] if file_data else "test.wav",
purpose=purpose_data,
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
# Manually add the hook to the proxy_hook_mapping
@ -245,8 +274,8 @@ def test_target_storage_invokes_storage_backend(
"""
Ensure target_storage is parsed and invokes the storage backend service.
"""
from litellm.proxy._types import LitellmUserRoles
import litellm.proxy.proxy_server as ps
from litellm.proxy._types import LitellmUserRoles
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
@ -304,8 +333,8 @@ def test_target_storage_with_target_models(
"""
Ensure target_storage and target_model_names are parsed and passed through.
"""
from litellm.proxy._types import LitellmUserRoles
import litellm.proxy.proxy_server as ps
from litellm.proxy._types import LitellmUserRoles
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
@ -611,7 +640,9 @@ def test_create_file_for_each_model(
assert openai_call_found, "OpenAI call not found with expected parameters"
def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_create_file_with_expires_after(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that expires_after is properly parsed and passed through when creating a file
"""
@ -624,18 +655,25 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_
proxy_logging_obj._add_proxy_hooks(llm_router)
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Verify expires_after is in the request
if isinstance(create_file_request, dict):
expires_after = create_file_request.get("expires_after")
else:
expires_after = getattr(create_file_request, "expires_after", None)
# Verify expires_after was passed correctly
assert expires_after is not None, "expires_after should be in the request"
assert expires_after["anchor"] == "created_at"
assert expires_after["seconds"] == 2592000
# Return a dummy response
return OpenAIFileObject(
id="file-abc123",
@ -646,17 +684,21 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_
purpose="fine-tune",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles()
@ -688,7 +730,9 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_
assert result["purpose"] == "fine-tune"
def test_create_file_with_expires_after_missing_anchor(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_create_file_with_expires_after_missing_anchor(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that an error is returned when expires_after[anchor] is missing
"""
@ -717,10 +761,15 @@ def test_create_file_with_expires_after_missing_anchor(mocker: MockerFixture, mo
assert response.status_code == 400
error_detail = response.json()
assert "expires_after" in error_detail["error"]["message"].lower() or "both" in error_detail["error"]["message"].lower()
assert (
"expires_after" in error_detail["error"]["message"].lower()
or "both" in error_detail["error"]["message"].lower()
)
def test_create_file_with_expires_after_missing_seconds(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_create_file_with_expires_after_missing_seconds(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that an error is returned when expires_after[seconds] is missing
"""
@ -749,10 +798,15 @@ def test_create_file_with_expires_after_missing_seconds(mocker: MockerFixture, m
assert response.status_code == 400
error_detail = response.json()
assert "expires_after" in error_detail["error"]["message"].lower() or "both" in error_detail["error"]["message"].lower()
assert (
"expires_after" in error_detail["error"]["message"].lower()
or "both" in error_detail["error"]["message"].lower()
)
def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_create_file_with_expires_after_valid_values(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that expires_after works with valid anchor and seconds values
"""
@ -765,18 +819,25 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk
proxy_logging_obj._add_proxy_hooks(llm_router)
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Verify expires_after is in the request
if isinstance(create_file_request, dict):
expires_after = create_file_request.get("expires_after")
else:
expires_after = getattr(create_file_request, "expires_after", None)
# Verify expires_after was passed correctly
assert expires_after is not None, "expires_after should be in the request"
assert expires_after["anchor"] == "created_at"
assert expires_after["seconds"] == 3600
return OpenAIFileObject(
id="file-abc123",
object="file",
@ -786,17 +847,21 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk
purpose="fine-tune",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles()
@ -827,7 +892,9 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk
assert result["purpose"] == "fine-tune"
def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_create_file_without_expires_after(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that file creation works normally without expires_after
"""
@ -840,16 +907,25 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l
proxy_logging_obj._add_proxy_hooks(llm_router)
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Verify expires_after is None when not provided
if isinstance(create_file_request, dict):
expires_after = create_file_request.get("expires_after")
else:
expires_after = getattr(create_file_request, "expires_after", None)
# expires_after should be None when not provided
assert expires_after is None, "expires_after should be None when not provided"
assert (
expires_after is None
), "expires_after should be None when not provided"
return OpenAIFileObject(
id="file-abc123",
object="file",
@ -859,17 +935,21 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l
purpose="fine-tune",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles()
@ -898,11 +978,13 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l
assert result["purpose"] == "fine-tune"
def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, llm_router: Router):
def test_managed_files_with_loadbalancing(
mocker: MockerFixture, monkeypatch, llm_router: Router
):
"""
Test that managed files work with loadbalancing when both target_model_names
and enable_loadbalancing_on_batch_endpoints are enabled.
This ensures that the priority order is correct:
- managed files should take precedence over deprecated loadbalancing
- managed files internally use llm_router.acreate_file() which provides loadbalancing
@ -912,28 +994,34 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll
# Enable loadbalancing on batch endpoints
monkeypatch.setattr("litellm.enable_loadbalancing_on_batch_endpoints", True)
proxy_logging_obj = ProxyLogging(
user_api_key_cache=DualCache(default_in_memory_ttl=1)
)
proxy_logging_obj._add_proxy_hooks(llm_router)
# Track calls to verify loadbalancing through router
router_acreate_file_calls = []
class ManagedFilesWithLoadbalancing(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Verify we receive the target model names
assert len(target_model_names_list) > 0, "Should have target_model_names_list"
assert (
len(target_model_names_list) > 0
), "Should have target_model_names_list"
# Simulate what managed files does - call llm_router.acreate_file for each model
# This is where loadbalancing happens internally
for model in target_model_names_list:
router_acreate_file_calls.append({
"model": model,
"via_router": True
})
router_acreate_file_calls.append({"model": model, "via_router": True})
# Return a managed file ID (base64 encoded)
return OpenAIFileObject(
id="litellm_managed_file_abc123",
@ -944,23 +1032,29 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll
purpose="batch",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
import litellm.proxy.proxy_server as ps
from litellm.proxy._types import LitellmUserRoles
proxy_logging_obj.proxy_hook_mapping["managed_files"] = ManagedFilesWithLoadbalancing()
proxy_logging_obj.proxy_hook_mapping["managed_files"] = (
ManagedFilesWithLoadbalancing()
)
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
monkeypatch.setattr(
"litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj
@ -971,12 +1065,12 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
api_key="test-key", user_role=LitellmUserRoles.PROXY_ADMIN
)
try:
# Create batch file content
test_file_content = b'{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello"}]}}'
test_file = ("batch_data.jsonl", test_file_content, "application/jsonl")
# Make request with both target_model_names AND enable_loadbalancing_on_batch_endpoints
response = client.post(
"/v1/files",
@ -987,7 +1081,7 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll
},
headers={"Authorization": "Bearer test-key"},
)
# Verify success
assert response.status_code == 200, response.text
finally:
@ -995,13 +1089,17 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll
result = response.json()
assert result["id"] == "litellm_managed_file_abc123"
assert result["purpose"] == "batch"
# Verify that managed files was called (via router for loadbalancing)
# This proves that managed files took precedence over deprecated loadbalancing
assert len(router_acreate_file_calls) == 2, "Should have called router for both models"
assert (
len(router_acreate_file_calls) == 2
), "Should have called router for both models"
assert router_acreate_file_calls[0]["model"] == "azure-gpt-3-5-turbo"
assert router_acreate_file_calls[1]["model"] == "gpt-3.5-turbo"
assert all(call["via_router"] for call in router_acreate_file_calls), "All calls should go through router"
assert all(
call["via_router"] for call in router_acreate_file_calls
), "All calls should go through router"
def test_create_file_with_nested_litellm_metadata(
@ -1009,22 +1107,29 @@ def test_create_file_with_nested_litellm_metadata(
):
"""
Test that nested litellm_metadata is correctly parsed from form data in bracket notation.
Regression test for: litellm_metadata[spend_logs_metadata][owner] format should be
correctly parsed into nested dictionary structure.
"""
from litellm.llms.base_llm.files.transformation import BaseFileEndpoints
from litellm.types.llms.openai import OpenAIFileObject
proxy_logging_obj = ProxyLogging(
user_api_key_cache=DualCache(default_in_memory_ttl=1)
)
proxy_logging_obj._add_proxy_hooks(llm_router)
captured_litellm_metadata = {}
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
# Capture litellm_metadata for verification
if isinstance(create_file_request, dict):
captured_litellm_metadata.update(
@ -1034,7 +1139,7 @@ def test_create_file_with_nested_litellm_metadata(
captured_litellm_metadata.update(
getattr(create_file_request, "litellm_metadata", {})
)
return OpenAIFileObject(
id="file-test-123",
object="file",
@ -1044,28 +1149,32 @@ def test_create_file_with_nested_litellm_metadata(
purpose="fine-tune",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles()
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
monkeypatch.setattr(
"litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj
)
test_file_content = b'{"prompt": "Hello", "completion": "Hi"}'
test_file = ("test.jsonl", test_file_content, "application/jsonl")
# Test with nested litellm_metadata in bracket notation
response = client.post(
"/v1/files",
@ -1080,12 +1189,12 @@ def test_create_file_with_nested_litellm_metadata(
},
headers={"Authorization": "Bearer test-key"},
)
# Verify success
assert response.status_code == 200
result = response.json()
assert result["id"] == "file-test-123"
# Verify nested metadata was correctly parsed
assert "spend_logs_metadata" in captured_litellm_metadata
assert captured_litellm_metadata["spend_logs_metadata"]["owner"] == "john_doe"
@ -1099,26 +1208,33 @@ def test_create_file_with_deep_nested_litellm_metadata(
):
"""
Test that deeply nested litellm_metadata is correctly parsed from form data.
Regression test for: litellm_metadata[a][b][c] format should be correctly parsed.
"""
import litellm.proxy.proxy_server as ps
from litellm.llms.base_llm.files.transformation import BaseFileEndpoints
from litellm.proxy._types import LitellmUserRoles
import litellm.proxy.proxy_server as ps
from litellm.types.llms.openai import OpenAIFileObject
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
proxy_logging_obj = ProxyLogging(
user_api_key_cache=DualCache(default_in_memory_ttl=1)
)
proxy_logging_obj._add_proxy_hooks(llm_router)
captured_litellm_metadata = {}
class DummyManagedFiles(BaseFileEndpoints):
async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict):
async def acreate_file(
self,
llm_router,
create_file_request,
target_model_names_list,
litellm_parent_otel_span,
user_api_key_dict,
):
if isinstance(create_file_request, dict):
captured_litellm_metadata.update(
create_file_request.get("litellm_metadata", {})
@ -1127,7 +1243,7 @@ def test_create_file_with_deep_nested_litellm_metadata(
captured_litellm_metadata.update(
getattr(create_file_request, "litellm_metadata", {})
)
return OpenAIFileObject(
id="file-test-456",
object="file",
@ -1137,33 +1253,37 @@ def test_create_file_with_deep_nested_litellm_metadata(
purpose="batch",
status="uploaded",
)
async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router):
raise NotImplementedError("Not implemented for test")
async def afile_list(self, purpose, litellm_parent_otel_span):
raise NotImplementedError("Not implemented for test")
async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_delete(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data):
async def afile_content(
self, file_id, litellm_parent_otel_span, llm_router, **data
):
raise NotImplementedError("Not implemented for test")
proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles()
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
monkeypatch.setattr(
"litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj
)
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="test-user"
)
try:
test_file_content = b'{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo"}}'
test_file = ("nested.jsonl", test_file_content, "application/jsonl")
# Test with deeply nested metadata
response = client.post(
"/v1/files",
@ -1177,12 +1297,12 @@ def test_create_file_with_deep_nested_litellm_metadata(
},
headers={"Authorization": "Bearer test-key"},
)
# Verify success
assert response.status_code == 200, response.text
result = response.json()
assert result["id"] == "file-test-456"
# Verify deeply nested metadata was correctly parsed
assert "config" in captured_litellm_metadata
assert "database" in captured_litellm_metadata["config"]
@ -1356,7 +1476,9 @@ def test_file_team_injects_when_caller_sends_nothing(
# ---------------------------------------------------------------------------
def _post_file_raw(monkeypatch, llm_router: Router, team_metadata: dict, form_data: dict):
def _post_file_raw(
monkeypatch, llm_router: Router, team_metadata: dict, form_data: dict
):
"""POST /v1/files and return the raw response (no status assertion)."""
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth