Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8adcea9
test(integration): poll the partition lock witness off the event loop
yuneng-berri Oct 9, 2026
c8394aa
test(e2e): retry the reasoning turn until the model emits a reasoning…
yuneng-berri Oct 9, 2026
81b4507
test(e2e): retry a Bedrock Converse model once when it reports no cac…
yuneng-berri Oct 9, 2026
564a488
test(e2e): poll until a deleted stored response stops being retrievable
yuneng-berri Oct 9, 2026
45d02fd
test(e2e): give the Together prompt cache five primed attempts
yuneng-berri Oct 9, 2026
d8b40e5
test(e2e): allow 30s for the idle RSS reading at session start
yuneng-berri Oct 9, 2026
2287d5b
test(e2e): scope the MCP submission check to its own card
yuneng-berri Oct 9, 2026
9cd8cec
test(router): wait for the primary success record before counting it
yuneng-berri Oct 9, 2026
2d17852
test(proxy): give the spend-log cleanup run a 1s budget
yuneng-berri Oct 9, 2026
7cdcb7b
test(autorouter): release the slow token count at 2.05s instead of 2.3s
yuneng-berri Oct 9, 2026
cc4c6f3
test(xai): run the reasoning-effort tests on grok-4.6 with a prompt t…
yuneng-berri Oct 9, 2026
b3ccee9
test(integration): give the two worker-kill chaos tests the owned-pro…
yuneng-berri Oct 9, 2026
28253c2
test(integration): compare regional image responses without their cre…
yuneng-berri Oct 9, 2026
eb30e11
test(integration): count only the S3 logger's flush task
yuneng-berri Oct 9, 2026
aca133a
test(integration): send guardrail timeout probes eight at a time
yuneng-berri Oct 9, 2026
3f35daf
test(integration): count a killed Arize chaos worker as gone once it …
yuneng-berri Oct 9, 2026
99b73f1
test(integration): send the Typesafe connection test its real model a…
yuneng-berri Oct 9, 2026
81dd0ec
test(integration): keep DB-stored models out of the usage-routing Red…
yuneng-berri Oct 9, 2026
3b9cc14
test(vertex_ai): prove the batch upload streams by laziness instead o…
yuneng-berri Oct 9, 2026
1575c82
test(integration): keep the alias test_connection call as a known bug
yuneng-berri Oct 9, 2026
9008e45
test: type the counting mapper and the Bedrock rerun helper
yuneng-berri Oct 9, 2026
6b76939
test: annotate the new test locals as Final and build them as tuples
yuneng-berri Oct 9, 2026
a50d62d
test(vertex_ai): prove pull-driven transforms with the garbage tail a…
yuneng-berri Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions tests/e2e/claude_code/prompt_caching_5m/test_bedrock_converse.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@

from __future__ import annotations

from typing import Any, Mapping, Optional
from typing import Any, Dict, Final, Mapping, Optional, Sequence

import pytest

