feat(webhook): ADR-059 GitHub → Gitea Webhook 遷移完成

- gitea_webhook.py: Header 全部改 X-Gitea-*,移除 workflow_run handler
- gitea_webhook_service.py: _fetch_pr_diff 改直接 httpx,不依賴 github_api_service
- 清除兩個檔案的所有殘留 github_ log key,review_id prefix 改 gitea-
- test_gitea_webhook.py: 10/10 通過,docstring 修正
- 03-secrets.yaml: 新增 GITEA_WEBHOOK_SECRET 佔位
- cd.yaml: 新增 GITEA_WEBHOOK_SECRET 注入步驟
- ADR-059: 建立架構決策文件

待統帥操作: Gitea Actions secret + Gitea UI Webhook 設定

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-05 14:44:32 +08:00
parent b2c0148f2b
commit 23364423fa
7 changed files with 218 additions and 1771 deletions

View File

@@ -1,12 +1,12 @@
"""
Phase 13.1: GitHub Webhook 整合測試
ADR-059: Gitea Webhook 整合測試
===================================
測試 GitHub Webhook OpenClaw AI 代碼審查整合
測試 Gitea Webhook OpenClaw AI 代碼審查整合
測試策略 (遵循 feedback_no_mock_testing.md):
- 使用 ASGITransport 撞擊真實端點
- 不使用 Mock直接測試 HTTP
- 驗證 HMAC 簽章邏輯
- 驗證 HMAC 簽章邏輯 (X-Gitea-Signature)
🔴 IMPORTANT: 禁止 Mock 測試
"""
@@ -20,14 +20,14 @@ import pytest
from fastapi import FastAPI
from httpx import ASGITransport
# 2026-04-05 Claude Code: 改用最小化 app只掛載 github_webhook router
# 2026-04-05 Claude Code: 改用最小化 app只掛載 gitea_webhook router
# 原 `from src.main import app` 會 import 整個應用,觸發 sqlalchemy.ext.asyncio
# C extension (asyncpg.protocol.protocol) 在 CI runner 上 segfault (exit 139)
# github_webhook router 的 import chain 不走 DB可獨立測試
from src.api.v1.github_webhook import router as github_webhook_router
# gitea_webhook router 的 import chain 不走 DB可獨立測試
from src.api.v1.gitea_webhook import router as gitea_webhook_router
app = FastAPI()
app.include_router(github_webhook_router, prefix="/api/v1")
app.include_router(gitea_webhook_router, prefix="/api/v1")
# 環境變數設定已移至 conftest.py (解決 E402)
@@ -138,7 +138,7 @@ def ping_payload():
def generate_signature(secret: str, body: bytes) -> str:
"""生成 GitHub Webhook 簽章"""
"""生成 Gitea Webhook 簽章 (X-Gitea-Signature)"""
signature = hmac.new(
secret.encode(),
body,
@@ -168,18 +168,18 @@ async def test_webhook_missing_signature(sample_pr_payload):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
# 故意不提供 X-Hub-Signature-256
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
# 故意不提供 X-Gitea-Signature
},
)
# 在 dev 環境下,缺少 secret 但配置了 secret應該要求簽章
# 由於我們設定了 GITHUB_WEBHOOK_SECRET缺少簽章應該返回 401
# 由於我們設定了 GITEA_WEBHOOK_SECRET缺少簽章應該返回 401
assert response.status_code == 401
@@ -193,13 +193,13 @@ async def test_webhook_invalid_signature(sample_pr_payload):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": "sha256=invalid_signature_here",
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": "sha256=invalid_signature_here",
},
)
@@ -216,13 +216,13 @@ async def test_webhook_valid_signature(sample_pr_payload, webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -274,13 +274,13 @@ async def test_webhook_repo_not_in_whitelist(webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -304,13 +304,13 @@ async def test_webhook_ping_event(ping_payload, webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "ping",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -331,13 +331,13 @@ async def test_webhook_push_event(sample_push_payload, webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "push",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "push",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -381,13 +381,13 @@ async def test_webhook_push_non_default_branch(webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "push",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "push",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -419,13 +419,13 @@ async def test_webhook_unsupported_event(webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "star", # 不支援的事件
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "star", # 不支援的事件
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -468,13 +468,13 @@ async def test_webhook_pr_unsupported_action(webhook_secret):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": signature,
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": signature,
},
)
@@ -498,13 +498,13 @@ async def test_webhook_wrong_signature_format(sample_pr_payload):
base_url="http://test",
) as client:
response = await client.post(
"/api/v1/webhooks/github",
"/api/v1/webhooks/gitea",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "pull_request",
"X-GitHub-Delivery": "test-delivery-id",
"X-Hub-Signature-256": "md5=wrong_format", # 錯誤格式
"X-Gitea-Event": "pull_request",
"X-Gitea-Delivery": "test-delivery-id",
"X-Gitea-Signature": "md5=wrong_format", # 錯誤格式
},
)