Files
sub2api-cn-relay-manager/scripts/acceptance/verify_user_key_self_service.sh
phamnazage-jpg 5b59ad7490
Some checks failed
CI / Build & Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Release (push) Has been cancelled
feat(vnext2): close user key self-service on real host
2026-06-05 19:58:02 +08:00

237 lines
8.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TS="${TS:-$(date +%Y%m%d_%H%M%S)}"
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT_DIR/artifacts/user-key-self-service/${TS}}"
CRM_BASE="${CRM_BASE:-https://sub.tksea.top/portal-admin-api}"
USER_CHAT_BASE="${USER_CHAT_BASE:-}"
CHAT_MODEL="${CHAT_MODEL:-gpt-5.4}"
USER_SUBJECT_ID="${USER_SUBJECT_ID:-}"
USER_AUTH_TOKEN="${USER_AUTH_TOKEN:-}"
mkdir -p "$ARTIFACT_DIR"
info() { printf 'INFO: %s\n' "$*"; }
ok() { printf 'OK: %s\n' "$*"; }
warn() { printf 'WARN: %s\n' "$*" >&2; }
die() { printf 'FATAL: %s\n' "$*" >&2; exit 1; }
usage() {
cat <<'EOF'
usage: verify_user_key_self_service.sh [--help|--env-check|--run]
Modes:
--help 显示帮助
--env-check 仅检查 CRM / chat 入口与认证前置
--run 执行真实 user-key 闭环create -> list -> get -> reset -> chat
Required env for --run:
CRM_BASE CRM API base, e.g. https://sub.tksea.top/portal-admin-api
USER_CHAT_BASE 最终 user-key 调用入口 base, e.g. https://sub.tksea.top
CHAT_MODEL chat 模型名default: gpt-5.4
Authentication for /api/keys endpoints (choose one):
USER_SUBJECT_ID 通过 X-Portal-Subject 头注入 subject联合部署/受信入口)
USER_AUTH_TOKEN 通过 Authorization: Bearer <token> 走用户链路
Artifacts:
artifacts/user-key-self-service/<timestamp>/
- 00-env.json
- 10-create.headers.txt / 10-create.body.json
- 11-list.headers.txt / 11-list.body.json
- 12-get.headers.txt / 12-get.body.json
- 13-reset.headers.txt / 13-reset.body.json
- 20-chat.headers.txt / 20-chat.body.json
- 99-summary.json
EOF
}
build_auth_args() {
if [[ -n "$USER_AUTH_TOKEN" ]]; then
printf '%s\n' "-H" "Authorization: Bearer $USER_AUTH_TOKEN"
return 0
fi
if [[ -n "$USER_SUBJECT_ID" ]]; then
printf '%s\n' "-H" "X-Portal-Subject: $USER_SUBJECT_ID"
return 0
fi
return 1
}
curl_json_with_capture() {
local method="$1"
local url="$2"
local headers_file="$3"
local body_file="$4"
local payload="${5:-}"
local -a args
args=(curl -sS --noproxy '*' -D "$headers_file" -o "$body_file" -X "$method")
while IFS= read -r line; do
args+=("$line")
done < <(build_auth_args)
if [[ -n "$payload" ]]; then
args+=(-H 'Content-Type: application/json' -d "$payload")
fi
args+=("$url")
"${args[@]}"
}
curl_chat_with_capture() {
local plaintext_key="$1"
local payload="$2"
local headers_file="$3"
local body_file="$4"
curl -sS --noproxy '*' \
-D "$headers_file" \
-o "$body_file" \
-H "Authorization: Bearer $plaintext_key" \
-H 'Content-Type: application/json' \
-X POST \
-d "$payload" \
"${USER_CHAT_BASE%/}/v1/chat/completions"
}
extract_http_code() {
local headers_file="$1"
awk 'toupper($1) ~ /^HTTP\// { code=$2 } END { print code }' "$headers_file"
}
json_get() {
local file="$1"
local expr="$2"
python3 - "$file" "$expr" <<'PY'
import json, sys
path, expr = sys.argv[1:3]
value = json.load(open(path, 'r', encoding='utf-8'))
for part in expr.split('.'):
if isinstance(value, dict):
value = value.get(part)
else:
raise SystemExit(2)
print("" if value is None else value)
PY
}
cmd_env_check() {
local crm_health="unreachable"
if crm_health=$(curl -sS --noproxy '*' "${CRM_BASE%/}/healthz" 2>/dev/null); then
:
else
crm_health="unreachable"
fi
local chat_health="unset"
if [[ -n "$USER_CHAT_BASE" ]]; then
if chat_health=$(curl -sS --noproxy '*' "${USER_CHAT_BASE%/}/healthz" 2>/dev/null); then
:
else
chat_health="unreachable"
fi
fi
OUT_PATH="$ARTIFACT_DIR/00-env.json" \
CRM_BASE_PY="$CRM_BASE" \
CRM_HEALTH="$crm_health" \
USER_CHAT_BASE_PY="$USER_CHAT_BASE" \
CHAT_HEALTH="$chat_health" \
HAS_SUBJECT_ID="$USER_SUBJECT_ID" \
HAS_AUTH_TOKEN="$USER_AUTH_TOKEN" \
python3 - <<'PY'
import json, os
out = {
"crm_base": os.environ["CRM_BASE_PY"],
"crm_health": os.environ["CRM_HEALTH"],
"user_chat_base": os.environ["USER_CHAT_BASE_PY"],
"user_chat_health": os.environ["CHAT_HEALTH"],
"has_user_subject_id": bool(os.environ["HAS_SUBJECT_ID"]),
"has_user_auth_token": bool(os.environ["HAS_AUTH_TOKEN"]),
}
with open(os.environ["OUT_PATH"], "w", encoding="utf-8") as fh:
json.dump(out, fh, ensure_ascii=False, indent=2)
PY
if [[ "$crm_health" == "ok" ]]; then ok "CRM healthz=ok"; else warn "CRM healthz=$crm_health"; fi
if [[ -n "$USER_CHAT_BASE" ]]; then info "user chat health=$chat_health"; fi
ok "env summary: $ARTIFACT_DIR/00-env.json"
}
cmd_run() {
cmd_env_check
[[ -n "$USER_CHAT_BASE" ]] || die "USER_CHAT_BASE is required for --run"
if ! build_auth_args >/dev/null; then
die "set USER_SUBJECT_ID or USER_AUTH_TOKEN for /api/keys authentication"
fi
local create_payload create_code key_id plaintext_key masked_preview create_body
create_payload='{"logical_group_id":"gpt-shared","display_name":"acceptance-key","allowed_models":["'"$CHAT_MODEL"'"]}'
curl_json_with_capture POST "${CRM_BASE%/}/api/keys" "$ARTIFACT_DIR/10-create.headers.txt" "$ARTIFACT_DIR/10-create.body.json" "$create_payload" >/dev/null
create_code="$(extract_http_code "$ARTIFACT_DIR/10-create.headers.txt")"
[[ "$create_code" == "201" ]] || die "create key failed: HTTP $create_code"
key_id="$(json_get "$ARTIFACT_DIR/10-create.body.json" 'key.key_id')"
plaintext_key="$(json_get "$ARTIFACT_DIR/10-create.body.json" 'plaintext_key')"
masked_preview="$(json_get "$ARTIFACT_DIR/10-create.body.json" 'key.masked_preview')"
[[ -n "$key_id" && -n "$plaintext_key" ]] || die "create key response missing key_id/plaintext_key"
ok "create key -> HTTP 201, key_id=$key_id"
curl_json_with_capture GET "${CRM_BASE%/}/api/keys" "$ARTIFACT_DIR/11-list.headers.txt" "$ARTIFACT_DIR/11-list.body.json" >/dev/null
[[ "$(extract_http_code "$ARTIFACT_DIR/11-list.headers.txt")" == "200" ]] || die "list keys failed"
ok "list keys -> HTTP 200"
curl_json_with_capture GET "${CRM_BASE%/}/api/keys/${key_id}" "$ARTIFACT_DIR/12-get.headers.txt" "$ARTIFACT_DIR/12-get.body.json" >/dev/null
[[ "$(extract_http_code "$ARTIFACT_DIR/12-get.headers.txt")" == "200" ]] || die "get key failed"
ok "get key -> HTTP 200"
curl_json_with_capture POST "${CRM_BASE%/}/api/keys/${key_id}/reset" "$ARTIFACT_DIR/13-reset.headers.txt" "$ARTIFACT_DIR/13-reset.body.json" '{}' >/dev/null
[[ "$(extract_http_code "$ARTIFACT_DIR/13-reset.headers.txt")" == "200" ]] || die "reset key failed"
plaintext_key="$(json_get "$ARTIFACT_DIR/13-reset.body.json" 'plaintext_key')"
masked_preview="$(json_get "$ARTIFACT_DIR/13-reset.body.json" 'masked_preview')"
[[ -n "$plaintext_key" && -n "$masked_preview" ]] || die "reset response missing plaintext_key/masked_preview"
ok "reset key -> HTTP 200"
local chat_payload chat_code
chat_payload='{"model":"'"$CHAT_MODEL"'","messages":[{"role":"user","content":"ping"}],"max_tokens":16,"temperature":0}'
curl_chat_with_capture "$plaintext_key" "$chat_payload" "$ARTIFACT_DIR/20-chat.headers.txt" "$ARTIFACT_DIR/20-chat.body.json" >/dev/null
chat_code="$(extract_http_code "$ARTIFACT_DIR/20-chat.headers.txt")"
[[ "$chat_code" == "200" ]] || die "user chat failed: HTTP $chat_code"
ok "user chat -> HTTP 200"
python3 - "$ARTIFACT_DIR/99-summary.json" <<PY
import json
summary = {
"crm_base": ${CRM_BASE@Q},
"user_chat_base": ${USER_CHAT_BASE@Q},
"chat_model": ${CHAT_MODEL@Q},
"key_id": ${key_id@Q},
"masked_preview": ${masked_preview@Q},
"create_http": int(${create_code@Q}),
"list_http": 200,
"get_http": 200,
"reset_http": 200,
"chat_http": 200,
"checks": {
"create_returns_plaintext_once": True,
"list_returns_200": True,
"get_returns_200": True,
"reset_returns_new_plaintext": True,
"user_chat_200": True,
}
}
json.dump(summary, open(${ARTIFACT_DIR@Q} + "/99-summary.json", "w"), ensure_ascii=False, indent=2)
PY
cat "$ARTIFACT_DIR/99-summary.json"
}
case "${1:---help}" in
--help|-h)
usage
;;
--env-check)
cmd_env_check
;;
--run)
cmd_run
;;
*)
usage
exit 1
;;
esac