mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
* test: point router/completion/triton tests at the local fake OpenAI endpoint The shared Railway-hosted mock (exampleopenaiendpoint-production.up.railway.app) takes down unrelated CI jobs whenever it is unreachable. #30695 moved the mounted proxy configs onto a job-local fake server but left these in-Python api_base literals pointing at the dead host, so litellm_router_testing, local_testing_part1, local_testing_part2 and llm_translation_testing still fail with a 404 "Application not found" when Railway is down Resolve the api_base from FAKE_OPENAI_API_BASE (default http://127.0.0.1:8190) through a shared helper, auto-start the canned server from the local_testing and llm_translation conftests when nothing is already serving, and extend the server with a Triton embeddings route and a slow-endpoint delay so the triton and latency-timeout tests run fully offline. The deliberately broken fallback URL is left as-is so fallback handling still has a failing upstream * fix: ignore non-loopback FAKE_OPENAI_API_BASE so the local mock is used in CI * fix: drop 0.0.0.0 from loopback hosts, an unreliable client connect target * fix(tests): keep fake OpenAI mock alive across xdist workers ensure_fake_openai_endpoint registered atexit on the worker that spawned the subprocess, so under -n 4 the first worker to drain its queue would terminate the shared mock while siblings were still hitting it. Detach the child via start_new_session and drop the per-worker teardown; reuse on /health handles re-runs and CI containers clean up themselves
102 lines
3.5 KiB
Python
102 lines
3.5 KiB
Python
"""Regression tests for the canned OpenAI mock that decouples the suite from a
|
|
hosted endpoint (see ``tests/fake_openai_endpoint.py`` and
|
|
``tests/_fake_openai_endpoint_server.py``).
|
|
|
|
Before this, router/completion/triton tests hardcoded a shared Railway mock as
|
|
their ``api_base``; when that single deployment went down, unrelated CI jobs
|
|
failed with ``404 Application not found``. These tests pin the two behaviors
|
|
those tests now rely on from the local stand-in (the Triton embeddings shape and
|
|
the ``slow-endpoint`` delay) and guard against the dead host creeping back in.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from tests.fake_openai_endpoint import (
|
|
_LOCAL_DEFAULT,
|
|
_resolve_base,
|
|
ensure_fake_openai_endpoint,
|
|
)
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
_MIGRATED_FILES = (
|
|
"tests/llm_translation/test_triton.py",
|
|
"tests/local_testing/test_router.py",
|
|
"tests/local_testing/test_router_custom_routing.py",
|
|
"tests/local_testing/test_router_fallback_handlers.py",
|
|
"tests/local_testing/test_router_fallbacks.py",
|
|
"tests/local_testing/test_secret_detect_hook.py",
|
|
"tests/local_testing/test_lowest_latency_routing.py",
|
|
"tests/local_testing/test_completion.py",
|
|
)
|
|
|
|
# A live ``railway.app/`` or ``railway.app"`` api_base reappearing in a migrated
|
|
# file would re-couple CI to an external service's uptime. The deliberately
|
|
# broken ``...railway.appzzzzz`` fallback URL is not a live host, so it does not
|
|
# match.
|
|
_LIVE_HOSTED_MOCK = re.compile(r"railway\.app(?:/|\")")
|
|
|
|
|
|
def test_chat_completion_shape():
|
|
base = ensure_fake_openai_endpoint()
|
|
response = httpx.post(
|
|
f"{base}/v1/chat/completions",
|
|
json={"model": "gpt-4", "messages": [{"role": "user", "content": "hi"}]},
|
|
timeout=10,
|
|
)
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
assert body["choices"][0]["message"]["content"]
|
|
assert body["usage"]["total_tokens"] == 40
|
|
|
|
|
|
def test_triton_embeddings_route():
|
|
base = ensure_fake_openai_endpoint()
|
|
response = httpx.post(f"{base}/triton/embeddings", json={"inputs": []}, timeout=10)
|
|
assert response.status_code == 200
|
|
output = response.json()["outputs"][0]
|
|
assert output["shape"] == [1, 2]
|
|
assert output["data"] == [0.1, 0.2]
|
|
|
|
|
|
def test_slow_model_blocks_past_client_timeout():
|
|
base = ensure_fake_openai_endpoint()
|
|
with pytest.raises(httpx.TimeoutException):
|
|
httpx.post(
|
|
f"{base}/v1/chat/completions",
|
|
json={
|
|
"model": "slow-endpoint",
|
|
"messages": [{"role": "user", "content": "hi"}],
|
|
},
|
|
timeout=0.5,
|
|
)
|
|
|
|
|
|
def test_remote_env_base_resolves_to_local(monkeypatch):
|
|
monkeypatch.setenv(
|
|
"FAKE_OPENAI_API_BASE",
|
|
"https://exampleopenaiendpoint-production.up.railway.app",
|
|
)
|
|
assert _resolve_base() == _LOCAL_DEFAULT
|
|
|
|
|
|
@pytest.mark.parametrize("host", ["127.0.0.1", "localhost", "[::1]"])
|
|
def test_loopback_env_base_is_honored(monkeypatch, host):
|
|
base = f"http://{host}:9191"
|
|
monkeypatch.setenv("FAKE_OPENAI_API_BASE", base)
|
|
assert _resolve_base() == base
|
|
|
|
|
|
@pytest.mark.parametrize("relative_path", _MIGRATED_FILES)
|
|
def test_migrated_files_have_no_live_hosted_mock(relative_path):
|
|
source = (_REPO_ROOT / relative_path).read_text()
|
|
assert not _LIVE_HOSTED_MOCK.search(source), (
|
|
f"{relative_path} references the hosted Railway mock; point api_base at "
|
|
"FAKE_OPENAI_API_BASE so CI does not depend on an external service"
|
|
)
|