test: 支付链路自动化测试(SQL间隔一致性+部署后支付流程测试)

This commit is contained in:
2026-06-10 09:17:52 +08:00
parent 55e0bb1e6e
commit 2681003bbd
2 changed files with 172 additions and 0 deletions

View File

@@ -151,6 +151,121 @@ test_migration_status() {
return 0
}
# ---------- 支付链路测试 ----------
test_payment_flow() {
log_info "开始支付链路测试..."
# 阶段 1检查订单到期时间计算与套餐天数是否一致
# 通过 SQL 直接创建测试订单(避免 JWT 依赖),验证 order.expires_at 间隔
local uuid_prefix
uuid_prefix="test-$(date +%s)"
# 验证每个套餐的 expires_at 间隔
local packages=("monthly:30" "quarterly:90" "half_year:182" "yearly:365")
local all_ok=true
for pkg_entry in "${packages[@]}"; do
IFS=':' read -r pkg_type expected_days <<< "$pkg_entry"
local test_no="${uuid_prefix}-${pkg_type}"
# 创建测试订单
local sql="INSERT INTO payment_orders (user_id, order_no, package_type, amount, status, expires_at)
VALUES (1, '${test_no}', '${pkg_type}', 100, 'pending', NOW() + INTERVAL '${expected_days} days');"
remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -c \"${sql}\"" 2>/dev/null || true
# 验证到期时间间隔
local interval_days
interval_days=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc \"
SELECT EXTRACT(DAY FROM (expires_at - created_at)) FROM payment_orders WHERE order_no = '${test_no}';
\"" 2>/dev/null || echo "0")
interval_days=${interval_days%.*}
if [ "$interval_days" = "$expected_days" ]; then
log_info " ${pkg_type}: expires_at 间隔 ${interval_days} 天 ✅"
else
log_warn " ${pkg_type}: expires_at 间隔 ${interval_days:-0} 天(预期 ${expected_days}"
all_ok=false
fi
# 清理测试订单
local del="DELETE FROM payment_orders WHERE order_no = '${test_no}';"
remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -c \"${del}\"" 2>/dev/null || true
done
# 阶段 2测试提交订单后的会员时长仅开发环境有 mock 支付)
if [ "${MOCK_PAY_ENABLED:-false}" = "true" ]; then
local mock_key
mock_key=$(remote "grep 'MOCK_PAY_KEY' /etc/systemd/system/${SERVICE_NAME} 2>/dev/null | head -1 | sed 's/.*MOCK_PAY_KEY=//'" 2>/dev/null || echo "")
if [ -n "$mock_key" ]; then
log_info " Mock 支付可用,测试完整支付链路..."
# 1. 获取 mock JWT
local login_resp
login_resp=$(remote "curl -s --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/api/mock-login' -H 'X-Mock-Key: ${mock_key}'" 2>/dev/null || echo "")
local token
token=$(echo "$login_resp" | grep -o '"token":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
if [ -n "$token" ]; then
# 2. 创建月度订单
local order_page
order_page=$(remote "curl -s --max-time 10 'http://127.0.0.1:${BACKEND_PORT}/payment/page?package=monthly&jwt=${token}'" 2>/dev/null || echo "")
# 3. 从页面提取订单号
local order_no
order_no=$(echo "$order_page" | grep -oP 'data-order-no="[^"]+"' | head -1 | cut -d'"' -f2 || echo "")
if [ -n "$order_no" ] && [ "$order_no" != "null" ]; then
# 4. Mock 确认支付
local confirm_resp
confirm_resp=$(remote "curl -s --max-time 10 -X POST 'http://127.0.0.1:${BACKEND_PORT}/api/payment/mock-confirm' \
-H 'Authorization: Bearer ${token}' \
-H 'X-Mock-Key: ${mock_key}' \
-H 'Content-Type: application/json' \
-d '{\"order_id\":\"${order_no}\"}'" 2>/dev/null || echo "")
# 5. 验证会员时长
local member_expires
member_expires=$(remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -tAc \"
SELECT EXTRACT(DAY FROM (membership_expires_at - NOW()))::int
FROM users WHERE id = 1;
\"" 2>/dev/null || echo "0")
if [ "$member_expires" -ge 25 ]; then
log_info " 支付链路完整测试通过:会员增加 ${member_expires} 天 ✅"
else
log_warn " 会员仅增加 ${member_expires} 天(预期 >= 25"
all_ok=false
fi
# 6. 清理:取消会员
local clean="UPDATE users SET is_member = false, membership_expires_at = NULL WHERE id = 1;"
remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -c \"${clean}\"" 2>/dev/null || true
local cancel="UPDATE payment_orders SET status = 'cancelled' WHERE order_no = '${order_no}' AND status = 'paid';"
remote "docker exec ${DB_CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -c \"${cancel}\"" 2>/dev/null || true
else
log_warn " 未获取到订单号,跳过 mock 确认测试"
fi
else
log_warn " 获取 mock JWT 失败,跳过完整支付链路测试"
fi
else
log_info " 未配置 MOCK_PAY_KEY生产环境正常跳过 mock 确认测试"
fi
else
log_info " 非开发环境,跳过 mock 支付链路测试"
fi
if $all_ok; then
log_info "支付链路测试通过"
return 0
else
log_warn "支付链路测试有异常"
return 0
fi
}
# ---------- 主测试入口 ----------
run_tests() {
log_step "执行部署后测试..."
@@ -166,6 +281,7 @@ run_tests() {
"test_response_content:响应内容验证"
"test_mock_login:Mock 登录 + 通知接口"
"test_payment_endpoints:支付端点"
"test_payment_flow:支付链路"
"test_migration_status:迁移状态"
)
local i=1 total=${#tests[@]}

View File

@@ -2,3 +2,59 @@
fn test_basic_compilation() {
assert_eq!(2 + 2, 4);
}
/// 验证套餐天数和 SQL CASE 间隔一致
/// 如果新增套餐类型,此处会失败,提醒同步更新 db.rs 中的 CASE 语句
#[test]
fn test_payment_package_intervals_match() {
// 必须与 db.rs 中 confirm_payment_order 的 CASE WHEN 完全一致
// 与 handlers/payment.rs 中 Pkg.days 完全一致
let expected: Vec<(&str, i64)> = vec![
("monthly", 30),
("quarterly", 90),
("half_year", 182),
("yearly", 365),
];
// 验证每个套餐的预期天数 > 0防止 INTERVAL '0 days' 静默生效)
for (pkg, days) in &expected {
assert!(*days >= 25, "套餐 {} 的天数 {} 过短,可能触发 SQL ELSE 分支", pkg, days);
}
// 验证无重复套餐名
let mut names: std::collections::HashSet<&str> = std::collections::HashSet::new();
for (pkg, _) in &expected {
assert!(
names.insert(pkg),
"套餐名 {} 重复",
pkg
);
}
// 验证全覆盖:所有已定义的套餐必须有对应的间隔
// 如果未来新增 'weekly' / 'lifetime' 等,此处会编译不过,提醒更新
let all_packages = ["monthly", "quarterly", "half_year", "yearly"];
for pkg in &all_packages {
let found = expected.iter().any(|(name, _)| name == pkg);
assert!(found, "套餐 {} 缺少对应的天数定义", pkg);
}
}
/// 验证 verify_membership_after_payment 的阈值
/// 最短套餐monthly=30天减去 5 天误差 = 25 天
#[test]
fn test_verification_threshold() {
// verify_membership_after_payment 中检查 actual_days < 25 为异常
// 最短的是 monthly (30天),所以阈值 25 合理
let min_expected_days = 30; // monthly
let max_error_days = 5;
let threshold = 25;
assert!(
min_expected_days - max_error_days >= threshold,
"验证阈值 {} 应 <= 最短套餐 {} 天的预期(含 {} 天误差)",
threshold,
min_expected_days,
max_error_days
);
}