from e2e_metadata import Capability, Domain, Mode, Provider, Route, Subject, meta
from claude_code._env import require_proxy
from claude_code.cli_driver import (
ClaudeCLIError,
ModelResult,
failure_diagnostic,
run_claude_models_parallel,
)
Expand Down Expand Up @@ -62,12 +63,21 @@ def test_prompt_caching_5m_bedrock_converse(compat_result):
upstream usage block surfaces a non-zero cache token count."""
base_url, api_key = require_proxy(compat_result)

outcomes = run_claude_models_parallel(
models=BEDROCK_CONVERSE_MODELS,
prompt="Reply with the single word 'pong' and nothing else.",
base_url=base_url,
api_key=api_key,
def run(models: Sequence[str]) -> Dict[str, ModelResult]:
return run_claude_models_parallel(
models=models,
prompt="Reply with the single word 'pong' and nothing else.",
base_url=base_url,
api_key=api_key,
)

first: Final = run(BEDROCK_CONVERSE_MODELS)
uncached: Final = tuple(
model
for model, outcome in first.items()
if not isinstance(outcome, ClaudeCLIError) and outcome.exit_code == 0 and _cache_tokens(outcome.usage) <= 0
)
outcomes: Final = {**first, **run(uncached)} if uncached else first

failures = []
for model in BEDROCK_CONVERSE_MODELS:
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
_CALL_PASSED = pytest.StashKey[bool]()
_IDLE_RSS = pytest.StashKey[RssCapture]()

IDLE_RSS_READ_TIMEOUT_SECONDS: Final = 10.0
IDLE_RSS_READ_TIMEOUT_SECONDS: Final = 30.0

OPT_IN_MARKERS: Final = MappingProxyType(
{
Expand Down
30 changes: 21 additions & 9 deletions tests/e2e/llm_translation/test_responses_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import contextlib
import json
import threading
from collections.abc import Mapping
from collections.abc import Callable, Mapping
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Final, cast
Expand Down Expand Up @@ -1031,6 +1031,7 @@ def test_empty_input_returns_client_error(self, proxy: ProxyClient, resources: R
REASONING_BACKEND: Final = "openai/gpt-5.4-mini"
SHELL_BACKEND: Final = "openai/gpt-5.5"
TOOL_DATE: Final = "2025-01-15"
REASONING_ATTEMPTS: Final = 3

GET_TODAY_TOOL: FunctionToolParam = {
"type": "function",
Expand Down Expand Up @@ -1062,6 +1063,15 @@ class TodayReport(BaseModel):
number_of_r: str


def _has_reasoning_item(response: Response) -> bool:
return any(isinstance(item, ResponseReasoningItem) for item in response.output)


def _until_reasoning_emitted(create: Callable[[], Response]) -> Response:
attempts: Final = (create() for _ in range(REASONING_ATTEMPTS - 1))
return next(filter(_has_reasoning_item, attempts), None) or create()


class TestResponsesOpenAIHostedFeatures:
@meta(
Subject(
Expand Down Expand Up @@ -1091,14 +1101,16 @@ def test_reasoning_items_replay_into_structured_output_after_tool_call(
),
}

first = client.responses.create(
model=model,
input=[question],
tools=[GET_TODAY_TOOL],
tool_choice={"type": "function", "name": "get_today"},
reasoning={"effort": "medium", "summary": "auto"},
text={"format": TODAY_REPORT_FORMAT},
extra_body=NO_PROXY_CACHE,
first: Final = _until_reasoning_emitted(
lambda: client.responses.create(
model=model,
input=[question],
tools=[GET_TODAY_TOOL],
tool_choice={"type": "function", "name": "get_today"},
reasoning={"effort": "medium", "summary": "auto"},
text={"format": TODAY_REPORT_FORMAT},
extra_body=NO_PROXY_CACHE,
)
)
assert any(isinstance(item, ResponseReasoningItem) for item in first.output), (
f"reasoning model returned no reasoning item: {first.output!r}"
Expand Down
19 changes: 15 additions & 4 deletions tests/e2e/llm_translation/test_responses_retrieve_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ def test_invalid_response_id_returns_error(self, proxy: ProxyClient, resources:
pytest.fail(f"invalid response id expected 404, got {other!r}")


def _retrieve_until_gone(client: openai.OpenAI, response_id: str) -> openai.APIStatusError:
deadline: Final = time.monotonic() + POLL_TIMEOUT
while time.monotonic() < deadline:
try:
client.responses.retrieve(response_id)
except openai.APIStatusError as error:
return error
time.sleep(POLL_INTERVAL)
raise AssertionError(f"response {response_id!r} was still retrievable {POLL_TIMEOUT}s after delete")


def _register_openai(proxy: ProxyClient, resources: ResourceManager, prefix: str) -> str:
return _register_response_deployment(proxy, resources, "openai", prefix)

Expand Down Expand Up @@ -236,8 +247,8 @@ def test_deleted_response_is_no_longer_retrievable(

client.responses.delete(created.id)

gone: Final = pytest.raises(openai.APIStatusError, client.responses.retrieve, created.id)
assert 400 <= gone.value.status_code < 500, f"retrieve after delete expected a 4xx: {gone.value!r}"
gone: Final = _retrieve_until_gone(client, created.id)
assert 400 <= gone.status_code < 500, f"retrieve after delete expected a 4xx: {gone!r}"

@pytest.mark.parametrize(
"deployment",
Expand Down Expand Up @@ -269,8 +280,8 @@ def test_streamed_response_can_be_deleted(
response_id: Final = completed.response.id

client.responses.delete(response_id)
gone: Final = pytest.raises(openai.APIStatusError, client.responses.retrieve, response_id)
assert 400 <= gone.value.status_code < 500, f"retrieve after streamed delete expected a 4xx: {gone.value!r}"
gone: Final = _retrieve_until_gone(client, response_id)
assert 400 <= gone.status_code < 500, f"retrieve after streamed delete expected a 4xx: {gone!r}"


@pytest.mark.provider_live
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/llm_translation/test_together_ai_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
ARITHMETIC_PROMPT = "What is 17 + 26? Answer with just the number."
PERSON_PROMPT = "Invent a fictional person."
CACHE_PREFIX_FACTS: Final = 600
CACHE_ATTEMPTS: Final = 3
CACHE_ATTEMPTS: Final = 5

PERSON_RESPONSE_FORMAT: dict[str, object] = {
"type": "json_schema",
Expand Down
10 changes: 8 additions & 2 deletions tests/e2e/ui/tests/mcp/mcpSubmissionRules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const test = base.extend<Fixtures>({
url: localServerUrl,
transport: "http",
},
timeout: 60_000,
});
expect(registration.status(), await registration.text()).toBe(201);

Expand Down Expand Up @@ -129,6 +130,11 @@ test("Submission Rules panel shows saved rules, preloads the editor, and save ke

await page.reload();
await page.getByRole("tab", { name: "Submitted MCPs" }).click();
await expect(page.getByText(submittedServerName, { exact: true })).toBeVisible();
await expect(page.getByText("3 passing, 1 failing", { exact: true })).toBeVisible();
const submittedCard = page
.locator("div")
.filter({ has: page.getByRole("heading", { name: submittedServerName, exact: true }) })
.filter({ hasText: /\d+ passing, \d+ failing/ })
.last();
await expect(submittedCard.getByRole("heading", { name: submittedServerName, exact: true })).toBeVisible();
await expect(submittedCard.getByText("3 passing, 1 failing", { exact: true })).toBeVisible();
});
5 changes: 3 additions & 2 deletions tests/integration/database/test_partition_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ async def test_real_partition_ddl_survives_witnessed_lock_and_is_idempotent() ->
operation: Final = asyncio.create_task(
manager.ensure_partitions(PartitionConnection(database), lambda: 7000)
)
wait_deadline: Final = time.monotonic() + 3
wait_deadline: Final = time.monotonic() + 10
try:
while True:
witnesses: Final = read_rows(
witnesses: Final = await asyncio.to_thread(
read_rows,
"SELECT a.pid, extract(epoch FROM "
"clock_timestamp()-a.query_start)::double precision AS age "
"FROM pg_stat_activity a WHERE %s = ANY(pg_blocking_pids(a.pid)) "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
_sse_json_values,
)
from integration._support.client import Gateway, eventually
from integration._support.process import graceful_stop_seconds
from integration._support.wire import Reply, Request, wire_server


Expand Down Expand Up @@ -333,6 +334,14 @@ def sink(request: Request) -> Reply:
_matching_marker_span(rig.destination, marker)


def _has_exited(process: psutil.Process) -> bool:
try:
return process.status() == psutil.STATUS_ZOMBIE
except psutil.NoSuchProcess:
return True


@pytest.mark.timeout(2 * graceful_stop_seconds() + 120)
def test_arize_otel_v2_f3_one_proxy_worker_can_die(gateway: Gateway, tmp_path: Path) -> None:
markers: Final = tuple("f3-" + uuid.uuid4().hex for _ in range(8))
release: Final = threading.Event()
Expand Down Expand Up @@ -380,7 +389,7 @@ def sink(_request: Request) -> Reply:
)
assert observed >= 2, observed
workers[0].kill()
assert eventually(lambda: not workers[0].is_running(), bool, seconds=10), workers[0]
assert eventually(lambda: _has_exited(workers[0]), bool, seconds=10), workers[0]
release.set()
in_flight: Final = tuple(
(marker, future.result(timeout=60)) for marker, future in zip(markers, futures, strict=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
~20s. With `timeout: 1` the outbound call must abort near the bound, so the chat round trip
completes in seconds instead of waiting on the sink. A control guardrail without `timeout`
points at a sink path that sleeps ~3s and must wait for the reply, proving unset keeps the
handler default. All probes are sent concurrently so their waits overlap.
handler default. Probes are sent eight at a time so their waits overlap without starving the
proxy's two workers.
"""

