hi-server/script/family_scenarios_34_quick.sh

336 lines
9.9 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
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
EMAIL_A="${EMAIL_A:-family34_a_$(date +%s)@example.com}"
DEVICE_A="${DEVICE_A:-qa34-device-a-$(date +%s)}"
DEVICE_B="${DEVICE_B:-qa34-device-b-$(date +%s)}"
SEND_CODE_RETRY_WAIT="${SEND_CODE_RETRY_WAIT:-61}"
SEND_CODE_MAX_RETRY="${SEND_CODE_MAX_RETRY:-8}"
VERIFY_CODE_PROVIDER="${VERIFY_CODE_PROVIDER:-}"
VERIFY_CODE_OVERRIDE="${VERIFY_CODE_OVERRIDE:-}"
REGISTER_CODE_OVERRIDE="${REGISTER_CODE_OVERRIDE:-}"
SECURITY_CODE_OVERRIDE="${SECURITY_CODE_OVERRIDE:-}"
ALLOW_INTERACTIVE_INPUT="${ALLOW_INTERACTIVE_INPUT:-auto}"
ENABLE_REDIS_CODE_FALLBACK="${ENABLE_REDIS_CODE_FALLBACK:-1}"
REDIS_CLI_BIN="${REDIS_CLI_BIN:-redis-cli}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_DB="${REDIS_DB:-0}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
BIND_VERIFY_RETRY="${BIND_VERIFY_RETRY:-1}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
fail() { printf "[FAIL] %s\n" "$*" >&2; exit 1; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
json_code() { jq -r '.code // ""' <<<"$1"; }
json_msg() { jq -r '.msg // ""' <<<"$1"; }
can_prompt_input() {
if [[ "$ALLOW_INTERACTIVE_INPUT" == "1" ]]; then
return 0
fi
if [[ "$ALLOW_INTERACTIVE_INPUT" == "0" ]]; then
return 1
fi
[[ -t 0 ]]
}
redis_get_key_raw() {
local key="$1"
command -v "$REDIS_CLI_BIN" >/dev/null 2>&1 || return 1
local -a args
args=("$REDIS_CLI_BIN" -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw)
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD")
fi
"${args[@]}" GET "$key" 2>/dev/null || return 1
}
get_verify_code_from_redis() {
local email="$1"
local typ="$2"
local scene key raw code
if [[ "$ENABLE_REDIS_CODE_FALLBACK" != "1" ]]; then
return 1
fi
case "$typ" in
1) scene="register" ;;
2) scene="security" ;;
*) scene="unknown" ;;
esac
key="auth:verify:email:${scene}:${email}"
raw="$(redis_get_key_raw "$key" || true)"
[[ -n "$raw" ]] || return 1
code="$(jq -r '.code // ""' <<<"$raw" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "null" ]] || return 1
printf "%s" "$code"
}
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local token="${4:-}"
local -a args
args=(-sS -X "$method" "${BASE_URL}${path}" -H "Content-Type: application/json")
if [[ -n "$token" ]]; then
args+=(-H "Authorization: ${token}")
fi
if [[ "$method" != "GET" ]]; then
args+=(-d "$body")
fi
curl "${args[@]}"
}
assert_success() {
local resp="$1"
local step="$2"
local code
code="$(json_code "$resp")"
if [[ "$code" != "200" ]]; then
fail "${step} 失败, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
fi
}
token_or_keep() {
local resp="$1"
local fallback="$2"
local token
token="$(jq -r '.data.token // ""' <<<"$resp")"
if [[ -n "$token" && "$token" != "null" ]]; then
printf "%s" "$token"
else
printf "%s" "$fallback"
fi
}
device_login() {
local identifier="$1"
local user_agent="$2"
local payload resp token
payload="$(jq -nc --arg identifier "$identifier" --arg user_agent "$user_agent" '{identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/device" "$payload")"
assert_success "$resp" "设备登录(${identifier})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "设备登录(${identifier})返回空 token: ${resp}"
printf "%s" "$token"
}
send_code_with_retry() {
local email="$1"
local typ="$2"
local payload resp code retry
payload="$(jq -nc --arg email "$email" --argjson type "$typ" '{email:$email,type:$type}')"
retry=0
while true; do
resp="$(api_request "POST" "/v1/common/send_code" "$payload")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$code" == "401" ]]; then
retry=$((retry + 1))
if (( retry > SEND_CODE_MAX_RETRY )); then
fail "发送验证码达到重试上限, email=${email}, type=${typ}, resp=${resp}"
fi
warn "验证码发送频率限制,等待 ${SEND_CODE_RETRY_WAIT}s 后重试(${retry}/${SEND_CODE_MAX_RETRY})..."
sleep "${SEND_CODE_RETRY_WAIT}"
continue
fi
fail "发送验证码失败, email=${email}, type=${typ}, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_verify_code() {
local email="$1"
local typ="$2"
local send_resp code
send_resp="$(send_code_with_retry "$email" "$typ")"
code="$(jq -r '.data.code // ""' <<<"$send_resp")"
if [[ -n "$code" && "$code" != "null" ]]; then
info "验证码已从接口返回(type=${typ})"
printf "%s" "$code"
return 0
fi
code="$(get_verify_code_from_redis "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码已从 Redis 回读(type=${typ})"
printf "%s" "$code"
return 0
fi
if [[ -n "$VERIFY_CODE_PROVIDER" ]]; then
code="$("$VERIFY_CODE_PROVIDER" "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码由 VERIFY_CODE_PROVIDER 提供(type=${typ})"
printf "%s" "$code"
return 0
fi
fi
if [[ "$typ" == "1" && -n "$REGISTER_CODE_OVERRIDE" ]]; then
printf "%s" "$REGISTER_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "2" && -n "$SECURITY_CODE_OVERRIDE" ]]; then
printf "%s" "$SECURITY_CODE_OVERRIDE"
return 0
fi
if [[ -n "$VERIFY_CODE_OVERRIDE" ]]; then
printf "%s" "$VERIFY_CODE_OVERRIDE"
return 0
fi
if ! can_prompt_input; then
fail "验证码不可自动获取(type=${typ}, email=${email})"
fi
read -r -p "请输入邮箱 ${email} 的验证码(type=${typ})" code
[[ -n "$code" ]] || fail "验证码为空"
printf "%s" "$code"
}
bind_email_with_verification() {
local token="$1"
local email="$2"
local code="$3"
local payload
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
api_request "POST" "/v1/public/user/bind_email_with_verification" "$payload" "$token"
}
bind_email_with_verification_auto() {
local token="$1"
local email="$2"
local typ="$3"
local max_retry="${4:-$BIND_VERIFY_RETRY}"
local attempt=0 code resp api_code
while true; do
code="$(get_verify_code "$email" "$typ")"
resp="$(bind_email_with_verification "$token" "$email" "$code")"
api_code="$(json_code "$resp")"
if [[ "$api_code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$api_code" == "70001" && "$attempt" -lt "$max_retry" ]]; then
warn "绑定邮箱验证码校验失败,重新获取验证码重试($((attempt+1))/${max_retry})..."
attempt=$((attempt + 1))
continue
fi
fail "绑定邮箱(${email}) 失败, code=${api_code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_devices() {
local token="$1"
local resp
resp="$(api_request "GET" "/v1/public/user/devices" "" "$token")"
assert_success "$resp" "获取设备列表"
printf "%s" "$resp"
}
find_device_id() {
local devices_resp="$1"
local identifier="$2"
jq -r --arg identifier "$identifier" '.data.list[]? | select(.identifier == $identifier) | .id' <<<"$devices_resp" | head -n1
}
assert_device_exists() {
local devices_resp="$1"
local identifier="$2"
jq -e --arg identifier "$identifier" '.data.list | any(.identifier == $identifier)' <<<"$devices_resp" >/dev/null \
|| fail "设备列表中未找到 identifier=${identifier}, resp=${devices_resp}"
}
unbind_device() {
local token="$1"
local device_id="$2"
local payload resp
payload="$(jq -nc --arg id "$device_id" '{id:($id|tonumber)}')"
resp="$(api_request "PUT" "/v1/public/user/unbind_device" "$payload" "$token")"
assert_success "$resp" "解绑设备(id=${device_id})"
}
assert_token_invalid() {
local token="$1"
local who="$2"
local resp code
resp="$(api_request "GET" "/v1/public/user/info" "" "$token")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
fail "${who} 预期已失效,但 token 仍可访问: ${resp}"
fi
info "${who} token 已失效,符合预期(code=${code}, msg=$(json_msg "$resp"))"
}
main() {
require_cmd curl
require_cmd jq
bold "家庭组快速回归: 场景3/4"
info "BASE_URL=${BASE_URL}"
info "EMAIL_A=${EMAIL_A}"
info "DEVICE_A=${DEVICE_A}"
info "DEVICE_B=${DEVICE_B}"
local token_a token_b token_a2
local bind_resp devices_resp
local device_id_a device_id_b
bold "预置: A设备登录并绑定邮箱A"
token_a="$(device_login "$DEVICE_A" "qa34-device-a")"
bind_resp="$(bind_email_with_verification_auto "$token_a" "$EMAIL_A" 1)"
token_a="$(token_or_keep "$bind_resp" "$token_a")"
bold "场景3: B绑定邮箱A后踢A"
token_b="$(device_login "$DEVICE_B" "qa34-device-b")"
bind_resp="$(bind_email_with_verification_auto "$token_b" "$EMAIL_A" 2)"
token_b="$(token_or_keep "$bind_resp" "$token_b")"
devices_resp="$(get_devices "$token_b")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_a="$(find_device_id "$devices_resp" "$DEVICE_A")"
[[ -n "$device_id_a" ]] || fail "场景3 无法找到设备A ID"
unbind_device "$token_b" "$device_id_a"
assert_token_invalid "$token_a" "设备A(被B踢后)"
info "场景3通过"
bold "场景4: A重登绑定邮箱A后踢B"
token_a2="$(device_login "$DEVICE_A" "qa34-device-a-relogin")"
bind_resp="$(bind_email_with_verification_auto "$token_a2" "$EMAIL_A" 2)"
token_a2="$(token_or_keep "$bind_resp" "$token_a2")"
devices_resp="$(get_devices "$token_a2")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_b="$(find_device_id "$devices_resp" "$DEVICE_B")"
[[ -n "$device_id_b" ]] || fail "场景4 无法找到设备B ID"
unbind_device "$token_a2" "$device_id_b"
assert_token_invalid "$token_b" "设备B(被A踢后)"
info "场景4通过"
bold "场景3/4 快速回归完成 ✅"
}
main "$@"