165 lines
5.4 KiB
Bash
Executable File
165 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
PROJECT_DIR="${1:-/home/long/project/蚊子}"
|
||
MODE="${2:-preflight}"
|
||
|
||
failures=()
|
||
warnings=()
|
||
|
||
fail() {
|
||
failures+=("$*")
|
||
}
|
||
|
||
warn() {
|
||
warnings+=("$*")
|
||
}
|
||
|
||
require_file() {
|
||
local file="$1"
|
||
local hint="$2"
|
||
if [ ! -f "$file" ]; then
|
||
fail "缺少文件: $file($hint)"
|
||
fi
|
||
}
|
||
|
||
require_contains() {
|
||
local file="$1"
|
||
local regex="$2"
|
||
local hint="$3"
|
||
if ! grep -Eq "$regex" "$file"; then
|
||
fail "文件契约不满足: $file($hint)"
|
||
fi
|
||
}
|
||
|
||
require_non_empty_kv() {
|
||
local file="$1"
|
||
local key="$2"
|
||
local hint="$3"
|
||
local line
|
||
line="$(grep -E "^${key}=" "$file" | tail -n1 || true)"
|
||
if [ -z "$line" ]; then
|
||
fail "缺少配置项: ${key}($hint)"
|
||
return
|
||
fi
|
||
local value="${line#*=}"
|
||
if [ -z "${value// /}" ]; then
|
||
fail "配置项为空: ${key}($hint)"
|
||
fi
|
||
}
|
||
|
||
E2E_PROPS="$PROJECT_DIR/src/main/resources/application-e2e.properties"
|
||
TEST_PROPS="$PROJECT_DIR/src/test/resources/application.properties"
|
||
TEST_YML="$PROJECT_DIR/src/main/resources/application-test.yml"
|
||
MIGRATION_DIR="$PROJECT_DIR/src/main/resources/db/migration"
|
||
STATE_DIR="$PROJECT_DIR/logs/e2e-automation"
|
||
|
||
require_file "$E2E_PROPS" "E2E环境配置"
|
||
require_file "$TEST_PROPS" "测试资源配置"
|
||
require_file "$TEST_YML" "test profile配置"
|
||
require_file "$MIGRATION_DIR/V26__Seed_roles_permissions.sql" "权限seed必须存在"
|
||
require_file "$MIGRATION_DIR/V37__Seed_user_roles.sql" "用户角色seed必须存在"
|
||
|
||
if [ -f "$E2E_PROPS" ]; then
|
||
require_contains "$E2E_PROPS" '^spring\.datasource\.url=jdbc:h2:mem:' 'E2E必须使用内存库,避免污染真实环境'
|
||
require_contains "$E2E_PROPS" '^spring\.flyway\.enabled=false' 'E2E依赖JPA自动建表时需关闭Flyway'
|
||
require_contains "$E2E_PROPS" '^mosquito\.security\.csrf\.enabled=false' 'E2E接口测试需要关闭CSRF'
|
||
require_contains "$E2E_PROPS" '^mosquito\.callback\.allow-localhost=true' 'E2E回调需允许localhost'
|
||
require_non_empty_kv "$E2E_PROPS" 'mosquito.security.jwt.secret' '鉴权token签名密钥必填'
|
||
fi
|
||
|
||
if [ -f "$TEST_PROPS" ]; then
|
||
require_contains "$TEST_PROPS" '^spring\.datasource\.url=jdbc:h2:mem:' '单测必须使用H2内存库'
|
||
require_contains "$TEST_PROPS" '^spring\.jpa\.hibernate\.ddl-auto=create-drop' '测试环境要求自动建删表'
|
||
fi
|
||
|
||
if [ -f "$MIGRATION_DIR/V37__Seed_user_roles.sql" ]; then
|
||
require_contains "$MIGRATION_DIR/V37__Seed_user_roles.sql" 'INSERT INTO sys_user_role' '必须初始化用户角色关联,避免鉴权假绿'
|
||
fi
|
||
|
||
if [ -f "$MIGRATION_DIR/V26__Seed_roles_permissions.sql" ]; then
|
||
require_contains "$MIGRATION_DIR/V26__Seed_roles_permissions.sql" 'INSERT INTO sys_role ' '必须初始化角色数据'
|
||
require_contains "$MIGRATION_DIR/V26__Seed_roles_permissions.sql" 'INSERT INTO sys_permission ' '必须初始化权限数据'
|
||
fi
|
||
|
||
if [ -n "${SPRING_PROFILES_ACTIVE:-}" ]; then
|
||
if echo "$SPRING_PROFILES_ACTIVE" | grep -Eqi '(^|,)(prod|production)(,|$)'; then
|
||
fail "检测到危险profile: SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE(禁止在测试链路使用prod)"
|
||
fi
|
||
else
|
||
warn "未设置 SPRING_PROFILES_ACTIVE(建议runner显式设为e2e)"
|
||
fi
|
||
|
||
CLAUDE_BIN_DEFAULT="$HOME/.cursor/extensions/anthropic.claude-code-2.1.15-linux-x64/resources/native-binary/claude"
|
||
CLAUDE_BIN_EFFECTIVE="${CLAUDE_BIN:-$CLAUDE_BIN_DEFAULT}"
|
||
if [ "$MODE" = "runner" ]; then
|
||
if [ ! -x "$CLAUDE_BIN_EFFECTIVE" ]; then
|
||
fail "CLAUDE_BIN不可执行: $CLAUDE_BIN_EFFECTIVE(runner无法产出报告)"
|
||
fi
|
||
fi
|
||
|
||
mkdir -p "$STATE_DIR" 2>/dev/null || fail "无法创建状态目录: $STATE_DIR"
|
||
if [ ! -w "$STATE_DIR" ]; then
|
||
fail "状态目录不可写: $STATE_DIR"
|
||
fi
|
||
|
||
# ============================================================
|
||
# E2E严格断言检查(MOSQ-P1-001)
|
||
# 检查 user-journey*.spec.ts 是否存在宽松断言模式
|
||
# ============================================================
|
||
E2E_TEST_DIR="$PROJECT_DIR/frontend/e2e/tests"
|
||
check_e2e_strict_assertions() {
|
||
local spec_files=("$E2E_TEST_DIR"/user-journey*.spec.ts)
|
||
local found_issues=0
|
||
|
||
for spec_file in "${spec_files[@]}"; do
|
||
if [ ! -f "$spec_file" ]; then
|
||
continue
|
||
fi
|
||
|
||
# 检查是否存在宽松断言:expect([200, 401, 403]) 这种模式
|
||
# 严格模式应该是:expect(status).toBeGreaterThanOrEqual(200) 或类似
|
||
if grep -q 'expect(\[200, 401' "$spec_file" 2>/dev/null; then
|
||
fail "E2E严格断言违规: $spec_file 包含宽松断言 expect([200, 401, 403])"
|
||
found_issues=$((found_issues + 1))
|
||
fi
|
||
|
||
if grep -q 'expect(\[200, 201, 401' "$spec_file" 2>/dev/null; then
|
||
fail "E2E严格断言违规: $spec_file 包含宽松断言 expect([200, 201, 401, 403])"
|
||
found_issues=$((found_issues + 1))
|
||
fi
|
||
|
||
# 检查是否存在 hasRealApiCredentials 函数调用
|
||
if ! grep -q 'hasRealApiCredentials' "$spec_file" 2>/dev/null; then
|
||
warn "E2E建议: $spec_file 未使用 hasRealApiCredentials 函数"
|
||
fi
|
||
|
||
# 检查是否存在 test.skip 保护
|
||
if ! grep -q 'test\.skip' "$spec_file" 2>/dev/null; then
|
||
warn "E2E建议: $spec_file 未使用 test.skip 进行条件跳过"
|
||
fi
|
||
done
|
||
|
||
return $found_issues
|
||
}
|
||
|
||
if [ -d "$E2E_TEST_DIR" ]; then
|
||
check_e2e_strict_assertions || true
|
||
fi
|
||
|
||
if [ ${#warnings[@]} -gt 0 ]; then
|
||
for w in "${warnings[@]}"; do
|
||
echo "[WARN] $w"
|
||
done
|
||
fi
|
||
|
||
if [ ${#failures[@]} -gt 0 ]; then
|
||
echo "[DATA-CONTRACT] FAIL (${#failures[@]}项)"
|
||
for f in "${failures[@]}"; do
|
||
echo " - $f"
|
||
done
|
||
exit 2
|
||
fi
|
||
|
||
echo "[DATA-CONTRACT] PASS mode=$MODE"
|