mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-14 11:08:37 +00:00
8a0ddd46d5
Add a self-contained Playwright E2E test suite that runs against a local PostgreSQL database instead of Neon. Tests cover role-based access for all 5 user roles (proxy admin, admin viewer, internal user, internal viewer, team admin) and authentication flows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
5.1 KiB
Bash
Executable File
163 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ================================================================
|
|
# UI E2E Test Runner
|
|
# Starts postgres, seeds DB, starts mock + proxy, runs Playwright.
|
|
# All credentials are generated per run — nothing is stored on disk.
|
|
#
|
|
# In CI (CI=true), expects:
|
|
# - PostgreSQL already running on 127.0.0.1:5432
|
|
# - DATABASE_URL already set
|
|
# - Python/Poetry already installed
|
|
# - Node.js/npx already available
|
|
# ================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
IS_CI="${CI:-false}"
|
|
CONTAINER_NAME="litellm-e2e-postgres-$$"
|
|
MOCK_PID=""
|
|
PROXY_PID=""
|
|
|
|
# --- Ensure common tool paths are available (local dev only) ---
|
|
if [ "$IS_CI" = "false" ]; then
|
|
for p in /usr/local/bin /opt/homebrew/bin "$HOME/.local/bin" /opt/homebrew/opt/postgresql@14/bin /opt/homebrew/opt/libpq/bin; do
|
|
[ -d "$p" ] && export PATH="$p:$PATH"
|
|
done
|
|
[ -s "$HOME/.nvm/nvm.sh" ] && source "$HOME/.nvm/nvm.sh"
|
|
fi
|
|
|
|
# --- Cleanup on exit ---
|
|
cleanup() {
|
|
echo "Cleaning up..."
|
|
[ -n "$MOCK_PID" ] && kill "$MOCK_PID" 2>/dev/null || true
|
|
[ -n "$PROXY_PID" ] && kill "$PROXY_PID" 2>/dev/null || true
|
|
if [ "$IS_CI" = "false" ]; then
|
|
docker stop "$CONTAINER_NAME" 2>/dev/null || true
|
|
fi
|
|
echo "Done."
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# --- Pre-flight checks ---
|
|
for cmd in python3 npx poetry; do
|
|
command -v "$cmd" >/dev/null 2>&1 || { echo "Error: $cmd not found."; exit 1; }
|
|
done
|
|
|
|
# --- Database setup ---
|
|
if [ "$IS_CI" = "false" ]; then
|
|
# Local: spin up a postgres container
|
|
for cmd in docker psql; do
|
|
command -v "$cmd" >/dev/null 2>&1 || { echo "Error: $cmd not found."; exit 1; }
|
|
done
|
|
for port in 4000 5432 8090; do
|
|
if lsof -ti ":$port" >/dev/null 2>&1; then
|
|
echo "Error: port $port is in use"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
export POSTGRES_USER="e2euser"
|
|
export POSTGRES_PASSWORD="$(openssl rand -hex 32)"
|
|
export POSTGRES_DB="litellm_e2e"
|
|
export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@127.0.0.1:5432/${POSTGRES_DB}"
|
|
|
|
echo "=== Starting PostgreSQL ==="
|
|
docker run -d --rm --name "$CONTAINER_NAME" \
|
|
-e POSTGRES_USER -e POSTGRES_PASSWORD -e POSTGRES_DB \
|
|
-p 127.0.0.1:5432:5432 \
|
|
postgres:16
|
|
|
|
echo "Waiting for PostgreSQL..."
|
|
for i in $(seq 1 30); do
|
|
if PGPASSWORD="$POSTGRES_PASSWORD" pg_isready -h 127.0.0.1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
else
|
|
# CI: postgres is already running as a service container
|
|
echo "=== Using CI PostgreSQL service ==="
|
|
: "${DATABASE_URL:?DATABASE_URL must be set in CI}"
|
|
fi
|
|
|
|
# --- Credentials ---
|
|
export LITELLM_MASTER_KEY="sk-e2e-$(openssl rand -hex 32)"
|
|
export MOCK_LLM_URL="http://127.0.0.1:8090/v1"
|
|
export DISABLE_SCHEMA_UPDATE="true"
|
|
|
|
# --- Python environment ---
|
|
echo "=== Setting up Python environment ==="
|
|
cd "$REPO_ROOT"
|
|
if ! poetry run python3 -c "import prisma" 2>/dev/null; then
|
|
echo "Installing Python dependencies (first run)..."
|
|
poetry install --with dev,proxy-dev --extras "proxy" --quiet
|
|
poetry run pip install nodejs-wheel-binaries 2>/dev/null || true
|
|
poetry run prisma generate --schema litellm/proxy/schema.prisma
|
|
fi
|
|
|
|
echo "=== Pushing Prisma schema to database ==="
|
|
poetry run prisma db push --schema litellm/proxy/schema.prisma --accept-data-loss
|
|
|
|
# --- Mock LLM server ---
|
|
echo "=== Starting mock LLM server ==="
|
|
poetry run python3 "$SCRIPT_DIR/fixtures/mock_llm_server/server.py" &
|
|
MOCK_PID=$!
|
|
|
|
for i in $(seq 1 15); do
|
|
if curl -sf http://127.0.0.1:8090/health >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
|
|
# --- LiteLLM proxy ---
|
|
echo "=== Starting LiteLLM proxy ==="
|
|
cd "$REPO_ROOT"
|
|
poetry run python3 -m litellm.proxy.proxy_cli \
|
|
--config "$SCRIPT_DIR/fixtures/config.yml" \
|
|
--port 4000 &
|
|
PROXY_PID=$!
|
|
|
|
echo "Waiting for proxy..."
|
|
PROXY_READY=0
|
|
for i in $(seq 1 180); do
|
|
if ! kill -0 "$PROXY_PID" 2>/dev/null; then
|
|
echo "Error: proxy process exited unexpectedly"
|
|
exit 1
|
|
fi
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:4000/health -H "Authorization: Bearer $LITELLM_MASTER_KEY" 2>/dev/null || true)
|
|
if [ "$HTTP_CODE" = "200" ]; then
|
|
PROXY_READY=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [ "$PROXY_READY" -ne 1 ]; then
|
|
echo "Error: proxy did not become healthy within 180 seconds"
|
|
exit 1
|
|
fi
|
|
echo "Proxy is ready."
|
|
|
|
# --- Seed database ---
|
|
echo "=== Seeding database ==="
|
|
# Extract credentials from DATABASE_URL for psql
|
|
DB_USER=$(echo "$DATABASE_URL" | sed -n 's|.*://\([^:]*\):.*|\1|p')
|
|
DB_PASS=$(echo "$DATABASE_URL" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p')
|
|
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's|.*@\([^:]*\):.*|\1|p')
|
|
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's|.*:\([0-9]*\)/.*|\1|p')
|
|
DB_NAME=$(echo "$DATABASE_URL" | sed -n 's|.*/\([^?]*\).*|\1|p')
|
|
|
|
PGPASSWORD="$DB_PASS" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
|
|
-f "$SCRIPT_DIR/fixtures/seed.sql"
|
|
|
|
# --- Playwright ---
|
|
echo "=== Installing Playwright dependencies ==="
|
|
cd "$SCRIPT_DIR"
|
|
npm install --silent
|
|
|
|
echo "=== Running Playwright tests ==="
|
|
npx playwright test "$@"
|
|
EXIT_CODE=$?
|
|
|
|
exit $EXIT_CODE
|