from __future__ import annotations
Expand Down Expand Up @@ -35,6 +36,7 @@
SLOW_SECONDS: Final = 20
FAST_SECONDS: Final = 3
BOUND_SECONDS: Final = 8
PROBE_CONCURRENCY: Final = 8
TOKEN_PATH: Final = "/token"
TOKEN_REPLY: Final = json.dumps(
{"access_token": "synthetic-google-token", "expires_in": 3600, "token_type": "Bearer"}
Expand Down Expand Up @@ -410,7 +412,7 @@ def outcomes(rig: Rig) -> Mapping[str, Outcome]:
values: Final = tuple(_provider_values())
names: Final = (*(value[0] for value in values), "control-generic")
exchanges: Final = (*(value[4] for value in values), False)
with ThreadPoolExecutor(max_workers=len(names)) as pool:
with ThreadPoolExecutor(max_workers=PROBE_CONCURRENCY) as pool:
results: Final = tuple(pool.map(partial(_chat, rig), names, exchanges))
return MappingProxyType(dict(zip(names, results, strict=True)))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import yaml
from integration._support.client import Gateway, eventually, object_value
from integration._support.database import read_rows
from integration._support.process import owned_proxy_process
from integration._support.process import graceful_stop_seconds, owned_proxy_process
from integration._support.wire import Reply, Request, wire_server
from pydantic import JsonValue

Expand Down Expand Up @@ -129,6 +129,7 @@ async def test_passthrough_upstream_outage_mid_burst_still_logs_errors_once(gate
), response.text


@pytest.mark.timeout(2 * graceful_stop_seconds() + 120)
async def test_passthrough_worker_sigkill_leaves_sibling_serving_and_logging(gateway: Gateway, tmp_path: Path) -> None:
config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text())
path: Final = tmp_path / "chaos-kill.yaml"
Expand Down
20 changes: 20 additions & 0 deletions tests/integration/providers/test_decisions_wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,26 @@ def test_each_provider_gets_its_own_path_key_and_body_and_is_billed_from_the_cos


