Files
asd-backend/lib/test.sh
milky0217 f9f1ffeff6 refactor: deploy.sh 拆分为模块化结构
- deploy.sh: 从 1574 行精简至 141 行,仅保留主流程编排
- lib/common.sh: 基础设施(日志/SSH复用/备份/上传/迁移/服务管理)
- lib/test.sh: 6 项部署后测试函数
- 迁移系统改用 _migrations 追踪表替代文件名模式匹配
- 所有 SSH 操作复用 ControlMaster socket
2026-05-18 10:09:40 +08:00

128 lines
6.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.
# ===========================================
# 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
}
run_tests() {
log_step "执行部署后测试..."
[ "$DRY_RUN" = true ] && { log_dry "完整测试套件6 项)"; 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:响应内容验证"
)
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} 项测试通过 =========="
}