diff --git a/test_channel_api_results.md b/test_channel_api_results.md new file mode 100644 index 0000000..4c8f249 --- /dev/null +++ b/test_channel_api_results.md @@ -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* diff --git a/test_channel_platform_apis.py b/test_channel_platform_apis.py new file mode 100644 index 0000000..582bbb4 --- /dev/null +++ b/test_channel_platform_apis.py @@ -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) \ No newline at end of file