Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

534 lignes
18 KiB

  1. #!/bin/bash
  2. # ============================================
  3. # 余额同步端到端测试脚本
  4. # 仿照真实用户使用场景测试 Master-Slave 余额同步
  5. #
  6. # 用法: ./test_region_sync_e2e.sh
  7. # ============================================
  8. set -euo pipefail
  9. # ==================== 配置 ====================
  10. MASTER="http://172.22.57.85:23000"
  11. SLAVE="http://172.22.57.85:13000"
  12. SYNC_API_KEY="123456789"
  13. MASTER_USER_KEY="sk-LDEPGBf5QJYgralE0cIJ47NGsaN8odTqn3fs5IGGJtOdLKSE:1"
  14. SLAVE_USER_KEY="sk-5zVnpGLzKxN4xeiWTBh8nuIdXajhEcBPlIZN29L0gpLdAwsu:1"
  15. USER_ID=7
  16. MODEL="deepseek-ai/DeepSeek-V3-0324"
  17. SYNC_WAIT_SECONDS=70 # 等待同步的时间(秒),需大于 SyncIntervalSeconds
  18. SYNC_POLL_INTERVAL=5 # 轮询间隔(秒)
  19. CONCURRENT_COUNT=5 # 并发请求数量
  20. DEDUCT_AMOUNT=500 # 直接同步 API 测试的扣费金额
  21. TIMEOUT=60 # curl 超时时间(秒)
  22. # ==================== 颜色定义 ====================
  23. GREEN='\033[0;32m'
  24. RED='\033[0;31m'
  25. YELLOW='\033[1;33m'
  26. BLUE='\033[0;34m'
  27. CYAN='\033[0;36m'
  28. BOLD='\033[1m'
  29. NC='\033[0m'
  30. # ==================== 计数器 ====================
  31. PASS_COUNT=0
  32. FAIL_COUNT=0
  33. SKIP_COUNT=0
  34. TOTAL_TESTS=0
  35. # ==================== 工具函数 ====================
  36. log_pass() {
  37. echo -e " ${GREEN}[PASS]${NC} $1"
  38. ((PASS_COUNT++)) || true
  39. ((TOTAL_TESTS++)) || true
  40. }
  41. log_fail() {
  42. echo -e " ${RED}[FAIL]${NC} $1"
  43. ((FAIL_COUNT++)) || true
  44. ((TOTAL_TESTS++)) || true
  45. }
  46. log_skip() {
  47. echo -e " ${YELLOW}[SKIP]${NC} $1"
  48. ((SKIP_COUNT++)) || true
  49. ((TOTAL_TESTS++)) || true
  50. }
  51. log_info() {
  52. echo -e " ${BLUE}[INFO]${NC} $1"
  53. }
  54. log_warn() {
  55. echo -e " ${YELLOW}[WARN]${NC} $1"
  56. }
  57. print_header() {
  58. echo ""
  59. echo -e "${CYAN}============================================${NC}"
  60. echo -e "${BOLD}$1${NC}"
  61. echo -e "${CYAN}============================================${NC}"
  62. echo ""
  63. }
  64. print_section() {
  65. echo ""
  66. echo -e "${BOLD}--- $1 ---${NC}"
  67. }
  68. # 查询 Master 余额
  69. query_master_balance() {
  70. local user_id=${1:-$USER_ID}
  71. local result=$(curl -s -m $TIMEOUT -X POST "$MASTER/api/internal/sync/quota/query" \
  72. -H "Content-Type: application/json" \
  73. -H "X-Sync-API-Key: $SYNC_API_KEY" \
  74. -H "X-Sync-Node: test-node" \
  75. -d "{\"user_id\": $user_id}")
  76. echo "$result" | grep -o '"quota":[0-9]*' | grep -o '[0-9]*'
  77. }
  78. # 等待同步完成(轮询 Master 余额,直到变化或超时)
  79. wait_for_sync() {
  80. local balance_before=$1
  81. local description=$2
  82. local waited=0
  83. echo -e " ${BLUE}[SYNC]${NC} 等待余额同步到 Master($description)..." >&2
  84. while [ $waited -lt $SYNC_WAIT_SECONDS ]; do
  85. local current=$(query_master_balance)
  86. if [ -n "$current" ] && [ "$current" != "$balance_before" ]; then
  87. echo -e " ${GREEN}[SYNC]${NC} 检测到余额变化(等待了 ${waited}s):$balance_before → $current" >&2
  88. # 只向 stdout 输出纯数字
  89. echo "$current"
  90. return 0
  91. fi
  92. sleep $SYNC_POLL_INTERVAL
  93. waited=$((waited + SYNC_POLL_INTERVAL))
  94. echo -e " ${BLUE}[SYNC]${NC} 已等待 ${waited}s / ${SYNC_WAIT_SECONDS}s" >&2
  95. done
  96. # 超时后再查一次
  97. local final=$(query_master_balance)
  98. echo -e " ${YELLOW}[SYNC]${NC} 等待超时(${SYNC_WAIT_SECONDS}s),最终余额: $final" >&2
  99. echo "${final:-$balance_before}"
  100. }
  101. # 发送聊天请求
  102. send_chat() {
  103. local stream=${1:-false}
  104. local max_tokens=${2:-10}
  105. local prompt=${3:-"Say hello in one word"}
  106. curl -s -m $TIMEOUT --no-buffer -X POST "$SLAVE/v1/chat/completions" \
  107. -H "Authorization: Bearer $SLAVE_USER_KEY" \
  108. -H "Content-Type: application/json" \
  109. -d "{
  110. \"model\": \"$MODEL\",
  111. \"messages\": [{\"role\": \"user\", \"content\": \"$prompt\"}],
  112. \"max_tokens\": $max_tokens,
  113. \"stream\": $stream
  114. }"
  115. }
  116. # ==================== 脚本开始 ====================
  117. print_header "余额同步端到端测试 (真实用户场景)"
  118. echo -e " Master: $MASTER"
  119. echo -e " Slave: $SLAVE"
  120. echo -e " 测试模型: $MODEL"
  121. echo -e " 用户 ID: $USER_ID"
  122. echo -e " 同步等待: ${SYNC_WAIT_SECONDS}s"
  123. echo -e " 并发数量: $CONCURRENT_COUNT"
  124. # ==================== 阶段 0: 环境验证 ====================
  125. print_header "阶段 0: 环境验证"
  126. # T0.1: Master 连通性
  127. print_section "T0.1 Master 同步 API 连通性"
  128. MASTER_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" -X POST "$MASTER/api/internal/sync/quota/query" \
  129. -H "Content-Type: application/json" \
  130. -H "X-Sync-API-Key: $SYNC_API_KEY" \
  131. -H "X-Sync-Node: test-node" \
  132. -d "{\"user_id\": $USER_ID}")
  133. MASTER_HTTP_CODE=$(echo "$MASTER_RESULT" | tail -1)
  134. MASTER_BODY=$(echo "$MASTER_RESULT" | sed '$d')
  135. if [ "$MASTER_HTTP_CODE" = "200" ]; then
  136. log_pass "Master 同步 API 可访问 (HTTP $MASTER_HTTP_CODE)"
  137. log_info "响应: $MASTER_BODY"
  138. else
  139. log_fail "Master 同步 API 不可访问 (HTTP $MASTER_HTTP_CODE)"
  140. log_info "响应: $MASTER_BODY"
  141. echo -e " ${RED}Master 不可访问,无法继续测试${NC}"
  142. exit 1
  143. fi
  144. # T0.2: Slave 连通性
  145. print_section "T0.2 Slave API 连通性"
  146. SLAVE_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" "$SLAVE/v1/models" \
  147. -H "Authorization: Bearer $SLAVE_USER_KEY")
  148. SLAVE_HTTP_CODE=$(echo "$SLAVE_RESULT" | tail -1)
  149. if [ "$SLAVE_HTTP_CODE" = "200" ]; then
  150. log_pass "Slave API 可访问 (HTTP $SLAVE_HTTP_CODE)"
  151. else
  152. log_fail "Slave API 不可访问 (HTTP $SLAVE_HTTP_CODE)"
  153. echo -e " ${RED}Slave 不可访问,无法继续测试${NC}"
  154. exit 1
  155. fi
  156. # ==================== 阶段 1: 基线余额 ====================
  157. print_header "阶段 1: 基线余额"
  158. print_section "T1.1 查询初始余额"
  159. BALANCE_BEFORE=$(query_master_balance)
  160. if [ -n "$BALANCE_BEFORE" ]; then
  161. log_pass "Master 初始余额: $BALANCE_BEFORE"
  162. else
  163. log_fail "无法查询 Master 余额"
  164. exit 1
  165. fi
  166. if [ "$BALANCE_BEFORE" -lt 1000 ]; then
  167. log_warn "余额较低 ($BALANCE_BEFORE),部分测试可能因余额不足而失败"
  168. fi
  169. # ==================== 阶段 2: 非流式聊天调用 ====================
  170. print_header "阶段 2: 非流式聊天调用 (Slave → 上游 → 同步 Master)"
  171. print_section "T2.1 通过 Slave 发起非流式聊天请求"
  172. log_info "模型: $MODEL, max_tokens: 10"
  173. CHAT_RESULT=$(send_chat false 10 "Say hello in one word")
  174. CHAT_HTTP_OK=true
  175. # 检查是否有错误
  176. if echo "$CHAT_RESULT" | grep -q '"error"'; then
  177. ERROR_MSG=$(echo "$CHAT_RESULT" | grep -o '"message":"[^"]*"' | head -1)
  178. log_fail "非流式请求失败: $ERROR_MSG"
  179. CHAT_HTTP_OK=false
  180. elif echo "$CHAT_RESULT" | grep -q '"choices"'; then
  181. log_pass "非流式请求成功"
  182. # 提取使用量
  183. USAGE=$(echo "$CHAT_RESULT" | grep -o '"total_tokens":[0-9]*' | grep -o '[0-9]*')
  184. if [ -n "$USAGE" ]; then
  185. log_info "Token 使用量: $USAGE"
  186. fi
  187. # 提取回复内容(截断显示)
  188. CONTENT=$(echo "$CHAT_RESULT" | grep -o '"content":"[^"]*"' | head -1 | sed 's/"content":"//;s/"$//')
  189. if [ -n "$CONTENT" ]; then
  190. log_info "模型回复: ${CONTENT:0:80}"
  191. fi
  192. else
  193. log_warn "响应格式未知: ${CHAT_RESULT:0:100}"
  194. CHAT_HTTP_OK=false
  195. fi
  196. if [ "$CHAT_HTTP_OK" = true ]; then
  197. print_section "T2.2 等待同步到 Master"
  198. BALANCE_AFTER_NON_STREAM=$(wait_for_sync "$BALANCE_BEFORE" "非流式调用")
  199. print_section "T2.3 验证 Master 余额变化"
  200. if [ "$BALANCE_AFTER_NON_STREAM" != "$BALANCE_BEFORE" ]; then
  201. DELTA=$((BALANCE_BEFORE - BALANCE_AFTER_NON_STREAM))
  202. if [ "$DELTA" -gt 0 ]; then
  203. log_pass "Master 余额已扣减: $BALANCE_BEFORE → $BALANCE_AFTER_NON_STREAM (扣除 $DELTA)"
  204. else
  205. log_fail "Master 余额变化异常: $BALANCE_BEFORE → $BALANCE_AFTER_NON_STREAM (变化 $DELTA)"
  206. fi
  207. else
  208. log_fail "Master 余额未变化,同步可能失败"
  209. fi
  210. else
  211. log_skip "T2.2-T2.3(非流式请求失败)"
  212. BALANCE_AFTER_NON_STREAM=$BALANCE_BEFORE
  213. fi
  214. # ==================== 阶段 3: 流式聊天调用 ====================
  215. print_header "阶段 3: 流式聊天调用 (SSE)"
  216. print_section "T3.1 通过 Slave 发起流式聊天请求"
  217. log_info "模型: $MODEL, stream: true, max_tokens: 20"
  218. STREAM_RESULT=$(curl -s -m $TIMEOUT --no-buffer -X POST "$SLAVE/v1/chat/completions" \
  219. -H "Authorization: Bearer $SLAVE_USER_KEY" \
  220. -H "Content-Type: application/json" \
  221. -d "{
  222. \"model\": \"$MODEL\",
  223. \"messages\": [{\"role\": \"user\", \"content\": \"Count from 1 to 3\"}],
  224. \"max_tokens\": 20,
  225. \"stream\": true
  226. }" 2>&1)
  227. STREAM_OK=true
  228. if echo "$STREAM_RESULT" | grep -q '"error"'; then
  229. ERROR_MSG=$(echo "$STREAM_RESULT" | grep -o '"message":"[^"]*"' | head -1)
  230. log_fail "流式请求失败: $ERROR_MSG"
  231. STREAM_OK=false
  232. elif echo "$STREAM_RESULT" | grep -q 'data:'; then
  233. log_pass "流式请求成功,收到 SSE 数据"
  234. # 验证 SSE 格式
  235. SSE_DATA_LINES=$(echo "$STREAM_RESULT" | grep -c "data:" || echo "0")
  236. HAS_DONE=$(echo "$STREAM_RESULT" | grep -c "data: \[DONE\]" || echo "0")
  237. if [ "$SSE_DATA_LINES" -gt 0 ]; then
  238. log_info "收到 $SSE_DATA_LINES 行 SSE 数据"
  239. fi
  240. if [ "$HAS_DONE" -gt 0 ]; then
  241. log_pass "流式响应包含 [DONE] 标记"
  242. else
  243. log_warn "流式响应未包含 [DONE] 标记"
  244. fi
  245. else
  246. log_warn "响应中无 SSE 数据: ${STREAM_RESULT:0:100}"
  247. STREAM_OK=false
  248. fi
  249. if [ "$STREAM_OK" = true ]; then
  250. print_section "T3.2 等待同步到 Master"
  251. BALANCE_BEFORE_STREAM=$BALANCE_AFTER_NON_STREAM
  252. BALANCE_AFTER_STREAM=$(wait_for_sync "$BALANCE_BEFORE_STREAM" "流式调用")
  253. print_section "T3.3 验证 Master 余额变化"
  254. if [ "$BALANCE_AFTER_STREAM" != "$BALANCE_BEFORE_STREAM" ]; then
  255. DELTA=$((BALANCE_BEFORE_STREAM - BALANCE_AFTER_STREAM))
  256. if [ "$DELTA" -gt 0 ]; then
  257. log_pass "流式调用同步成功: $BALANCE_BEFORE_STREAM → $BALANCE_AFTER_STREAM (扣除 $DELTA)"
  258. else
  259. log_fail "流式调用余额变化异常: $DELTA"
  260. fi
  261. else
  262. log_warn "流式调用后 Master 余额未变化(可能 token 消耗为 0)"
  263. fi
  264. else
  265. log_skip "T3.2-T3.3(流式请求失败)"
  266. BALANCE_AFTER_STREAM=$BALANCE_AFTER_NON_STREAM
  267. fi
  268. # ==================== 阶段 4: 并发请求 ====================
  269. print_header "阶段 4: 并发请求 ($CONCURRENT_COUNT 个)"
  270. print_section "T4.1 并发发送 $CONCURRENT_COUNT 个聊天请求"
  271. BALANCE_BEFORE_CONCURRENT=$BALANCE_AFTER_STREAM
  272. # 准备不同的 prompt 以避免缓存
  273. PROMPTS=("What is 1+1?" "Tell me a color" "Say yes or no" "What day is it?" "Name a fruit"
  274. "What is 2+2?" "Say goodbye" "What is water?" "Name an animal" "Say OK")
  275. PIDS=()
  276. for i in $(seq 1 $CONCURRENT_COUNT); do
  277. PROMPT="${PROMPTS[$((i - 1))]}"
  278. send_chat false 10 "$PROMPT" > "/tmp/sync_test_concurrent_$i.json" 2>&1 &
  279. PIDS+=($!)
  280. done
  281. log_info "已发送 $CONCURRENT_COUNT 个并发请求,等待完成..."
  282. SUCCESS_COUNT=0
  283. FAIL_COUNT_CONCURRENT=0
  284. for pid in "${PIDS[@]}"; do
  285. if wait "$pid"; then
  286. ((SUCCESS_COUNT++)) || true
  287. else
  288. ((FAIL_COUNT_CONCURRENT++)) || true
  289. fi
  290. done
  291. if [ "$FAIL_COUNT_CONCURRENT" -eq 0 ]; then
  292. log_pass "全部 $CONCURRENT_COUNT 个请求完成"
  293. else
  294. log_warn "$SUCCESS_COUNT 成功, $FAIL_COUNT_CONCURRENT 失败"
  295. fi
  296. # 检查响应
  297. print_section "T4.2 验证并发请求响应"
  298. VALID_RESPONSES=0
  299. for i in $(seq 1 $CONCURRENT_COUNT); do
  300. if [ -f "/tmp/sync_test_concurrent_$i.json" ]; then
  301. if grep -q '"choices"' "/tmp/sync_test_concurrent_$i.json"; then
  302. ((VALID_RESPONSES++)) || true
  303. fi
  304. fi
  305. done
  306. log_info "有效响应: $VALID_RESPONSES / $CONCURRENT_COUNT"
  307. if [ "$VALID_RESPONSES" -gt 0 ]; then
  308. print_section "T4.3 等待同步到 Master"
  309. BALANCE_AFTER_CONCURRENT=$(wait_for_sync "$BALANCE_BEFORE_CONCURRENT" "并发调用")
  310. print_section "T4.4 验证 Master 余额变化"
  311. if [ "$BALANCE_AFTER_CONCURRENT" != "$BALANCE_BEFORE_CONCURRENT" ]; then
  312. DELTA=$((BALANCE_BEFORE_CONCURRENT - BALANCE_AFTER_CONCURRENT))
  313. if [ "$DELTA" -gt 0 ]; then
  314. log_pass "并发调用同步成功: $BALANCE_BEFORE_CONCURRENT → $BALANCE_AFTER_CONCURRENT (共扣除 $DELTA)"
  315. else
  316. log_fail "并发调用余额变化异常: $DELTA"
  317. fi
  318. else
  319. log_warn "并发调用后 Master 余额未变化"
  320. fi
  321. else
  322. log_skip "T4.3-T4.4(无有效并发响应)"
  323. BALANCE_AFTER_CONCURRENT=$BALANCE_BEFORE_CONCURRENT
  324. fi
  325. # 清理临时文件
  326. rm -f /tmp/sync_test_concurrent_*.json
  327. # ==================== 阶段 5: 错误场景 ====================
  328. print_header "阶段 5: 错误场景测试"
  329. # T5.1: 错误模型名
  330. print_section "T5.1 错误模型名"
  331. ERR_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" -X POST "$SLAVE/v1/chat/completions" \
  332. -H "Authorization: Bearer $SLAVE_USER_KEY" \
  333. -H "Content-Type: application/json" \
  334. -d '{"model":"nonexistent-model-xyz-12345","messages":[{"role":"user","content":"hi"}],"max_tokens":5}')
  335. ERR_HTTP=$(echo "$ERR_RESULT" | tail -1)
  336. ERR_BODY=$(echo "$ERR_RESULT" | sed '$d')
  337. if [ "$ERR_HTTP" != "200" ]; then
  338. log_pass "错误模型名被拒绝 (HTTP $ERR_HTTP)"
  339. else
  340. log_fail "错误模型名未被拒绝 (HTTP $ERR_HTTP)"
  341. fi
  342. log_info "响应: ${ERR_BODY:0:100}"
  343. # T5.2: 无认证
  344. print_section "T5.2 无认证请求"
  345. NO_AUTH_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" -X POST "$SLAVE/v1/chat/completions" \
  346. -H "Content-Type: application/json" \
  347. -d '{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"hi"}],"max_tokens":5}')
  348. NO_AUTH_HTTP=$(echo "$NO_AUTH_RESULT" | tail -1)
  349. if [ "$NO_AUTH_HTTP" != "200" ]; then
  350. log_pass "无认证请求被拒绝 (HTTP $NO_AUTH_HTTP)"
  351. else
  352. log_fail "无认证请求未被拒绝 (HTTP $NO_AUTH_HTTP)"
  353. fi
  354. # T5.3: 错误 API Key
  355. print_section "T5.3 错误 API Key"
  356. BAD_KEY_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" -X POST "$SLAVE/v1/chat/completions" \
  357. -H "Authorization: Bearer sk-invalid-key-12345" \
  358. -H "Content-Type: application/json" \
  359. -d '{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"hi"}],"max_tokens":5}')
  360. BAD_KEY_HTTP=$(echo "$BAD_KEY_RESULT" | tail -1)
  361. if [ "$BAD_KEY_HTTP" != "200" ]; then
  362. log_pass "错误 API Key 被拒绝 (HTTP $BAD_KEY_HTTP)"
  363. else
  364. log_fail "错误 API Key 未被拒绝 (HTTP $BAD_KEY_HTTP)"
  365. fi
  366. # T5.4: 空消息
  367. print_section "T5.4 空消息列表"
  368. EMPTY_MSG_RESULT=$(curl -s -m $TIMEOUT -w "\n%{http_code}" -X POST "$SLAVE/v1/chat/completions" \
  369. -H "Authorization: Bearer $SLAVE_USER_KEY" \
  370. -H "Content-Type: application/json" \
  371. -d '{"model":"gpt-3.5-turbo","messages":[],"max_tokens":5}')
  372. EMPTY_MSG_HTTP=$(echo "$EMPTY_MSG_RESULT" | tail -1)
  373. if [ "$EMPTY_MSG_HTTP" != "200" ]; then
  374. log_pass "空消息被拒绝 (HTTP $EMPTY_MSG_HTTP)"
  375. else
  376. log_warn "空消息未被拒绝 (HTTP $EMPTY_MSG_HTTP) — 某些实现可能允许"
  377. fi
  378. # ==================== 阶段 6: 直接同步 API 测试 ====================
  379. print_header "阶段 6: 直接同步 API 测试"
  380. BALANCE_BEFORE_DIRECT=$(query_master_balance)
  381. log_info "直接测试前 Master 余额: $BALANCE_BEFORE_DIRECT"
  382. # T6.1: 批量扣费
  383. print_section "T6.1 批量扣费 ($DEDUCT_AMOUNT)"
  384. REQUEST_ID="req-e2e-$(date +%s%N)"
  385. DEDUCT_RESULT=$(curl -s -m $TIMEOUT -X POST "$MASTER/api/internal/sync/quota/batch-deduct" \
  386. -H "Content-Type: application/json" \
  387. -H "X-Sync-API-Key: $SYNC_API_KEY" \
  388. -H "X-Sync-Node: test-node" \
  389. -d "{\"records\": [{\"user_id\": $USER_ID, \"request_id\": \"$REQUEST_ID\", \"quota\": $DEDUCT_AMOUNT}]}")
  390. log_info "响应: $DEDUCT_RESULT"
  391. if echo "$DEDUCT_RESULT" | grep -qi "success\|true\|200"; then
  392. log_pass "批量扣费请求成功"
  393. else
  394. log_fail "批量扣费请求失败: $DEDUCT_RESULT"
  395. fi
  396. # T6.2: 幂等性测试(相同 request_id)
  397. print_section "T6.2 幂等性测试(相同 request_id)"
  398. IDEMPOTENT_RESULT=$(curl -s -m $TIMEOUT -X POST "$MASTER/api/internal/sync/quota/batch-deduct" \
  399. -H "Content-Type: application/json" \
  400. -H "X-Sync-API-Key: $SYNC_API_KEY" \
  401. -H "X-Sync-Node: test-node" \
  402. -d "{\"records\": [{\"user_id\": $USER_ID, \"request_id\": \"$REQUEST_ID\", \"quota\": $DEDUCT_AMOUNT}]}")
  403. log_info "响应: $IDEMPOTENT_RESULT"
  404. BALANCE_AFTER_FIRST=$(query_master_balance)
  405. log_info "幂等测试后余额: $BALANCE_AFTER_FIRST"
  406. # 再查一次确认没变
  407. sleep 1
  408. BALANCE_AFTER_IDEMPOTENT=$(query_master_balance)
  409. if [ "$BALANCE_AFTER_IDEMPOTENT" = "$BALANCE_AFTER_FIRST" ]; then
  410. log_pass "幂等性验证通过:重复请求未重复扣费"
  411. else
  412. log_fail "幂等性验证失败:余额发生了变化 ($BALANCE_AFTER_FIRST → $BALANCE_AFTER_IDEMPOTENT)"
  413. fi
  414. # T6.3: 精确金额验证
  415. print_section "T6.3 精确扣费金额验证"
  416. log_info "扣费前: $BALANCE_BEFORE_DIRECT"
  417. log_info "扣费后: $BALANCE_AFTER_IDEMPOTENT"
  418. ACTUAL_DEDUCT=$((BALANCE_BEFORE_DIRECT - BALANCE_AFTER_IDEMPOTENT))
  419. if [ "$ACTUAL_DEDUCT" -eq $DEDUCT_AMOUNT ]; then
  420. log_pass "精确金额验证通过:扣除了 $ACTUAL_DEDUCT (期望 $DEDUCT_AMOUNT)"
  421. else
  422. log_fail "精确金额验证失败:扣除了 $ACTUAL_DEDUCT (期望 $DEDUCT_AMOUNT)"
  423. fi
  424. # ==================== 阶段 7: 总结报告 ====================
  425. print_header "测试总结"
  426. FINAL_BALANCE=$(query_master_balance)
  427. echo -e " ${BOLD}测试统计:${NC}"
  428. echo -e " ${GREEN}PASS${NC}: $PASS_COUNT"
  429. echo -e " ${RED}FAIL${NC}: $FAIL_COUNT"
  430. echo -e " ${YELLOW}SKIP${NC}: $SKIP_COUNT"
  431. echo -e " 总计: $TOTAL_TESTS"
  432. echo ""
  433. echo -e " ${BOLD}余额变化:${NC}"
  434. echo -e " 初始余额: $BALANCE_BEFORE"
  435. echo -e " 最终余额: $FINAL_BALANCE"
  436. echo -e " 总扣除: $((BALANCE_BEFORE - FINAL_BALANCE))"
  437. echo ""
  438. echo -e " 测试模型: $MODEL"
  439. echo ""
  440. if [ "$FAIL_COUNT" -eq 0 ]; then
  441. echo -e " ${GREEN}${BOLD}全部测试通过!${NC}"
  442. else
  443. echo -e " ${RED}${BOLD}存在 $FAIL_COUNT 个失败${NC}"
  444. fi
  445. echo ""
  446. echo -e "${CYAN}============================================${NC}"
  447. # 返回退出码
  448. if [ "$FAIL_COUNT" -gt 0 ]; then
  449. exit 1
  450. fi
  451. exit 0