#!/usr/bin/env bash # 端到端回归测试:走完整 next-auth OTP 登录 → /api/me → /api/vote 4 种路径 # # 测试用户:全新手机号 13800138000(测试结束 cleanup-test-user.mjs 删除) # dev OTP 万能码:123456 # 实际命中 route handler,不绕过任何中间件。 set -uo pipefail BASE="http://localhost:3000" PHONE="13800138000" CODE="123456" COOKIES=$(mktemp) trap 'rm -f "$COOKIES"' EXIT green() { echo -e "\033[32m$1\033[0m"; } red() { echo -e "\033[31m$1\033[0m"; } yel() { echo -e "\033[33m$1\033[0m"; } # 累计通过 / 失败 PASS=0 FAIL=0 assert_eq() { local name="$1" expected="$2" actual="$3" if [[ "$actual" == "$expected" ]]; then PASS=$((PASS+1)); green " [PASS] $name" else FAIL=$((FAIL+1)); red " [FAIL] $name -- expected=$expected actual=$actual" fi } assert_contains() { local name="$1" needle="$2" haystack="$3" if echo "$haystack" | grep -q "$needle"; then PASS=$((PASS+1)); green " [PASS] $name" else FAIL=$((FAIL+1)); red " [FAIL] $name -- did not find '$needle' in: $haystack" fi } echo "=== 端到端回归测试 ===" echo "测试用户 phone=$PHONE" echo "" # ===== 0. cleanup 任何上次残留的测试数据(测试幂等) ===== yel "[0] cleanup 上次测试残留" node scripts/cleanup-test-user.mjs "$PHONE" 2>&1 | sed 's/^/ /' # ===== 1. 取 CSRF token ===== yel "[1] 获取 CSRF token" CSRF_RAW=$(curl -s -c "$COOKIES" "$BASE/api/auth/csrf") CSRF=$(echo "$CSRF_RAW" | sed -n 's/.*"csrfToken":"\([^"]*\)".*/\1/p') if [[ -z "$CSRF" ]]; then red "无法获取 csrfToken,响应:$CSRF_RAW" exit 1 fi green " csrfToken=${CSRF:0:20}..." # ===== 2. OTP 登录(Credentials provider 路径 /api/auth/callback/phone-otp) ===== yel "[2] OTP 登录 phone=$PHONE code=$CODE" LOGIN_HTTP=$(curl -s -b "$COOKIES" -c "$COOKIES" -o /tmp/login.body -w "%{http_code}" \ -X POST "$BASE/api/auth/callback/phone-otp" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "phone=$PHONE" \ --data-urlencode "code=$CODE" \ --data-urlencode "csrfToken=$CSRF" \ --data-urlencode "redirect=false" \ --data-urlencode "json=true") echo " HTTP $LOGIN_HTTP" # ===== 3. 验证 session 已建立 ===== yel "[3] 验证 session" SESSION=$(curl -s -b "$COOKIES" "$BASE/api/auth/session") echo " session: $SESSION" assert_contains "session 包含 user.id" '"id"' "$SESSION" # ===== 4. GET /api/me 验返回结构 ===== yel "[4] GET /api/me 验证新字段" ME=$(curl -s -b "$COOKIES" "$BASE/api/me") echo " body 摘要: $(echo "$ME" | head -c 300)..." assert_contains "/api/me 返回 ok:true" '"ok":true' "$ME" assert_contains "/api/me 含 voteQuota 字段" '"voteQuota"' "$ME" assert_contains "/api/me voteQuota.total=12" '"total":12' "$ME" assert_contains "/api/me 含 votedArtists 字段" '"votedArtists"' "$ME" if echo "$ME" | grep -q '"dailyQuota"'; then FAIL=$((FAIL+1)); red " [FAIL] /api/me 仍含 dailyQuota 字段(应已被 voteQuota 替换)" else PASS=$((PASS+1)); green " [PASS] /api/me 不再含旧 dailyQuota 字段" fi # ===== 5. POST /api/vote 投未投过的艺人(预期成功) ===== TEST_ARTIST="001" yel "[5] POST /api/vote artistId=$TEST_ARTIST(首次投,预期 200)" V1=$(curl -s -b "$COOKIES" -X POST "$BASE/api/vote" \ -H "Content-Type: application/json" \ -d "{\"artistId\":\"$TEST_ARTIST\"}") echo " body: $V1" assert_contains "首投返回 ok:true" '"ok":true' "$V1" assert_contains "首投返回 totalQuota:12" '"totalQuota":12' "$V1" assert_contains "首投返回 votedCount=1" '"votedCount":1' "$V1" assert_contains "首投返回 remaining=11" '"remaining":11' "$V1" # ===== 6. POST /api/vote 同一艺人(预期 409 ALREADY_VOTED) ===== yel "[6] POST /api/vote artistId=$TEST_ARTIST(重投,预期 409 ALREADY_VOTED)" V2=$(curl -s -b "$COOKIES" -X POST "$BASE/api/vote" \ -H "Content-Type: application/json" \ -d "{\"artistId\":\"$TEST_ARTIST\"}") echo " body: $V2" assert_contains "重投返回 ok:false" '"ok":false' "$V2" assert_contains "重投返回 ALREADY_VOTED" '"code":"ALREADY_VOTED"' "$V2" # ===== 7. POST /api/vote 不存在的艺人(预期 404 NOT_FOUND) ===== yel "[7] POST /api/vote artistId=999(不存在,预期 404)" V3=$(curl -s -b "$COOKIES" -X POST "$BASE/api/vote" \ -H "Content-Type: application/json" \ -d '{"artistId":"999"}') echo " body: $V3" assert_contains "无效艺人返回 ok:false" '"ok":false' "$V3" assert_contains "无效艺人返回 NOT_FOUND" '"code":"NOT_FOUND"' "$V3" # ===== 8. 再次 GET /api/me 验 votedArtists 含新投艺人 ===== yel "[8] GET /api/me 复查 votedArtists" ME2=$(curl -s -b "$COOKIES" "$BASE/api/me") assert_contains "复查 votedArtists 含 $TEST_ARTIST" "\"$TEST_ARTIST\"" "$ME2" assert_contains "复查 voteQuota.used=1" '"used":1' "$ME2" assert_contains "复查 voteQuota.remaining=11" '"remaining":11' "$ME2" # ===== 9. 验证未登录 → 401 ===== yel "[9] 未登录调 /api/vote(预期 401)" V_NOAUTH=$(curl -s -X POST "$BASE/api/vote" \ -H "Content-Type: application/json" \ -d '{"artistId":"001"}') assert_contains "无 session 返回 UNAUTHORIZED" '"code":"UNAUTHORIZED"' "$V_NOAUTH" # ===== 10. 最终 cleanup ===== yel "[10] 测试结束 cleanup" node scripts/cleanup-test-user.mjs "$PHONE" 2>&1 | sed 's/^/ /' # ===== 汇总 ===== echo "" echo "=== 汇总 ===" echo "通过 $PASS · 失败 $FAIL" if [[ $FAIL -gt 0 ]]; then red "回归测试有失败,详见上方" exit 1 else green "全部通过" fi