Commit graph

316 commits

Author SHA1 Message Date
Yassin Kortam
5c8e7e6924
test(e2e): cover organization update persistence via /organization/info (#34010) 2026-07-20 22:36:39 +00:00
Yassin Kortam
61b906f9b6
test(e2e): cover model deletion removing it from the catalog (#34006) 2026-07-20 22:36:15 +00:00
Yassin Kortam
6db7328afb
chore(e2e): prune reliability.perf.latency.under_slo coverage cell (#34024)
It is a non-binary latency SLO threshold rather than a deterministic pass/fail
behavior a single e2e test can assert, so it does not fit the coverage registry's
one-test-per-cell contract. The registry README already flagged the perf cells for
a support-check or prune, and throughput SLO under load is covered structurally by
the Locust load suite. Removing it keeps the denominator to behaviors an e2e test
can deterministically prove.
2026-07-20 15:28:23 -07:00
Yassin Kortam
f21704c672
test(e2e): cover user update persistence via /user/info (#33998)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 22:18:36 +00:00
Yassin Kortam
71131190ec
test(e2e): cover model registration persistence in /model/info (#33996)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 22:17:40 +00:00
Yassin Kortam
eb27447a1d
test(e2e): cover team update persistence via /team/info (#33997)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 21:56:07 +00:00
Yassin Kortam
68be053e96
test(e2e): cover created user appearing in /user/list (#34016) 2026-07-20 14:29:13 -07:00
Yassin Kortam
4c77a5433a
test(e2e): cover created team appearing in /team/list (#34015) 2026-07-20 14:27:49 -07:00
Yassin Kortam
53f5a8c380
test(e2e): cover key block persisting to /key/info (#34014)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 14:26:18 -07:00
Yassin Kortam
b9c59c37cc
test(e2e): cover model update persisting to /model/info (#34017)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 21:13:54 +00:00
Yassin Kortam
214945a223
test(e2e): cover organization deletion removing it from /organization/info (#34009)
Co-authored-by: mubashir1osmani <mubashir.osmani777@gmail.com>
2026-07-20 19:36:59 +00:00
Yassin Kortam
72be5a9bc0
test(e2e): cover tag creation persisting for spend categorization (#34018) 2026-07-20 12:24:50 -07:00
Yassin Kortam
51df801159
test(e2e): cover key regeneration rotating to a working new key (#34000) 2026-07-20 12:23:22 -07:00
Yassin Kortam
8d96e959db
test(e2e): guard destructive spend-log truncate behind an explicit opt-in (#33751)
tests/e2e/conftest.py's pytest_sessionfinish truncated LiteLLM_SpendLogs
against whatever DATABASE_URL resolved to, gated only by "an e2e test body
ran". Pointed at a shared or staging DB, a routine local run wiped real
spend data. It also reached the truncate helper through a sys.path.insert
into quota_management/spend_tracking/spend_e2e_client.py, a cross-suite
import-by-path hack it then unwound in a finally.

The cleanup now routes through a new run_spend_log_cleanup in a top-level
tests/e2e/e2e_db.py, which fires the destructive truncate only when the
operator set E2E_RESET_SPEND_LOGS=1 and an e2e test actually ran. Any other
value (unset, 0, true, empty) leaves the DB untouched, so presence of the
variable alone or a test run alone never arms the truncate. The decision
plus the injectable truncate callable live in that pure helper, and conftest
is a thin adapter that supplies os.environ.get(...), the session stash, and
reset_spend_logs.

reset_spend_logs itself moved from spend_e2e_client.py into e2e_db.py
(implementation unchanged), sitting next to e2e_config and lifecycle so both
conftest and any suite import it by name; the sys.path munging is gone.
Nothing else imported reset_spend_logs, so spend_e2e_client.py drops the
definition, its __all__ entry, and the now-unused os import.
2026-07-20 08:47:39 -07:00
devin-ai-integration[bot]
b83c60b9b7
test(e2e): cover credential-backed /v1/messages request (#33863)
* test(e2e): cover credential-backed /v1/messages request

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): use runtime Anthropic credential

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 18:30:55 -07:00
ryan-crabbe-berri
595e72472a
test(e2e): assert the long budget window keeps blocking after the short window resets (#33832)
* test(e2e): assert the long budget window keeps blocking after the short window resets

The multi-window budget tests proved the tight window blocks and self-heals
but never asserted the other direction: a long (1d) window whose cap the
accumulated spend already crossed must keep refusing calls even inside a
fresh short window. Adds one test per file (key and team) that drives spend
to a block, waits for the short window's reset_at to strictly advance (the
reset job zeroes that window's counter in the same pass), then polls until
the refusal is attributed to the 1d window ("over 1d budget"), failing
immediately if any call succeeds or a non-budget error leaks. Harness gains
per-window reset_at readback: BudgetWindowState in models.py and
key_window_reset_at / team_window_reset_at on BudgetClient.

* refactor(e2e): hoist shared budget-suite helpers into budget_client

drive_to_block and as_datetime existed as five and four per-file copies in
the budgets suite; both move to budget_client with each file keeping a thin
delegating wrapper so call sites and per-file pacing stay unchanged. The
three /team/info readers in budget_client now share a private _team_info.
Also guard the long-window reset_at snapshots with explicit non-None asserts
so the midnight-roll diagnostic cannot misreport when the window is missing
from the info response (greptile P2s).

* docs(e2e): tighten the multi-window module docstrings

* refactor(e2e): type window reset_at as datetime and expose plain window readers

BudgetWindowState.reset_at becomes a pydantic-parsed datetime, so the
multi-window tests compare real datetimes instead of hand-parsing strings.
The duration-keyed accessors are replaced by two plain readers,
key_budget_windows and team_budget_windows, with the pure window_reset_at
lookup exported; the client no longer encodes one test's access pattern.

* test(e2e): name the tiny short-window cap and comment the wait loops

* test(e2e): surface the 429 budget-block assert in the multi-window tests

drive_to_block now returns the blocking response so a test body can assert
on its shape; the two long-window tests assert status 429 explicitly, which
also pins the multi-window enforcement path's HTTP mapping (the enforcement
suite only covers the single-budget path). Other callers ignore the return
and are unchanged.

* refactor(e2e): scope this PR to the multi-window test, drop the cross-suite hoist

The helper hoist rewrote four unrelated budget test files (reset, reset_advances,
team_member_reset, user_across_keys) to pull drive_to_block and as_datetime out
of budget_client, which is refactor churn beyond this PR's multi-window scope.
This restores those four to their pre-PR state and gives the two multi-window
tests their own inline drive-to-block loop again, so the PR touches only the
multi-window feature: its two tests plus the budget_client window readers and the
reset_at datetime typing they actually use. The suite-wide helper dedup can land
on its own PR

* docs(e2e): number the long-window key test steps inline

* docs(e2e): number the long-window team test steps inline
2026-07-18 18:00:15 -07:00
Yassin Kortam
e238e89537
test(e2e): spendlog cost for streaming /v1/messages via responses bridge (#33753)
Add a live spend-tracking e2e that drives a streaming anthropic-format
/v1/messages request through litellm's anthropic-messages -> OpenAI Responses
adapter and asserts the consumed stream writes exactly one SpendLogs row with
nonzero cost and token counts, attributed to the calling key under
custom_llm_provider openai and the /v1/messages call_type.

The deployment is a Responses-only OpenAI model (gpt-5.3-codex), so a served,
costed row proves the Responses path was taken; the chat-completions bridge
would have failed at OpenAI on an endpoint the model does not expose. Adds a
streaming /v1/messages method to the shared Gateway and the suite client, the
model to the inline compose config and driver-model registration, a coverage
registry row (quota_management.spend_tracking.messages_bridge.logs_cost), and
the matching variant vocab entry. The _summarize spend-row detail also gains
call_type and custom_llm_provider so a failed assertion prints the fields it
asserts on.

Resolves LIT-4546
2026-07-18 14:12:26 -07:00
devin-ai-integration[bot]
4f8d83ca85
test(e2e): cover /v1/responses OpenAI vision and Anthropic basic (#33838)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 14:00:43 -07:00
devin-ai-integration[bot]
7a42f25550
test(e2e): cover /v1/responses openai cost_logged and tool_use (#33835)
* test(e2e): cover /v1/responses openai basic nonstream and stream

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): assert responses stream ends on final raw completed event

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): cover /v1/responses openai cost_logged and tool_use

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): centralize responses stream event models

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 13:45:26 -07:00
devin-ai-integration[bot]
a1fb07f42c
test(e2e): cover /v1/responses openai basic nonstream and stream (#33830)
* test(e2e): cover /v1/responses openai basic nonstream and stream

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): assert responses stream ends on final raw completed event

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(e2e): centralize responses stream event models

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-18 13:25:19 -07:00
devin-ai-integration[bot]
66dea7df8f
chore(e2e): remove tests/e2e/docker-compose.yml (#33837)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 12:50:23 -07:00
mubashir1osmani
6a2e0a8528
fix(e2e): migrate load suite from e2e_gateway to ProxyClient (#33839)
* test(e2e): harden stage flakes for batches, UI, and MCP

Unique batch model names avoid load-balancing onto stale azure-batch
deployments that still pointed at the retired gpt-4.1-mini-batch, which
only the managed/unified path was hitting. Retry batch retrieve on 500
and /ui/api-keys navigation on ERR_ABORTED. Skip the MCP key-access suite
when the compose-only mcp-upstream is unreachable on stage k8s

* test(e2e): cover Datadog remote MCP via search_datadog_logs

Register the regional Datadog MCP endpoint with DD-API-KEY /
DD-APPLICATION-KEY static headers (CI-safe header auth; browser OAuth is
not headless-automatable). Seed a chat completion marked e2e-datadog-mcp-*,
assert the proxy shipped it, list tools, call search_datadog_logs for the
marker, and delete the server on teardown. Math-upstream key-access tests
only skip when that compose service is unreachable

* test(e2e): drop compose math MCP upstream; use Datadog only

Key-access denial and happy-path MCP e2e both register the real regional
Datadog remote MCP server with DD-API-KEY / DD-APPLICATION-KEY headers.
Remove the mcp-upstream compose service and FastMCP add/multiply fixture

* docs(e2e): require real Datadog MCP for all mcp suite tests

Document that tests/e2e/mcp must register via datadog_mcp helpers against
mcp.<site>/v1/mcp and must not introduce compose or fake MCP upstreams

* chore: restore mcp_e2e_upstream_server.py

Keep the FastMCP fixture file; e2e no longer wires it in compose, but the
module itself is not part of the Datadog-only cleanup

* fix(e2e): load tests/e2e/.env and fix datadog_reader importlib load

pytest on the host never inherited compose env_file keys, so DD_API_KEY
stayed empty. load_dotenv tests/e2e/.env in e2e_config. Register the
dynamically loaded datadog_reader module in sys.modules so dataclasses
do not crash under Python 3.12

* test(e2e/batches): harden azure/vertex unified lifecycle flakes

Put the provider deployment name in every JSONL body so Azure does not
depend on a perfect model rewrite. Retry create/retrieve/cancel on
transient statuses with backoff. Drop cancel assertions for azure and
vertex (registry only has a shared basic cell; create+retrieve prove
routing, cancel stays best-effort cleanup)

* test(e2e/ui): treat api-keys shell as success after SPA ERR_ABORTED

Post-login client redirects abort the first /ui/api-keys/ goto on stage.
Wait off /ui/login after cookie set, then accept the page once Create New
Key is visible even if goto raised ERR_ABORTED

* test(e2e): drop flaky key models dropdown Playwright suite

API management e2e already covers key generate/update persistence. The
UI Models-dropdown sentinel cases only added SPA ERR_ABORTED noise and
no unique product signal. Remove the suite and unused browser fixtures

* test(e2e/batches): fail clearly when OPENAI/AZURE provider is missing

Replace bare next() over PROVIDERS with _model_for that raises ValueError
naming the missing provider and the known list, instead of StopIteration

* fix(e2e): migrate load suite from e2e_gateway to ProxyClient

Stage collection failed with ModuleNotFoundError: e2e_gateway after the
Gateway rename. Wire load/conftest and LoadClient to the shared
ProxyClient fixture like every other suite

* fix(e2e): drop duplicate datadog_mcp_url and CLAUDE section after merge
2026-07-18 19:41:03 +00:00
mubashir1osmani
fdf380d0e3
test(e2e): harden stage flakes for batches, UI, and MCP (#33831)
* test(e2e): harden stage flakes for batches, UI, and MCP

Unique batch model names avoid load-balancing onto stale azure-batch
deployments that still pointed at the retired gpt-4.1-mini-batch, which
only the managed/unified path was hitting. Retry batch retrieve on 500
and /ui/api-keys navigation on ERR_ABORTED. Skip the MCP key-access suite
when the compose-only mcp-upstream is unreachable on stage k8s

* test(e2e): cover Datadog remote MCP via search_datadog_logs

Register the regional Datadog MCP endpoint with DD-API-KEY /
DD-APPLICATION-KEY static headers (CI-safe header auth; browser OAuth is
not headless-automatable). Seed a chat completion marked e2e-datadog-mcp-*,
assert the proxy shipped it, list tools, call search_datadog_logs for the
marker, and delete the server on teardown. Math-upstream key-access tests
only skip when that compose service is unreachable

* test(e2e): drop compose math MCP upstream; use Datadog only

Key-access denial and happy-path MCP e2e both register the real regional
Datadog remote MCP server with DD-API-KEY / DD-APPLICATION-KEY headers.
Remove the mcp-upstream compose service and FastMCP add/multiply fixture

* docs(e2e): require real Datadog MCP for all mcp suite tests

Document that tests/e2e/mcp must register via datadog_mcp helpers against
mcp.<site>/v1/mcp and must not introduce compose or fake MCP upstreams

* chore: restore mcp_e2e_upstream_server.py

Keep the FastMCP fixture file; e2e no longer wires it in compose, but the
module itself is not part of the Datadog-only cleanup

* fix(e2e): load tests/e2e/.env and fix datadog_reader importlib load

pytest on the host never inherited compose env_file keys, so DD_API_KEY
stayed empty. load_dotenv tests/e2e/.env in e2e_config. Register the
dynamically loaded datadog_reader module in sys.modules so dataclasses
do not crash under Python 3.12

* test(e2e/batches): harden azure/vertex unified lifecycle flakes

Put the provider deployment name in every JSONL body so Azure does not
depend on a perfect model rewrite. Retry create/retrieve/cancel on
transient statuses with backoff. Drop cancel assertions for azure and
vertex (registry only has a shared basic cell; create+retrieve prove
routing, cancel stays best-effort cleanup)

* test(e2e/ui): treat api-keys shell as success after SPA ERR_ABORTED

Post-login client redirects abort the first /ui/api-keys/ goto on stage.
Wait off /ui/login after cookie set, then accept the page once Create New
Key is visible even if goto raised ERR_ABORTED

* test(e2e): drop flaky key models dropdown Playwright suite

API management e2e already covers key generate/update persistence. The
UI Models-dropdown sentinel cases only added SPA ERR_ABORTED noise and
no unique product signal. Remove the suite and unused browser fixtures
2026-07-18 19:11:54 +00:00
Yassin Kortam
0439bcbfed
refactor(e2e): fold claude_code HTTP probes onto shared Gateway methods (#33760)
* refactor(e2e): fold claude_code HTTP probes onto shared Gateway methods

Migrate tests/e2e/claude_code/http_probe.py off its own httpx client onto the
shared transport, and promote count_tokens and native anthropic messages to
first-class Gateway methods (Gateway.count_tokens / Gateway.messages) with typed
request/response models in the shared models.py so other suites reuse them.

The probes now take an injected Gateway and issue their request through the
shared count_tokens/messages methods, reusing the split control/data-plane
routing, timeout, and typed Result handling the rest of tests/e2e uses. The wire
shape is preserved: the pydantic bodies serialize byte-for-byte to what the old
httpx probes sent, and the anthropic-version header is carried by a small
AnthropicHeaders model. httpx is gone from the module.

* test(e2e): drop unit-level probe harness test

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-18 19:03:01 +00:00
Yassin Kortam
a2614b1239
test(e2e): add Locust throughput load test that runs last (#33748)
CodSpeed benchmarks the SDK with no IO, so it can't catch regressions that
only appear under real concurrent load through the full proxy stack (auth,
routing, logging, spend, Postgres, Redis). This adds a Locust load test under
tests/e2e/load that drives concurrent POST /chat/completions traffic against a
mock deployment (litellm_params.mock_response), so the measured throughput
reflects proxy overhead rather than a provider's latency, and asserts an
aggregate RPS SLO with a failure-ratio guard. The test is marked load and the
parent conftest sorts load-marked items last so it never perturbs
latency-sensitive suites. Covers reliability.perf.throughput.under_slo.
2026-07-18 11:57:41 -07:00
Yassin Kortam
08fa25042c
test(e2e): rename Gateway to ProxyClient and expose it as a session-scoped fixture (#33750)
The shared proxy wrapper in tests/e2e/e2e_gateway.py was misnamed: Gateway is
not a gateway server, it is the client every suite uses to talk to the proxy
(keys, models, chat/embed/ocr, spend read-backs, poll helpers). Rename the
module to proxy_client.py and the class to ProxyClient, with build_gateway
becoming build_proxy_client and the GatewayProvider protocol becoming
ProxyClientProvider. The .gateway attribute suites held is now .proxy. Only
identifiers changed; prose and string literals that use the word gateway for the
proxy-server concept were left alone.

Each suite previously built its own instance through a per-suite build_client()
that called build_gateway() inside, duplicating the proxy wiring across suites.
There is now one session-scoped proxy fixture in tests/e2e/conftest.py; every
suite's client fixture depends on it and injects it, so the wiring lives in one
place. claude_code keeps building its own client directly since it has its own
harness and does not use the shared fixtures.

Behavior is unchanged: shared transport, data-plane/control-plane split routing,
poll budget, typed request/response models, and resource cleanup all go through
the same object.
2026-07-18 18:41:18 +00:00
ryan-crabbe-berri
dbb5b813c1
test(e2e): budget reset diagonal for team, org, user, and #32005 team-member keys (#33771)
* test(e2e): budget reset diagonal for team, org, user, and #32005 team-member keys

Adds E2E-7/8/10/11 from the budget-level x key-kind coverage matrix: each budget
level serves traffic again after its budget_duration window elapses, walking the
same ladder as the enforcement diagonal. New registry rows and tests cover the
team, organization, and internal-user reset rungs, plus the #32005 interplay
where a team-member key frozen by its owner's user budget comes back when the
user's window renews; the bare-key and per-team-member rungs already had coverage

Each case isolates the cap to one entity, drives spend to a budget_exceeded
block, then polls past the window until a call succeeds, holding every refusal
as a budget block so a reset that no-ops (stays blocked forever) or crashes
(leaks a 5xx) fails the test. budget_duration becomes an optional param on the
budget_client create_team / create_user / create_org helpers

* test(e2e): fold the reset diagonal into test_budget_reset_e2e.py and address greptile nits

Move the team / org / user / #32005 reset cases out of the standalone
test_budget_reset_diagonal_e2e.py and into test_budget_reset_e2e.py, absorbing
the pre-existing bare-key reset into the same TestBudgetResetDiagonal spec class
so the whole reset ladder reads as one file (mirroring how the enforcement
diagonal lives in test_budget_enforcement_e2e.py) and the drive/poll helpers are
defined once instead of duplicated across reset files.

Greptile nits: bound the drive phase to under one window (12 attempts x 2s < 30s)
so a block is observed before the reset job can fire, and replace the bare assert
in the poll loop with a pytest.fail that prints the HTTP status, so a provider 429
or a crashed reset path is distinguishable from a budget block at a glance.

* test(e2e): trim reset diagonal docstrings back to the file's original style

* test(e2e): inline single-use drive-loop bounds

* test(e2e): cut the reset module docstring to one line

* test(e2e): make the org reset test wait for a scheduled window (bugbot)

/organization/new stores budget_duration without scheduling budget_reset_at, so
the reset job's NULL catch-up branch zeroes org spend on its first 5-10s tick;
the org reset test could pass off that catch-up instead of a real window roll
(tracked as LIT-4570). The test now reads the org's budget_id and polls
/budget/info until budget_reset_at is scheduled before driving spend, so the
recovery it observes can only come from a genuine window expiry. Verified live:
the org case now runs ~33s (a full window) instead of beating the rescheduler
2026-07-18 03:02:22 +00:00
mubashir1osmani
13ecf55cd0
test(e2e): skip flaky OpenAI GPT cells; raise multi-window max_tokens (#33799)
OpenAI GPT-5.6 Claude Code cells burn minutes on CLI timeouts under the
full stage suite; gate them behind COMPAT_OPENAI_GPT_CELLS=1 like Mantle.
Multi-window budget e2e used max_tokens=1 which gpt-5.5 rejects mid-message
2026-07-17 19:07:18 -07:00
ryan-crabbe-berri
6a26a3aee7
test(e2e): a user's max_budget follows the person across personal and team keys (#33762) 2026-07-17 18:53:09 -07:00
ryan-crabbe-berri
0e03795013
test(e2e): a member's team budget cuts off only that member's key (#33718)
* test(e2e): a member's team budget cuts off only that member's key

* test(e2e): drop the float-formatted cap string from the member budget assert
2026-07-17 18:50:54 -07:00
Yassin Kortam
89c87ae59a
test(e2e): mcp suite for key-without-access denial (#33752)
Add an e2e suite at tests/e2e/mcp/ that proves MCP authorization over the
api_key auth family. An admin registers an upstream MCP server through the
management API (POST /v1/mcp/server, persisted in the DB and picked up without
a restart) and queues its deletion. Two keys are created against that one
server: one granted access through object_permission.mcp_servers and one with
no MCP grant. The permitted key is a live control proving the upstream is
reachable and the tool is callable, so a denial on the ungranted key is an
authorization decision rather than a dead server. The denied key then sees
none of the server's tools on tools/list and is refused a tools/call with a
403 access_denied.

A deterministic self-hosted FastMCP upstream (add/multiply over
streamable-http) is added to the e2e compose stack so the suite runs offline
with a known tool set. KeyGenerateBody gains an optional typed
object_permission so the shared gateway can create a key with an MCP grant.
2026-07-17 16:04:43 -07:00
Yassin Kortam
45273f1943
refactor(e2e): remove bob_the_builder; drive remediation from a Grafana alert (provisioned outside the repo) (#33749) 2026-07-17 14:23:21 -07:00
Yassin Kortam
62207ac057
test(e2e): user budget across keys and team member budget isolation (#33745) 2026-07-17 14:22:32 -07:00
Yassin Kortam
71e0251341
refactor(e2e): replace bespoke result reporter with standard JUnit report (#33758)
* refactor(e2e): replace bespoke result reporter with standard JUnit report

tests/e2e/e2e_result_reporter.py hand-rolled a per-test logfmt emitter that
reimplemented outcome mapping, logfmt escaping, and node-id parsing to print one
E2E_RESULT line per finished test. Outcome, duration, and node id are all things
a standard pytest reporter already produces, so the only genuinely custom data is
the covers marker ids and the normalized package label

Delete the module and emit a standard pytest JUnit XML report (--junitxml)
instead, carrying the two custom signals as user_properties (JUnit <property>
entries) attached at collection time in pytest_collection_modifyitems, so they
land on every test on every outcome including skips and setup errors. The small
package/covers extraction lives in junit_properties.py and is unit tested plus
checked end to end against a real JUnit artifact in test_junit_properties.py

Shipping the JUnit report to Loki is a thin infra-side transform, documented in
grafana/status_history_panels.md

* chore(e2e): remove grafana status history panels doc and junit properties e2e test

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-17 20:53:22 +00:00
Yassin Kortam
442fdc181e
docs(tests/e2e): align docs with the hard-fail-on-dead-proxy contract and scope the no-unit-tests rule (#33755)
The e2e docs claimed `e2e`-marked tests skip when no proxy answers the
liveness probe, but the harness has always hard-failed: conftest.py's
pytest_runtest_setup calls pytest.fail, its module docstring states
"hard failures only ... never skip", and logging/conftest.py forbids
skipping outright. Align the docs to the code so the single most
important contract reads the same everywhere; a dead proxy turns a run
red instead of being silently skipped and mistaken for a pass. The
per-suite conftest docstrings that described the shared hook as a
"proxy liveness skip" are corrected to "liveness gate" for the same
reason.

Also scope the no-unit-tests hard rule to what it means: never
substitute a unit test for e2e feature coverage, while explicitly
allowing tests that cover the harness itself (e.g.
coverage_registry/test_collector.py), which carry no e2e marker and
run whether or not a proxy is up.

No product code and no harness logic changed.

Resolves LIT-4554
2026-07-17 12:56:10 -07:00
Yassin Kortam
ad65cad820
test(e2e): delete unreferenced Grafana panel docs (#33743)
tests/e2e/grafana/status_history_panels.md was prose describing Loki/Grafana
status-history panels and LogQL queries. Nothing in the tree imports, reads, or
links to it; the e2e suite only emits the E2E_RESULT lines those panels consume
(tests/e2e/conftest.py, tests/e2e/e2e_result_reporter.py) and never depends on
this file. Dashboards drift when versioned as prose in the repo, so remove it;
if we want them versioned it should be dashboard-as-code in the observability
repo, not markdown here.
2026-07-17 12:29:20 -07:00
ryan-crabbe-berri
7015bd2ea1
test(e2e): assert an org budget block is a 429 naming the organization (#33638)
* test(e2e): assert bare-key budget refusal is 429 and /key/info spend reaches the cap

* test(e2e): keep the bare-key budget assertion to the 429 refusal shape

* test(e2e): assert a team's max_budget blocks every key on the team

* test(e2e): focus the team budget case on the 429 blocking behavior

* test(e2e): assert an org budget block is a 429 naming the organization
2026-07-17 11:48:18 -07:00
ryan-crabbe-berri
e5a9f3f5d7
test(e2e): budget refusals are 429 for bare keys and team caps block every team key (#33632)
* test(e2e): assert bare-key budget refusal is 429 and /key/info spend reaches the cap

* test(e2e): keep the bare-key budget assertion to the 429 refusal shape

* test(e2e): assert a team's max_budget blocks every key on the team

* test(e2e): focus the team budget case on the 429 blocking behavior
2026-07-17 11:29:16 -07:00
mubashir1osmani
10462eddaf
test(e2e): harness fixes for stage job green (skips + router/UI/budget) (#33634)
* test(e2e): harness fixes for long_context, complexity router, UI, and unit coverage

Point long_context_1m at 1M-capable models, harden complexity-smart-router
registration and spend-log assertions, fix key models dropdown selectors, and
add gateway/lifecycle/transport and claude_code unit tests

* test(e2e): harden remaining stage failures in harness

Register complexity-smart-router via create_model + callable probe, fix
create-key UI navigation race, retry management writes and budget ALB
502s, mark Vertex count_tokens N/A when unsupported, and tighten
tool_search model lists for Azure/Bedrock capability gaps

* test(e2e): drop claude_code and harness unit tests from this PR

Keep management, router, budget, and shared conftest harness fixes only

* test(e2e): restore E2E_RESULT pytest_runtest_makereport hook

Accidentally dropped in an earlier harness commit; Grafana status history
depends on these structured log lines

* test(e2e): drop management control-plane write retries

Transient 500 retries do not fix the underlying control plane failures

* test(e2e): skip stage-red claude_code cells; fix multi-window budget latency

Mark the twelve failing claude_code matrix cells skip until product/config
lands. Multi-window budget polls gpt-5.5 with max_tokens=1 instead of
Claude so the reset wait stays under ALB target idle timeout rather than
masking awselb 502s

* test(e2e): require exactly one LLM-tier spend row for complexity router

Keep alias membership for compose vs stage model names, but assert
len(served) == 1 so a leaked classifier sub-call cannot pass. Also pin
LIT-4521 skip and align LIT-4522/23/24 skip reasons

* test(e2e): harden router callable probe and multi-window budget exhaustion

_router_is_callable treated any non-success chat whose body lacked "Invalid
model name" as callable, so an unpropagated probe key (401), a generic 502, or
a connection reset let the session proceed and hit real "Invalid model name"
failures inside the tests. Require a Success outcome instead; the reload-race
400 and every infra/auth error now correctly read as not-callable.

The multi-window budget test capped the tight window at 3e-6, which gpt-5.5
exhausts on the first call but a cheaper CHEAP_OPENAI_MODEL might not within the
20-call loop, turning a reset test into a spurious "window never enforced"
failure. Drop the tight cap to 1e-9 so the first billed call exhausts it
regardless of model price; the roomy 1m window stays at 1.0 and never blocks.

* test(e2e): use a tradeoff-decision prompt for the complexity router classifier

"Is P equal to NP?" reads to the LLM classifier as a short yes/no question, so
gpt-5.5 classified it SIMPLE and the request routed to the openai backend, which
made the test fail even though the classifier was running. The tier definitions
key on what the request demands, not how hard the answer is, and a short direct
question maps to SIMPLE regardless of subject.

Swap in "Should I pay off my mortgage early or invest the extra money instead?".
It carries none of the heuristic scorer's reasoning/technical/code keywords and
stays short, so heuristic scoring still lands SIMPLE (openai), but the LLM reads
it as a decision that has to weigh tradeoffs and lands it above SIMPLE, which the
config routes to anthropic. Any non-SIMPLE tier serves anthropic, so the classifier
only has to avoid SIMPLE for the test to distinguish a real classifier run from the
heuristic fallback.
2026-07-16 20:30:30 -07:00
yucheng-berri
0223383d94
test(e2e): datadog log delivery for streamed routes, read back from the real datadog api (#33566)
* fix(e2e): make the datadog read-back find what DataDog actually indexes

Live verification of the merged #33604 against real DataDog (us5) exposed
three read-back defects that the local-sink tests could never see; all
three fixes are verified against the real API:

- Marker search: DataDog consumes the shipped JSON message into the
  event's attributes and leaves the indexed message EMPTY, so the
  full-text '"marker"' query matched nothing and every test failed with
  zero events. The query is now '*:*marker*', which scans all attributes
  (the marker sits in messages.content); verified to return exactly the
  event for the call.

- Rate limit: the Logs Search API budget is 2 requests per 10s org-wide
  (x-ratelimit-name logs_public_search_api). Polling at POLL_INTERVAL=5s
  sat exactly at the limit and the reader hard-failed on the first 429.
  Searches now pace at DD_SEARCH_INTERVAL (10s default) and a 429 backs
  off and retries up to 5 times; only non-429 failures stay hard fails.

- Envelope status: DataDog re-derives the indexed event status from the
  parsed payload's status attribute ('success') and normalizes it to its
  OK severity, so the assertion expects 'ok', not the shipped 'info'.

Live run: chat_completions and responses pass every assertion including
the exact response-cost cross-check; messages red-pins the LIT-4447
duplicate for real (one call -> two sync-sweep copies + one async batch
copy, same request id, confirmed in proxy debug logs). The duplicate is
race-dependent, so the pin flickers until #33589 lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(e2e): datadog log delivery for streamed chat, messages, and responses

Rewritten from the dd-sink version (original #33566) to judge delivery on
what real DataDog ingested, matching the merged #33604 conversion: the
dd_logs reader searches events back through the Logs Search API and the
assertions validate the indexed envelope (source:litellm tag, ok status)
and the StandardLoggingPayload fields under the event's attributes.

Each streamed test drives one STREAMED call per route, asserts the stream
actually streamed (event-stream content type, >0 chunks, no upstream error
event), then pins exactly one DataDog event whose payload records
stream=true, the aggregated token count, and a response_cost equal to the
/spend/logs row for the call - a stream's headers ship before its cost
exists, so the spend row is the cross-check anchor, and the spend row and
DataDog event must also agree on total_tokens.

Coverage registry: adds logging.datadog.stream.exports_metric exercised on
chat_completions, messages, and responses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Update test_datadog_log_e2e.py

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 19:37:07 -07:00
yucheng-berri
ac29a6a283
test(e2e): otel streaming spans record a real ttft below span duration (#33588) 2026-07-16 18:39:44 -07:00
Mateo Wang
7eb94eac8c
Merge pull request #33474 from BerriAI/litellm_claude_code_e2e_gpt_big3
test(e2e/claude_code): add GPT-5.6 Sol/Terra/Luna columns for OpenAI, Azure OpenAI, and Bedrock Mantle
2026-07-16 18:39:19 -07:00
mubashir1osmani
224fe67f10
test: e2e staging leftovers (#33613)
* test(e2e): read datadog log delivery back from the real datadog api (#33604)

* test(e2e): read datadog log delivery back from the real datadog api

* test(e2e): compare datadog-read cost with math.isclose, not bit-equality

The response_cost now round-trips through DataDog's attribute indexing
pipeline, whose float serialization is not guaranteed to preserve the
exact bit pattern the proxy shipped. rel_tol=1e-9 (equal to 9 significant
digits) still fails on any real cost discrepancy while tolerating
representation drift. Addresses the Greptile P2 on this PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(e2e): widen the duplicate-settle window to 30s for real DataDog

Against the local sink one poll interval (5s) after the first hit was
enough to catch a same-call duplicate, because both events arrived in the
same flush batch. Against real DataDog, ingestion jitter can make one
call's two events searchable tens of seconds apart, so a 5s settle could
let the LIT-4447 duplicate slip past the exactly-one assertion. The reader
now keeps re-reading for DD_SETTLE_SECONDS (default 30s, env-overridable
via E2E_DD_SETTLE_SECONDS) after the first event appears, returning early
only when a duplicate is already visible - more waiting cannot clear it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(e2e): point UI tests at dashboard service; register complexity router

Stage gateway 404s /ui; the Next.js dashboard is litellm-ui:3000. Drive
playwright against E2E_UI_BASE_URL and wait on login placeholders after
client render. Register complexity-smart-router via /model/new when the
proxy does not already list it so stage matches compose config

* docs(e2e): clarify E2E_UI_BASE_URL should be ALB when ingress splits UI

* docs(e2e): prefer single path-routing host for control plane and UI

CONTROL_PLANE and UI already default to PROXY_BASE_URL; clarify that
stage should set one ALB host rather than three endpoints

* fix(e2e): always capture complexity router model_id for teardown

Split /model/new from the data-plane wait so a propagation timeout still
deletes the control-plane registration (greptile orphan-model concern)

* fix(e2e): click exact Login button so SSO control is not matched

Playwright strict mode matched both Login and Login with SSO

* fix(router): score complexity by difficulty not request length

The LLM classifier prompt treated short wording as SIMPLE, so probes like
"Is P equal to NP?" stayed on the SIMPLE backend even though the classifier
ran. Judge intellectual difficulty so short hard questions route higher

* fix(e2e): open key edit via Key ID and wait for team models

Key Alias text is not the row open control on the virtual keys table;
KeyInfoView opens from the Key ID button in that row. Also wait for a
real team model in the edit Models dropdown so we do not race the async
availableModels fetch that only has All Team Models on first paint

* fix(e2e): keep settled DD events on empty search; bump mcp for OSV

Do not let a transient empty DataDog search wipe events already seen in
the settle window (Greptile P1). Make the logs-search from window
env-overridable via E2E_DD_SEARCH_FROM (Greptile P2). Prefer the mono
Key ID button when opening key edit. Bump mcp 1.26.0 -> 1.28.1 so OSV
clears the three high GHSA findings on the staging PR

* revert: drop mcp lock bump from e2e staging PR

OSV mcp upgrade is unrelated to the e2e fixes; leave the dep pin alone

---------

Co-authored-by: yucheng-berri <yucheng@berri.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 17:52:41 -07:00
mateo-berri
a017b95e2f test(e2e/claude_code): update run_compat.sh flag docs to COMPAT_MANTLE_CELLS 2026-07-16 17:26:46 -07:00
mateo-berri
bb04a1ed15 test(e2e/claude_code): run openai and azure GPT cells unconditionally, gate only bedrock_mantle
The openai and azure_openai columns have working credentials in every suite runner, so only the bedrock_mantle column still needs an opt-in flag while the AWS account waits on the Mantle allowlist; COMPAT_GPT_CELLS becomes COMPAT_MANTLE_CELLS. The six tool_use cells now resolve the proxy through claude_code._env like the basic_messaging cells instead of hardcoding LITELLM_PROXY_BASE_URL/LITELLM_PROXY_API_KEY.
2026-07-16 17:25:39 -07:00
mateo-berri
f93a84b01e test(e2e/claude_code): reuse AZURE_API_BASE/KEY for the azure_openai GPT column 2026-07-16 16:45:18 -07:00
mateo-berri
c462c51e25 docs(e2e): drop duplicate claude_code suite entry left by the base merge 2026-07-16 15:20:24 -07:00
mateo-berri
2998bd09f4 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_claude_code_e2e_gpt_big3
# Conflicts:
#	tests/e2e/claude_code/_builder_unit_tests/test_matrix_builder.py
#	tests/e2e/claude_code/_builder_unit_tests/test_v0_layout.py
#	tests/e2e/claude_code/_driver_unit_tests/test_rate_limiter.py
#	tests/e2e/claude_code/_pr_gate_unit_tests/test_bash_tool_restrictions.py
#	tests/e2e/claude_code/_pr_gate_unit_tests/test_compat_models.py
2026-07-16 15:08:25 -07:00
mateo-berri
35b042ffa5 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_claude_code_e2e_gpt_big3
# Conflicts:
#	tests/e2e/claude_code/_pr_gate_unit_tests/test_bash_tool_restrictions.py
2026-07-16 15:04:08 -07:00
mubashir1osmani
98765f65af
feat(e2e): emit structured E2E_RESULT lines for package status history (#33578)
* feat(e2e): emit structured E2E_RESULT lines for package status history

Pytest progress logs only expose file basenames and collapse multi-test files
into one status-history row. Emit one logfmt E2E_RESULT per finished node with
package, file, outcome, duration_ms, node_id, and covers so Grafana can roll up
by package (stable cardinality) and drill down by node_id in Explore

* test(e2e): drop unit tests from the live e2e tree

tests/e2e is for live proxy suites only. Remove harness, coverage-registry,
and claude_code unit trees so the e2e run does not collect them

* fix(e2e): type E2E_RESULT hook for basedpyright zero-error gate

Protocol-typed covers extraction and pluggy Result typing on the
makereport hook so tests/e2e stays under the e2e basedpyright ceiling

* fix(e2e): drop unused xfailed/xpassed from E2E_RESULT Outcome

We never emit those states; xfail collapses to skipped/passed via pytest
report flags. Keep the literal honest so the dashboard only sees real outcomes

* fix(e2e): strip tests/e2e prefix when deriving E2E_RESULT package

Repo-root pytest nodeids are tests/e2e/<suite>/...; without stripping,
every line would package=tests and status history would be useless

* fix(e2e): use pytest wrapper=True instead of deprecated hookwrapper

pytest 8.1+ deprecates hookwrapper; yield returns the TestReport directly
so we return it for the outer chain and drop pluggy.Result

* fix(e2e): import e2e_result_reporter at module load

Surface a missing module as a collection-time ImportError instead of a
per-test hook failure mid-run

* test(e2e): restore coverage_registry/test_collector.py

Needed to validate registry coverage math and the checked-in cell
denominator; not a live proxy suite
2026-07-16 15:01:01 -07:00