litellm/tests/AGENTS.md
ryan-crabbe-berri 048a1500df
test(e2e): move the harness self-tests out of tests/e2e (#45172)
* test(e2e): move the harness self-tests out of tests/e2e

The nightly Buildkite run copies tests/e2e into the runner image and runs
bare pytest, so the 672 tests of the harness itself (fixture parsing, JUnit
properties, the stack lock, the load aggregators, the Claude Code driver)
counted as e2e tests on the status page even though none of them reaches a
proxy. They now live in tests/e2e_harness, mirroring the tests/e2e layout,
and run in the GitHub Actions lint job and the CircleCI
provider_replay_harness job instead

* fix(ci): point the providers replay controls at tests/e2e_harness

The providers integration job still selected the four replay-control
tests under tests/e2e/test_provider_edge.py, so pytest exited before
they ran. The raw-HTTP check's file walk also drops to one loop per
comprehension

* style(tests): mark the raw-HTTP check's bindings Final
2026-10-07 22:20:02 +00:00

2 KiB

Tests

Nothing on the other side of the call: tests/unit. A proxy we start with an upstream we script: tests/integration. Someone else's service with real credentials: tests/e2e. Tests of that harness itself, no proxy at all: tests/e2e_harness. Two fit, split it

What good looks like

Red when the claim in the name is broken. Prove it: mutate the behaviour, red; restore, green. Put the mutation in the PR body

def test_custom_price_is_reported_and_charged(gateway: Gateway) -> None:
    with gateway.scenario() as scenario:
        model = scenario.model(input_cost_per_token=0.001, output_cost_per_token=0.002)
        response = gateway.request("POST", "/v1/chat/completions", {"model": model, "messages": [{"role": "user", "content": "price control"}]})
        assert response.status_code == 200, response.text
        assert float(response.headers["x-litellm-response-cost"]) == pytest.approx(20 * 0.001 + 20 * 0.002)

Rates in the test, expected computed by hand, one call, response.text in the assert

Assert the whole value. Iterating expected_body.items() (test_responses_api_request_body.py) cannot see an extra key; that is the shape of stream_options.include_usage (#19777, #28553)

The linter catches no-assert, mock-echo and credential skips. It cannot see an assert behind an if (a poll that ends in pytest.fail is fine), except Exception around the call (test_router.py: except Exception as e: print(f"FAILED TEST")), or blanket --reruns

Where it goes

What the assertion depends on goes in the test; everything else in conftest. A rate in a fixture three directories up makes a failed assertion unreadable. Extend the file that already covers the behaviour

Writing it so a human can read it

Name says what broke: test_send_batched_with_valid_data says nothing. Build, one call, assert, on one screen. Helpers named for what they return, _pii_prompt(marker, email), not _setup(). Context in the assert message, not a comment