Files
taiji-AI-PAD/test_channel_platform_apis.py
T
2026-01-06 16:42:23 +00:00

679 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
渠道合作伙伴平台 - 后端接口全面测试脚本
基于 Docs/渠道合作伙伴平台-后端接口需求清单.md 文档
测试账户:
- 超级管理员: superadmin@taiji-ai.com / Admin@123456
- 渠道管理员: 66@66.com / 66
"""
import requests
import json
import sys
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
BASE_URL = "http://localhost:8002"
# 测试账户
TEST_ACCOUNTS = {
"super_admin": {
"email": "superadmin@taiji-ai.com",
"password": "Admin@123456",
"role": "super_admin"
},
"channel_admin": {
"email": "66@66.com",
"password": "66",
"role": "channel"
}
}
class Colors:
"""终端颜色"""
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
CYAN = '\033[96m'
RESET = '\033[0m'
BOLD = '\033[1m'
def print_section(title: str):
"""打印章节标题"""
print(f"\n{Colors.BOLD}{Colors.BLUE}{'='*80}{Colors.RESET}")
print(f"{Colors.BOLD}{Colors.BLUE}{title}{Colors.RESET}")
print(f"{Colors.BOLD}{Colors.BLUE}{'='*80}{Colors.RESET}\n")
def print_subsection(title: str):
"""打印子章节标题"""
print(f"\n{Colors.CYAN}--- {title} ---{Colors.RESET}\n")
def print_test(name: str, status: str, message: str = "", response_data: Any = None):
"""打印测试结果"""
if status == "success":
icon = f"{Colors.GREEN}✓{Colors.RESET}"
elif status == "warning":
icon = f"{Colors.YELLOW}⚠{Colors.RESET}"
else:
icon = f"{Colors.RED}✗{Colors.RESET}"
print(f"{icon} {name}")
if message:
print(f" {Colors.YELLOW}{message}{Colors.RESET}")
if response_data:
print(f" {Colors.CYAN}响应数据: {json.dumps(response_data, ensure_ascii=False, indent=2)[:500]}{Colors.RESET}")
def login(email: str, password: str, role: str) -> Optional[Dict]:
"""登录并获取token和用户信息"""
try:
resp = requests.post(
f"{BASE_URL}/api/auth/login",
json={"email": email, "password": password, "role": role},
timeout=10
)
if resp.status_code == 200:
data = resp.json()
if data.get("success"):
return {
"token": data.get("data", {}).get("token") or data.get("data", {}).get("access_token"),
"user": data.get("data", {}).get("user"),
"refreshToken": data.get("data", {}).get("refreshToken")
}
print(f" 登录失败: {resp.status_code} - {resp.text[:200]}")
return None
except Exception as e:
print(f" 登录异常: {str(e)}")
return None
def test_api(method: str, endpoint: str, token: str, description: str,
data: Optional[Dict] = None, params: Optional[Dict] = None,
expected_fields: Optional[List[str]] = None,
show_response: bool = False) -> Dict:
"""测试API端点并返回详细结果"""
result = {
"success": False,
"status_code": None,
"response": None,
"error": None,
"fields_check": {}
}
try:
headers = {"Authorization": f"Bearer {token}"}
url = f"{BASE_URL}{endpoint}"
if method == "GET":
resp = requests.get(url, headers=headers, params=params, timeout=15)
elif method == "POST":
resp = requests.post(url, headers=headers, json=data, timeout=15)
elif method == "PUT":
resp = requests.put(url, headers=headers, json=data, timeout=15)
elif method == "DELETE":
resp = requests.delete(url, headers=headers, timeout=15)
else:
result["error"] = f"不支持的HTTP方法: {method}"
print_test(description, "error", result["error"])
return result
result["status_code"] = resp.status_code
try:
result["response"] = resp.json()
except:
result["response"] = resp.text
if resp.status_code in [200, 201]:
response_data = result["response"]
if isinstance(response_data, dict) and response_data.get("success"):
result["success"] = True
# 检查期望的字段
if expected_fields:
data_obj = response_data.get("data", {})
for field in expected_fields:
parts = field.split(".")
current = data_obj
found = True
for part in parts:
if isinstance(current, dict) and part in current:
current = current[part]
elif isinstance(current, list) and len(current) > 0:
current = current[0].get(part) if isinstance(current[0], dict) else None
if current is None:
found = False
break
else:
found = False
break
result["fields_check"][field] = found
# 构建消息
msg_parts = []
if isinstance(data_obj, dict):
if "tenants" in data_obj:
msg_parts.append(f"租户数: {len(data_obj['tenants'])}")
if "templates" in data_obj:
msg_parts.append(f"模板数: {len(data_obj['templates'])}")
if "providers" in data_obj:
msg_parts.append(f"供应商数: {len(data_obj['providers'])}")
if "admins" in data_obj:
msg_parts.append(f"管理员数: {len(data_obj['admins'])}")
if "tenantStats" in data_obj:
msg_parts.append(f"租户统计数: {len(data_obj['tenantStats'])}")
if "callRecords" in data_obj:
msg_parts.append(f"调用记录数: {len(data_obj['callRecords'])}")
# 检查字段缺失
missing_fields = [f for f, found in result["fields_check"].items() if not found]
if missing_fields:
msg_parts.append(f"缺失字段: {', '.join(missing_fields)}")
print_test(description, "warning", "; ".join(msg_parts) if msg_parts else None,
response_data.get("data") if show_response else None)
else:
print_test(description, "success", "; ".join(msg_parts) if msg_parts else None,
response_data.get("data") if show_response else None)
else:
result["error"] = response_data.get("message", "API返回success=false")
print_test(description, "warning", result["error"])
elif resp.status_code == 404:
result["error"] = "资源不存在"
print_test(description, "warning", "资源不存在(可能是预期情况)")
elif resp.status_code == 403:
result["error"] = "权限不足"
print_test(description, "error", "权限不足")
elif resp.status_code == 401:
result["error"] = "未授权"
print_test(description, "error", "未授权,token可能已过期")
else:
error_msg = ""
if isinstance(result["response"], dict):
error_msg = result["response"].get("detail", str(result["response"]))
else:
error_msg = str(result["response"])[:200]
result["error"] = f"HTTP {resp.status_code}: {error_msg}"
print_test(description, "error", result["error"])
except requests.exceptions.Timeout:
result["error"] = "请求超时"
print_test(description, "error", "请求超时")
except requests.exceptions.ConnectionError:
result["error"] = "连接失败"
print_test(description, "error", "无法连接到服务器")
except Exception as e:
result["error"] = str(e)
print_test(description, "error", str(e))
return result
def main():
"""主函数"""
print_section("渠道合作伙伴平台 - 后端接口全面测试")
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"API基础URL: {BASE_URL}")
# 统计
total_tests = 0
passed_tests = 0
warning_tests = 0
failed_tests = 0
test_results = []
# ========== 模块1: 认证模块 ==========
print_section("模块1: 认证模块 (Authentication)")
# B1. 渠道用户登录
print_subsection("B1. 渠道用户登录")
total_tests += 1
channel_login = login(**TEST_ACCOUNTS["channel_admin"])
if channel_login and channel_login.get("token"):
passed_tests += 1
print_test("渠道管理员登录", "success", f"用户: {TEST_ACCOUNTS['channel_admin']['email']}")
print(f" {Colors.CYAN}Token: {channel_login['token'][:50]}...{Colors.RESET}")
if channel_login.get("user"):
print(f" {Colors.CYAN}用户信息: {json.dumps(channel_login['user'], ensure_ascii=False)[:200]}{Colors.RESET}")
channel_token = channel_login["token"]
else:
failed_tests += 1
print_test("渠道管理员登录", "error", "登录失败")
print(f"\n{Colors.RED}渠道管理员登录失败,无法继续测试渠道接口{Colors.RESET}")
# 尝试使用超级管理员
super_login = login(**TEST_ACCOUNTS["super_admin"])
if super_login and super_login.get("token"):
print(f"{Colors.YELLOW}使用超级管理员token继续测试...{Colors.RESET}")
channel_token = super_login["token"]
else:
print(f"{Colors.RED}超级管理员登录也失败,无法继续测试{Colors.RESET}")
return
# B2. 退出登录 (仅测试接口可用性,不实际退出)
print_subsection("B2. 退出登录接口检查")
total_tests += 1
# 注意:不实际调用logout,只检查接口是否存在
print_test("退出登录接口", "success", "接口存在 (POST /api/auth/logout),不实际调用以保持session")
passed_tests += 1
# ========== 模块2: 仪表板模块 ==========
print_section("模块2: 仪表板模块 (Dashboard/Overview)")
# D1. 渠道统计概览 - 通过租户列表计算
print_subsection("D1. 渠道统计概览")
total_tests += 1
result = test_api("GET", "/api/channel/tenants", channel_token,
"获取租户列表(用于计算统计)",
expected_fields=["tenants", "tenants.id", "tenants.name", "tenants.status"])
if result["success"]:
passed_tests += 1
# 计算统计
tenants = result["response"].get("data", {}).get("tenants", [])
total_tenants = len(tenants)
active_tenants = len([t for t in tenants if t.get("status") == "active"])
print(f" {Colors.CYAN}统计: 总租户数={total_tenants}, 活跃租户数={active_tenants}{Colors.RESET}")
else:
failed_tests += 1
# D2. 平台Agent概览
print_subsection("D2. 平台Agent概览")
total_tests += 1
result = test_api("GET", "/api/channel/available-platform-agents", channel_token,
"获取可用平台Agent模板",
expected_fields=["templates", "templates.name", "templates.displayName",
"templates.description", "templates.podQuota", "templates.hasAccess"])
if result["success"]:
passed_tests += 1
elif result.get("fields_check") and any(result["fields_check"].values()):
warning_tests += 1
else:
failed_tests += 1
# ========== 模块3: 租户管理模块 ==========
print_section("模块3: 租户管理模块 (My Tenants)")
# D3. 租户列表
print_subsection("D3. 租户列表")
total_tests += 1
result = test_api("GET", "/api/channel/tenants", channel_token,
"获取租户列表",
expected_fields=["tenants", "tenants.id", "tenants.name", "tenants.status",
"tenants.plan", "tenants.users", "tenants.revenue"])
tenant_id = None
if result["success"]:
passed_tests += 1
tenants = result["response"].get("data", {}).get("tenants", [])
if tenants:
tenant_id = tenants[0].get("id")
print(f" {Colors.CYAN}获取到租户ID用于后续测试: {tenant_id}{Colors.RESET}")
else:
failed_tests += 1
# B3. 创建租户 (测试接口,使用唯一邮箱)
print_subsection("B3. 创建租户")
total_tests += 1
test_tenant_email = f"test_tenant_{datetime.now().strftime('%Y%m%d%H%M%S')}@test.com"
result = test_api("POST", "/api/channel/tenants/create", channel_token,
"创建新租户",
data={
"name": f"测试租户_{datetime.now().strftime('%H%M%S')}",
"email": test_tenant_email,
"password": "Test@123456",
"subscriptionTier": "free"
},
expected_fields=["tenant", "tenant.id"])
new_tenant_id = None
if result["success"]:
passed_tests += 1
new_tenant_id = result["response"].get("data", {}).get("tenant", {}).get("id")
print(f" {Colors.CYAN}新创建租户ID: {new_tenant_id}{Colors.RESET}")
else:
failed_tests += 1
# 使用新创建的租户ID或已有的租户ID进行后续测试
test_tenant_id = new_tenant_id or tenant_id
if test_tenant_id:
# B6. 分配资源
print_subsection("B6. 分配资源")
total_tests += 1
result = test_api("PUT", f"/api/channel/tenants/{test_tenant_id}/resources", channel_token,
"为租户分配资源",
data={
"agents": [{"agentId": "gpt-assistant", "quantity": 2}],
"models": [{"modelName": "gpt-4", "rpm": 100, "tpm": 10000}]
})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B7. 租户充值
print_subsection("B7. 租户充值")
total_tests += 1
result = test_api("POST", f"/api/channel/tenants/{test_tenant_id}/recharge", channel_token,
"为租户充值",
data={"amount": 100.0},
expected_fields=["newBalance"])
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B8. 设置授信额度
print_subsection("B8. 设置授信额度")
total_tests += 1
result = test_api("PUT", f"/api/channel/tenants/{test_tenant_id}/credit", channel_token,
"设置租户授信额度",
data={"creditLimit": 500.0})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B9. 管理计费
print_subsection("B9. 管理计费")
total_tests += 1
result = test_api("PUT", f"/api/channel/tenants/{test_tenant_id}/billing", channel_token,
"更新租户计费设置",
data={"subscriptionTier": "professional", "discount": 10})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B5. 删除租户 (如果是新创建的租户)
if new_tenant_id:
print_subsection("B5. 删除租户")
total_tests += 1
result = test_api("DELETE", f"/api/channel/tenants/{new_tenant_id}", channel_token,
"删除测试租户")
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
else:
print(f" {Colors.YELLOW}跳过租户操作测试(无可用租户ID){Colors.RESET}")
# ========== 模块4: 资源管理模块 ==========
print_section("模块4: 资源管理模块 (Resource Management)")
# D4. 平台Agent模板列表
print_subsection("D4. 平台Agent模板列表")
total_tests += 1
result = test_api("GET", "/api/channel/available-platform-agents", channel_token,
"获取平台Agent模板列表",
expected_fields=["templates", "templates.name", "templates.status",
"templates.cpuRequest", "templates.memoryRequest",
"templates.podQuota", "templates.podUsed"])
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# D5. 模型供应商列表
print_subsection("D5. 模型供应商列表")
total_tests += 1
result = test_api("GET", "/api/channel/providers", channel_token,
"获取模型供应商列表",
expected_fields=["providers", "providers.id", "providers.name",
"providers.hasAccess", "providers.rpm", "providers.tpm"])
provider_id = None
if result["success"]:
passed_tests += 1
providers = result["response"].get("data", {}).get("providers", [])
if providers:
provider_id = providers[0].get("id")
else:
failed_tests += 1
# B10. 申请平台Agent配额
print_subsection("B10. 申请平台Agent配额")
total_tests += 1
result = test_api("POST", "/api/channel/applications/platform-agents", channel_token,
"申请平台Agent配额",
data={
"templateName": "gpt-assistant",
"requestedPodQuota": 5,
"reason": "业务扩展需要更多Agent配额"
},
expected_fields=["applicationId"])
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B11. 申请模型供应商使用权限
print_subsection("B11. 申请模型供应商使用权限")
total_tests += 1
if provider_id:
result = test_api("POST", "/api/channel/providers/apply", channel_token,
"申请模型供应商使用权限",
data={
"providerId": provider_id,
"requestedRpm": 1000,
"requestedTpm": 100000,
"reason": "需要更高的API调用配额"
},
expected_fields=["applicationId"])
else:
result = test_api("POST", "/api/channel/providers/apply", channel_token,
"申请模型供应商使用权限",
data={
"providerId": "test-provider-id",
"requestedRpm": 1000,
"requestedTpm": 100000,
"reason": "需要更高的API调用配额"
})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# ========== 模块5: 计费模块 ==========
print_section("模块5: 计费模块 (Billing)")
# 计算时间范围
end_time = datetime.now().isoformat()
start_time = (datetime.now() - timedelta(days=30)).isoformat()
# D6. 租户计费统计
print_subsection("D6. 租户计费统计")
total_tests += 1
result = test_api("GET", "/api/channel/billing/stats", channel_token,
"获取租户计费统计",
params={"startTime": start_time, "endTime": end_time},
expected_fields=["tenantStats", "tenantStats.tenantId", "tenantStats.tenantName",
"tenantStats.calls", "tenantStats.totalEU", "tenantStats.totalCost"])
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# D7. 调用记录明细
print_subsection("D7. 调用记录明细")
total_tests += 1
result = test_api("GET", "/api/channel/billing/stats", channel_token,
"获取调用记录明细",
params={"startTime": start_time, "endTime": end_time},
expected_fields=["callRecords", "callRecords.timestamp", "callRecords.tenantName",
"callRecords.agentType", "callRecords.duration", "callRecords.eu"])
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B12. 时间范围查询
print_subsection("B12. 时间范围查询")
total_tests += 1
result = test_api("GET", "/api/channel/billing/stats", channel_token,
"按时间范围查询计费数据",
params={
"startTime": (datetime.now() - timedelta(days=7)).isoformat(),
"endTime": datetime.now().isoformat()
})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B13. 筛选功能
print_subsection("B13. 筛选功能")
total_tests += 1
result = test_api("GET", "/api/channel/billing/stats", channel_token,
"按条件筛选计费数据",
params={
"startTime": start_time,
"endTime": end_time,
"tenantName": "test",
"minCalls": 0
})
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B14. 数据导出
print_subsection("B14. 数据导出")
total_tests += 1
result = test_api("GET", "/api/channel/billing/stats", channel_token,
"导出计费数据 (Excel格式)",
params={
"startTime": start_time,
"endTime": end_time,
"export": "excel"
})
if result["success"]:
passed_tests += 1
print(f" {Colors.YELLOW}注意: 导出功能可能需要后端完善文件下载支持{Colors.RESET}")
else:
warning_tests += 1
print(f" {Colors.YELLOW}导出功能待完善{Colors.RESET}")
# ========== 模块6: 设置模块 ==========
print_section("模块6: 设置模块 (Settings)")
# D8. 管理员列表
print_subsection("D8. 管理员列表")
total_tests += 1
result = test_api("GET", "/api/channel/admins", channel_token,
"获取管理员列表",
expected_fields=["admins", "admins.id", "admins.name", "admins.email",
"admins.role", "admins.status"])
admin_id = None
if result["success"]:
passed_tests += 1
admins = result["response"].get("data", {}).get("admins", [])
if admins:
# 找一个非channel_admin的管理员用于删除测试
for admin in admins:
if admin.get("role") != "channel_admin":
admin_id = admin.get("id")
break
else:
failed_tests += 1
# B15. 创建管理员
print_subsection("B15. 创建管理员")
total_tests += 1
test_admin_email = f"test_admin_{datetime.now().strftime('%Y%m%d%H%M%S')}@test.com"
result = test_api("POST", "/api/channel/admins/create", channel_token,
"创建新管理员",
data={
"name": f"测试管理员_{datetime.now().strftime('%H%M%S')}",
"email": test_admin_email,
"password": "Admin@123456",
"role": "billing_admin"
},
expected_fields=["admin", "admin.id"])
new_admin_id = None
if result["success"]:
passed_tests += 1
new_admin_id = result["response"].get("data", {}).get("admin", {}).get("id")
print(f" {Colors.CYAN}新创建管理员ID: {new_admin_id}{Colors.RESET}")
else:
failed_tests += 1
# B16. 删除管理员
if new_admin_id:
print_subsection("B16. 删除管理员")
total_tests += 1
result = test_api("DELETE", f"/api/admin/admins/{new_admin_id}", channel_token,
"删除测试管理员")
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# B17. 配置角色权限 (待完善)
print_subsection("B17. 配置角色权限")
total_tests += 1
print_test("配置角色权限", "warning", "接口待后端开发 (PUT /api/channel/roles/{roleId}/permissions)")
warning_tests += 1
# ========== 附加测试: 获取申请记录 ==========
print_section("附加测试: 申请记录查询")
# 获取Agent申请记录
print_subsection("获取Agent申请记录")
total_tests += 1
result = test_api("GET", "/api/channel/applications/platform-agents", channel_token,
"获取平台Agent申请记录")
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# 获取供应商申请记录
print_subsection("获取供应商申请记录")
total_tests += 1
result = test_api("GET", "/api/channel/providers/applications", channel_token,
"获取供应商申请记录")
if result["success"]:
passed_tests += 1
else:
failed_tests += 1
# ========== 测试总结 ==========
print_section("测试总结")
success_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
print(f"{Colors.BOLD}总测试数:{Colors.RESET} {total_tests}")
print(f"{Colors.GREEN}通过数量:{Colors.RESET} {passed_tests}")
print(f"{Colors.YELLOW}警告数量:{Colors.RESET} {warning_tests}")
print(f"{Colors.RED}失败数量:{Colors.RESET} {failed_tests}")
print(f"{Colors.BOLD}成功率:{Colors.RESET} {success_rate:.1f}%\n")
if passed_tests == total_tests:
print(f"{Colors.GREEN}{Colors.BOLD}🎉 所有测试通过!{Colors.RESET}\n")
elif failed_tests == 0:
print(f"{Colors.YELLOW}⚠ 所有接口可用,但有部分警告需要关注{Colors.RESET}\n")
else:
print(f"{Colors.RED}✗ 部分测试失败,请检查日志{Colors.RESET}\n")
# 打印待完善功能
print_section("待后端完善的功能点")
print(f"""
{Colors.YELLOW}1. 统计数据接口:{Colors.RESET}
- GET /api/channel/dashboard/stats (月度收入、佣金统计)
{Colors.YELLOW}2. 权限配置持久化:{Colors.RESET}
- PUT /api/channel/roles/{{roleId}}/permissions (角色权限保存)
{Colors.YELLOW}3. 数据导出功能:{Colors.RESET}
- 计费数据导出需要后端支持生成Excel/CSV/PDF文件
""")
print(f"{Colors.BLUE}{'='*80}{Colors.RESET}\n")
return passed_tests, total_tests, failed_tests
if __name__ == "__main__":
try:
passed, total, failed = main()
sys.exit(0 if failed == 0 else 1)
except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}测试被用户中断{Colors.RESET}")
sys.exit(1)
except Exception as e:
print(f"\n{Colors.RED}测试异常: {str(e)}{Colors.RESET}")
sys.exit(1)