fix(provision): replace duplicate accounts before closure probe
This commit is contained in:
137
scripts/check_deepseek_completion_split.sh
Executable file
137
scripts/check_deepseek_completion_split.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
require_var() {
|
||||
local name="$1"
|
||||
if [[ -z "${!name:-}" ]]; then
|
||||
echo "missing required env: $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
json_has_model() {
|
||||
local file="$1"
|
||||
local model="$2"
|
||||
python3 - "$file" "$model" <<'PY'
|
||||
import json, pathlib, sys
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
model = sys.argv[2].strip()
|
||||
obj = json.loads(path.read_text(encoding='utf-8'))
|
||||
for item in obj.get('data', []):
|
||||
if str(item.get('id', '')).strip() == model:
|
||||
print('true')
|
||||
raise SystemExit(0)
|
||||
print('false')
|
||||
PY
|
||||
}
|
||||
|
||||
status_from_headers() {
|
||||
local file="$1"
|
||||
python3 - "$file" <<'PY'
|
||||
import pathlib, re, sys
|
||||
text = pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')
|
||||
for line in text.splitlines():
|
||||
m = re.match(r'^HTTP/\S+\s+(\d{3})\b', line.strip())
|
||||
if m:
|
||||
print(m.group(1))
|
||||
raise SystemExit(0)
|
||||
print('0')
|
||||
PY
|
||||
}
|
||||
|
||||
content_type_from_headers() {
|
||||
local file="$1"
|
||||
python3 - "$file" <<'PY'
|
||||
import pathlib, sys
|
||||
text = pathlib.Path(sys.argv[1]).read_text(encoding='utf-8')
|
||||
for line in text.splitlines():
|
||||
if ':' not in line:
|
||||
continue
|
||||
k, v = line.split(':', 1)
|
||||
if k.strip().lower() == 'content-type':
|
||||
print(v.strip())
|
||||
raise SystemExit(0)
|
||||
print('')
|
||||
PY
|
||||
}
|
||||
|
||||
require_var ARTIFACT_DIR
|
||||
require_var HOST_BASE
|
||||
require_var HOST_MANAGED_KEY
|
||||
require_var UPSTREAM_BASE
|
||||
require_var UPSTREAM_API_KEY
|
||||
|
||||
MODEL="${MODEL:-deepseek-v4-flash}"
|
||||
PROMPT="${PROMPT:-ping}"
|
||||
ARTIFACT_DIR="${ARTIFACT_DIR%/}"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
|
||||
host_models_headers="$ARTIFACT_DIR/01-host-models.headers.txt"
|
||||
host_models_body="$ARTIFACT_DIR/02-host-models.body.json"
|
||||
host_chat_headers="$ARTIFACT_DIR/03-host-chat.headers.txt"
|
||||
host_chat_body="$ARTIFACT_DIR/04-host-chat.body.json"
|
||||
upstream_chat_headers="$ARTIFACT_DIR/05-upstream-chat.headers.txt"
|
||||
upstream_chat_body="$ARTIFACT_DIR/06-upstream-chat.body.txt"
|
||||
summary_file="$ARTIFACT_DIR/summary.json"
|
||||
|
||||
chat_payload="$(python3 - "$MODEL" "$PROMPT" <<'PY'
|
||||
import json, sys
|
||||
print(json.dumps({
|
||||
'model': sys.argv[1],
|
||||
'messages': [{'role': 'user', 'content': sys.argv[2]}],
|
||||
'max_tokens': 8,
|
||||
'temperature': 0,
|
||||
}, ensure_ascii=False))
|
||||
PY
|
||||
)"
|
||||
|
||||
curl -sS -D "$host_models_headers" -o "$host_models_body" \
|
||||
-H "Authorization: Bearer $HOST_MANAGED_KEY" \
|
||||
"${HOST_BASE%/}/v1/models"
|
||||
|
||||
curl -sS -D "$host_chat_headers" -o "$host_chat_body" \
|
||||
-H "Authorization: Bearer $HOST_MANAGED_KEY" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${HOST_BASE%/}/v1/chat/completions" \
|
||||
-d "$chat_payload"
|
||||
|
||||
curl -sS -D "$upstream_chat_headers" -o "$upstream_chat_body" \
|
||||
-H "Authorization: Bearer $UPSTREAM_API_KEY" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${UPSTREAM_BASE%/}/chat/completions" \
|
||||
-d "$chat_payload"
|
||||
|
||||
host_models_status="$(status_from_headers "$host_models_headers")"
|
||||
host_chat_status="$(status_from_headers "$host_chat_headers")"
|
||||
upstream_chat_status="$(status_from_headers "$upstream_chat_headers")"
|
||||
host_has_expected_model="$(json_has_model "$host_models_body" "$MODEL")"
|
||||
upstream_content_type="$(content_type_from_headers "$upstream_chat_headers")"
|
||||
|
||||
python3 - "$summary_file" "$host_models_status" "$host_has_expected_model" "$host_chat_status" "$upstream_chat_status" "$upstream_content_type" "$host_chat_body" "$upstream_chat_body" <<'PY'
|
||||
import json, pathlib, sys
|
||||
summary_path = pathlib.Path(sys.argv[1])
|
||||
host_models_status = int(sys.argv[2])
|
||||
host_has_expected_model = sys.argv[3].strip().lower() == 'true'
|
||||
host_chat_status = int(sys.argv[4])
|
||||
upstream_chat_status = int(sys.argv[5])
|
||||
upstream_content_type = sys.argv[6].strip()
|
||||
host_chat_body = pathlib.Path(sys.argv[7]).read_text(encoding='utf-8').strip()
|
||||
upstream_chat_body = pathlib.Path(sys.argv[8]).read_text(encoding='utf-8').strip()
|
||||
classification = 'unknown'
|
||||
if host_models_status == 200 and host_has_expected_model and host_chat_status == 502 and upstream_chat_status == 200:
|
||||
classification = 'host_compatibility_gap'
|
||||
elif host_models_status == 200 and host_has_expected_model and upstream_chat_status == 403 and 'insufficient_user_quota' in upstream_chat_body:
|
||||
classification = 'upstream_key_quota_issue'
|
||||
summary = {
|
||||
'host_models_status': host_models_status,
|
||||
'host_has_expected_model': host_has_expected_model,
|
||||
'host_chat_status': host_chat_status,
|
||||
'upstream_chat_status': upstream_chat_status,
|
||||
'upstream_chat_content_type': upstream_content_type,
|
||||
'classification': classification,
|
||||
'host_chat_body': host_chat_body,
|
||||
'upstream_chat_body_preview': upstream_chat_body[:400],
|
||||
}
|
||||
summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||
PY
|
||||
@@ -46,12 +46,14 @@ run_test_build_subscription_access_prep_sql() {
|
||||
}
|
||||
|
||||
run_test_real_host_acceptance_after_import_hook() {
|
||||
local tmpdir fakebin artifact_dir hook_file
|
||||
local tmpdir fakebin artifact_dir hook_file guide_file stdout_file
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' RETURN
|
||||
fakebin="$tmpdir/bin"
|
||||
artifact_dir="$tmpdir/artifacts"
|
||||
hook_file="$artifact_dir/hook.txt"
|
||||
guide_file="$artifact_dir/00-artifact-guide.txt"
|
||||
stdout_file="$tmpdir/real_host_acceptance.stdout.txt"
|
||||
mkdir -p "$fakebin"
|
||||
|
||||
cat > "$fakebin/curl" <<'EOF'
|
||||
@@ -133,13 +135,111 @@ EOF
|
||||
SUBSCRIPTION_USERS="42" \
|
||||
SKIP_ROLLBACK="1" \
|
||||
AFTER_IMPORT_HOOK_COMMAND='printf "%s\n" "$BATCH_ID:$BATCH_DETAIL_FILE:$ACCESS_MODE" > "$ARTIFACT_DIR/hook.txt"' \
|
||||
"$ROOT_DIR/scripts/real_host_acceptance.sh" >/dev/null
|
||||
"$ROOT_DIR/scripts/real_host_acceptance.sh" >"$stdout_file"
|
||||
|
||||
[[ -f "$hook_file" ]] || fail "after-import hook did not create $hook_file"
|
||||
[[ -f "$guide_file" ]] || fail "artifact guide was not created"
|
||||
local hook_contents
|
||||
hook_contents="$(cat "$hook_file")"
|
||||
assert_contains "$hook_contents" "123:"
|
||||
assert_contains "$hook_contents" "05a-batch-detail-pre-access.json:subscription"
|
||||
|
||||
local guide_contents stdout_contents
|
||||
guide_contents="$(cat "$guide_file")"
|
||||
stdout_contents="$(cat "$stdout_file")"
|
||||
assert_contains "$guide_contents" "清单 4(必须分层留证据,不可混用)"
|
||||
assert_contains "$guide_contents" "/api/v1/admin/accounts/:id/models 正确 ≠ /v1/models 正确"
|
||||
assert_contains "$guide_contents" "/v1/models 正确 ≠ /v1/chat/completions 正确"
|
||||
assert_contains "$stdout_contents" "artifact guide: $artifact_dir/00-artifact-guide.txt"
|
||||
assert_contains "$stdout_contents" "checklist layered evidence: see 05b-after-import-hook.stdout.txt / 05b-after-import-hook.stderr.txt"
|
||||
}
|
||||
|
||||
run_test_check_deepseek_completion_split() {
|
||||
local tmpdir fakebin artifact_dir summary_file stdout_file
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' RETURN
|
||||
fakebin="$tmpdir/bin"
|
||||
artifact_dir="$tmpdir/artifacts"
|
||||
summary_file="$artifact_dir/summary.json"
|
||||
stdout_file="$tmpdir/check_deepseek_completion_split.stdout.txt"
|
||||
mkdir -p "$fakebin" "$artifact_dir"
|
||||
|
||||
cat > "$fakebin/curl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
headers_file=""
|
||||
body_file=""
|
||||
url=""
|
||||
prev=""
|
||||
for arg in "$@"; do
|
||||
case "$prev" in
|
||||
-D)
|
||||
headers_file="$arg"
|
||||
prev=""
|
||||
continue
|
||||
;;
|
||||
-o)
|
||||
body_file="$arg"
|
||||
prev=""
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
case "$arg" in
|
||||
-D|-o)
|
||||
prev="$arg"
|
||||
continue
|
||||
;;
|
||||
http://*|https://*)
|
||||
url="$arg"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$headers_file" && -n "$body_file" && -n "$url" ]] || {
|
||||
echo "missing curl capture args: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$url" in
|
||||
http://host.example.com/v1/models)
|
||||
printf '%s
|
||||
Content-Type: application/json
|
||||
' 'HTTP/1.1 200 OK' > "$headers_file"
|
||||
printf '%s
|
||||
' '{"data":[{"id":"deepseek-v4-flash"},{"id":"deepseek-v4-pro"}]}' > "$body_file"
|
||||
;;
|
||||
http://host.example.com/v1/chat/completions)
|
||||
printf '%s
|
||||
Content-Type: application/json
|
||||
' 'HTTP/1.1 502 Bad Gateway' > "$headers_file"
|
||||
printf '%s
|
||||
' '{"error":{"message":"Upstream service temporarily unavailable","type":"upstream_error"}}' > "$body_file"
|
||||
;;
|
||||
https://upstream.example.com/v1/chat/completions)
|
||||
printf '%s
|
||||
Content-Type: text/event-stream
|
||||
' 'HTTP/1.1 200 OK' > "$headers_file"
|
||||
printf '%s
|
||||
' 'data: {"choices":[{"delta":{"content":"pong"}}]}' > "$body_file"
|
||||
;;
|
||||
*)
|
||||
echo "unexpected curl url: $url" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$fakebin/curl"
|
||||
|
||||
PATH="$fakebin:$PATH" ARTIFACT_DIR="$artifact_dir" HOST_BASE="http://host.example.com" HOST_MANAGED_KEY="managed-key" UPSTREAM_BASE="https://upstream.example.com/v1" UPSTREAM_API_KEY="upstream-key" MODEL="deepseek-v4-flash" bash "$ROOT_DIR/scripts/check_deepseek_completion_split.sh" >"$stdout_file"
|
||||
|
||||
[[ -f "$summary_file" ]] || fail "missing summary file: $summary_file"
|
||||
local summary stdout_contents
|
||||
summary="$(cat "$summary_file")"
|
||||
stdout_contents="$(cat "$stdout_file")"
|
||||
assert_contains "$summary" '"classification": "host_compatibility_gap"'
|
||||
assert_contains "$summary" '"host_models_status": 200'
|
||||
assert_contains "$summary" '"host_chat_status": 502'
|
||||
assert_contains "$summary" '"upstream_chat_status": 200'
|
||||
assert_contains "$summary" '"upstream_chat_content_type": "text/event-stream"'
|
||||
assert_contains "$stdout_contents" '"classification": "host_compatibility_gap"'
|
||||
}
|
||||
|
||||
run_test_import_remote43_provider_subscription_prep() {
|
||||
@@ -387,6 +487,7 @@ EOF
|
||||
|
||||
run_test_build_subscription_access_prep_sql
|
||||
run_test_real_host_acceptance_after_import_hook
|
||||
run_test_check_deepseek_completion_split
|
||||
run_test_import_remote43_provider_subscription_prep
|
||||
|
||||
echo "PASS: real host script regression checks"
|
||||
|
||||
Reference in New Issue
Block a user