#!/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"