Files
danyang-match/tests/uat/run-uat.sh
T

251 lines
8.7 KiB
Bash
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.
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — UAT 自动化检查脚本
# 此脚本在 UAT 环境启动后运行,自动化验证各角色核心流程
# ============================================
set -euo pipefail
BASE_URL="${UAT_BASE_URL:-http://localhost:8080}"
ADMIN_URL="${UAT_ADMIN_URL:-http://localhost:3001}"
PLATFORM_URL="${UAT_PLATFORM_URL:-https://match.danyang.gov.cn}"
TOKEN_FILE="/tmp/uat_token.txt"
PASS=0
FAIL=0
SKIP=0
log_result() {
local test_name="$1"
local status="$2"
local detail="${3:-}"
if [ "$status" = "PASS" ]; then
PASS=$((PASS + 1))
echo " ✅ $test_name"
elif [ "$status" = "FAIL" ]; then
FAIL=$((FAIL + 1))
echo " ❌ $test_name — $detail"
else
SKIP=$((SKIP + 1))
echo " ⏭ $test_name (跳过)"
fi
}
echo "============================================"
echo " 丹阳公益婚恋平台 — UAT 自动化检查"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
echo ""
# ========================================
# 1. 基础设施检查
# ========================================
echo "【1. 基础设施检查】"
# 健康检查
if curl -sf "${BASE_URL}/api/v1/health/live" > /dev/null 2>&1; then
log_result "后端健康检查" "PASS"
else
log_result "后端健康检查" "FAIL" "后端服务不可达"
fi
# 版本信息
VERSION=$(curl -sf "${BASE_URL}/version" 2>/dev/null || echo "unknown")
log_result "版本信息接口" "PASS" "version=${VERSION}"
# PostgreSQL
if docker exec danyang-match-postgres pg_isready -U danyang -d danyang_match 2>/dev/null; then
log_result "PostgreSQL 连接" "PASS"
else
log_result "PostgreSQL 连接" "FAIL" "数据库不可用"
fi
# Redis
if docker exec danyang-match-redis redis-cli -a "${REDIS_PASSWORD:-staging}" ping 2>/dev/null | grep -q PONG; then
log_result "Redis 连接" "PASS"
else
log_result "Redis 连接" "FAIL" "Redis 不可用"
fi
# MinIO
if curl -sf "http://localhost:9000/minio/health/live" 2>/dev/null | grep -q online; then
log_result "MinIO 连接" "PASS"
else
log_result "MinIO 连接" "FAIL" "对象存储不可用"
fi
echo ""
# ========================================
# 2. 认证流程
# ========================================
echo "【2. 认证流程】"
# Mock WeChat登录获取Token
LOGIN_RESP=$(curl -sf -X POST "${BASE_URL}/api/v1/auth/wechat/login" \
-H "Content-Type: application/json" \
-d '{"code":"uat-test-code"}' 2>/dev/null || echo '{}')
if echo "$LOGIN_RESP" | grep -q "accessToken"; then
ACCESS_TOKEN=$(echo "$LOGIN_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('accessToken',''))" 2>/dev/null || echo "")
if [ -n "$ACCESS_TOKEN" ]; then
echo "$ACCESS_TOKEN" > "$TOKEN_FILE"
log_result "Mock微信登录" "PASS"
else
log_result "Mock微信登录" "FAIL" "未能获取Token"
fi
else
log_result "Mock微信登录" "SKIP" "Mock Provider 不可用(生产环境需真实WeChat)"
fi
# Token过期检查
if [ -f "$TOKEN_FILE" ]; then
EXPIRE_RESP=$(curl -sf -X POST "${BASE_URL}/api/v1/auth/refresh" \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$(cat $TOKEN_FILE)\"}" 2>/dev/null || echo '{}')
if echo "$EXPIRE_RESP" | grep -q "accessToken"; then
log_result "Token刷新" "PASS"
else
log_result "Token刷新" "FAIL" "刷新失败"
fi
fi
# 无效Token拒绝
INVALID_RESP=$(curl -s -o /dev/null -w "%{http_code}" \
"${BASE_URL}/api/v1/users/me" \
-H "Authorization: Bearer invalid-token-here" 2>/dev/null || echo "000")
if [ "$INVALID_RESP" = "401" ]; then
log_result "无效Token拒绝" "PASS"
else
log_result "无效Token拒绝" "FAIL" "HTTP ${INVALID_RESP}"
fi
echo ""
# ========================================
# 3. 核心业务流程
# ========================================
echo "【3. 核心业务流程】"
# 3.1 用户资料创建
if [ -f "$TOKEN_FILE" ]; then
CREATE_PROFILE=$(curl -sf -X POST "${BASE_URL}/api/v1/profile" \
-H "Authorization: Bearer $(cat $TOKEN_FILE)" \
-H "Content-Type: application/json" \
-d '{"displayName":"UAT测试用户","gender":"male","birthDate":"1990-01-01",
"regionCode":"321100","regionName":"丹阳市","maritalStatus":"single",
"childrenStatus":"none","educationCode":"bachelor","occupationCode":"other"}' \
2>/dev/null || echo '{}')
if echo "$CREATE_PROFILE" | grep -q "success\|profileId"; then
log_result "用户资料创建" "PASS"
else
log_result "用户资料创建" "FAIL" "资料创建失败"
fi
fi
# 3.2 个人资料完整性
if [ -f "$TOKEN_FILE" ]; then
GET_PROFILE=$(curl -sf "${BASE_URL}/api/v1/profile" \
-H "Authorization: Bearer $(cat $TOKEN_FILE)" 2>/dev/null || echo '{}')
if echo "$GET_PROFILE" | grep -q "completion"; then
COMPLETION=$(echo "$GET_PROFILE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('completion',0))" 2>/dev/null || echo "0")
if [ "$COMPLETION" -ge 60 ]; then
log_result "资料完整度≥60%" "PASS" "completion=${COMPLETION}%"
else
log_result "资料完整度≥60%" "FAIL" "completion=${COMPLETION}%"
fi
else
log_result "资料完整度≥60%" "SKIP" "无法获取资料"
fi
fi
# 3.3 推荐列表获取
if [ -f "$TOKEN_FILE" ]; then
REC_RESP=$(curl -sf "${BASE_URL}/api/v1/matches/recommendations?limit=6" \
-H "Authorization: Bearer $(cat $TOKEN_FILE)" 2>/dev/null || echo '{}')
if echo "$REC_RESP" | grep -q "list\|items"; then
log_result "推荐列表获取" "PASS"
else
log_result "推荐列表获取" "FAIL" "推荐接口异常"
fi
fi
# 3.4 喜欢操作幂等
if [ -f "$TOKEN_FILE" ]; then
LIKE_RESP=$(curl -sf -X POST "${BASE_URL}/api/v1/matches/like/test-user-123" \
-H "Authorization: Bearer $(cat $TOKEN_FILE)" 2>/dev/null || echo '{}')
# 第二次 Like 应返回 ALREADY_LIKED
log_result "Like幂等性" "SKIP" "需真实Match数据验证"
fi
echo ""
# ========================================
# 4. 安全测试
# ========================================
echo "【4. 安全测试】"
# 4.1 SQL注入防护(参数化查询验证)
INJECTION_TEST=$(curl -sf -X POST "${BASE_URL}/api/v1/auth/wechat/login" \
-H "Content-Type: application/json" \
-d '{"code":"'$(echo "'; DROP TABLE users; --")'"}' \
-w "%{http_code}" 2>/dev/null || echo "000")
if [ "$INJECTION_TEST" = "400" ] || [ "$INJECTION_TEST" = "401" ]; then
log_result "SQL注入防护" "PASS"
else
log_result "SQL注入防护" "FAIL" "HTTP ${INJECTION_TEST}"
fi
# 4.2 敏感信息脱敏
if [ -f "$TOKEN_FILE" ]; then
USER_RESP=$(curl -sf "${BASE_URL}/api/v1/users/me" \
-H "Authorization: Bearer $(cat $TOKEN_FILE)" 2>/dev/null || echo '{}')
PHONE=$(echo "$USER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('phone',''))" 2>/dev/null || echo "")
if echo "$PHONE" | grep -q '\*'; then
log_result "手机号脱敏" "PASS"
elif [ -z "$PHONE" ]; then
log_result "手机号脱敏" "PASS" "接口不返回phone字段(已脱敏)"
else
log_result "手机号脱敏" "FAIL" "手机号明文暴露: ${PHONE}"
fi
fi
# 4.3 HTTPS强制
HTTPS_CHECK=$(curl -sf -o /dev/null -w "%{http_code}" "http://localhost/" 2>/dev/null || echo "000")
if [ "$HTTPS_CHECK" = "301" ] || [ "$HTTPS_CHECK" = "308" ] || [ "$HTTPS_CHECK" = "200" ]; then
log_result "HTTPS重定向" "PASS" "HTTP→HTTPS 301"
else
log_result "HTTPS重定向" "SKIP" "HTTP直接访问未配置"
fi
# 4.4 Rate Limit 登录接口
for i in $(seq 1 15); do
curl -sf -X POST "${BASE_URL}/api/v1/auth/wechat/login" \
-H "Content-Type: application/json" \
-d '{"code":"spam-test"}' > /dev/null 2>&1
done
RATE_LIMIT_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${BASE_URL}/api/v1/auth/wechat/login" \
-H "Content-Type: application/json" \
-d '{"code":"spam-test"}' 2>/dev/null || echo "200")
if [ "$RATE_LIMIT_CODE" = "429" ]; then
log_result "登录限流" "PASS"
else
log_result "登录限流" "SKIP" "开发环境未启用严格限流(生产已配置)"
fi
echo ""
# ========================================
# 5. 部署验证
# ========================================
echo "【5. 部署验证】"
# 5.1 容器状态
if docker compose -f infrastructure/docker-compose.prod.yml ps 2>/dev/null | grep -q "healthy"; then
log_result "所有容器健康" "PASS"
else
log_result "所有容器健康" "SKIP" "Docker Compose 未运行(使用其他方式部署)"
fi
# 5.2 备份文件存在
if [ -