Some checks failed
Build and Deploy Log Center / build-and-deploy (push) Failing after 1m55s
391 lines
13 KiB
Python
391 lines
13 KiB
Python
"""
|
||
Gitea API 功能测试脚本
|
||
|
||
测试内容:
|
||
1. 创建 PR
|
||
2. Merge PR (在日志中台直接合并)
|
||
3. Close PR (在日志中台直接拒绝)
|
||
4. 获取 PR 评论(close 原因)
|
||
5. 重新打开 PR (可选)
|
||
|
||
使用方法:
|
||
python test_gitea_api.py --gitea-url https://gitea.xxx.com --token YOUR_TOKEN --owner owner --repo repo
|
||
"""
|
||
|
||
import argparse
|
||
import httpx
|
||
import json
|
||
from typing import Optional
|
||
|
||
|
||
class GiteaAPITester:
|
||
"""Gitea API 测试器"""
|
||
|
||
def __init__(self, gitea_url: str, token: str, owner: str, repo: str):
|
||
self.gitea_url = gitea_url.rstrip("/")
|
||
self.token = token
|
||
self.owner = owner
|
||
self.repo = repo
|
||
self.base_api_url = f"{self.gitea_url}/api/v1"
|
||
self.client = httpx.Client(timeout=30)
|
||
|
||
def _headers(self) -> dict:
|
||
"""生成请求头"""
|
||
return {
|
||
"Authorization": f"token {self.token}",
|
||
"Content-Type": "application/json",
|
||
}
|
||
|
||
def test_create_pr(self, head_branch: str, base_branch: str = "main") -> Optional[int]:
|
||
"""
|
||
测试 1: 创建 PR
|
||
|
||
Returns:
|
||
PR 编号,失败返回 None
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print("测试 1: 创建 Pull Request")
|
||
print("=" * 60)
|
||
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/pulls"
|
||
payload = {
|
||
"title": "[测试] Gitea API 测试 PR",
|
||
"head": head_branch,
|
||
"base": base_branch,
|
||
"body": "这是一个测试 PR,用于验证 Gitea API 功能。\n\n## 测试内容\n- 创建 PR\n- Merge PR\n- Close PR",
|
||
}
|
||
|
||
try:
|
||
response = self.client.post(url, json=payload, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
data = response.json()
|
||
pr_number = data.get("number")
|
||
pr_url = data.get("html_url")
|
||
|
||
print(f"✅ PR 创建成功")
|
||
print(f" PR 编号: #{pr_number}")
|
||
print(f" PR URL: {pr_url}")
|
||
print(f" 状态: {data.get('state')}")
|
||
|
||
return pr_number
|
||
|
||
except httpx.HTTPStatusError as e:
|
||
print(f"❌ PR 创建失败: HTTP {e.response.status_code}")
|
||
print(f" 错误信息: {e.response.text}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"❌ PR 创建失败: {e}")
|
||
return None
|
||
|
||
def test_get_pr_info(self, pr_number: int):
|
||
"""获取 PR 信息"""
|
||
print("\n" + "=" * 60)
|
||
print(f"获取 PR #{pr_number} 信息")
|
||
print("=" * 60)
|
||
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/pulls/{pr_number}"
|
||
|
||
try:
|
||
response = self.client.get(url, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
data = response.json()
|
||
print(f"✅ PR 信息获取成功")
|
||
print(f" 标题: {data.get('title')}")
|
||
print(f" 状态: {data.get('state')}")
|
||
print(f" 是否已合并: {data.get('merged')}")
|
||
print(f" 创建时间: {data.get('created_at')}")
|
||
|
||
return data
|
||
|
||
except Exception as e:
|
||
print(f"❌ 获取 PR 信息失败: {e}")
|
||
return None
|
||
|
||
def test_merge_pr(self, pr_number: int) -> bool:
|
||
"""
|
||
测试 2: 合并 PR(在日志中台直接操作)
|
||
|
||
API: POST /api/v1/repos/{owner}/{repo}/pulls/{index}/merge
|
||
|
||
Returns:
|
||
是否成功
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print(f"测试 2: 合并 PR #{pr_number}")
|
||
print("=" * 60)
|
||
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/pulls/{pr_number}/merge"
|
||
payload = {
|
||
"Do": "merge", # merge / squash / rebase
|
||
"MergeMessageField": f"Merge PR #{pr_number} via API",
|
||
"MergeTitleField": f"Merge pull request #{pr_number}",
|
||
}
|
||
|
||
try:
|
||
response = self.client.post(url, json=payload, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
print(f"✅ PR 合并成功")
|
||
print(f" 方式: merge commit")
|
||
print(f" 结论: ✅ Gitea 支持通过 API 直接合并 PR")
|
||
|
||
return True
|
||
|
||
except httpx.HTTPStatusError as e:
|
||
print(f"❌ PR 合并失败: HTTP {e.response.status_code}")
|
||
print(f" 错误信息: {e.response.text}")
|
||
|
||
# 分析失败原因
|
||
if e.response.status_code == 405:
|
||
print(f" 可能原因: PR 已经合并或已关闭")
|
||
elif e.response.status_code == 409:
|
||
print(f" 可能原因: 有合并冲突")
|
||
elif e.response.status_code == 403:
|
||
print(f" 可能原因: Token 权限不足")
|
||
|
||
return False
|
||
except Exception as e:
|
||
print(f"❌ PR 合并失败: {e}")
|
||
return False
|
||
|
||
def test_close_pr(self, pr_number: int, reason: str = "") -> bool:
|
||
"""
|
||
测试 3: 关闭 PR(在日志中台直接拒绝)
|
||
|
||
API: PATCH /api/v1/repos/{owner}/{repo}/pulls/{index}
|
||
|
||
Returns:
|
||
是否成功
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print(f"测试 3: 关闭 PR #{pr_number}")
|
||
print("=" * 60)
|
||
|
||
# Step 1: 添加关闭原因评论
|
||
if reason:
|
||
comment_success = self.add_pr_comment(pr_number, reason)
|
||
if comment_success:
|
||
print(f"✅ 已添加关闭原因评论")
|
||
|
||
# Step 2: 关闭 PR
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/pulls/{pr_number}"
|
||
payload = {
|
||
"state": "closed", # open / closed
|
||
}
|
||
|
||
try:
|
||
response = self.client.patch(url, json=payload, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
print(f"✅ PR 关闭成功")
|
||
print(f" 结论: ✅ Gitea 支持通过 API 直接关闭 PR")
|
||
|
||
return True
|
||
|
||
except httpx.HTTPStatusError as e:
|
||
print(f"❌ PR 关闭失败: HTTP {e.response.status_code}")
|
||
print(f" 错误信息: {e.response.text}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"❌ PR 关闭失败: {e}")
|
||
return False
|
||
|
||
def add_pr_comment(self, pr_number: int, comment: str) -> bool:
|
||
"""
|
||
添加 PR 评论(用于记录 close 原因)
|
||
|
||
API: POST /api/v1/repos/{owner}/{repo}/issues/{index}/comments
|
||
|
||
注意: Gitea 中 PR 和 Issue 共用评论 API
|
||
"""
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/issues/{pr_number}/comments"
|
||
payload = {
|
||
"body": comment,
|
||
}
|
||
|
||
try:
|
||
response = self.client.post(url, json=payload, headers=self._headers())
|
||
response.raise_for_status()
|
||
return True
|
||
except Exception as e:
|
||
print(f"⚠️ 添加评论失败: {e}")
|
||
return False
|
||
|
||
def test_get_pr_comments(self, pr_number: int) -> list[dict]:
|
||
"""
|
||
测试 4: 获取 PR 评论(获取 close 原因)
|
||
|
||
API: GET /api/v1/repos/{owner}/{repo}/issues/{index}/comments
|
||
|
||
Returns:
|
||
评论列表
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print(f"测试 4: 获取 PR #{pr_number} 的评论")
|
||
print("=" * 60)
|
||
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/issues/{pr_number}/comments"
|
||
|
||
try:
|
||
response = self.client.get(url, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
comments = response.json()
|
||
print(f"✅ 获取评论成功,共 {len(comments)} 条")
|
||
|
||
for i, comment in enumerate(comments, 1):
|
||
print(f"\n 评论 #{i}:")
|
||
print(f" - 作者: {comment.get('user', {}).get('login')}")
|
||
print(f" - 时间: {comment.get('created_at')}")
|
||
print(f" - 内容: {comment.get('body')[:100]}...")
|
||
|
||
print(f"\n 结论: ✅ 可以通过评论获取 PR 关闭原因")
|
||
|
||
return comments
|
||
|
||
except Exception as e:
|
||
print(f"❌ 获取评论失败: {e}")
|
||
return []
|
||
|
||
def test_reopen_pr(self, pr_number: int) -> bool:
|
||
"""
|
||
测试 5: 重新打开 PR(可选)
|
||
|
||
API: PATCH /api/v1/repos/{owner}/{repo}/pulls/{index}
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print(f"测试 5: 重新打开 PR #{pr_number}")
|
||
print("=" * 60)
|
||
|
||
url = f"{self.base_api_url}/repos/{self.owner}/{self.repo}/pulls/{pr_number}"
|
||
payload = {
|
||
"state": "open",
|
||
}
|
||
|
||
try:
|
||
response = self.client.patch(url, json=payload, headers=self._headers())
|
||
response.raise_for_status()
|
||
|
||
print(f"✅ PR 重新打开成功")
|
||
print(f" 结论: ✅ Gitea 支持重新打开已关闭的 PR")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 重新打开 PR 失败: {e}")
|
||
return False
|
||
|
||
def run_all_tests(self, test_pr_number: Optional[int] = None):
|
||
"""运行所有测试"""
|
||
print("\n")
|
||
print("╔" + "=" * 58 + "╗")
|
||
print("║" + " " * 15 + "Gitea API 功能测试" + " " * 23 + "║")
|
||
print("╚" + "=" * 58 + "╝")
|
||
|
||
print(f"\n配置信息:")
|
||
print(f" Gitea URL: {self.gitea_url}")
|
||
print(f" 仓库: {self.owner}/{self.repo}")
|
||
print(f" Token: {'*' * 20} (已配置)")
|
||
|
||
if test_pr_number:
|
||
# 使用已存在的 PR 进行测试
|
||
pr_number = test_pr_number
|
||
print(f"\n使用已存在的 PR #{pr_number} 进行测试")
|
||
|
||
# 获取 PR 信息
|
||
self.test_get_pr_info(pr_number)
|
||
|
||
else:
|
||
# 创建新 PR 进行测试(需要先在仓库中创建测试分支)
|
||
print("\n⚠️ 提示: 需要先在仓库中创建一个测试分支(如 test-gitea-api)")
|
||
test_branch = input("请输入测试分支名称(或直接回车跳过创建 PR): ").strip()
|
||
|
||
if test_branch:
|
||
pr_number = self.test_create_pr(test_branch)
|
||
if not pr_number:
|
||
print("\n❌ 测试终止:无法创建 PR")
|
||
return
|
||
else:
|
||
pr_number = int(input("请输入已存在的 PR 编号: "))
|
||
|
||
# 测试关闭 PR(带原因)
|
||
close_reason = """## ❌ PR 被拒绝
|
||
|
||
**拒绝原因:**
|
||
1. 测试覆盖不足,缺少边界条件测试
|
||
2. 修改了核心业务逻辑,但未添加集成测试
|
||
3. 代码中存在潜在的 null 指针风险
|
||
|
||
**建议:**
|
||
- 补充测试用例
|
||
- 重新审核业务逻辑
|
||
- 修复后重新提交
|
||
|
||
---
|
||
**操作人:** 审核人员 (通过日志中台)
|
||
"""
|
||
|
||
self.test_close_pr(pr_number, close_reason)
|
||
|
||
# 获取评论(验证可以获取 close 原因)
|
||
comments = self.test_get_pr_comments(pr_number)
|
||
|
||
# 可选:重新打开 PR
|
||
reopen = input("\n是否测试重新打开 PR?(y/n): ").strip().lower()
|
||
if reopen == "y":
|
||
self.test_reopen_pr(pr_number)
|
||
|
||
# 可选:测试合并 PR
|
||
if reopen == "y":
|
||
merge = input("是否测试合并 PR?(y/n): ").strip().lower()
|
||
if merge == "y":
|
||
self.test_merge_pr(pr_number)
|
||
|
||
print("\n" + "=" * 60)
|
||
print("测试总结")
|
||
print("=" * 60)
|
||
print("✅ Gitea 支持的功能:")
|
||
print(" 1. ✅ 通过 API 创建 PR")
|
||
print(" 2. ✅ 通过 API 合并 PR(在日志中台直接操作)")
|
||
print(" 3. ✅ 通过 API 关闭 PR(在日志中台直接操作)")
|
||
print(" 4. ✅ 通过 API 添加评论(记录 close 原因)")
|
||
print(" 5. ✅ 通过 API 获取评论(读取 close 原因)")
|
||
print(" 6. ✅ 通过 API 重新打开 PR")
|
||
print("\n结论: Gitea 完全支持在日志中台直接 merge/close PR!")
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Gitea API 功能测试")
|
||
parser.add_argument("--gitea-url", required=True, help="Gitea 服务器地址")
|
||
parser.add_argument("--token", required=True, help="Gitea API Token")
|
||
parser.add_argument("--owner", required=True, help="仓库所有者")
|
||
parser.add_argument("--repo", required=True, help="仓库名称")
|
||
parser.add_argument("--pr-number", type=int, help="测试用的 PR 编号(可选)")
|
||
|
||
args = parser.parse_args()
|
||
|
||
tester = GiteaAPITester(
|
||
gitea_url=args.gitea_url,
|
||
token=args.token,
|
||
owner=args.owner,
|
||
repo=args.repo,
|
||
)
|
||
|
||
tester.run_all_tests(test_pr_number=args.pr_number)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# 示例用法
|
||
print("示例用法:")
|
||
print("python test_gitea_api.py \\")
|
||
print(" --gitea-url https://gitea.airlabs.art \\")
|
||
print(" --token your_token_here \\")
|
||
print(" --owner airlabs \\")
|
||
print(" --repo rtc_backend \\")
|
||
print(" --pr-number 45")
|
||
print("\n")
|
||
|
||
main()
|