test(store_model_in_db): assert the 400 contract in the unknown-model spend log test - #39842
Conversation
Greptile SummaryTest-only updates align the unknown-model spend-log test with both supported HTTP 400 failure paths.
Confidence Score: 5/5The PR appears safe to merge because it changes only test expectations and preserves meaningful validation for both supported failure representations. No actionable new issue remains. The previous telemetry-coverage finding was manually resolved after model-group and error-class checks were restored.
|
| Filename | Overview |
|---|---|
| tests/store_model_in_db_tests/test_openai_error_handling.py | Updates unknown-model error assertions to cover both legitimate proxy and router failure shapes without changing production behavior. |
Reviews (4): Last reviewed commit: "test(store_model_in_db): accept both 400..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a61bead. Configure here.
Pull request was closed
Pull request was closed
b77b7f1
into
litellm_internal_staging
TLDR
Problem this solves:
proxy_store_model_in_db_testsred on staging since 2026-09-04 03:09 UTCmodel_groupandlitellm.BadRequestErrorwordingHow it solves it:
User Flow
Before: a contributor merging into
litellm_internal_stagingfinds the required jobproxy_store_model_in_db_testsred on every run since 2026-09-04 03:09 UTC, on a test their change never touchedproxy_store_model_in_db_testsfailed ontest_chat_completion_bad_model_with_spend_logs"model": "non-existent-model"on a key that lists that model, and got HTTP 400 withInvalid model name passed in model=non-existent-model. Call /v1/models to view available models for your key.plus anx-litellm-call-idheadermodel "non-existent-model",spend 0,error_code "400", the same message, andmodel_group ""model_group == "non-existent-model"and onlitellm.BadRequestErrorwording, so it fails withassert '' == 'non-existent-model'and the job stays red on every scheduled staging run (89097, 89104, 89115) and on unrelated PRs merged with stagingAfter: the same job is green because the test checks what a caller of an unknown model actually gets
proxy_store_model_in_db_testsgreen"model": "non-existent-model"on a key that lists that model, and got HTTP 400 withInvalid model name passed in model=non-existent-model. Call /v1/models to view available models for your key.plus anx-litellm-call-idheadermodel "non-existent-model",spend 0,error_code "400", the same message, andmodel_group ""model "non-existent-model", zero spend and tokens,error_code "400",model_group "",error_class "ProxyModelNotFoundError", and the model name in the error message, so it passes; it also passes when a sibling suite leaves a wildcard route behind, where the row carriesmodel_group "non-existent-model",error_class "BadRequestError", and alitellm.BadRequestErrormessage instead, and fails on any other model group or error classRelevant issues
Linear ticket
Resolves LIT-6949
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Why the job flipped: the last green staging job (2155012) answered the same request from the router (
litellm.BadRequestError: You passed in model=non-existent-model. There are no healthy deployments for this model. Received Model Group=non-existent-model), which only happens while a wildcard route is registered. The only wildcard in the job is theassemblyai/*modeltest_adding_passthrough_model.pyadds throughPOST /model/newand deletes throughPOST /model/delete. Before #39664 that delete left the wildcard behind, so every later unknown-model request went through the router; #39664 evicts it, and the proxy answers the plain 400 it always gave without wildcards. Case 2 below shows the original test passing only in that stale stateShared setup, the CI job mirrored locally: proxy at the commit named by each heading, booted with
--num_workers 2(one process, two uvicorn workers behind 127.0.0.1:41237, one fresh Postgres database), fromlitellm/proxy/example_config_yaml/store_model_db_config.yamlwithteam_metadata_validator_e2e.pybeside it,STORE_MODEL_IN_DB=True,LITELLM_MASTER_KEY=sk-1234,FAKE_OPENAI_API_BASEontests/_fake_openai_endpoint_server.py,TEAM_METADATA_VALIDATION_SERVICE_URLontests/store_model_in_db_tests/cost_center_service.py,LITELLM_LOG=ERROR. CI itself runs one container with the default single worker; a single-worker run gave the same results line for line. Both test files run in CI glob order from copies with0.0.0.0:4000replaced by127.0.0.1:41237, since the files hardcode the port.test_e2e_assemblyai_passthroughfails here on a 401 because this machine has noASSEMBLYAI_API_KEY; its fixture still adds and deletes the wildcard, which is the part that mattersBefore (59d42d3)
Case 1: unknown model with no wildcard route registered (CI's state today)
uv run pytest <copies>/test_adding_passthrough_model.py <copies>/test_openai_error_handling.py -vvcurl -s -D - $P/v1/chat/completions -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"model":"non-existent-model","messages":[{"role":"user","content":"Hello!"}]}'sleep 15; curl -s "$P/spend/logs?request_id=e4aac64b-6336-49ca-8b71-797c76be0b69" -H "Authorization: Bearer sk-1234" | jq '.[0] | {request_id, model, model_group, spend, total_tokens, status: .metadata.status, error_code: .metadata.error_information.error_code, error_class: .metadata.error_information.error_class, error_message: .metadata.error_information.error_message}'Case 2: unknown model with a wildcard route registered (the state a stale
assemblyai/*left before #39664)curl -s $P/model/new -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"model_name":"assemblyai/*","litellm_params":{"model":"assemblyai/*","custom_llm_provider":"assemblyai","api_key":"dummy","api_base":"https://api.assemblyai.com","use_in_pass_through":true},"model_info":{}}'sleep 12; for i in 1 2 3 4; do curl -s $P/v1/chat/completions -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"model":"non-existent-model","messages":[{"role":"user","content":"Hello!"}]}' | jq -r .error.message; done(both workers now hold the wildcard)uv run pytest "<copies>/test_openai_error_handling.py::test_chat_completion_bad_model_with_spend_logs" -vv(the original test)curl -s $P/model/delete -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" -d '{"id":"48f592dd-5816-4554-bfde-7f6d2eaa5abe"}'; sleep 12then the four-request loop from step 2 again: the proxy is back in case 1's state on both workersAfter (a61bead)
Case 1: unknown model with no wildcard route registered (CI's state today)
uv run pytest <copies>/test_adding_passthrough_model.py <copies>/test_openai_error_handling.py -vvmodel,model_groupone of""or"non-existent-model", zero spend and tokens,error_code "400",error_classone ofProxyModelNotFoundErrororBadRequestError, and the model name in the messageCase 2: unknown model with a wildcard route registered (the state a stale
assemblyai/*left before #39664)POST /model/newforassemblyai/*as before (model_id 0873709c-1a14-420b-8aa1-677de2919e45), same 12 second wait and four-request check: all four answers come from the routeruv run pytest "<copies>/test_openai_error_handling.py::test_chat_completion_bad_model_with_spend_logs" -vv(the fixed test)POST /model/deleteas before (Model: 0873709c-1a14-420b-8aa1-677de2919e45 deleted successfully); both workers were back to case 1's answer within a minute, andGET /v2/model/infolisted noassemblyai/*rowObservations from the run, none caused or changed by this PR:
assemblyai/*row behind; CI runs one workermodel_group ""and no tracebackPOST /model/newwithoutlitellm_paramsanswers a 500, not a 422Type
✅ Test
Caveats (if any)
Low
/v2/model/infocan lag a/model/newor/model/deleteby a sync tick, so the sibling suite's cleanup can miss a row; CI runs one worker and this PR leaves it alonemodel_group ""; that is how the proxy already behaves, unchanged heretest_e2e_assemblyai_passthroughneedsASSEMBLYAI_API_KEY, which CI has and this machine does notFinal Attestation
Note
Low Risk
Test-only assertion updates; no production code or runtime behavior changes.
Overview
Fixes flaky
proxy_store_model_in_db_testsby aligningtest_chat_completion_bad_model_with_spend_logswith how the proxy records unknown-model failures after wildcard routes are properly evicted (#39664).The test now asserts HTTP 400 on the chat completion call before checking spend logs. Spend-log expectations no longer assume the router-only shape:
model_groupmay be""(direct proxy rejection) or"non-existent-model"(wildcard/router path), anderror_classmay beProxyModelNotFoundErrororBadRequestError, with the model name still required in the error message. The strictlitellm.BadRequestErrormessage check was dropped in favor of that broader contract.Reviewed by Cursor Bugbot for commit a61bead. Bugbot is set up for automated code reviews on this repo. Configure here.