def test_test_connection_evaluation_mode_uses_typesafe_decisions_path(gateway: Gateway) -> None:
provider: Final = _PROVIDERS[1]
with gateway.scenario() as scenario:
handle: Final = _register(scenario, _answer_body(provider))
response: Final = gateway.request(
"POST",
"/health/test_connection",
{
"litellm_params": {"model": provider.model, "api_base": handle.api_base(), "api_key": provider.api_key},
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved
"mode": "evaluation",
},
)

assert response.status_code == 200, response.text
assert response.json()["status"] == "success", response.text
(call,) = _upstream_calls(gateway, handle)
assert call["path"] == f"/{handle.scenario_id}{provider.path}"


def test_test_connection_by_configured_alias_probes_the_stored_model(gateway: Gateway) -> None:
pytest.skip("BUG: /health/test_connection given only a configured alias sends the alias as the model")
provider: Final = _PROVIDERS[1]
with gateway.scenario() as scenario:
handle: Final = _register(scenario, _answer_body(provider))
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/providers/test_s3_wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def verify(request: Request) -> Reply:
prior: Final = asyncio.all_tasks()
logger: Final = S3Logger(s3_bucket_name="integration-bucket", s3_region_name="us-east-1", s3_endpoint_url=wire.url,
s3_aws_access_key_id=ACCESS, s3_aws_secret_access_key=SECRET, s3_callback_params_override={})
owned: Final = asyncio.all_tasks() - prior
owned: Final = {task for task in asyncio.all_tasks() - prior if task.get_coro().__qualname__ == "CustomBatchLogger.periodic_flush"}
assert len(owned) == 1
try:
for mode in ("sync", "async"):
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/routing/test_audit_any_sweep_chaos.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@

