litellm/tests/test_litellm
Ishaan Jaff 7697b1c397
fix(sso): direct PKCE token exchange + Redis wiring for multi-instance SSO (#22923)
* fix(sso): add direct PKCE token exchange and Redis cache wiring for multi-instance SSO

When PKCE is enabled, bypass fastapi-sso and perform direct token exchange so
code_verifier is correctly included. Store PKCE verifiers as dict in cache
for proper JSON serialization in Redis. Wire user_api_key_cache to Redis when
available so PKCE verifiers are shared across ECS tasks/pods.

Also adds clearer error messages when PKCE is required but not configured.

* refactor(sso): extract PKCE token exchange into SSOAuthenticationHandler methods

- Move import httpx/jwt to module level (top of file, not inside function)
- Extract inline PKCE token exchange + userinfo logic into two static methods:
  _pkce_token_exchange() and _get_pkce_userinfo()
- get_generic_sso_response PKCE path is now a single method call
- Fix double-logging in except block for non-PKCE errors
- Use %-style log formatting (no f-strings in log calls)

* fix: address greptile review feedback

- Fix access_token missing in PKCE path: read from combined_response directly
  instead of generic_sso.access_token (which is only set by verify_and_process)
- Fix PKCE error hint firing when PKCE is already enabled: only show
  'set GENERIC_CLIENT_USE_PKCE=true' advice when code_verifier was absent
- Fix unguarded KeyError on access_token: check for error field in HTTP 200
  responses before accessing token_response['access_token']
- Fix silent empty userinfo: raise ProxyException when both userinfo endpoint
  and id_token fallback produce no user data
- Fix backward-incompatible Redis wiring: only attach Redis to user_api_key_cache
  when GENERIC_CLIENT_USE_PKCE=true, preserving existing in-memory behaviour

* fix: address second round of greptile review feedback

- Fix PKCE error hint: check env var directly (not code_verifier presence) to
  distinguish 'PKCE not configured' from 'PKCE enabled but cache miss'
- Fix misleading Redis TTL comment in proxy_server.py

* fix: address third round of greptile review feedback

- Fix CRITICAL log firing on every non-PKCE callback: only log when PKCE is enabled
- Remove unused pkce_env_value intermediate variable
- Prefer reusing redis_usage_cache over creating separate RedisCache instance
  (avoids losing advanced connection options like SSL, timeouts, db)

* fix: address fourth round of greptile review feedback

- Strip OAuth token credentials from response_convertor input to prevent
  access_token/id_token appearing in restricted-group error messages
- Reuse single httpx.AsyncClient for both token exchange and userinfo requests
  to avoid a second TCP/TLS handshake per SSO callback
- Revert Redis wiring to user_api_key_cache: PKCE code already uses
  redis_usage_cache directly; wiring would route all API-key lookups through
  Redis unnecessarily. Add startup warning instead when PKCE+Redis mismatch.
- Move _OAUTH_TOKEN_FIELDS to module level

* fix remaining PKCE test assertion for dict-format verifier storage

* sanitize PKCE cache log to not expose verifier content

* address greptile review feedback (greploop iteration 3)

* address greptile review feedback (greploop iteration 4)

* address greptile review feedback (greploop iteration 5)

* simplify _get_pkce_userinfo: remove shared-client complexity, use async with directly

* address greptile review feedback (greploop iteration 6)

* address greptile review feedback (greploop iteration 7)

* address greptile review feedback (greploop iteration 8)

* address greptile review feedback (greploop iteration 9)

* address greptile review feedback (greploop iteration 10)

* address greptile review feedback (greploop iteration 11)

* address greptile review feedback (greploop iteration 12)

* fix misleading comment on user_api_key_cache TTL line

* address greptile review feedback (greploop iteration 13)

* address greptile review feedback (greploop iteration 14)

* address greptile review feedback (greploop iteration 15)

* address greptile review feedback (greploop iteration 16)

* address greptile review feedback (greploop iteration 17)

* address greptile review feedback (greploop iteration 18)

* address greptile review feedback (greploop iteration 19)

* address greptile review feedback (greploop iteration 20)

* address greptile review feedback (greploop iteration 21)

* address greptile review feedback (greploop iteration 22)

* address greptile review feedback (greploop iteration 23)

* address greptile review feedback (greploop iteration 24)

* address greptile review feedback (greploop iteration 25)

* address greptile review feedback (greploop iteration 26)

* address greptile review feedback (greploop iteration 27)

* address greptile review feedback (greploop iteration 28)

* address greptile review feedback (greploop iteration 29)

* address greptile review feedback (greploop iteration 30)

* address greptile review feedback (greploop iteration 31)

* address greptile review feedback (greploop iteration 32)

* address greptile review feedback (greploop iteration 33)

* address greptile review feedback (greploop iteration 34)

* address greptile review feedback (greploop iteration 35)

* address greptile review feedback (greploop iteration 37)

- read GENERIC_CLIENT_USE_PKCE env var once in prepare_token_exchange_parameters
- include actual decode error in jwt.decode failure exception message
- add GENERIC_CLIENT_USE_PKCE=true to no-state regression test

* defer PKCE verifier deletion until after all downstream processing

Move _delete_pkce_verifier to after response_convertor and
process_sso_jwt_access_token complete. If JWT processing raises,
the verifier stays in cache so the user can retry without restarting
the full OAuth flow.

* address greptile review feedback (greploop iteration 38)

- fix strict-mode cache miss error message to differentiate
  cross-instance routing failures (Redis configured) from single-instance
  issues (TTL expiry, pod restart) when only in-memory cache is available
- add comment above _get_pkce_userinfo call explaining that bearer
  credentials are always sourced from token_response in the merge step

* fix null JSON response body in _pkce_token_exchange

- Guard against HTTP 200 with body null: response.json() returns None
  for JSON null, and calling .get() on None raises AttributeError.
  Now raises a clean ProxyException with a clear error message.
- Fix misleading userinfo warning: was always saying "empty dict" but
  also fires for JSON null responses; updated to say "empty or null".
- Add HTTP status code assertion to cache miss test.

* address greptile review feedback (greploop iteration 39)

- fix credential leakage: directly assign received_response from
  combined_response instead of relying on nonlocal mutation; Pyright
  was flagging the old guard as unreachable, meaning credential stripping
  might not execute — now it always runs unconditionally
- add test for legacy plain-string cache format backward compat branch
- add test for HTTP 200 with no error field and no access_token (else branch)
- add test for HTTP 200 with JSON null body (new AttributeError guard)

* fix _OAUTH_TOKEN_FIELDS merge loop to preserve userinfo values on absent fields

When the token endpoint omits a bearer-credential field entirely (field
absent from token_response), the previous code deleted it from merged even
if userinfo provided a valid value. Now:
- non-null in token_response → restore authoritative token endpoint value
- explicit null in token_response → remove key from merged (clean absence)
- field absent from token_response → leave userinfo value unchanged

* use HTTP 401 for PKCE missing config errors

GENERIC_CLIENT_ID and GENERIC_TOKEN_ENDPOINT missing when PKCE is
enabled are auth-flow failures, not server errors. Use 401 instead
of 500 to avoid triggering false-positive server error alerts in
monitoring systems.

* address greptile review feedback (greploop iteration 40)

- fix duplicate error logging: demote first format-error log to DEBUG
  so the detailed ERROR in strict-mode branch is not duplicated
- add HTTP status code assertions to all PKCE ProxyException tests
  for better regression protection against accidental code changes

* add credential absence assertions to test_pkce_token_exchange_basic_auth

Verify that client_id and client_secret are NOT double-sent in the POST
body when Basic Auth is used (include_client_id=False with client_secret).
Catches regressions where credentials leak into both Auth header and body.

* address greptile review feedback (greploop iteration 41)

- add Bearer token header assertion to test_pkce_token_exchange_credentials_in_body
- add cache query assertions to both non-strict mode tests to confirm
  the cache was accessed before the warning path triggers

* address greptile review feedback (greploop iteration 42)

- assert null id_token is absent from merged result in basic auth test
- add test for HTTP 200 empty/null userinfo body with no id_token fallback

* use caplog to verify warning logs in non-strict cache miss tests

The two non-strict mode tests now use pytest's caplog fixture to assert
that a warning is actually emitted, not just that the code continues
without raising. This catches regressions where the warning silently
disappears.

* remove dead-code response=None guard in _pkce_token_exchange

* clean up stale pkce verifier cache entries in non-strict mode

* fix test: configure async_delete_cache as AsyncMock and assert cleanup called

* add sentinel guard so pkce-no-redis warning only fires once across hot-reloads

* fix misleading comments: code_verifier init and bearer-credential merge docs

* add best-effort cleanup in strict-mode for corrupt/empty cache entries

* add redirect_uri assertion, userinfo body in non-200 log, sentinel comment
2026-03-12 12:41:39 -07:00
..
a2a_protocol [Fix] A2a Agent Gateway Fixes - A2A agents deployed with localhost/internal URLs in their agent cards (e.g., http://0.0.0.0:8001/) (#20604) 2026-02-06 15:02:34 -08:00
anthropic_interface/exceptions [bug fix] do not fallback to token counter if disable_token_counter is enabled (#19041) 2026-01-13 16:53:38 -08:00
caching Reapply "feat: add model_cost aliases expansion support" 2026-03-12 13:36:57 -03:00
completion_extras Reapply "feat: add model_cost aliases expansion support" 2026-03-12 13:36:57 -03:00
containers fix: add missing OpenAI chat completion params to OPENAI_CHAT_COMPLETION_PARAMS (#21360) 2026-02-16 20:31:21 -08:00
enterprise Address review feedback: remove dead code, add error handling, strengthen test assertions 2026-03-06 09:25:50 -08:00
expected_responses_api_request [Feat] Adds support for server-side compaction on the OpenAI Responses API context_management (#21058) 2026-02-12 10:00:30 -08:00
experimental_mcp_client feat(mcp): add token authentication support for MCP servers 2026-03-10 18:33:08 +02:00
google_genai litellm_fix_mapped_tests_core: fix test isolation and mock injection issues (#20209) 2026-01-31 17:53:54 -08:00
images Merge pull request #22307 from Chesars/fix/22244-image-edit-custom-pricing 2026-02-27 16:38:34 -03:00
integrations merge: resolve conflicts between main and litellm_oss_staging_03_11_2026 2026-03-12 09:38:31 -03:00
interactions fix(test): Update status enum values to match Google Interactions OpenAPI spec (#22061) 2026-02-24 20:26:11 -08:00
litellm_core_utils Merge branch 'main' into litellm_oss_staging_03_11_2026 2026-03-12 10:43:08 -03:00
llms fix(openai): drop all reasoning_effort for gpt-5.4 + tools, including 'none' 2026-03-12 16:22:40 -03:00
ocr Enable local file support for OCR (#22133) 2026-02-27 10:50:02 -08:00
passthrough fix(passthrough): propagate Azure 429/5xx errors in async streaming instead of silent HTTP 200 (#22913) 2026-03-05 10:12:43 -08:00
proxy fix(sso): direct PKCE token exchange + Redis wiring for multi-instance SSO (#22923) 2026-03-12 12:41:39 -07:00
responses Reapply "feat: add model_cost aliases expansion support" 2026-03-12 13:36:57 -03:00
router_strategy fix: complexity_router crashes on list-format message content (OpenAI multi-part messages) (#22761) 2026-03-04 16:18:49 -08:00
router_utils Fix encrypted content streaming affinity issue 2026-03-03 18:37:22 +05:30
secret_managers fix(tests): isolate flaky files endpoint tests from global proxy state (#21788) 2026-02-21 11:20:32 -08:00
test_router fix: use atomic increment-first pattern for model RPM rate limiting 2026-02-24 09:55:07 -03:00
types merge: resolve conflicts between main and litellm_oss_staging_03_11_2026 2026-03-12 09:38:31 -03:00
vector_stores litellm_fix_mapped_tests_core: fix test isolation and mock injection issues (#20209) 2026-01-31 17:53:54 -08:00
__init__.py
conftest.py fix(tests): restore disable_aiohttp_transport and force_ipv4 in isolate_litellm_state 2026-02-17 21:18:49 -03:00
log.txt
readme.md
test_a2a_registry_lookup.py [Feat] Use A2A registered agents with /chat/completions (#20362) 2026-02-03 15:25:38 -08:00
test_acompletion_session_reuse_e2e.py
test_add_deployment_no_master_key.py
test_aembedding_session_reuse_e2e.py
test_anthropic_beta_headers_filtering.py Make tests run with local beta header mapping json 2026-02-13 22:31:42 +05:30
test_anthropic_skills_transformation.py Clean up skills test: remove duplicate imports, parameterize mock HTTP method (#23360) 2026-03-11 22:10:27 +05:30
test_azure_video_router.py
test_chat_ui_responses_session.py [Docs] Fix "Page Not Found" link for Anthropic endpoint (#23349) 2026-03-11 20:17:41 +05:30
test_claude_haiku_4_5_config.py
test_claude_opus_4_6_config.py Fix apac.anthropic.claude-sonnet-4-6 -> au.anthropic.claude-sonnet-4-6 (#21466) 2026-03-05 15:54:26 -08:00
test_constants.py added configurable env for mcp timeouts (#22287) 2026-03-02 13:13:41 -08:00
test_container_router.py
test_cost_calculation_log_level.py fix(tests): use record.getMessage() instead of record.message for LogRecord 2026-02-18 11:46:32 -03:00
test_cost_calculator.py fix(ci): fix deprecated model refs and schema validation in unit tests 2026-03-12 03:28:24 +00:00
test_count_tokens_public_api.py merge: resolve conflicts between main and litellm_oss_staging_03_11_2026 2026-03-12 09:38:31 -03:00
test_deepseek_model_metadata.py fix(model-info): sync DeepSeek model metadata and add bare-name fallback (#20885) 2026-02-11 12:48:10 +05:30
test_eager_tiktoken_load.py fix(main): use local tiktoken cache in lazy loading (#19774) 2026-01-27 18:16:58 -08:00
test_exception_exports.py fix: export PermissionDeniedError from litellm.__init__ 2026-02-11 13:39:19 +01:00
test_exception_header_preservation.py Update test to righ place 2026-02-26 13:26:51 -08:00
test_exception_mapping_request_attribute.py
test_filter_out_litellm_params.py
test_get_blog_posts.py fix(ollama): thread api_base to get_model_info + graceful fallback (#21970) 2026-02-23 21:00:37 -08:00
test_gpt_image_cost_calculator.py Fix gpt-image-1.5 cost calculation not including output image tokens (#19515) 2026-01-22 19:42:15 -08:00
test_groq_streaming_encoding.py
test_lazy_imports.py Fix: test_token_counter_lazy_imports 2026-01-08 16:44:35 +05:30
test_litellm_params_reserved_keys.py fix(snowflake): transform tool_choice string to object format (#23268) 2026-03-11 01:41:24 +05:30
test_logging.py fix:Parse embedded JSON in the message field of logs (#20366) 2026-02-10 16:13:33 +05:30
test_lowest_latency_zero_tokens.py
test_main.py fix(models): set gpt-5.4-pro mode to responses instead of chat 2026-03-09 12:10:08 +01:00
test_model_cost_aliases.py merge: resolve conflicts between main and litellm_oss_staging_03_11_2026 2026-03-12 09:38:31 -03:00
test_model_param_helper.py perf: cache _get_relevant_args_to_use_for_logging() at module level (#20077) 2026-02-02 10:54:49 -08:00
test_model_response_normalization.py fix(types): remove StreamingChoices from ModelResponse, use ModelResponseStream 2026-02-20 17:47:42 -03:00
test_nested_drop_params.py
test_project_tags_pydantic.py fix: req changes 2026-02-27 13:33:34 +05:30
test_redis.py
test_register_model_custom_pricing.py test: fix misleading precedence test per review feedback 2026-03-02 08:31:33 +00:00
test_responses_api_bridge_non_stream.py fix: Pydantic will fail to parse it because cached_tokens is required but not provided 2026-01-28 11:51:26 +05:30
test_responses_id_security.py Fix responses ID security test for new request_cache parameter 2026-03-04 11:29:51 -03:00
test_router.py CircleCI test stability (#23055) 2026-03-07 15:19:39 -08:00
test_router_google_genai.py
test_router_model_cost_isolation.py [Fix] prevent shared backend model key from being polluted by per-deployment custom pricing (#20679) 2026-02-09 19:38:44 -08:00
test_router_per_deployment_num_retries.py Bugfix/19481 num retries env var type (#19507) 2026-01-22 19:39:58 -08:00
test_router_redis_init.py fix: handle deprecated 'redis_db' arg to prevent crash (#19808) 2026-02-02 18:18:05 +05:30
test_router_retry_non_retryable_errors.py Reapply "feat: add model_cost aliases expansion support" 2026-03-12 13:36:57 -03:00
test_router_silent_experiment.py fix: req changes from greptile 2026-03-10 13:03:29 +05:30
test_service_logger.py fix(proxy): fix master key rotation Prisma validation errors (#21330) 2026-02-16 15:13:05 -08:00
test_shared_session_integration.py
test_ssl_verify_unit.py BUMP Enterprise PIP 2026-02-14 13:40:48 -08:00
test_streaming_connection_cleanup.py fix: add debug logging to stream cleanup, improve tests 2026-02-14 17:31:39 -08:00
test_system_message_format_bug.py
test_utils.py Merge branch 'main' into litellm_oss_staging_03_11_2026 2026-03-12 10:43:08 -03:00
test_uuid_helper.py
test_video_generation.py fix(ollama): thread api_base to get_model_info + graceful fallback (#21970) 2026-02-23 21:00:37 -08:00
test_xai_responses_auto_routing.py Add routing of xai chat completions to responses when web search options is present 2026-01-30 14:15:35 +05:30

Testing for litellm/

This directory 1:1 maps the the litellm/ directory, and can only contain mocked tests.

The point of this is to:

  1. Increase test coverage of litellm/
  2. Make it easy for contributors to add tests for the litellm/ package and easily run tests without needing LLM API keys.

File name conventions

  • litellm/proxy/test_caching_routes.py maps to litellm/proxy/caching_routes.py
  • test_<filename>.py maps to litellm/<filename>.py