mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-03-27 10:05:50 +00:00
test: 添加E2E测试基础设施(docker-compose + seed + test)
docker-compose.e2e.yml启动Gitea+assistant;seed.sh自动创建用户/仓库/Webhook/PR;test.sh轮询验证AI评论出现 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
71
docker-compose.e2e.yml
Normal file
71
docker-compose.e2e.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
version: '3.8'
|
||||
|
||||
# E2E 测试环境:Gitea + gitea-assistant
|
||||
# 用法:
|
||||
# docker compose -f docker-compose.e2e.yml up -d
|
||||
# # 等待服务启动后运行 seed 脚本:
|
||||
# ./e2e/seed.sh
|
||||
# # 运行 E2E 测试:
|
||||
# ./e2e/test.sh
|
||||
# # 清理:
|
||||
# docker compose -f docker-compose.e2e.yml down -v
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.22
|
||||
container_name: e2e-gitea
|
||||
environment:
|
||||
- GITEA__database__DB_TYPE=sqlite3
|
||||
- GITEA__server__ROOT_URL=http://gitea:3000
|
||||
- GITEA__server__HTTP_PORT=3000
|
||||
- GITEA__security__INSTALL_LOCK=true
|
||||
- GITEA__service__DISABLE_REGISTRATION=false
|
||||
- GITEA__service__REQUIRE_SIGNIN_VIEW=false
|
||||
- GITEA__webhook__ALLOWED_HOST_LIST=*
|
||||
- GITEA__webhook__SKIP_TLS_VERIFY=true
|
||||
ports:
|
||||
- "3333:3000"
|
||||
volumes:
|
||||
- gitea-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/version"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
|
||||
assistant:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: e2e-assistant
|
||||
depends_on:
|
||||
gitea:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- GITEA_API_URL=http://gitea:3000/api/v1
|
||||
- GITEA_ACCESS_TOKEN=${E2E_GITEA_TOKEN:-placeholder}
|
||||
- OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-test_key}
|
||||
- OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o-mini}
|
||||
- FEISHU_WEBHOOK_URL=http://localhost:9999/noop
|
||||
- PORT=3000
|
||||
- WEBHOOK_SECRET=e2e-test-secret
|
||||
- REVIEW_ENGINE=agent
|
||||
- REVIEW_WORKDIR=/tmp/e2e-review
|
||||
- REVIEW_AUTO_PUBLISH_MIN_CONFIDENCE=0.5
|
||||
- REVIEW_ENABLE_HUMAN_GATE=false
|
||||
- REVIEW_ALLOWED_COMMANDS=git,rg,cat,sed,wc
|
||||
- REVIEW_COMMAND_TIMEOUT_MS=30000
|
||||
ports:
|
||||
- "3334:3000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
|
||||
volumes:
|
||||
gitea-data:
|
||||
175
e2e/seed.sh
Executable file
175
e2e/seed.sh
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# E2E Seed Script
|
||||
# 初始化 Gitea 测试环境:创建用户、生成 Token、创建仓库、推送代码、配置 Webhook、创建 PR
|
||||
#
|
||||
# 前置条件: docker compose -f docker-compose.e2e.yml up -d && 等待 healthy
|
||||
# 产出: 写入 e2e/.env.e2e 供 test.sh 使用
|
||||
|
||||
GITEA_URL="http://localhost:3333"
|
||||
ASSISTANT_URL="http://localhost:3334"
|
||||
ADMIN_USER="e2e-admin"
|
||||
ADMIN_PASS="e2ePassword123!"
|
||||
ADMIN_EMAIL="admin@e2e-test.local"
|
||||
WEBHOOK_SECRET="e2e-test-secret"
|
||||
REPO_NAME="e2e-test-repo"
|
||||
|
||||
echo "=== [1/6] 等待 Gitea 就绪 ==="
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf "${GITEA_URL}/api/v1/version" > /dev/null 2>&1; then
|
||||
echo "Gitea 已就绪"
|
||||
break
|
||||
fi
|
||||
echo " 等待中... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "=== [2/6] 创建管理员用户 ==="
|
||||
docker exec e2e-gitea gitea admin user create \
|
||||
--username "${ADMIN_USER}" \
|
||||
--password "${ADMIN_PASS}" \
|
||||
--email "${ADMIN_EMAIL}" \
|
||||
--admin \
|
||||
--must-change-password=false 2>/dev/null || echo " 用户已存在,跳过"
|
||||
|
||||
echo "=== [3/6] 生成 API Token ==="
|
||||
TOKEN_RESPONSE=$(curl -sf -X POST "${GITEA_URL}/api/v1/users/${ADMIN_USER}/tokens" \
|
||||
-u "${ADMIN_USER}:${ADMIN_PASS}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"e2e-token-$(date +%s)\", \"scopes\": [\"all\"]}" 2>/dev/null || true)
|
||||
|
||||
if [ -z "${TOKEN_RESPONSE}" ]; then
|
||||
echo " Token 创建失败,尝试使用密码认证"
|
||||
GITEA_TOKEN=""
|
||||
else
|
||||
GITEA_TOKEN=$(echo "${TOKEN_RESPONSE}" | grep -o '"sha1":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
if [ -z "${GITEA_TOKEN}" ]; then
|
||||
GITEA_TOKEN=$(echo "${TOKEN_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha1',''))" 2>/dev/null || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${GITEA_TOKEN}" ]; then
|
||||
echo " ERROR: 无法获取 Token"
|
||||
exit 1
|
||||
fi
|
||||
echo " Token: ${GITEA_TOKEN:0:8}..."
|
||||
|
||||
echo "=== [4/6] 创建测试仓库并推送代码 ==="
|
||||
curl -sf -X POST "${GITEA_URL}/api/v1/user/repos" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"${REPO_NAME}\", \"auto_init\": true, \"default_branch\": \"main\"}" > /dev/null 2>&1 || echo " 仓库已存在,跳过"
|
||||
|
||||
CLONE_DIR=$(mktemp -d)
|
||||
trap "rm -rf ${CLONE_DIR}" EXIT
|
||||
|
||||
git clone "http://${ADMIN_USER}:${ADMIN_PASS}@localhost:3333/${ADMIN_USER}/${REPO_NAME}.git" "${CLONE_DIR}/repo" 2>/dev/null
|
||||
|
||||
pushd "${CLONE_DIR}/repo" > /dev/null
|
||||
git config user.email "e2e@test.local"
|
||||
git config user.name "E2E Bot"
|
||||
|
||||
cat > src/auth.ts << 'TSEOF'
|
||||
export function authenticate(token: string): boolean {
|
||||
// 正确的认证实现
|
||||
if (!token || token.length < 10) {
|
||||
return false;
|
||||
}
|
||||
return verifyToken(token);
|
||||
}
|
||||
|
||||
function verifyToken(token: string): boolean {
|
||||
return token.startsWith('valid-');
|
||||
}
|
||||
TSEOF
|
||||
|
||||
mkdir -p src
|
||||
git add -A
|
||||
git commit -m "initial: add auth module" --allow-empty 2>/dev/null || true
|
||||
git push origin main 2>/dev/null || true
|
||||
|
||||
git checkout -b feature/add-user-handler
|
||||
cat > src/user-handler.ts << 'TSEOF'
|
||||
import { authenticate } from './auth';
|
||||
|
||||
export function handleUserRequest(input: any) {
|
||||
// Bug: 没有对 input 做 null 检查
|
||||
const userId = input.userId;
|
||||
|
||||
// Bug: SQL 注入风险
|
||||
const query = `SELECT * FROM users WHERE id = '${userId}'`;
|
||||
|
||||
// Bug: 硬编码密钥
|
||||
const secret = "super-secret-api-key-12345";
|
||||
|
||||
// Bug: 不安全的 eval
|
||||
const config = eval(input.config);
|
||||
|
||||
return { query, config };
|
||||
}
|
||||
TSEOF
|
||||
|
||||
git add -A
|
||||
git commit -m "feat: add user handler"
|
||||
git push origin feature/add-user-handler 2>/dev/null
|
||||
popd > /dev/null
|
||||
|
||||
echo "=== [5/6] 配置 Webhook ==="
|
||||
curl -sf -X POST "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/hooks" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"type\": \"gitea\",
|
||||
\"active\": true,
|
||||
\"events\": [\"pull_request\"],
|
||||
\"config\": {
|
||||
\"url\": \"http://assistant:3000/webhook/gitea\",
|
||||
\"content_type\": \"json\",
|
||||
\"secret\": \"${WEBHOOK_SECRET}\"
|
||||
}
|
||||
}" > /dev/null 2>&1 || echo " Webhook 配置失败(可能已存在)"
|
||||
|
||||
echo "=== [6/6] 创建 Pull Request ==="
|
||||
PR_RESPONSE=$(curl -sf -X POST "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/pulls" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"feat: add user handler\",
|
||||
\"body\": \"Add user request handler with authentication\",
|
||||
\"head\": \"feature/add-user-handler\",
|
||||
\"base\": \"main\"
|
||||
}" 2>/dev/null || true)
|
||||
|
||||
PR_NUMBER=$(echo "${PR_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('number',''))" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "${PR_NUMBER}" ]; then
|
||||
echo " PR 创建失败或已存在,尝试查找现有 PR..."
|
||||
PR_NUMBER=$(curl -sf "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/pulls?state=open" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" | \
|
||||
python3 -c "import sys,json; prs=json.load(sys.stdin); print(prs[0]['number'] if prs else '')" 2>/dev/null || echo "1")
|
||||
fi
|
||||
|
||||
echo " PR #${PR_NUMBER} 已创建"
|
||||
|
||||
cat > e2e/.env.e2e << EOF
|
||||
GITEA_URL=${GITEA_URL}
|
||||
ASSISTANT_URL=${ASSISTANT_URL}
|
||||
GITEA_TOKEN=${GITEA_TOKEN}
|
||||
ADMIN_USER=${ADMIN_USER}
|
||||
REPO_NAME=${REPO_NAME}
|
||||
PR_NUMBER=${PR_NUMBER}
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "=== Seed 完成 ==="
|
||||
echo " Gitea: ${GITEA_URL}"
|
||||
echo " Assistant: ${ASSISTANT_URL}"
|
||||
echo " Repo: ${ADMIN_USER}/${REPO_NAME}"
|
||||
echo " PR: #${PR_NUMBER}"
|
||||
echo " Token: ${GITEA_TOKEN:0:8}..."
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 更新 assistant 容器的 GITEA_ACCESS_TOKEN:"
|
||||
echo " E2E_GITEA_TOKEN=${GITEA_TOKEN} docker compose -f docker-compose.e2e.yml up -d assistant"
|
||||
echo " 2. 运行测试: ./e2e/test.sh"
|
||||
146
e2e/test.sh
Executable file
146
e2e/test.sh
Executable file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# E2E Test Script
|
||||
# 验证 AI 代码审查是否在 PR 上产生了评论
|
||||
#
|
||||
# 前置条件:
|
||||
# 1. docker compose -f docker-compose.e2e.yml up -d
|
||||
# 2. ./e2e/seed.sh
|
||||
# 3. E2E_GITEA_TOKEN=xxx docker compose -f docker-compose.e2e.yml up -d assistant
|
||||
|
||||
ENV_FILE="e2e/.env.e2e"
|
||||
if [ ! -f "${ENV_FILE}" ]; then
|
||||
echo "ERROR: ${ENV_FILE} 不存在,请先运行 ./e2e/seed.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "${ENV_FILE}"
|
||||
|
||||
MAX_WAIT=180 # 最多等待 3 分钟
|
||||
POLL_INTERVAL=5
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
echo "=== E2E 测试开始 ==="
|
||||
echo " Gitea: ${GITEA_URL}"
|
||||
echo " Repo: ${ADMIN_USER}/${REPO_NAME}"
|
||||
echo " PR: #${PR_NUMBER}"
|
||||
echo ""
|
||||
|
||||
# ─── 测试 1: Assistant 服务健康检查 ───
|
||||
echo "[TEST 1] Assistant 服务健康检查"
|
||||
if curl -sf "${ASSISTANT_URL}/" > /dev/null 2>&1; then
|
||||
echo " ✅ PASS: Assistant 服务正常"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo " ❌ FAIL: Assistant 服务不可达"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# ─── 测试 2: Gitea API 可用 ───
|
||||
echo "[TEST 2] Gitea API 可用性"
|
||||
VERSION=$(curl -sf "${GITEA_URL}/api/v1/version" | python3 -c "import sys,json; print(json.load(sys.stdin).get('version','unknown'))" 2>/dev/null || echo "unknown")
|
||||
if [ "${VERSION}" != "unknown" ]; then
|
||||
echo " ✅ PASS: Gitea v${VERSION}"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo " ❌ FAIL: Gitea API 不可用"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# ─── 测试 3: PR 存在 ───
|
||||
echo "[TEST 3] PR 存在性"
|
||||
PR_STATE=$(curl -sf "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/pulls/${PR_NUMBER}" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('state',''))" 2>/dev/null || echo "")
|
||||
|
||||
if [ "${PR_STATE}" = "open" ]; then
|
||||
echo " ✅ PASS: PR #${PR_NUMBER} 状态为 open"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo " ❌ FAIL: PR #${PR_NUMBER} 状态异常 (${PR_STATE})"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# ─── 测试 4: 等待 AI 审查评论出现 ───
|
||||
echo "[TEST 4] AI 审查评论(最多等待 ${MAX_WAIT}s)"
|
||||
COMMENT_FOUND=false
|
||||
WAITED=0
|
||||
|
||||
while [ ${WAITED} -lt ${MAX_WAIT} ]; do
|
||||
COMMENTS=$(curl -sf "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/issues/${PR_NUMBER}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" 2>/dev/null || echo "[]")
|
||||
|
||||
AI_COMMENTS=$(echo "${COMMENTS}" | python3 -c "
|
||||
import sys, json
|
||||
comments = json.load(sys.stdin)
|
||||
ai = [c for c in comments if 'AI' in c.get('body', '') or 'Agent' in c.get('body', '')]
|
||||
print(len(ai))
|
||||
" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "${AI_COMMENTS}" -gt "0" ]; then
|
||||
COMMENT_FOUND=true
|
||||
echo " ✅ PASS: 发现 ${AI_COMMENTS} 条 AI 审查评论 (${WAITED}s)"
|
||||
PASS=$((PASS + 1))
|
||||
break
|
||||
fi
|
||||
|
||||
echo " ⏳ 等待中... (${WAITED}/${MAX_WAIT}s, 已有评论: $(echo "${COMMENTS}" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 0))"
|
||||
sleep ${POLL_INTERVAL}
|
||||
WAITED=$((WAITED + POLL_INTERVAL))
|
||||
done
|
||||
|
||||
if [ "${COMMENT_FOUND}" = false ]; then
|
||||
echo " ❌ FAIL: ${MAX_WAIT}s 内未发现 AI 审查评论"
|
||||
FAIL=$((FAIL + 1))
|
||||
|
||||
echo " --- 调试信息 ---"
|
||||
echo " PR 所有评论:"
|
||||
curl -sf "${GITEA_URL}/api/v1/repos/${ADMIN_USER}/${REPO_NAME}/issues/${PR_NUMBER}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" 2>/dev/null | python3 -m json.tool 2>/dev/null || echo " (无法获取)"
|
||||
|
||||
echo " Assistant review runs:"
|
||||
curl -sf "${ASSISTANT_URL}/admin/api/review/runs" 2>/dev/null | python3 -m json.tool 2>/dev/null || echo " (无法获取)"
|
||||
fi
|
||||
|
||||
# ─── 测试 5: Review Run 状态检查 ───
|
||||
echo "[TEST 5] Review Run 状态"
|
||||
RUNS=$(curl -sf "${ASSISTANT_URL}/admin/api/review/runs" 2>/dev/null || echo "[]")
|
||||
RUN_COUNT=$(echo "${RUNS}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,list) else len(d.get('runs',[])))" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "${RUN_COUNT}" -gt "0" ]; then
|
||||
echo " ✅ PASS: 发现 ${RUN_COUNT} 个 review run(s)"
|
||||
PASS=$((PASS + 1))
|
||||
|
||||
echo "${RUNS}" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
runs = data if isinstance(data, list) else data.get('runs', [])
|
||||
for r in runs[:3]:
|
||||
print(f\" - {r.get('id','?')[:8]}... status={r.get('status','?')} attempts={r.get('attempts','?')}\")
|
||||
" 2>/dev/null || true
|
||||
else
|
||||
echo " ❌ FAIL: 无 review runs"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
# ─── 结果汇总 ───
|
||||
echo ""
|
||||
echo "=== E2E 测试结果 ==="
|
||||
TOTAL=$((PASS + FAIL))
|
||||
echo " 通过: ${PASS}/${TOTAL}"
|
||||
echo " 失败: ${FAIL}/${TOTAL}"
|
||||
|
||||
if [ ${FAIL} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "⚠️ 部分测试失败。如果 AI 评论测试失败,请确保:"
|
||||
echo " 1. OPENAI_API_KEY 已正确配置"
|
||||
echo " 2. assistant 容器的 GITEA_ACCESS_TOKEN 已设置为 seed 生成的 token"
|
||||
echo " 3. Webhook 已正确触发(检查 Gitea webhook 日志)"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "🎉 所有 E2E 测试通过!"
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user