From edde95197a5c37c7af3d9967a70b65ab47b6913c Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:11:48 -0700 Subject: [PATCH] test(proxy): cover every /v1/files route error type and param --- .../test_files_endpoint.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py index 123d54789bf..2257ae1ab29 100644 --- a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py +++ b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py @@ -4666,3 +4666,102 @@ def test_create_file_path_traversal_filename_rejected_before_forwarding(monkeypa assert error["param"] == "file" assert "traversal" in error["message"].lower() assert forwarded_calls == [] + + +def _setup_managed_file_route_answering_404(mocker: MockerFixture, monkeypatch, llm_router: Router): + """Wire the single-file routes to a managed file store that knows no file, the way the + managed files hook answers once a file has been deleted or was never the caller's.""" + import litellm.proxy.proxy_server as ps + from fastapi import HTTPException + from litellm.llms.base_llm.files.transformation import BaseFileEndpoints + from litellm.proxy._types import LitellmUserRoles + + async def _file_not_found(file_id: str, **kwargs): + raise HTTPException(status_code=404, detail=f"File not found: {file_id}") + + proxy_logging_obj = setup_proxy_logging_object(monkeypatch, llm_router) + managed_files = mocker.MagicMock(spec=BaseFileEndpoints) + managed_files.afile_retrieve = mocker.AsyncMock(side_effect=_file_not_found) + managed_files.afile_delete = mocker.AsyncMock(side_effect=_file_not_found) + managed_files.afile_content = mocker.AsyncMock(side_effect=_file_not_found) + proxy_logging_obj.proxy_hook_mapping["managed_files"] = managed_files + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) + + app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth( + api_key="test-key", + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + +def _call_managed_file_route(method: str, path: str): + try: + return client.request(method, path, headers={"Authorization": "Bearer test-key"}) + finally: + import litellm.proxy.proxy_server as ps + + app.dependency_overrides.pop(ps.user_api_key_auth, None) + + +def _missing_managed_file_error(file_id: str) -> dict: + return { + "error": { + "message": f"File not found: {file_id}", + "type": "invalid_request_error", + "param": None, + "code": "404", + } + } + + +def test_create_file_reports_a_half_specified_expires_after_as_a_400(monkeypatch, llm_router: Router): + """A 400 raised inside the route answers with the type a 400 stands for and a JSON null + param, not the literal string "None" in both fields, so a client can classify it.""" + setup_proxy_logging_object(monkeypatch, llm_router) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) + + response = client.post( + "/v1/files", + files={"file": ("mydata.jsonl", VALID_BATCH_LINE, "application/jsonl")}, + data={"purpose": "batch", "target_model_names": "gpt-3.5-turbo", "expires_after[anchor]": "created_at"}, + headers={"Authorization": "Bearer test-key"}, + ) + + assert response.status_code == 400, response.text + error = response.json()["error"] + assert "expires_after[seconds]" in error["message"] + assert error["type"] == "invalid_request_error" + assert error["param"] is None + assert error["code"] == "400" + + +def test_get_file_reports_a_missing_managed_file_as_a_404(mocker: MockerFixture, monkeypatch, llm_router: Router): + _setup_managed_file_route_answering_404(mocker, monkeypatch, llm_router) + file_id = _unified_managed_file_id() + + response = _call_managed_file_route("GET", f"/v1/files/{file_id}") + + assert response.status_code == 404, response.text + assert response.json() == _missing_managed_file_error(file_id) + + +def test_delete_file_reports_a_missing_managed_file_as_a_404(mocker: MockerFixture, monkeypatch, llm_router: Router): + _setup_managed_file_route_answering_404(mocker, monkeypatch, llm_router) + file_id = _unified_managed_file_id() + + response = _call_managed_file_route("DELETE", f"/v1/files/{file_id}") + + assert response.status_code == 404, response.text + assert response.json() == _missing_managed_file_error(file_id) + + +def test_get_file_content_reports_a_missing_managed_file_as_a_404( + mocker: MockerFixture, monkeypatch, llm_router: Router +): + _setup_managed_file_route_answering_404(mocker, monkeypatch, llm_router) + file_id = _unified_managed_file_id() + + response = _call_managed_file_route("GET", f"/v1/files/{file_id}/content") + + assert response.status_code == 404, response.text + assert response.json() == _missing_managed_file_error(file_id)