Files
asd-backend/lib/test.sh

180 lines
9.8 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.
# ===========================================
# test.sh — 部署后测试(依赖 common.sh 的 remote()
# ===========================================
# 测试失败自动回滚
rollback_and_exit() {
local reason="$1"
log_error "测试失败: ${reason}"
local last_known_good
last_known_good=$(remote "cat ${REMOTE_DIR}/.last_deployed 2>/dev/null || echo ''")
if [ -z "$last_known_good" ]; then
log_error "无法回滚: 未找到上一个正常版本"; exit 1
fi
rollback_on_failure "$last_known_good"
exit 1
}
test_basic_connectivity() {
local max_retries=3 retry=0
while [ $retry -lt $max_retries ]; do
retry=$((retry+1)); log_info "尝试 ${retry}/${max_retries}: 测试本地后端 (127.0.0.1:${BACKEND_PORT})"
local all=true
for test in \
"/health:200" \
"/api/login:400:POST:-H Content-Type:application/json -d '{\"code\":\"test\"}'" \
"/api/refresh-token:401:POST:-H Content-Type:application/json -d '{\"refresh_token\":\"bad\"}'" \
"/:200"; do
IFS=':' read -r path expected method extra <<< "$test"
[ -z "$method" ] && method="GET"
local code; code=$(remote "curl -s -o /dev/null -w '%{http_code}' --max-time 10 -X ${method} http://127.0.0.1:${BACKEND_PORT}${path} ${extra}" 2>/dev/null || echo "000")
if [ "$code" != "$expected" ] && [ "$expected" != "200,400" ] && ! { [ "$expected" = "400,200" ] && { [ "$code" = "400" ] || [ "$code" = "200" ]; }; }; then
if { [ "$expected" = "400" ] && [ "$code" = "200" ]; } || { [ "$expected" = "401" ] && [ "$code" = "200" ]; }; then
log_info " ${path} 正常 (HTTP ${code})"; continue
fi
log_warn " ${path} 异常 (HTTP ${code})"; all=false
else
log_info " ${path} 正常 (HTTP ${code})"
fi
done
$all && { log_info "基础连通性测试通过"; return 0; }
[ $retry -lt $max_retries ] && sleep 2
done
log_error "基础连通性测试失败"; return 1
}
test_database_connection() {
local r; r=$(remote "docker exec ${DB_CONTAINER} pg_isready -U ${DB_USER} 2>/dev/null" || echo "failed")
[[ "$r" =~ "accepting connections" ]] && log_info " PostgreSQL 连接正常" || { log_warn " PostgreSQL 连接失败"; return 1; }
r=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc 'SELECT 1'" 2>/dev/null || echo "failed")
[ "$r" = "1" ] && log_info " 数据库查询正常" || { log_warn " 数据库查询失败"; return 1; }
r=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc 'SELECT tablename FROM pg_tables WHERE schemaname='\''public'\'' LIMIT 1'" 2>/dev/null || echo "failed")
[ -n "$r" ] && log_info " 表存在: ${r}" || { log_warn " 表查询失败"; return 1; }
log_info "数据库连接测试通过"; return 0
}
test_token_flow() {
local login; login=$(remote "curl -s --max-time 10 -X POST http://127.0.0.1:${BACKEND_PORT}/api/login -H 'Content-Type: application/json' -d '{\"code\":\"test\"}'" 2>/dev/null || echo "")
local token; token=$(echo "$login" | grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
[ -z "$token" ] && log_warn " 未获取到 token微信 code 无效是正常的)" || log_info " 获取到 token"
# 测试受保护端点
local profile; profile=$(remote "curl -s --max-time 10 http://127.0.0.1:${BACKEND_PORT}/api/user/profile -H 'Authorization: Bearer ${token}'" 2>/dev/null || echo "")
echo "$profile" | grep -q '"success":true' && log_info " /api/user/profile JWT 验证成功" || log_warn " /api/user/profile 无权限(正常)"
local refresh; refresh=$(remote "curl -s --max-time 10 -X POST http://127.0.0.1:${BACKEND_PORT}/api/refresh-token -H 'Content-Type: application/json' -d '{\"refresh_token\":\"invalid\"}'" 2>/dev/null || echo "")
log_info " refresh_token 端点响应正常"
log_info "Token 流程测试通过"; return 0
}
test_weather_details_jwt() {
local login; login=$(remote "curl -s --max-time 10 -X POST http://127.0.0.1:${BACKEND_PORT}/api/login -H 'Content-Type: application/json' -d '{\"code\":\"test\"}'" 2>/dev/null || echo "")
local token; token=$(echo "$login" | grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
[ -z "$token" ] && { log_warn " 无 token跳过 weather/details 测试(微信 code 无效是正常的)"; return 0; }
local resp; resp=$(remote "curl -s --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/weather/details?id=1' -H 'Authorization: Bearer ${token}'" 2>/dev/null || echo "")
if echo "$resp" | grep -q '"success":true' || echo "$resp" | grep -q "Forbidden"; then
log_info " weather/details JWT 验证通过"
else
log_warn " weather/details 响应异常"
fi
return 0
}
test_invalid_token_401() {
local bad="eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjB9.bad"
local all=true
for path in "/api/user/profile" "/weather/details?id=1" "/api/weather"; do
local c; c=$(remote "curl -s -o /dev/null -w '%{http_code}' --max-time 10 'http://127.0.0.1:${BACKEND_PORT}${path}' -H 'Authorization: Bearer ${bad}'" 2>/dev/null || echo "000")
[ "$c" = "401" ] && log_info " ${path} 返回 401 ✓" || { log_warn " ${path} 返回 ${c}"; all=false; }
done
$all && log_info "无效 Token 401 测试通过" && return 0
return 1
}
test_response_content() {
local r; r=$(remote "curl -s --max-time 10 http://127.0.0.1:${BACKEND_PORT}/health" 2>/dev/null || echo "")
echo "$r" | grep -q '"status"' && echo "$r" | grep -q '"database"' && \
log_info " /health JSON 格式正确 ✓" || { log_warn " /health 响应异常"; return 1; }
echo "$r" | grep -q '"database":"connected"' && \
log_info " 数据库连接状态正常 ✓" || { log_warn " 数据库未连接"; return 1; }
log_info "响应内容验证通过"; return 0
}
# ---------- 新增测试 ----------
test_mock_login() {
if [ "${APP_ENV}" != "development" ]; then
log_info " mock-login 仅在开发环境中测试"
return 0
fi
local key; key=$(remote "grep 'MOCK_LOGIN_KEY' /etc/systemd/system/${SERVICE_NAME} 2>/dev/null | head -1 | sed 's/.*MOCK_LOGIN_KEY=//'" 2>/dev/null || echo "")
[ -z "$key" ] && { log_warn " 未配置 MOCK_LOGIN_KEY跳过"; return 0; }
local resp; resp=$(remote "curl -s --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/api/mock-login' -H 'X-Mock-Key: ${key}'" 2>/dev/null || echo "")
local token; token=$(echo "$resp" | grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
if [ -n "$token" ]; then
log_info " mock-login 返回有效 JWT ✅"
# 用获取到的 token 测试通知接口
local notif; notif=$(remote "curl -s --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/api/notifications/unread-count' -H 'Authorization: Bearer ${token}'" 2>/dev/null || echo "")
echo "$notif" | grep -q '"success":true' && log_info " /api/notifications/unread-count 正常 ✅" || log_warn " 通知接口响应异常"
else
log_warn " mock-login 未返回 token"
fi
return 0
}
test_payment_endpoints() {
local all=true
local c; c=$(remote "curl -s -o /dev/null -w '%{http_code}' --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/payment'" 2>/dev/null || echo "000")
[ "$c" = "200" ] && log_info " /payment 返回 200 ✅" || { log_warn " /payment 返回 ${c}"; all=false; }
c=$(remote "curl -s -o /dev/null -w '%{http_code}' --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/payment/page?package=monthly&jwt=bad'" 2>/dev/null || echo "000")
[ "$c" = "401" ] && log_info " /payment/page (bad JWT) 返回 401 ✅" || { log_warn " /payment/page 返回 ${c}"; all=false; }
c=$(remote "curl -s -o /dev/null -w '%{http_code}' --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/api/user/quota' -H 'Authorization: Bearer bad'" 2>/dev/null || echo "000")
[ "$c" = "401" ] && log_info " /api/user/quota (bad JWT) 返回 401 ✅" || { log_warn " /api/user/quota 返回 ${c}"; all=false; }
$all && log_info "支付端点测试通过" && return 0
log_warn "支付端点测试有异常(部分失败)"; return 0
}
test_migration_status() {
local r; r=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc 'SELECT COUNT(*) FROM _migrations'" 2>/dev/null || echo "0")
[ "$r" -gt "0" ] 2>/dev/null && log_info " _migrations 表存在,已执行 ${r} 个迁移 ✅" || { log_warn " _migrations 表不存在或无记录"; return 0; }
local last; last=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc 'SELECT name FROM _migrations ORDER BY applied_at DESC LIMIT 1'" 2>/dev/null || echo "")
[ -n "$last" ] && log_info " 最新迁移: ${last}"
return 0
}
# ---------- 主测试入口 ----------
run_tests() {
log_step "执行部署后测试..."
[ "$DRY_RUN" = true ] && { log_dry "完整测试套件9 项)"; return 0; }
[ "$SKIP_TESTS" = true ] && { log_warn "跳过测试"; return 0; }
local tests=(
"test_basic_connectivity:基础连通性"
"test_database_connection:数据库连接"
"test_token_flow:Token 流程"
"test_weather_details_jwt:weather/details JWT"
"test_invalid_token_401:无效 Token 401"
"test_response_content:响应内容验证"
"test_mock_login:Mock 登录 + 通知接口"
"test_payment_endpoints:支付端点"
"test_migration_status:迁移状态"
)
local i=1 total=${#tests[@]}
for entry in "${tests[@]}"; do
IFS=':' read -r fn name <<< "$entry"
log_step "测试 ${i}/${total}: ${name}..."
$fn || rollback_and_exit "${name} 测试失败"
i=$((i+1))
done
log_info "========== 全部 ${total} 项测试通过 =========="
}