diff --git a/scripts/test/redpacket_api_test.sh b/scripts/test/redpacket_api_test.sh new file mode 100755 index 000000000..005d253b7 --- /dev/null +++ b/scripts/test/redpacket_api_test.sh @@ -0,0 +1,284 @@ +#!/usr/bin/env bash +# ============================================================ +# 红包 HTTP 接口测试:create_order / created_callback +# +# 路由(与 internal/api/router.go 一致): +# POST ${HOST}/redpacket/create_order +# POST ${HOST}/redpacket/created_callback +# +# 鉴权:两接口均不在白名单,需在 Header 携带 token(见 protocol/constant constant.Token = "token")。 +# 追踪:Header 需携带 operationID。 +# +# 依赖:curl、jq;自动拉管理员 token 时另需 python3。 +# +# 用法示例: +# chmod +x scripts/test/redpacket_api_test.sh +# GROUP_ID=你的群ID USER_ID=你的用户ID ./scripts/test/redpacket_api_test.sh +# ./scripts/test/redpacket_api_test.sh --host http://127.0.0.1:10002 --group-id xxx --try-callback +# TOKEN=已有用户token GROUP_ID=xxx ./scripts/test/redpacket_api_test.sh --skip-token-smoke +# +# 说明: +# - create_order 在 packetType=0(拼手气固定份)时要求 scopeType=GROUP 且当前用户在该群内。 +# - 若 RPC 侧未配置 EVM chain client,created_callback 可走「离线」路径:传任意非空 txHash, +# 并在 body 中提供与订单一致的 packetID(见 internal/rpc/redpacket resolveCreatedPacket EVM 分支)。 +# - 生产环境若已接链,created_callback 需真实上链交易哈希,此时请自行设置 TX_HASH / PACKET_ID。 +# ============================================================ + +set -euo pipefail + +HOST="${HOST:-http://127.0.0.1:10002}" +USER_ID="${USER_ID:-5694418935}" +PLATFORM_ID="${PLATFORM_ID:-2}" +ADMIN_TOKEN="${ADMIN_TOKEN:-}" +OPENIM_SECRET="${OPENIM_SECRET:-openIM123}" +ADMIN_USER_ID="${ADMIN_USER_ID:-imAdmin}" +TOKEN="${TOKEN:-}" + +GROUP_ID="${GROUP_ID:-}" +CHAIN_TYPE="${CHAIN_TYPE:-EVM}" +CHAIN_ID="${CHAIN_ID:-0}" +SCOPE_TYPE="${SCOPE_TYPE:-GROUP}" +PACKET_TYPE="${PACKET_TYPE:-0}" +CREATOR_WALLET="${CREATOR_WALLET:-0x0000000000000000000000000000000000000001}" +TOKEN_ADDR="${TOKEN_ADDR:-0x0000000000000000000000000000000000000000}" +TOTAL_AMOUNT="${TOTAL_AMOUNT:-100}" +TOTAL_SHARES="${TOTAL_SHARES:-5}" +EXPIRY_AT="${EXPIRY_AT:-0}" +REMARK="${REMARK:-api-test}" + +TRY_CALLBACK="${TRY_CALLBACK:-0}" +TX_HASH="${TX_HASH:-0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}" +CALLBACK_PACKET_ID="${CALLBACK_PACKET_ID:-}" + +SKIP_TOKEN_SMOKE="${SKIP_TOKEN_SMOKE:-0}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) HOST="$2"; shift 2 ;; + --user-id) USER_ID="$2"; shift 2 ;; + --platform-id) PLATFORM_ID="$2"; shift 2 ;; + --group-id) GROUP_ID="$2"; shift 2 ;; + --token) TOKEN="$2"; shift 2 ;; + --try-callback) TRY_CALLBACK="1"; shift ;; + --skip-token-smoke) SKIP_TOKEN_SMOKE="1"; shift ;; + *) + echo "未知参数: $1" + exit 1 + ;; + esac +done + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "缺少依赖命令: $1" + exit 1 + } +} + +need_cmd curl +need_cmd jq + +op_id() { + echo "redpacket-test-$$-$(date +%s%N)" +} + +get_admin_token() { + local uid body resp token last_resp + local -a candidates=("${ADMIN_USER_ID}" "openIM123456" "imAdmin") + last_resp="" + + for uid in "${candidates[@]}"; do + body="{\"secret\":\"${OPENIM_SECRET}\",\"userID\":\"${uid}\"}" + resp="$(curl -sS -X POST "${HOST}/auth/get_admin_token" \ + -H "Content-Type: application/json" \ + -H "operationID: $(op_id)" \ + -d "$body")" + last_resp="$resp" + + token="$(python3 - <<'PY' "$resp" +import json +import sys + +raw = sys.argv[1] +try: + obj = json.loads(raw) +except Exception: + print("") + raise SystemExit(0) + +token = "" +if isinstance(obj, dict): + data = obj.get("data") + if isinstance(data, dict): + token = data.get("token") or data.get("Token") or "" + if not token: + token = obj.get("token") or obj.get("Token") or "" +print(token) +PY +)" + if [[ -n "$token" ]]; then + echo "自动获取管理员 token 成功,userID=${uid}" >&2 + printf '%s' "$token" + return 0 + fi + done + + echo "get_admin_token raw response: $last_resp" >&2 + echo "自动获取管理员 token 失败,请检查 HOST/OPENIM_SECRET/ADMIN_USER_ID 或直接设置 ADMIN_TOKEN" >&2 + exit 1 +} + +resolve_user_token() { + if [[ -n "${TOKEN}" ]]; then + echo "使用环境变量/参数 TOKEN(跳过 get_user_token)" >&2 + return 0 + fi + + need_cmd python3 + + if [[ -z "${ADMIN_TOKEN}" ]]; then + echo "==> ADMIN_TOKEN 未设置,尝试自动获取管理员 token" >&2 + ADMIN_TOKEN="$(get_admin_token)" + fi + + echo "==> 获取用户 token(userID=${USER_ID})" >&2 + local TOKEN_RESP + TOKEN_RESP=$(curl -sS -X POST \ + -H "Content-Type: application/json" \ + -H "operationID: $(op_id)" \ + -H "token: ${ADMIN_TOKEN}" \ + -d "{\"userID\":\"${USER_ID}\",\"platformID\":${PLATFORM_ID}}" \ + "${HOST}/auth/get_user_token") + + local ERR_CODE + ERR_CODE=$(echo "${TOKEN_RESP}" | jq -r '.errCode // "null"') + if [[ "${ERR_CODE}" != "0" ]]; then + echo "获取用户 token 失败: ${TOKEN_RESP}" >&2 + exit 1 + fi + TOKEN=$(echo "${TOKEN_RESP}" | jq -r '.data.token // empty') + if [[ -z "${TOKEN}" ]]; then + echo "token 为空: ${TOKEN_RESP}" >&2 + exit 1 + fi + echo "用户 token 获取成功" >&2 +} + +# ─── 用例:无 token 应被 GinParseToken 拒绝 ───────────────── +if [[ "${SKIP_TOKEN_SMOKE}" != "1" ]]; then + echo "==> 用例:POST /redpacket/create_order 无 token(应返回 errCode != 0)" + NO_TOKEN_RESP=$(curl -sS -X POST \ + -H "Content-Type: application/json" \ + -H "operationID: $(op_id)" \ + -d '{"chainType":"EVM","chainID":1,"groupID":"x","scopeType":"GROUP","packetType":0,"token":"0x0000000000000000000000000000000000000000","totalAmount":"1","totalShares":1,"creatorWallet":"0x0000000000000000000000000000000000000001"}' \ + "${HOST}/redpacket/create_order") + echo "${NO_TOKEN_RESP}" | jq . + NT_ERR=$(echo "${NO_TOKEN_RESP}" | jq -r '.errCode // "null"') + if [[ "${NT_ERR}" == "0" ]]; then + echo "预期无 token 时 errCode != 0,实际 errCode=0" >&2 + exit 1 + fi + echo "无 token 用例通过(errCode=${NT_ERR})" +else + echo "==> 跳过无 token 用例(SKIP_TOKEN_SMOKE=1 或 --skip-token-smoke)" +fi + +if [[ -z "${GROUP_ID}" ]]; then + echo "错误:未设置 GROUP_ID。固定份红包(packetType=0)需要 scopeType=GROUP 且 group_id 非空。" >&2 + echo "示例:GROUP_ID=你的群ID USER_ID=在群内的用户 ./scripts/test/redpacket_api_test.sh" >&2 + exit 1 +fi + +resolve_user_token + +echo "==> POST /redpacket/create_order" +CREATE_BODY=$(jq -n \ + --arg chainType "${CHAIN_TYPE}" \ + --argjson chainID "${CHAIN_ID}" \ + --arg groupID "${GROUP_ID}" \ + --arg scopeType "${SCOPE_TYPE}" \ + --argjson packetType "${PACKET_TYPE}" \ + --arg token "${TOKEN_ADDR}" \ + --arg totalAmount "${TOTAL_AMOUNT}" \ + --argjson totalShares "${TOTAL_SHARES}" \ + --argjson expiryAt "${EXPIRY_AT}" \ + --arg remark "${REMARK}" \ + --arg creatorWallet "${CREATOR_WALLET}" \ + '{ + chainType: $chainType, + chainID: $chainID, + groupID: $groupID, + scopeType: $scopeType, + packetType: $packetType, + token: $token, + totalAmount: $totalAmount, + totalShares: $totalShares, + expiryAt: $expiryAt, + remark: $remark, + creatorWallet: $creatorWallet + }') + +CREATE_RESP=$(curl -sS -X POST \ + -H "Content-Type: application/json" \ + -H "operationID: $(op_id)" \ + -H "token: ${TOKEN}" \ + -d "${CREATE_BODY}" \ + "${HOST}/redpacket/create_order") + +echo "${CREATE_RESP}" | jq . + +CO_ERR=$(echo "${CREATE_RESP}" | jq -r '.errCode // "null"') +if [[ "${CO_ERR}" != "0" ]]; then + echo "create_order 失败(errCode=${CO_ERR})。请确认 USER_ID/TOKEN 对应用户在 GROUP_ID 群内,且 totalAmount 可被 totalShares 整除(固定份)。" >&2 + exit 1 +fi + +BIZ_ID=$(echo "${CREATE_RESP}" | jq -r '.data.bizID // empty') +if [[ -z "${BIZ_ID}" ]]; then + echo "create_order 返回 errCode=0 但 data.bizID 为空: ${CREATE_RESP}" >&2 + exit 1 +fi +echo "create_order 成功,bizID=${BIZ_ID}" + +if [[ "${TRY_CALLBACK}" != "1" ]]; then + echo "==> 未调用 created_callback(设置 TRY_CALLBACK=1 或传入 --try-callback 以继续)" + echo " 离线 EVM:可设置 CALLBACK_PACKET_ID(默认用时间戳十进制字符串);TX_HASH 可用环境变量 TX_HASH 覆盖。" + exit 0 +fi + +if [[ -z "${CALLBACK_PACKET_ID}" ]]; then + CALLBACK_PACKET_ID="$(date +%s)" +fi + +echo "==> POST /redpacket/created_callback(bizID=${BIZ_ID}, packetID=${CALLBACK_PACKET_ID})" +CALLBACK_BODY=$(jq -n \ + --arg bizID "${BIZ_ID}" \ + --arg txHash "${TX_HASH}" \ + --arg packetID "${CALLBACK_PACKET_ID}" \ + --arg groupID "${GROUP_ID}" \ + --arg scopeType "${SCOPE_TYPE}" \ + '{ + bizID: $bizID, + txHash: $txHash, + packetID: $packetID, + groupID: $groupID, + scopeType: $scopeType + }') + +CALLBACK_RESP=$(curl -sS -X POST \ + -H "Content-Type: application/json" \ + -H "operationID: $(op_id)" \ + -H "token: ${TOKEN}" \ + -d "${CALLBACK_BODY}" \ + "${HOST}/redpacket/created_callback") + +echo "${CALLBACK_RESP}" | jq . + +CB_ERR=$(echo "${CALLBACK_RESP}" | jq -r '.errCode // "null"') +if [[ "${CB_ERR}" != "0" ]]; then + echo "created_callback 失败(errCode=${CB_ERR})。若已配置链上客户端,请使用真实交易哈希或关闭 TRY_CALLBACK。" >&2 + exit 1 +fi + +echo "created_callback 成功,红包状态应已更新为 ACTIVE(视部署与链配置而定)。" +echo "测试通过: /redpacket/create_order + /redpacket/created_callback"