From 9eedf608e7451b307b85dbe9fd0c2769dad080ee Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 9 Nov 2026 22:31:39 -0800 Subject: [PATCH 1/4] test(vertex_ai): read the bundled beta header config in the prompt caching scope test The test read whatever config the previous test left loaded. The local_beta_headers_config fixture reloads from the hosted file on main at teardown, and since #49409 that file lets vertex_ai send prompt-caching-scope-2026-01-05 as a header, which this line's bundled config does not. --- .../test_vertex_ai_partner_models_anthropic_transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py b/tests/unit/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py index 0a582c95749..582d844418b 100644 --- a/tests/unit/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py +++ b/tests/unit/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py @@ -640,7 +640,7 @@ async def test_vertex_ai_anthropic_prompt_caching_does_not_add_beta_header( assert "anthropic_beta" not in json.loads(request.content) -def test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_header(): +def test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_header(local_beta_headers_config: None) -> None: """ Test that remove_unsupported_beta correctly filters out prompt-caching-scope-2026-01-05 from the anthropic-beta headers. From f8eeb05ba87b731f897024643016642fcdbd71c4 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 9 Nov 2026 22:41:32 -0800 Subject: [PATCH 2/4] test(integration): empty the shared client cache before the tier pin batch test The test stores its recording Redis client in litellm's shared client cache with the default ttl. When earlier tests in the same process have filled that cache with longer lived clients, the next insert evicts the recording client, the pin is read through a fresh client, and the test sees no commands on the wire. --- tests/integration/sdk/test_dual_cache_redis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/sdk/test_dual_cache_redis.py b/tests/integration/sdk/test_dual_cache_redis.py index 7d05eb2795b..b79683c126c 100644 --- a/tests/integration/sdk/test_dual_cache_redis.py +++ b/tests/integration/sdk/test_dual_cache_redis.py @@ -222,6 +222,7 @@ async def test_tier_pin_joins_pending_reads_without_reusing_request_state(mode: ) client: Final = Redis(connection_pool=pool) client_key: Final = redis._get_async_client_cache_key() + litellm.in_memory_llm_clients_cache.flush_cache() litellm.in_memory_llm_clients_cache.set_cache(key=client_key, value=client) cache: Final = DualCache(redis_cache=redis) key: Final = redis.check_and_fix_namespace("pin") From d99337739638b4acf8e2d99f31b12afb0a7290f3 Mon Sep 17 00:00:00 2001 From: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:08:23 -0800 Subject: [PATCH 3/4] test(integration): give each kill-burst its own prompts so cached control replies cannot stand in for late traffic (#50569) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit 0d0bf8f75f63a9c76ab75d121cdd6b7f498f2607) --- .../spend/test_normalized_error_long_message.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integration/spend/test_normalized_error_long_message.py b/tests/integration/spend/test_normalized_error_long_message.py index 23200c0450e..23769391427 100644 --- a/tests/integration/spend/test_normalized_error_long_message.py +++ b/tests/integration/spend/test_normalized_error_long_message.py @@ -358,16 +358,17 @@ def _burst( *, rejected_model: str = CRAFTED_MODEL, ) -> tuple[_BurstResult, ...]: + nonce: Final = uuid.uuid4().hex crafted: Final = tuple( (f"crafted-{path}-{index}", path, _body(path, rejected_model, uuid.uuid4().hex, stream=index % 2 == 1), open_key) for path in (CHAT, MESSAGES, RESPONSES) for index in range(4) ) happy: Final = tuple( - (f"happy-{index}", CHAT, _body(CHAT, happy_model, f"happy-{index}"), happy_key) for index in range(8) + (f"happy-{index}", CHAT, _body(CHAT, happy_model, f"happy-{index}-{nonce}"), happy_key) for index in range(8) ) late: Final = tuple( - (f"late-{index}", CHAT, _body(CHAT, happy_model, f"late-{index}"), happy_key) for index in range(8) + (f"late-{index}", CHAT, _body(CHAT, happy_model, f"late-{index}-{nonce}"), happy_key) for index in range(8) ) with ( ThreadPoolExecutor(max_workers=28) as pool, @@ -427,6 +428,7 @@ def test_killing_one_worker_mid_burst_leaves_the_other_serving_crafted_and_happy open_key: Final = scenario.key() control: Final = _burst(client, model, key, open_key, lambda: None, rejected_model=BENIGN_288KB_MODEL) assert all(result.status == (400 if result.label.startswith("crafted") else 200) for result in control), control + wire.drain() workers: Final = owned.worker_pids() assert len(workers) == 2, workers victim: Final = min(workers) @@ -438,6 +440,8 @@ def test_killing_one_worker_mid_burst_leaves_the_other_serving_crafted_and_happy _assert_burst_rejections_fast(results, control) late: Final = tuple(result for result in results if result.label.startswith("late")) assert all(result.status == 200 for result in late), late + seen: Final = tuple(request.body.decode() for request in wire.drain()) + assert all(any(f"normalized error audit {result.label}-" in body for body in seen) for result in late), seen _assert_rows_land_exactly_once(results, "late") survivor: Final = tuple(pid for pid in owned.worker_pids() if pid != victim) assert survivor, "no worker left serving" From e4f74548a3a3658c17baa35b862590bbf6756841 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 9 Nov 2026 23:57:47 -0800 Subject: [PATCH 4/4] test(integration): cap the OAuth metadata lookup in the mixed capabilities subscription case The second server in this case is a plain MCP SDK server registered as oauth_delegate. The gateway's first catalog listing waits for that server's OAuth metadata lookup, which never resolves and runs for the full MCP_METADATA_TIMEOUT of 10 seconds. The MCP SDK client also gives server/discover 10 seconds, so the case passed or failed on about a tenth of a second of head start. Setting LITELLM_MCP_METADATA_TIMEOUT to 5 for this case keeps the lookup well inside the client limit. The case still asserts that subscription changes are delivered and tools are refreshed across both servers. --- tests/integration/mcp/test_interactions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/mcp/test_interactions.py b/tests/integration/mcp/test_interactions.py index 5577cf16067..b77deb5236a 100644 --- a/tests/integration/mcp/test_interactions.py +++ b/tests/integration/mcp/test_interactions.py @@ -1227,6 +1227,7 @@ def test_modern_subscription_delivers_changes_between_calls_and_refreshes_tools( "PYTHONPATH": os.pathsep.join((str(source), str(source / "tests"))), "LITELLM_SALT_KEY": "subscription-pagination-key", **({"LITELLM_MCP_TOOL_LISTING_TIMEOUT": "2"} if access == "probe-timeout" else {}), + **({"LITELLM_MCP_METADATA_TIMEOUT": "5"} if access == "mixed-capabilities" else {}), "STORE_MODEL_IN_DB": "False", "DISABLE_SCHEMA_UPDATE": "true", "LITELLM_LOCAL_MODEL_COST_MAP": "True",