更新渠道前代码

This commit is contained in:
zhanggangyong
2026-01-06 16:42:23 +00:00
parent 81bb3146ff
commit e560835e5b
2 changed files with 930 additions and 0 deletions
+251
View File
@@ -0,0 +1,251 @@
# 渠道合作伙伴平台 - 接口测试报告
> **测试时间**: 2026-01-06 16:33 - 16:40 UTC
> **测试环境**: localhost:8002 (Docker Compose)
> **测试账户**:
> - 渠道管理员: 66@66.com / 66
> - 超级管理员: superadmin@taiji-ai.com / Admin@123456
---
## 测试结果总览
| 状态 | 数量 | 说明 |
|------|------|------|
| ✅ 通过 | 19 | 接口正常工作 |
| ⚠️ 警告 | 3 | 接口可用但有问题 |
| ❌ 失败 | 2 | 接口返回500错误 |
| **总计** | **24** | |
---
## 详细测试结果
### 1. 认证模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 1 | 渠道用户登录 | POST | `/api/auth/login` | ✅ 通过 | 返回token和用户信息正常 |
| 2 | 退出登录 | POST | `/api/auth/logout` | ❌ 失败 | **返回500错误** |
**登录响应示例**:
```json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "893b8a13-977e-4a6f-8010-4a6fe5e0bb39",
"name": "渠道66",
"email": "66@66.com",
"role": "channel_admin",
"channelId": "893b8a13-977e-4a6f-8010-4a6fe5e0bb39"
}
}
}
```
---
### 2. 仪表板模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 3 | 获取租户列表(统计) | GET | `/api/channel/tenants` | ✅ 通过 | 返回8个租户 |
| 4 | 平台Agent概览 | GET | `/api/channel/available-platform-agents` | ✅ 通过 | 返回5个模板 |
---
### 3. 租户管理模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 5 | 获取租户列表 | GET | `/api/channel/tenants` | ✅ 通过 | 字段完整 |
| 6 | 创建租户 | POST | `/api/channel/tenants/create` | ✅ 通过 | 返回租户ID |
| 7 | 删除租户 | DELETE | `/api/channel/tenants/{tenantId}` | ⚠️ 警告 | 有余额时无法删除(预期行为) |
| 8 | 分配资源 | PUT | `/api/channel/tenants/{tenantId}/resources` | ⚠️ 警告 | 接口成功但**配额未减少** |
| 9 | 租户充值 | POST | `/api/channel/tenants/{tenantId}/recharge` | ✅ 通过 | 返回新余额 |
| 10 | 设置授信额度 | PUT | `/api/channel/tenants/{tenantId}/credit` | ✅ 通过 | |
| 11 | 管理计费 | PUT | `/api/channel/tenants/{tenantId}/billing` | ✅ 通过 | subscriptionTier只支持free/pro/enterprise |
| 12 | 更新租户状态 | PUT | `/api/channel/tenants/{tenantId}/status` | ✅ 通过 | |
| 13 | 更新租户权限 | PUT | `/api/channel/tenants/{tenantId}/permissions` | ✅ 通过 | 需使用正确的权限格式 |
**有效权限列表**:
- `use:platform_agents` - 使用平台Agent
- `use:custom_agents` - 使用自定义Agent
- `create:agents` - 创建Agent
- `read:billing` - 查看计费信息
- `export:data` - 导出数据
**分配资源响应示例**:
```json
{
"success": true,
"data": null,
"message": "资源分配成功"
}
```
**问题**: 分配资源后,渠道的 `podUsed` 没有增加,`podRemaining` 没有减少。
---
### 4. 资源管理模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 14 | 平台Agent模板列表 | GET | `/api/channel/available-platform-agents` | ✅ 通过 | |
| 15 | 模型供应商列表 | GET | `/api/channel/providers` | ✅ 通过 | 返回1个供应商 |
| 16 | 申请平台Agent配额 | POST | `/api/channel/applications/platform-agents` | ✅ 通过 | |
| 17 | 申请模型供应商权限 | POST | `/api/channel/providers/apply` | ✅ 通过 | providerId需使用UUID格式 |
**供应商列表响应示例**:
```json
{
"success": true,
"data": {
"providers": [
{
"id": "753aae69-8c60-488a-972f-397b18eaf1c3",
"name": "openai",
"provider": "openai",
"supportedModels": ["GPT"],
"rpm": 100,
"tpm": 1000,
"status": "active",
"hasAccess": false,
"pendingApplication": true
}
]
}
}
```
---
### 5. 计费模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 18 | 租户计费统计 | GET | `/api/channel/billing/stats` | ❌ 失败 | **返回500错误** |
| 19 | 时间范围查询 | GET | `/api/channel/billing/stats` | ❌ 失败 | 同上 |
| 20 | 筛选功能 | GET | `/api/channel/billing/stats` | ❌ 失败 | 同上 |
| 21 | 数据导出 | GET | `/api/channel/billing/stats` | ❌ 失败 | 同上 |
**错误原因**: 接口内部错误,可能是数据库查询问题。
---
### 6. 设置模块
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 22 | 管理员列表 | GET | `/api/channel/admins` | ✅ 通过 | |
| 23 | 创建管理员 | POST | `/api/channel/admins/create` | ✅ 通过 | |
| 24 | 删除管理员 | DELETE | `/api/admin/admins/{adminId}` | ✅ 通过 | 需要super_admin权限 |
| 25 | 配置角色权限 | PUT | `/api/channel/roles/{roleId}/permissions` | ⚠️ 待开发 | 接口不存在 |
---
### 7. 附加接口
| 序号 | 接口 | 方法 | 路径 | 状态 | 说明 |
|------|------|------|------|------|------|
| 26 | 获取Agent申请记录 | GET | `/api/channel/applications/platform-agents` | ✅ 通过 | |
| 27 | 获取供应商申请记录 | GET | `/api/channel/providers/applications` | ✅ 通过 | |
| 28 | 获取渠道Agent配额 | GET | `/api/channel/platform-agents` | ✅ 通过 | |
| 29 | 获取租户自定义Agent配额 | GET | `/api/channel/tenants/{tenantId}/custom-agent-quota` | ✅ 通过 | |
---
## 发现的问题
### 1. 严重问题 (需要修复)
#### 1.1 退出登录接口返回500错误
- **接口**: `POST /api/auth/logout`
- **错误**: Internal Server Error
- **可能原因**: `add_token_to_blacklist` 函数中 `user_id` 参数类型问题
#### 1.2 计费统计接口返回500错误
- **接口**: `GET /api/channel/billing/stats`
- **错误**: Internal Server Error
- **可能原因**: 数据库查询错误,可能是 `BillingRecord` 表结构或数据问题
### 2. 中等问题 (建议修复)
#### 2.1 分配资源后配额未正确更新
- **接口**: `PUT /api/channel/tenants/{tenantId}/resources`
- **问题**: 分配Agent给租户后,渠道的 `podUsed` 没有增加
- **影响**: 无法正确追踪配额使用情况
#### 2.2 申请供应商时providerId格式问题
- **接口**: `POST /api/channel/providers/apply`
- **问题**: 使用字符串"openai"会导致UUID解析错误
- **建议**: 前端应使用供应商列表返回的UUID格式ID
### 3. 低优先级问题
#### 3.1 角色权限配置接口待开发
- **接口**: `PUT /api/channel/roles/{roleId}/permissions`
- **状态**: 后端接口不存在
#### 3.2 文档与实际接口不一致
- **问题**: 文档中 `subscriptionTier` 支持 `professional`,但实际只支持 `pro`
- **建议**: 统一文档和代码
---
## 接口响应字段对照
### 租户列表返回字段
| 文档字段 | 实际返回 | 状态 |
|----------|----------|------|
| id | ✅ | 有 |
| name | ✅ | 有 |
| email | ✅ | 有 |
| status | ✅ | 有 |
| plan | ❌ subscriptionTier | 字段名不同 |
| users | ❌ | 缺失 |
| revenue | ❌ | 缺失 |
| balance | ✅ | 有 |
| creditLimit | ✅ | 有 |
| createdAt | ✅ | 有 |
### 平台Agent模板返回字段
| 文档字段 | 实际返回 | 状态 |
|----------|----------|------|
| name | ✅ | 有 |
| displayName | ✅ | 有 |
| description | ✅ | 有 |
| status | ✅ | 有 |
| hasAccess | ✅ | 有 |
| podQuota | ✅ | 有 |
| podUsed | ✅ | 有 |
| podRemaining | ✅ | 有 |
| cpuRequest | ✅ | 有 |
| memoryRequest | ✅ | 有 |
| pendingApplication | ✅ | 有 |
---
## 建议修复优先级
1. **高优先级**
- 修复 `/api/auth/logout` 500错误
- 修复 `/api/channel/billing/stats` 500错误
2. **中优先级**
- 修复分配资源后配额更新逻辑
- 统一 `subscriptionTier` 的值(pro vs professional)
3. **低优先级**
- 实现角色权限配置接口
- 补充租户列表中的 `users` 和 `revenue` 字段
---
*报告生成时间: 2026-01-06T16:40:00Z*
+679
View File
@@ -0,0 +1,679 @@
#!/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)