litellm/tests/unit/litellm_proxy_extras/test_prisma_toolchain.py
devin-ai-integration[bot] 102619d47a
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
Deploy main to gateway-dev / dispatch (push) Waiting to run
Publish lint base counts / publish (basedpyright, scripts/type_check_gate.py) (push) Waiting to run
Publish lint base counts / publish (openapi-docs, scripts/openapi_docs_gate.py) (push) Waiting to run
Publish lint base counts / publish (ruff-strict, scripts/ruff_strict_gate.py) (push) Waiting to run
Publish lint base counts / publish (test-quality, scripts/test_quality_gate.py) (push) Waiting to run
Publish lint base counts / publish (type-discipline, scripts/type_discipline_gate.py) (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run
unit / proxy-server-core (push) Blocked by required conditions
unit / root (push) Blocked by required conditions
unit / proxy-utils (push) Blocked by required conditions
unit / mcp-elicitation (push) Blocked by required conditions
unit / proxy-auth (push) Blocked by required conditions
unit / proxy-endpoints (push) Blocked by required conditions
unit / proxy-extras (push) Blocked by required conditions
unit / proxy-feature-endpoints (push) Blocked by required conditions
unit / proxy-hooks-client (push) Blocked by required conditions
unit / proxy-infra (push) Blocked by required conditions
unit / proxy-infra-root (push) Blocked by required conditions
unit / responses-caching-types (push) Blocked by required conditions
unit / router (push) Blocked by required conditions
unit / rust-bridge-harness (push) Blocked by required conditions
unit / auth-checks (push) Blocked by required conditions
unit / budgets (push) Blocked by required conditions
unit / custom-logging (push) Blocked by required conditions
unit / enterprise-routing (push) Blocked by required conditions
unit / guardrails-hooks (push) Blocked by required conditions
unit / key-generation (push) Blocked by required conditions
unit / logging-misc (push) Blocked by required conditions
unit / assert-shard-coverage (push) Waiting to run
unit / llms-openai-meta (push) Blocked by required conditions
unit / llms-providers (push) Blocked by required conditions
unit / llms-vertex-ai (push) Blocked by required conditions
unit / ui-unit (push) Waiting to run
unit / docs (push) Waiting to run
unit / OpenAI 3.25.0 compatibility (push) Waiting to run
unit / coverage (push) Blocked by required conditions
unit / db-and-spend (push) Blocked by required conditions
unit / endpoints-and-responses (push) Blocked by required conditions
unit / jwt-and-keys (push) Blocked by required conditions
unit / mcp-oauth (push) Blocked by required conditions
unit / proxy-runtime (push) Blocked by required conditions
unit / rust-bridge (push) Waiting to run
unit / enterprise-managed-files (push) Blocked by required conditions
unit / proxy-server (push) Blocked by required conditions
unit / lens-python-310 (push) Blocked by required conditions
unit / caching-local (push) Blocked by required conditions
unit / core-utils (push) Blocked by required conditions
unit / endpoints (push) Blocked by required conditions
unit / enterprise-package (push) Blocked by required conditions
unit / enterprise-repositories-secrets (push) Blocked by required conditions
unit / integrations (push) Blocked by required conditions
unit / llms-anthropic (push) Blocked by required conditions
unit / llms-bedrock (push) Blocked by required conditions
unit / helm (push) Waiting to run
unit / OpenAI 2.20.0 compatibility (push) Waiting to run
unit / OpenAI 3.0.0 compatibility (push) Waiting to run
unit / unit passed (push) Blocked by required conditions
GitHub Actions Security Analysis / zizmor (push) Waiting to run
test: migrate proxy security, schema migration and MCP legacy tests (#45937)
* test: migrate proxy security, schema migration and MCP legacy tests

* test: run the MCP proxy e2e upstream under the SDK1 peer in integration-mcp

* test: warm the baseline store before timing the locked projection

* test(e2e): hold the stack exclusively while the semantic filter flag is flipped

* test(mcp): scope the in-process proxy e2e fixtures to their module

The session-scoped autouse fixture deleted DATABASE_URL and overrode LITELLM_MASTER_KEY for the rest of the worker, which broke every later test in the integration mcp suite

* test(e2e): assert the semantic filter keeps listed tools instead of a ranking

Which Datadog tool an OpenAI embedding ranks first is not something litellm owns, so the test now checks that the kept tools are a non-empty subset of the key's listing within top_k

* test(mcp): keep the in-process proxy e2e tests on one worker

loadgroup spread the file's tests across workers and interleaved them with other files, so the module-scoped in-process proxy was torn down and restarted between tests and requests timed out

* test(mcp): name the upstream requests that carried the wrong token

The bare all() assertion hid which request broke the forwarded token, which made an intermittent CircleCI failure undiagnosable

---------

Co-authored-by: yuneng <yuneng@berri.ai>
2026-10-11 16:17:24 -07:00

257 lines
8.8 KiB
Python

import os
import sys
from collections.abc import Callable
from pathlib import Path
import pytest
from litellm_proxy_extras.prisma_toolchain import (
DEFAULT_PRISMA_COMMAND_TIMEOUT,
DEFAULT_PRISMA_MIGRATE_DEPLOY_TIMEOUT,
PRISMA_BOOTSTRAP_TIMEOUT_ENV_VAR,
PRISMA_COMMAND_TIMEOUT_ENV_VAR,
PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR,
heal_incomplete_nodeenv_cache,
node_binary_path,
prisma_bootstrap_timeout,
prisma_cli_available,
prisma_command_timeout,
prisma_migrate_deploy_timeout,
resolve_prisma_argv,
)
FAKE_PRISMA = """#!{python}
import json
import os
import pathlib
import subprocess
import sys
import time
args = sys.argv[1:]
cache_dir = os.environ["PRISMA_NODEENV_CACHE_DIR"]
log_path = pathlib.Path(os.environ["FAKE_PRISMA_LOG"])
earlier_same_command = sum(
1
for line in (log_path.read_text().splitlines() if log_path.exists() else [])
if json.loads(line)["args"][:2] == args[:2]
)
with log_path.open("a") as log:
log.write(
json.dumps({{"args": args, "cache_dir_present": os.path.isdir(cache_dir)}})
+ "\\n"
)
time.sleep(float(os.environ.get("FAKE_PRISMA_SLEEP", "0")))
if args[:2] == ["migrate", "deploy"]:
if earlier_same_command == 0:
if os.environ.get("FAKE_PRISMA_GRANDCHILD_PIDFILE"):
grandchild = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(600)"])
pathlib.Path(os.environ["FAKE_PRISMA_GRANDCHILD_PIDFILE"]).write_text(str(grandchild.pid))
time.sleep(float(os.environ.get("FAKE_PRISMA_FIRST_DEPLOY_SLEEP", "0")))
elif os.environ.get("FAKE_PRISMA_LATER_DEPLOY_STDERR"):
print(os.environ["FAKE_PRISMA_LATER_DEPLOY_STDERR"], file=sys.stderr)
sys.exit(1)
print("No pending migrations to apply")
if args[:2] == ["db", "push"] and earlier_same_command == 0:
time.sleep(float(os.environ.get("FAKE_PRISMA_FIRST_PUSH_SLEEP", "0")))
sys.exit(0)
"""
def _write_fake_prisma(tmp_path: Path) -> Path:
bin_dir = tmp_path / "fakebin"
bin_dir.mkdir()
script = bin_dir / "prisma"
script.write_text(FAKE_PRISMA.format(python=sys.executable))
script.chmod(0o755)
return bin_dir
@pytest.fixture
def toolchain_env(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
"""Point the toolchain at a scratch cache dir driven by a fake Prisma CLI."""
cache_dir = tmp_path / "nodeenv"
log_path = tmp_path / "prisma-calls.jsonl"
bin_dir = _write_fake_prisma(tmp_path)
monkeypatch.setenv("PRISMA_NODEENV_CACHE_DIR", str(cache_dir))
monkeypatch.setenv("FAKE_PRISMA_LOG", str(log_path))
monkeypatch.setenv("PATH", f"{bin_dir}{os.pathsep}{os.environ['PATH']}")
monkeypatch.delenv(PRISMA_COMMAND_TIMEOUT_ENV_VAR, raising=False)
monkeypatch.delenv(PRISMA_BOOTSTRAP_TIMEOUT_ENV_VAR, raising=False)
monkeypatch.delenv(PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR, raising=False)
return cache_dir, log_path
def _make_incomplete_cache(cache_dir: Path) -> None:
(cache_dir / "lib").mkdir(parents=True)
(cache_dir / "bin").mkdir()
FAKE_PRISMA_MODULE_MAIN = """import json
import sys
print(json.dumps({"module_argv": sys.argv[1:]}))
"""
def _write_fake_prisma_module(tmp_path: Path) -> Path:
package_dir = tmp_path / "fakemodule" / "prisma"
package_dir.mkdir(parents=True)
(package_dir / "__init__.py").write_text("")
(package_dir / "__main__.py").write_text(FAKE_PRISMA_MODULE_MAIN)
return package_dir.parent
def _empty_bin(tmp_path: Path) -> Path:
bin_dir = tmp_path / "emptybin"
bin_dir.mkdir()
return bin_dir
def _make_complete_cache(cache_dir: Path) -> None:
node = node_binary_path(cache_dir)
node.parent.mkdir(parents=True)
node.write_text("")
def test_interrupted_toolchain_install_is_removed(
toolchain_env: tuple[Path, Path],
) -> None:
cache_dir, _ = toolchain_env
_make_incomplete_cache(cache_dir)
assert heal_incomplete_nodeenv_cache() is True
assert not cache_dir.exists()
def test_installed_toolchain_is_left_alone(toolchain_env: tuple[Path, Path]) -> None:
cache_dir, _ = toolchain_env
_make_complete_cache(cache_dir)
assert heal_incomplete_nodeenv_cache() is False
assert node_binary_path(cache_dir).exists()
def test_absent_toolchain_is_not_an_error(toolchain_env: tuple[Path, Path]) -> None:
cache_dir, _ = toolchain_env
assert heal_incomplete_nodeenv_cache() is False
assert not cache_dir.exists()
_CAN_DENY_ACCESS = os.name != "nt" and hasattr(os, "geteuid") and os.geteuid() != 0
@pytest.mark.skipif(not _CAN_DENY_ACCESS, reason="root and Windows do not honour a 0o000 directory")
def test_unreadable_cache_dir_is_not_an_error(
toolchain_env: tuple[Path, Path],
) -> None:
"""A cache dir this process cannot stat means nothing to heal, not a crash.
Images bake the cache under the build user's home, and a container started
under any other uid cannot search that directory. `Path.is_dir()` only
swallows ENOENT-shaped errnos, so it raises `PermissionError` there and
kills the migration before Prisma is ever invoked.
"""
cache_dir, _ = toolchain_env
_make_incomplete_cache(cache_dir)
cache_dir.parent.chmod(0o000)
try:
assert heal_incomplete_nodeenv_cache() is False
finally:
cache_dir.parent.chmod(0o700)
@pytest.mark.parametrize(
("command_timeout", "deploy_timeout", "expected"),
[
("900", None, 900.0),
("12", None, DEFAULT_PRISMA_MIGRATE_DEPLOY_TIMEOUT),
("900", "1200", 1200.0),
("900", "300", 300.0),
],
ids=[
"raised_command_budget_carries_over",
"lowered_command_budget_does_not",
"override_wins_upward",
"override_wins_downward",
],
)
def test_migrate_deploy_budget_keeps_a_raised_command_budget(
command_timeout: str, deploy_timeout: str | None, expected: float, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Deployments that raised the per-command budget to survive a long deploy keep that budget for deploy."""
monkeypatch.setenv(PRISMA_COMMAND_TIMEOUT_ENV_VAR, command_timeout)
if deploy_timeout is None:
monkeypatch.delenv(PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR, raising=False)
else:
monkeypatch.setenv(PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR, deploy_timeout)
assert prisma_migrate_deploy_timeout() == expected
@pytest.mark.parametrize(
"raw",
["", "0", "-5", "not-a-number", "nan", "inf", "-inf", "1e400"],
)
@pytest.mark.parametrize(
("env_var", "read_timeout", "default"),
[
(PRISMA_COMMAND_TIMEOUT_ENV_VAR, prisma_command_timeout, DEFAULT_PRISMA_COMMAND_TIMEOUT),
(PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR, prisma_migrate_deploy_timeout, DEFAULT_PRISMA_MIGRATE_DEPLOY_TIMEOUT),
],
ids=["command", "migrate_deploy"],
)
def test_unusable_timeout_override_falls_back_to_the_default(
raw: str, env_var: str, read_timeout: Callable[[], float], default: float, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A non-finite override would silently disable the timeout it configures."""
monkeypatch.setenv(env_var, raw)
assert read_timeout() == default
def test_timeout_overrides_are_independent(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv(PRISMA_COMMAND_TIMEOUT_ENV_VAR, "12")
monkeypatch.setenv(PRISMA_BOOTSTRAP_TIMEOUT_ENV_VAR, "900")
monkeypatch.setenv(PRISMA_MIGRATE_DEPLOY_TIMEOUT_ENV_VAR, "1200")
assert prisma_command_timeout() == 12
assert prisma_bootstrap_timeout() == 900
assert prisma_migrate_deploy_timeout() == 1200
def test_resolve_prisma_argv_leaves_an_explicit_cli_path_alone(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("PATH", str(_empty_bin(tmp_path)))
explicit = ("/app/.cache/prisma-python/prisma", "migrate", "deploy")
assert resolve_prisma_argv(explicit) == explicit
def test_prisma_cli_is_unavailable_with_neither_script_nor_package(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("PATH", str(_empty_bin(tmp_path)))
monkeypatch.delitem(sys.modules, "prisma", raising=False)
monkeypatch.setattr(sys, "path", [])
assert prisma_cli_available() is False
def test_prisma_cli_is_available_through_the_package_alone(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("PATH", str(_empty_bin(tmp_path)))
monkeypatch.delitem(sys.modules, "prisma", raising=False)
monkeypatch.setattr(sys, "path", [str(_write_fake_prisma_module(tmp_path))])
assert prisma_cli_available() is True
def test_prisma_cli_is_available_through_the_console_script_alone(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("PATH", str(_write_fake_prisma(tmp_path)))
monkeypatch.delitem(sys.modules, "prisma", raising=False)
monkeypatch.setattr(sys, "path", [])
assert prisma_cli_available() is True