litellm/tests/unit/proxy/common_utils
mrinal-berri b0c580d391
feat: email key owners before opted-in virtual keys expire (#51482)
* feat: email owners before opted-in virtual keys expire

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: batch expiry alert queries and use branded email template

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: cast expiry alert query bounds as UTC timestamps

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: satisfy expiry alert CI contracts

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat: single-day key expiry alerts with global default and daily check

- metadata.key_expiry_alert_days is one integer (1-365) instead of a list
- general_settings.key_expiry_alert_days sets a default for every key with an expiry and owner email; key metadata overrides it
- LITELLM_KEY_EXPIRY_ALERT_CHECK_INTERVAL_SECONDS defaults to 24 hours

* fix: isolate key expiry alert setup and check every 6 hours

- build the expiry alert manager in its own startup step that logs and returns None on bad config or setup errors, so later background jobs still register
- default LITELLM_KEY_EXPIRY_ALERT_CHECK_INTERVAL_SECONDS to 6 hours

* refactor: import litellm_enterprise email helpers directly in key expiry alerts

Replace the import_module + Protocol module validation with plain imports, matching the key rotation hook and check_batch_cost. litellm_enterprise is absent from the type-check venv, so the imported names carry the same reasoned pyright ignore used in response_api_endpoints.

* fix: apply global key expiry default to keys stored with null metadata

Keys created without metadata (for example via /user/new) store JSON null, which failed metadata parsing and skipped the global key_expiry_alert_days default. Read null metadata as empty.

* fix: guard litellm_enterprise import in key expiry alert job

check_unsafe_enterprise_import requires enterprise imports outside the enterprise package to sit in try/except ImportError. Skip the run when the package is absent, matching the key rotation email hook.

* feat: run key expiry alert scan on one pod per cycle, every 12 hours

Take the shared PodLockManager lock (litellm_key_expiry_alert_job) like key rotation and expired UI session cleanup, so a single pod scans LiteLLM_VerificationToken each cycle; the per-key Redis claim still guards each email. Default LITELLM_KEY_EXPIRY_ALERT_CHECK_INTERVAL_SECONDS to 12 hours.

* fix: scan for key expiry alerts when the job lock cannot be read from Redis

acquire_lock returns False both when another pod holds the lock and when Redis errors. Skip the cycle only when another pod's id is on the lock key; otherwise scan without the lock, keeping the per-key claim as the duplicate guard, so a Redis blip cannot drop warnings for a 12-hour cycle. Replace the startup wiring assertion with a behaviour test against the proxy's pod lock.

* fix: hold the key expiry alert lock for the whole cycle

Each pod's timer fires at its own boot time plus jitter, so releasing the lock after a millisecond scan let every pod run each cycle. Take it non-reentrant for nearly the full interval and never release it, like the weekly spend report, so the first pod to fire runs the cycle and the rest skip.

* fix: key the key expiry alert lock by clock window

Pods' interval timers fire at their own boot time plus jitter, so a fixed lock name with a TTL shorter than the interval let a late-booted pod run twice in one interval, and a TTL equal to the interval made a pod skip its own next run. Name the lock after the clock window (int(now // interval) * interval, as file_usage_caps buckets its counters) with a one-interval TTL: the first pod in each window runs and the rest skip. Release the window if the scan raises so another pod can retry, and run anyway when the Redis circuit breaker is open.

* test: keep key expiry lock tests in-process with a fixed clock

Simulate the Redis outage with a disconnected in-process fakeredis server (wired like the router_redis fixture) instead of a socket to a closed port, and give the startup-built job the test clock instead of the wall clock, per tests/unit/AGENTS.md.

* fix: declare the key expiry alerts service family on process_alerts

process_alerts reads the window lock holder from Redis directly, so test_every_function_that_reaches_redis_directly_declares_its_family requires it to carry its family, as _process_key already does.

* refactor: align key expiry alerts with existing litellm surfaces

- read general_settings.key_expiry_alert_days on every run via _current_general_settings(), like the performance alert and spend capture jobs, so a UI or API edit applies without a restart; an invalid value is logged and ignored
- share the event name as VIRTUAL_KEY_EXPIRING_EMAIL_EVENT in litellm/types, like MODEL_DEPRECATION_EMAIL_EVENT
- default the Virtual Key Expiring email event off, like the other scheduled emails
- name the Redis family for the claim data (key_expiry_alert_claims) and read the window lock under POD_LOCK_TARGET, mirroring reset_budget_job._lease_is_held
- give the per-key check its own validator, run with the logging check through raise_on_invalid_key_metadata

* chore: drop two comments that restate the code

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-11-14 13:47:16 -08:00
..
html_forms test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
__init__.py ci: move tests/proxy_unit_tests to tests/unit/proxy and run the proxy-db shards from litellm-tests (#42903) 2026-09-24 22:59:11 +00:00
test_admin_ui_utils.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_auth_cache_invalidation_pubsub.py feat(redis): serve the proxy's common Redis call with Rust stores (#50170) 2026-11-06 06:01:11 +00:00
test_cache_aware_routing.py refactor(router): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_cache_codec.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_callback_config_validation.py feat(otel): let a team or key OTel v2 destination add its own request metadata keys (#50813) 2026-11-11 13:57:41 -08:00
test_callback_utils.py feat(proxy): let a key's logging entry reference a proxy credential (#50572) 2026-11-09 16:02:46 -08:00
test_check_batch_cost.py chore: move workspace packages under litellm (#51000) 2026-11-12 04:19:57 +00:00
test_check_responses_cost.py fix(proxy): align ownership and scope handling (#50600) 2026-11-08 07:03:36 +00:00
test_codex_catalog_source.py feat(proxy): serve Codex its own release's model catalog, fetched from GitHub (#50318) 2026-11-06 21:58:54 +00:00
test_codex_model_catalog.py fix(proxy): send the whole Codex model catalog to Codex 0.166.0-alpha.1 and newer (#50736) 2026-11-10 07:10:30 +00:00
test_codex_model_translation.py fix(proxy): list images in the Codex catalog only for models known to read them (#50738) 2026-11-10 03:46:37 +00:00
test_codex_version.py fix(proxy): send the whole Codex model catalog to Codex 0.166.0-alpha.1 and newer (#50736) 2026-11-10 07:10:30 +00:00
test_config_sync_pubsub.py refactor(rust_bridge): route every Rust-to-Python call through host modules (#50822) 2026-11-11 01:57:54 +00:00
test_credential_hydration.py fix(secret_managers)!: resolve oidc/file and oidc/env refs in admin-configured api_key per request (#47341) 2026-10-20 01:43:59 +00:00
test_custom_openapi_spec.py docs(proxy): describe /v1/decisions and /v1/systemone in Swagger UI (#45838) 2026-10-10 21:04:59 +00:00
test_customer_usage.py feat(customer-portal): complete hierarchy management (#51521) 2026-11-13 17:58:34 +00:00
test_debug_utils.py fix(router): keep the prompt caching affinity pin alive as long as the prefix's cache_control ttl (#46084) 2026-10-14 21:10:09 -07:00
test_discoverable_model_filter.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_encrypt_decrypt_utils.py fix(mcp): derive MCP bridge and session keys with the FIPS provider's KBKDF (#50032) 2026-11-07 13:33:27 -08:00
test_error_body_call_id.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_expired_ui_session_key_cleanup_manager.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_fips.py feat(proxy): hash UI passwords with PBKDF2 in FIPS mode (#49874) 2026-11-06 01:31:59 -08:00
test_get_routes.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_http_parsing_utils.py fix(auth): enforce model access on Google and image URL routes (#51226) 2026-11-11 12:22:12 -08:00
test_json_merge_patch.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_key_expiry_alert_manager.py feat: email key owners before opted-in virtual keys expire (#51482) 2026-11-14 13:47:16 -08:00
test_key_rotation_e2e.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_key_rotation_integration.py fix(security): remove the publicly known master key from the repo (#44718) 2026-10-06 10:55:24 -07:00
test_key_rotation_lock.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_key_rotation_manager.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_load_config_utils.py fix(bedrock): derive the web identity session policy per AWS caller (#50592) 2026-11-08 19:13:53 +00:00
test_model_deprecation.py refactor(rust_bridge): route every Rust-to-Python call through host modules (#50822) 2026-11-11 01:57:54 +00:00
test_model_listing_utils.py fix(proxy): resolve model aliases to their target deployment for listing metadata (#41041) 2026-10-15 00:38:50 -07:00
test_openai_endpoint_utils.py feat(router): add the Switchyard stage router as an auto router strategy (#50407) 2026-11-06 12:07:57 -08:00
test_openai_error_payload.py fix(proxy)!: label shared-handler errors the way the route catch-alls do (#39555) 2026-10-30 06:58:05 -07:00
test_openapi_route.py fix(proxy): build the OpenAPI schema off the event loop so a cold /openapi.json no longer stalls the worker (#47352) 2026-10-20 04:31:37 +00:00
test_openapi_schema_compat.py fix(proxy): build the OpenAPI schema off the event loop so a cold /openapi.json no longer stalls the worker (#47352) 2026-10-20 04:31:37 +00:00
test_periodic_reload_schedule.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_proxy_encrypt_decrypt.py fix(security): remove the publicly known master key from the repo (#44718) 2026-10-06 10:55:24 -07:00
test_proxy_rate_limit_error.py feat(proxy): router_settings.fallback_on_saturation walks key and team fallbacks before a model saturates, replacing team model_preferences (#51439) 2026-11-13 05:57:18 +00:00
test_rbac_utils.py refactor(proxy): expose public names for private proxy helpers (#45170) 2026-10-07 18:59:27 -07:00
test_realtime_cache.py feat(realtime)!: support latest OpenAI audio models (#35600) 2026-11-03 07:27:52 +00:00
test_registry_read_through.py fix(proxy): hold DB_RECONCILE_LOCK around credential create, update and delete (#50551) 2026-11-07 16:02:44 -08:00
test_request_tag_mappings.py fix(proxy): run baggage tag mappings and budget checks on /queue/chat/completions (#51753) 2026-11-14 08:54:54 -05:00
test_reset_budget_job.py feat(proxy): support budget_limits windows on internal users (#51763) 2026-11-14 07:44:35 -05:00
test_resource_ownership.py refactor(core_utils): move resource_ownership out of the proxy (#45805) 2026-10-10 21:02:53 +00:00
test_scheduled_job_stagger.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_semantic_text_index.py fix(mcp): leave out tool texts too long to embed instead of failing tool search (#43554) 2026-10-14 14:53:00 -07:00
test_slow_gc_logger.py test(e2e): event_loop_stalls perf case fails on a gateway event-loop hold over 100 ms that is not on its allowlist (#46974) 2026-10-17 06:55:53 +00:00
test_sse_keepalive.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_static_asset_utils.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_swagger_utils.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_timezone_utils.py test(proxy): move middleware, spend_tracking, pass_through, common_utils and root proxy tests into tests/unit/proxy (#44015) 2026-10-01 18:23:31 +00:00
test_token_count_log.py fix(proxy): answer x-litellm-call-id on every token count response (#51037) 2026-11-11 22:26:47 +00:00
test_token_count_settings.py fix(count_tokens): count grok-4-1, grok-4.3 and DeepSeek V4.1 tool requests the way their hosts bill them (#49074) 2026-11-02 12:37:35 -08:00
test_upsert_budget_membership.py fix(team): keep the team reset schedule when a temp budget increase is set on a member inheriting the team default (#48495) 2026-10-26 17:13:56 -07:00
test_user_api_key_cache.py fix(proxy): keep a customer budget reset from being undone by a stale hierarchy cache entry (#51694) 2026-11-13 14:21:20 -08:00
test_validation_error_body.py fix(proxy): keep the submitted body out of 422 validation errors (#43231) 2026-09-25 17:05:53 -07:00