Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
81795c9
refactor(e2e/claude_code): align proxy env names with the rest of tes…
mubashir1osmani Jul 15, 2026
b7c4066
fix(e2e): anchor claude_code Bash pin at parents[1] so container run …
mubashir1osmani Jul 15, 2026
f31be7e
feat(e2e/claude_code): register compat deployments via /model/new fro…
mubashir1osmani Jul 15, 2026
ec13002
refactor(e2e/claude_code): inject env + runner instead of monkeypatching
mubashir1osmani Jul 15, 2026
8dbf8f3
handwrote rules
mubashir1osmani Jul 15, 2026
7fce78e
new rule
mubashir1osmani Jul 15, 2026
6c0f22c
refactor(e2e/claude_code): parse test_config.yaml once per resolution
mubashir1osmani Jul 15, 2026
3a2ed90
security(e2e/claude_code): mint inference-only CLI key; stop handing …
mubashir1osmani Jul 15, 2026
24d7931
fix(e2e/claude_code): hard-fail cells with a missing CLI key; never skip
mubashir1osmani Jul 15, 2026
2e5a2cb
revert(e2e/claude_code): drop the inference-only CLI-key layer; stand…
mubashir1osmani Jul 15, 2026
edd028a
fix(e2e/claude_code): register all 15 compat deployments regardless o…
mubashir1osmani Jul 15, 2026
2de6c38
rename(e2e/claude_code): sonnet-4-6 -> sonnet-4-5 across the compat m…
mubashir1osmani Jul 15, 2026
b398a04
fix(e2e/claude_code): hardcode vertex_ai_location=us-east5 for compat…
mubashir1osmani Jul 15, 2026
d72d3ec
fix(e2e/claude_code): fix run_daily.sh pagination loop under set -u w…
mubashir1osmani Jul 15, 2026
2c9e3ae
chore(e2e/claude_code): delete the cron_vm publisher; matrix runs on …
mubashir1osmani Jul 15, 2026
f00a397
feat(e2e/claude_code): attach anthropic-beta: context-1m-2025-08-07 h…
mubashir1osmani Jul 15, 2026
dc2d30e
feat(e2e/coverage): wire every claude_code compat cell to a registry …
mubashir1osmani Jul 15, 2026
a42d2b7
fix(e2e/claude_code): point the compat-model fixture at the resolved …
mubashir1osmani Jul 16, 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
13 changes: 13 additions & 0 deletions tests/e2e/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,16 @@ other.<area>.<case>.<assertion>
e.g. other.auth.jwt.valid_token_allows
other.lifecycle.readiness.reports_db
```

## Hard Rules
- no monkeypatching, mock tests or unit tests of any kind. if a contributor asks you to write an end to end test, do NOT stage a unit test with it. if you find a product gap, call it out in the PR description

- use model management endpoints to create new models for a test. this could be in a conftest / inline for each test. ask the user what they want.

- do not overengineer a test, i need you to write readable, clean code of what would look like a natural user scenario

- when it comes to typing an input schema for an api endpoint, have it type X = A | B | C ... where X = exhaustive union of all supported input schemas and A, B, C typically are composed by a base type. types are only pretty for a api request / response body. make sure to compose types instead of repeating the same base attributes over and over again.

- use the docker-compose to your advantage and spin up a local proxy, make sure all tests pass. if a test fails due to an internally found issue, let users know to create a linear ticket for it.

- do not use xfail markers, tests should be written in a form that the end user expects it to pass
30 changes: 9 additions & 21 deletions tests/e2e/claude_code/_basic_messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,20 @@

from __future__ import annotations

import os
from typing import Any, Mapping, Sequence
from typing import Any, Callable, Mapping, Sequence

import pytest

from claude_code._env import require_proxy
from claude_code.cli_driver import (
ClaudeCLIError,
DriverResult,
failure_diagnostic,
run_claude_models_parallel,
)

PROXY_BASE_URL_ENV = "LITELLM_PROXY_BASE_URL"
PROXY_API_KEY_ENV = "LITELLM_PROXY_API_KEY"

ClaudeRunner = Callable[..., Mapping[str, DriverResult | ClaudeCLIError]]

# Floor on the number of `stream_event` records (with delta payloads)
# we expect to see when the proxy actually streams. With
Expand Down Expand Up @@ -79,6 +80,8 @@ def run_basic_messaging_cell(
models: Sequence[str],
prompt: str,
verify_streaming: bool = False,
env: Mapping[str, str] | None = None,
runner: ClaudeRunner = run_claude_models_parallel,
) -> None:
"""Run the shared `basic_messaging_*` × <provider> cell body.

Expand All @@ -99,28 +102,13 @@ def run_basic_messaging_cell(
streamed reply to a single ``assistant`` event in
``--print --output-format stream-json`` mode).
"""
base_url = os.environ.get(PROXY_BASE_URL_ENV)
api_key = os.environ.get(PROXY_API_KEY_ENV)
if not base_url or not api_key:
compat_result.set(
{
"status": "fail",
"error": (
f"missing required env: set {PROXY_BASE_URL_ENV} and "
f"{PROXY_API_KEY_ENV} to point at a running LiteLLM proxy"
),
}
)
pytest.fail(
f"{PROXY_BASE_URL_ENV} / {PROXY_API_KEY_ENV} not configured",
pytrace=False,
)
base_url, api_key = require_proxy(compat_result, env=env)

extra_args: Sequence[str] = (
("--include-partial-messages",) if verify_streaming else ()
)

outcomes = run_claude_models_parallel(
outcomes = runner(
models=models,
prompt=prompt,
base_url=base_url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"providers": {
"anthropic": {
"status": "fail",
"error": "[claude-sonnet-4-6] tool call dropped"
"error": "[claude-sonnet-4-5] tool call dropped"
},
"bedrock_invoke": {
"status": "not_applicable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{
"feature_id": "basic_messaging_non_streaming",
"provider": "anthropic",
"nodeid": "tests/e2e/claude_code/basic_messaging_non_streaming/test_anthropic.py::test_basic_messaging_non_streaming_anthropic[claude-sonnet-4-6]",
"nodeid": "tests/e2e/claude_code/basic_messaging_non_streaming/test_anthropic.py::test_basic_messaging_non_streaming_anthropic[claude-sonnet-4-5]",
"result": {"status": "pass"}
},
{
Expand All @@ -28,8 +28,8 @@
{
"feature_id": "tool_use",
"provider": "anthropic",
"nodeid": "tests/e2e/claude_code/tool_use/test_anthropic.py::test_x[claude-sonnet-4-6]",
"result": {"status": "fail", "error": "[claude-sonnet-4-6] tool call dropped"}
"nodeid": "tests/e2e/claude_code/tool_use/test_anthropic.py::test_x[claude-sonnet-4-5]",
"result": {"status": "fail", "error": "[claude-sonnet-4-5] tool call dropped"}
},
{
"feature_id": "tool_use",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ def test_build_matrix_6x5_grid_matches_published_sample():

feature_ids = [feature["id"] for feature in manifest["features"]]
providers = manifest["providers"]
models = ["claude-haiku-4-5", "claude-sonnet-4-6", "claude-opus-4-7"]
models = ["claude-haiku-4-5", "claude-sonnet-4-5", "claude-opus-4-7"]

results = []
for feature_id in feature_ids:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def test_per_provider_test_file_imports_and_parametrizes_three_models(
`claude-opus-4-7-bedrock-invoke`), so we check for the tier
substrings rather than exact alias names."""
text = (REPO_ROOT / feature_id / f"test_{provider}.py").read_text()
for tier in ("haiku-4-5", "sonnet-4-6", "opus-4-7"):
for tier in ("haiku-4-5", "sonnet-4-5", "opus-4-7"):
assert (
tier in text
), f"{feature_id}/test_{provider}.py does not reference {tier}"
Expand Down
86 changes: 86 additions & 0 deletions tests/e2e/claude_code/_compat_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Load the claude_code compat matrix's deployment list from
``test_config.yaml``.

``test_config.yaml`` is the ground-truth config the stage deployment
uses; parsing it at fixture time means a change there (new tier, tier
retirement, provider swap, endpoint rename) reaches the fixture with
no extra edit. A drift-check test asserts every ``*_MODELS`` list
referenced by the compat cells is covered by the yaml, so a cell that
adds a probe for a name the yaml doesn't know about fails loudly at
collection instead of at 400-time.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Mapping

import yaml

from models import LiteLLMParamsBody

CONFIG_PATH = Path(__file__).resolve().parent / "test_config.yaml"


@dataclass(frozen=True, slots=True)
class CompatDeployment:
model_name: str
litellm_params: LiteLLMParamsBody


# The yaml uses ``vertex_ai_*`` for the vertex project/location fields
# (that is the spelling the proxy config file historically standardized
# on), while ``LiteLLMParamsBody`` names them without the ``_ai`` infix
# (matching the proxy's DB column). Both spellings resolve at call time
# on the proxy side, but pydantic silently drops unknown fields, so a
# raw ``LiteLLMParamsBody(**entry)`` would produce a body with the
# vertex project stripped - the resulting deployment 400s at
# ``/v1/messages`` with "Invalid model name". Normalize the yaml keys
# to the pydantic names in one place.
_YAML_TO_PYDANTIC_ALIASES = {
"vertex_ai_project": "vertex_project",
"vertex_ai_location": "vertex_location",
"vertex_ai_credentials": "vertex_credentials",
}


def _normalize_params(raw: Mapping[str, object]) -> dict[str, object]:
return {_YAML_TO_PYDANTIC_ALIASES.get(k, k): v for k, v in raw.items()}


ConfigReader = Callable[[Path], str]


def _default_reader(path: Path) -> str:
return path.read_text()


def load_all_deployments(
config_path: Path = CONFIG_PATH,
reader: ConfigReader = _default_reader,
) -> tuple[CompatDeployment, ...]:
"""Every deployment declared in the yaml, in file order."""
doc = yaml.safe_load(reader(config_path))
model_list = doc.get("model_list") or []
return tuple(
CompatDeployment(
model_name=entry["model_name"],
litellm_params=LiteLLMParamsBody(
**_normalize_params(entry["litellm_params"])
),
)
for entry in model_list
)


def all_expected_model_names(
*,
config_path: Path = CONFIG_PATH,
reader: ConfigReader = _default_reader,
) -> frozenset[str]:
"""Every virtual name the compat matrix declares - the ground truth
the cells are supposed to probe. Used by the drift-check test."""
return frozenset(
d.model_name for d in load_all_deployments(config_path, reader)
)
Loading