mirror of
https://github.com/whit3rabbit/anyllm-proxy.git
synced 2026-09-21 16:00:49 +00:00
- ci: replace inline smoke test with docker-compose.test.yml + scripts/docker-smoke-test.sh (9 checks) - add .env.example, .env.example.test, .anyllm.env.example, docker-compose.test.yml - gitignore: add .anyllm.env*, *.db-shm, *.db-wal patterns - admin UI: fix auth store, vite config, App.tsx updates; rebuild dist - admin routes: keys, traffic, mod, db, health_check updates - server: gemini_input, mod, routes adjustments - translator: lib.rs, gemini_message_map, tools_map fixes - batch: anthropic_batch cleanup - docs: add React admin UI plan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
4.5 KiB
Bash
Executable File
137 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# scripts/docker-smoke-test.sh
|
|
#
|
|
# Smoke-tests a running proxy + admin stack.
|
|
# Requires: curl, jq
|
|
#
|
|
# Environment (all optional, defaults shown):
|
|
# PROXY_URL http://localhost:3000
|
|
# ADMIN_URL http://localhost:3001
|
|
# ADMIN_TOKEN test-admin-token-docker-smoke-0000
|
|
#
|
|
# Usage:
|
|
# bash scripts/docker-smoke-test.sh
|
|
# PROXY_URL=http://localhost:3000 bash scripts/docker-smoke-test.sh
|
|
|
|
set -euo pipefail
|
|
|
|
PROXY_URL="${PROXY_URL:-http://localhost:3000}"
|
|
ADMIN_URL="${ADMIN_URL:-http://localhost:3001}"
|
|
ADMIN_TOKEN="${ADMIN_TOKEN:-test-admin-token-docker-smoke-0000}"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
# Color output if terminal supports it.
|
|
if [ -t 1 ]; then
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; RESET='\033[0m'
|
|
else
|
|
GREEN=''; RED=''; RESET=''
|
|
fi
|
|
|
|
pass() { echo -e "${GREEN}PASS${RESET}: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo -e "${RED}FAIL${RESET}: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
# check <description> <expected-substring> <actual>
|
|
check() {
|
|
local desc="$1" expected="$2" actual="$3"
|
|
if echo "$actual" | grep -qF "$expected"; then
|
|
pass "$desc"
|
|
else
|
|
fail "$desc — expected substring '$expected', got: $actual"
|
|
fi
|
|
}
|
|
|
|
# check_status <description> <expected-http-code> <url> [curl-args...]
|
|
check_status() {
|
|
local desc="$1" expected="$2" url="$3"
|
|
shift 3
|
|
local code
|
|
code=$(curl -s -o /dev/null -w "%{http_code}" "$@" "$url")
|
|
if [ "$code" = "$expected" ]; then
|
|
pass "$desc (HTTP $code)"
|
|
else
|
|
fail "$desc — expected HTTP $expected, got $code"
|
|
fi
|
|
}
|
|
|
|
echo "Smoke-testing proxy at $PROXY_URL, admin at $ADMIN_URL"
|
|
echo "---"
|
|
|
|
# Cookie jar for CSRF double-submit cookie pattern.
|
|
COOKIEJAR=$(mktemp)
|
|
trap 'rm -f "$COOKIEJAR"' EXIT
|
|
|
|
# 1. Proxy health (no auth)
|
|
check "proxy health" '"status":"ok"' "$(curl -sf "$PROXY_URL/health")"
|
|
|
|
# 2. Admin health (no auth, public route)
|
|
check "admin health" '"status":"ok"' "$(curl -sf "$ADMIN_URL/admin/health")"
|
|
|
|
# 3. Auth enforcement — missing key must return 401
|
|
check_status "missing auth key returns 401" "401" "$PROXY_URL/v1/messages" \
|
|
-X POST \
|
|
-H "content-type: application/json" \
|
|
-d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}'
|
|
|
|
# 4. /v1/models — no backend call needed (hardcoded list)
|
|
check "/v1/models returns data array" '"data"' \
|
|
"$(curl -sf "$PROXY_URL/v1/models" -H "x-api-key: smoke-test-key")"
|
|
|
|
# 5. CSRF token — GET /admin/csrf-token sets a cookie AND returns the token body.
|
|
# Both must be replayed on state-mutating admin requests (double-submit pattern).
|
|
CSRF_RESP=$(curl -sf -c "$COOKIEJAR" "$ADMIN_URL/admin/csrf-token")
|
|
CSRF=$(echo "$CSRF_RESP" | jq -r '.csrf_token // .token // empty')
|
|
if [ -n "$CSRF" ]; then
|
|
pass "csrf token fetch"
|
|
else
|
|
fail "csrf token fetch — response: $CSRF_RESP"
|
|
CSRF="invalid"
|
|
fi
|
|
|
|
# 6. Create virtual key via admin API
|
|
CREATE_RESP=$(curl -sf -b "$COOKIEJAR" -X POST "$ADMIN_URL/admin/api/keys" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
-H "X-CSRF-Token: $CSRF" \
|
|
-H "content-type: application/json" \
|
|
-d '{"description":"docker-smoke-test-key"}')
|
|
check "create virtual key returns sk-vk prefix" '"sk-vk' "$CREATE_RESP"
|
|
|
|
KEY_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty')
|
|
VK=$(echo "$CREATE_RESP" | jq -r '.key // empty')
|
|
|
|
# 7. List keys — created key description must appear
|
|
LIST_RESP=$(curl -sf "$ADMIN_URL/admin/api/keys" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN")
|
|
check "list keys includes smoke-test-key" 'docker-smoke-test-key' "$LIST_RESP"
|
|
|
|
# 8. Virtual key auth — verify the created key works against the proxy.
|
|
# Auth via /v1/models (no backend call needed).
|
|
if [ -n "$VK" ]; then
|
|
check "/v1/models accessible with virtual key" '"data"' \
|
|
"$(curl -sf "$PROXY_URL/v1/models" -H "x-api-key: $VK")"
|
|
else
|
|
fail "virtual key auth — could not extract key from create response"
|
|
fi
|
|
|
|
# 9. Delete virtual key — CSRF tokens are one-time-use; fetch a fresh one.
|
|
if [ -n "$KEY_ID" ]; then
|
|
CSRF_DEL_RESP=$(curl -sf -c "$COOKIEJAR" "$ADMIN_URL/admin/csrf-token")
|
|
CSRF_DEL=$(echo "$CSRF_DEL_RESP" | jq -r '.csrf_token // .token // empty')
|
|
DEL_CODE=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIEJAR" -X DELETE \
|
|
"$ADMIN_URL/admin/api/keys/$KEY_ID" \
|
|
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
-H "X-CSRF-Token: $CSRF_DEL")
|
|
if [ "$DEL_CODE" = "200" ] || [ "$DEL_CODE" = "204" ]; then
|
|
pass "delete virtual key (HTTP $DEL_CODE)"
|
|
else
|
|
fail "delete virtual key — expected 200 or 204, got $DEL_CODE"
|
|
fi
|
|
else
|
|
fail "delete virtual key — could not extract key id from create response"
|
|
fi
|
|
|
|
echo "---"
|
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
[ "$FAIL" -eq 0 ]
|