import httpx
import psutil
import pytest
from integration._support.client import Gateway, eventually, gateway_from_environment
from integration._support.process import owned_proxy_process
from integration._support.process import graceful_stop_seconds, owned_proxy_process

_RESPONSES_JSON_SCENARIO: Final = "audit-chaos-responses"
_RESPONSES_STREAM_SCENARIO: Final = "audit-chaos-responses-stream"
Expand Down Expand Up @@ -145,6 +146,7 @@ def _worker_pids(log: Path) -> tuple[int, ...]:
return tuple(int(match) for match in _WORKER_PID.findall(log.read_text()))


@pytest.mark.timeout(2 * graceful_stop_seconds() + 120)
def test_proxy_survives_worker_kill_mid_burst(tmp_path: Path) -> None:
with gateway_from_environment() as upstream_gateway:
directory: Final = tmp_path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ def test_proxy_usage_routing_reads_cooldown_tpm_then_rpm_from_redis(
)
config: Final = {
**configuration,
"general_settings": {
**JSON_OBJECT.validate_python(configuration["general_settings"]),
"store_model_in_db": False,
},
"model_list": _deployment_list(model_name, f"{wire.url}/v1", deployment_ids),
"router_settings": {
"routing_strategy": "usage-based-routing-v2",
Expand All @@ -205,7 +209,7 @@ def test_proxy_usage_routing_reads_cooldown_tpm_then_rpm_from_redis(
with owned_proxy(
bootstrap,
tmp_path,
{"REDIS_HOST": cache.host, "REDIS_PORT": str(cache.port)},
{"REDIS_HOST": cache.host, "REDIS_PORT": str(cache.port), "STORE_MODEL_IN_DB": "False"},
config=config_path,
) as candidate:
eventually(
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/spend/test_vertex_image_regional_uplift.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,18 @@ def test_invalid_multiplier_is_refused_at_registration(gateway: Gateway) -> None
assert wire.drain() == ()


def _body_without_created(response: httpx.Response) -> dict[str, object]:
return {key: value for key, value in response.json().items() if key != "created"}


def test_identical_regional_generations_each_bill_the_uplift_once(gateway: Gateway) -> None:
with wire_server(_scripted()) as wire, gateway.scenario() as scenario:
model: Final = _deployment(gateway, scenario, wire, location={"vertex_location": _REGION})
first_id, first = _generate(gateway, model)
second_id, second = _generate(gateway, model)
assert first.status_code == 200, first.text
assert second.status_code == 200, second.text
assert first.content == second.content, (first.text, second.text)
assert _body_without_created(first) == _body_without_created(second), (first.text, second.text)
rows: Final = _spend_rows((first_id, second_id))
assert rows == {first_id: pytest.approx(_REGIONAL_TOKEN_COST), second_id: pytest.approx(_REGIONAL_TOKEN_COST)}
assert len(wire.drain()) == 2
Expand Down
6 changes: 4 additions & 2 deletions tests/llm_translation/test_xai.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ def test_xai_message_name_filtering():
class TestXAIReasoningEffort(BaseReasoningLLMTests):
def get_base_completion_call_args(self):
return {
"model": "xai/grok-4.7",
"messages": [{"role": "user", "content": "Hello"}],
"model": "xai/grok-4.6",
"messages": [
{"role": "user", "content": "What is 17 times 23? Work it out step by step, then give the number."}
],
}


Expand Down
Loading
Loading