feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
570
tests/test_ai_functions.py
Normal file
570
tests/test_ai_functions.py
Normal file
@@ -0,0 +1,570 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
考培练系统 AI 功能完整测试脚本
|
||||
|
||||
测试所有 AI 功能是否正常:
|
||||
1. AI 基础服务连通性(4sapi + OpenRouter)
|
||||
2. 试题生成功能(V2 引擎)
|
||||
3. 答案判断功能(V2 引擎)
|
||||
4. 知识点分析功能(V2 引擎)
|
||||
5. 课程对话功能(V2 引擎)
|
||||
6. 陪练场景提取功能(V2 引擎)
|
||||
7. 能力分析功能(V2 引擎)
|
||||
|
||||
测试方式:通过 HTTP API 调用后端服务
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
# ==================== 配置 ====================
|
||||
|
||||
# 测试基础URL(演示版后端)
|
||||
BASE_URL = "http://localhost:8000/api/v1"
|
||||
|
||||
# 测试账户(从数据库获取有效用户)
|
||||
TEST_USERNAME = "admin"
|
||||
TEST_PASSWORD = "admin123"
|
||||
|
||||
# 测试超时时间(秒)
|
||||
TIMEOUT = 120
|
||||
|
||||
# 测试结果统计
|
||||
test_results: List[Dict[str, Any]] = []
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
|
||||
def print_header(title: str):
|
||||
"""打印标题"""
|
||||
print("\n" + "=" * 60)
|
||||
print(f" {title}")
|
||||
print("=" * 60)
|
||||
|
||||
def print_result(test_name: str, success: bool, message: str, duration_ms: int = 0):
|
||||
"""打印测试结果"""
|
||||
status = "✅ 通过" if success else "❌ 失败"
|
||||
duration_str = f" ({duration_ms}ms)" if duration_ms > 0 else ""
|
||||
print(f" {status} | {test_name}{duration_str}")
|
||||
if not success:
|
||||
print(f" └─ {message}")
|
||||
|
||||
# 记录结果
|
||||
test_results.append({
|
||||
"name": test_name,
|
||||
"success": success,
|
||||
"message": message,
|
||||
"duration_ms": duration_ms,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
async def get_auth_token(client: httpx.AsyncClient) -> Optional[str]:
|
||||
"""获取认证 token"""
|
||||
try:
|
||||
response = await client.post(
|
||||
f"{BASE_URL}/auth/login",
|
||||
data={"username": TEST_USERNAME, "password": TEST_PASSWORD}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get("data", {}).get("access_token")
|
||||
else:
|
||||
print(f" ⚠️ 登录失败: {response.status_code} - {response.text}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 登录异常: {e}")
|
||||
return None
|
||||
|
||||
# ==================== 测试用例 ====================
|
||||
|
||||
async def test_1_ai_basic_connectivity():
|
||||
"""测试1: AI 基础服务连通性"""
|
||||
print_header("测试1: AI 基础服务连通性")
|
||||
|
||||
# 直接测试 4sapi
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
# 4sapi 测试
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
"https://4sapi.com/v1/chat/completions",
|
||||
headers={
|
||||
"Authorization": "Bearer sk-9yMCXjRGANbacz20kJY8doSNy6Rf446aYwmgGIuIXQ7DAyBw",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
json={
|
||||
"model": "gemini-3-flash-preview",
|
||||
"messages": [{"role": "user", "content": "你好,请回复'测试成功'"}],
|
||||
"max_tokens": 50
|
||||
}
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||
print_result("4sapi.com 连通性", True, f"响应: {content[:50]}...", duration)
|
||||
else:
|
||||
print_result("4sapi.com 连通性", False, f"HTTP {response.status_code}: {response.text[:100]}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("4sapi.com 连通性", False, str(e), duration)
|
||||
|
||||
# OpenRouter 测试
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
"https://openrouter.ai/api/v1/chat/completions",
|
||||
headers={
|
||||
"Authorization": "Bearer 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",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
json={
|
||||
"model": "google/gemini-3-flash-preview",
|
||||
"messages": [{"role": "user", "content": "你好,请回复'测试成功'"}],
|
||||
"max_tokens": 50
|
||||
}
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||
print_result("OpenRouter 连通性", True, f"响应: {content[:50]}...", duration)
|
||||
else:
|
||||
print_result("OpenRouter 连通性", False, f"HTTP {response.status_code}: {response.text[:100]}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("OpenRouter 连通性", False, str(e), duration)
|
||||
|
||||
async def test_2_exam_generation():
|
||||
"""测试2: 试题生成功能"""
|
||||
print_header("测试2: 试题生成功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("试题生成", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 先查询一个有知识点的课程
|
||||
try:
|
||||
response = await client.get(f"{BASE_URL}/courses", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("试题生成 - 查询课程", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
courses = response.json().get("data", {}).get("items", [])
|
||||
if not courses:
|
||||
print_result("试题生成", False, "没有可用的课程")
|
||||
return
|
||||
|
||||
course_id = courses[0].get("id")
|
||||
print(f" 📝 使用课程ID: {course_id}")
|
||||
except Exception as e:
|
||||
print_result("试题生成 - 查询课程", False, str(e))
|
||||
return
|
||||
|
||||
# 测试试题生成 API
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
f"{BASE_URL}/exams/generate?engine=v2",
|
||||
headers=headers,
|
||||
json={
|
||||
"course_id": course_id,
|
||||
"single_choice_count": 2,
|
||||
"multiple_choice_count": 1,
|
||||
"true_false_count": 1,
|
||||
"fill_blank_count": 0,
|
||||
"essay_count": 0,
|
||||
"difficulty_level": 3,
|
||||
"current_round": 1
|
||||
}
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
result = data.get("data", {}).get("result", "")
|
||||
try:
|
||||
questions = json.loads(result) if result else []
|
||||
print_result("试题生成 (V2)", True, f"生成了 {len(questions)} 道题目", duration)
|
||||
except:
|
||||
print_result("试题生成 (V2)", True, f"返回结果: {result[:100]}...", duration)
|
||||
else:
|
||||
print_result("试题生成 (V2)", False, f"HTTP {response.status_code}: {response.text[:200]}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("试题生成 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_3_answer_judge():
|
||||
"""测试3: 答案判断功能"""
|
||||
print_header("测试3: 答案判断功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("答案判断", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 测试正确答案
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
f"{BASE_URL}/exams/judge-answer?engine=v2",
|
||||
headers=headers,
|
||||
json={
|
||||
"question": "玻尿酸的主要作用是什么?",
|
||||
"correct_answer": "保湿、填充、塑形",
|
||||
"user_answer": "保湿和填充",
|
||||
"analysis": "玻尿酸具有强大的保水能力,可用于皮肤保湿和面部填充"
|
||||
}
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
is_correct = data.get("data", {}).get("is_correct")
|
||||
print_result("答案判断 (V2)", True, f"判断结果: {'正确' if is_correct else '错误'}", duration)
|
||||
else:
|
||||
print_result("答案判断 (V2)", False, f"HTTP {response.status_code}: {response.text[:200]}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("答案判断 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_4_knowledge_analysis():
|
||||
"""测试4: 知识点分析功能"""
|
||||
print_header("测试4: 知识点分析功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("知识点分析", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 先查询课程和资料
|
||||
try:
|
||||
response = await client.get(f"{BASE_URL}/courses", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("知识点分析 - 查询课程", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
courses = response.json().get("data", {}).get("items", [])
|
||||
if not courses:
|
||||
print_result("知识点分析", False, "没有可用的课程")
|
||||
return
|
||||
|
||||
course_id = courses[0].get("id")
|
||||
|
||||
# 查询课程资料
|
||||
response = await client.get(f"{BASE_URL}/courses/{course_id}/materials", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("知识点分析 - 查询资料", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
materials = response.json().get("data", {}).get("items", [])
|
||||
if not materials:
|
||||
print_result("知识点分析", False, "课程没有可用的资料")
|
||||
return
|
||||
|
||||
material_id = materials[0].get("id")
|
||||
print(f" 📝 使用课程ID: {course_id}, 资料ID: {material_id}")
|
||||
except Exception as e:
|
||||
print_result("知识点分析 - 查询资料", False, str(e))
|
||||
return
|
||||
|
||||
# 测试知识点分析 API
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
f"{BASE_URL}/courses/{course_id}/materials/{material_id}/analyze?engine=v2",
|
||||
headers=headers
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
kps = data.get("data", {}).get("knowledge_points", [])
|
||||
print_result("知识点分析 (V2)", True, f"提取了 {len(kps)} 个知识点", duration)
|
||||
else:
|
||||
# 可能返回 400 因为没有文件内容
|
||||
error_detail = response.json().get("detail", response.text[:200])
|
||||
print_result("知识点分析 (V2)", False, f"HTTP {response.status_code}: {error_detail}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("知识点分析 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_5_course_chat():
|
||||
"""测试5: 课程对话功能"""
|
||||
print_header("测试5: 课程对话功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("课程对话", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 先查询课程
|
||||
try:
|
||||
response = await client.get(f"{BASE_URL}/courses", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("课程对话 - 查询课程", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
courses = response.json().get("data", {}).get("items", [])
|
||||
if not courses:
|
||||
print_result("课程对话", False, "没有可用的课程")
|
||||
return
|
||||
|
||||
course_id = courses[0].get("id")
|
||||
print(f" 📝 使用课程ID: {course_id}")
|
||||
except Exception as e:
|
||||
print_result("课程对话 - 查询课程", False, str(e))
|
||||
return
|
||||
|
||||
# 测试课程对话 API(流式响应)
|
||||
start = time.time()
|
||||
try:
|
||||
# 注意:课程对话是流式响应,需要特殊处理
|
||||
async with client.stream(
|
||||
"POST",
|
||||
f"{BASE_URL}/course/chat?engine=v2",
|
||||
headers=headers,
|
||||
json={
|
||||
"course_id": course_id,
|
||||
"query": "请简单介绍一下这门课程的主要内容"
|
||||
}
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
content = ""
|
||||
async for line in response.aiter_lines():
|
||||
if line.startswith("data:"):
|
||||
try:
|
||||
data = json.loads(line[5:].strip())
|
||||
if data.get("event") == "message_chunk":
|
||||
content += data.get("content", "")
|
||||
except:
|
||||
pass
|
||||
|
||||
duration = int((time.time() - start) * 1000)
|
||||
if content:
|
||||
print_result("课程对话 (V2)", True, f"响应: {content[:100]}...", duration)
|
||||
else:
|
||||
print_result("课程对话 (V2)", True, "收到流式响应", duration)
|
||||
else:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
body = await response.aread()
|
||||
print_result("课程对话 (V2)", False, f"HTTP {response.status_code}: {body.decode()[:200]}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("课程对话 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_6_practice_scene():
|
||||
"""测试6: 陪练场景提取功能"""
|
||||
print_header("测试6: 陪练场景提取功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("陪练场景提取", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 先查询课程
|
||||
try:
|
||||
response = await client.get(f"{BASE_URL}/courses", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("陪练场景提取 - 查询课程", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
courses = response.json().get("data", {}).get("items", [])
|
||||
if not courses:
|
||||
print_result("陪练场景提取", False, "没有可用的课程")
|
||||
return
|
||||
|
||||
course_id = courses[0].get("id")
|
||||
print(f" 📝 使用课程ID: {course_id}")
|
||||
except Exception as e:
|
||||
print_result("陪练场景提取 - 查询课程", False, str(e))
|
||||
return
|
||||
|
||||
# 测试陪练场景提取 API
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.post(
|
||||
f"{BASE_URL}/practice/extract-scene?engine=v2",
|
||||
headers=headers,
|
||||
json={
|
||||
"course_id": course_id,
|
||||
"scene_type": "consultation" # 咨询场景
|
||||
}
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
scenes = data.get("data", {}).get("scenes", [])
|
||||
print_result("陪练场景提取 (V2)", True, f"提取了 {len(scenes)} 个场景", duration)
|
||||
else:
|
||||
error_detail = response.json().get("detail", response.text[:200])
|
||||
print_result("陪练场景提取 (V2)", False, f"HTTP {response.status_code}: {error_detail}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("陪练场景提取 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_7_ability_analysis():
|
||||
"""测试7: 能力分析功能"""
|
||||
print_header("测试7: 能力分析功能 (V2 引擎)")
|
||||
|
||||
async with httpx.AsyncClient(timeout=TIMEOUT) as client:
|
||||
# 获取 token
|
||||
token = await get_auth_token(client)
|
||||
if not token:
|
||||
print_result("能力分析", False, "无法获取认证 token")
|
||||
return
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 测试能力分析 API(需要员工的言迹数据)
|
||||
start = time.time()
|
||||
try:
|
||||
# 先查询是否有员工数据
|
||||
response = await client.get(f"{BASE_URL}/users/me", headers=headers)
|
||||
if response.status_code != 200:
|
||||
print_result("能力分析 - 获取用户信息", False, f"HTTP {response.status_code}")
|
||||
return
|
||||
|
||||
user = response.json().get("data", {})
|
||||
user_id = user.get("id")
|
||||
print(f" 📝 当前用户ID: {user_id}")
|
||||
|
||||
# 测试能力分析 API
|
||||
response = await client.get(
|
||||
f"{BASE_URL}/ability/user/{user_id}/analysis?engine=v2",
|
||||
headers=headers
|
||||
)
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
dimensions = data.get("data", {}).get("dimensions", [])
|
||||
print_result("能力分析 (V2)", True, f"分析了 {len(dimensions)} 个能力维度", duration)
|
||||
elif response.status_code == 404:
|
||||
# 可能没有言迹数据
|
||||
print_result("能力分析 (V2)", True, "接口正常(无言迹数据)", duration)
|
||||
else:
|
||||
error_detail = response.json().get("detail", response.text[:200])
|
||||
print_result("能力分析 (V2)", False, f"HTTP {response.status_code}: {error_detail}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result("能力分析 (V2)", False, str(e), duration)
|
||||
|
||||
async def test_backend_health():
|
||||
"""测试后端健康状态"""
|
||||
print_header("后端服务健康检查")
|
||||
|
||||
backends = [
|
||||
("演示版 (aiedu)", "http://localhost:8000"),
|
||||
("瑞小美 (kpl)", "http://localhost:8001"),
|
||||
("华尔倍丽 (hua)", "http://localhost:8010"),
|
||||
("杨扬宠物 (yy)", "http://localhost:8011"),
|
||||
("武汉禾丽 (hl)", "http://localhost:8012"),
|
||||
("芯颜定制 (xy)", "http://localhost:8013"),
|
||||
("飞沃 (fw)", "http://localhost:8014"),
|
||||
("恩喜成都 (ex)", "http://localhost:8015"),
|
||||
("陪练试用版 (pl)", "http://localhost:8020"),
|
||||
]
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
for name, url in backends:
|
||||
start = time.time()
|
||||
try:
|
||||
response = await client.get(f"{url}/health")
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
if response.status_code == 200:
|
||||
print_result(name, True, "健康", duration)
|
||||
else:
|
||||
print_result(name, False, f"HTTP {response.status_code}", duration)
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
print_result(name, False, str(e), duration)
|
||||
|
||||
def print_summary():
|
||||
"""打印测试汇总"""
|
||||
print_header("测试汇总")
|
||||
|
||||
total = len(test_results)
|
||||
passed = sum(1 for r in test_results if r["success"])
|
||||
failed = total - passed
|
||||
|
||||
print(f"\n 总计: {total} 项测试")
|
||||
print(f" ✅ 通过: {passed}")
|
||||
print(f" ❌ 失败: {failed}")
|
||||
|
||||
if failed > 0:
|
||||
print("\n 失败项目:")
|
||||
for r in test_results:
|
||||
if not r["success"]:
|
||||
print(f" - {r['name']}: {r['message']}")
|
||||
|
||||
# 计算平均延迟
|
||||
durations = [r["duration_ms"] for r in test_results if r["duration_ms"] > 0]
|
||||
if durations:
|
||||
avg_duration = sum(durations) / len(durations)
|
||||
print(f"\n 平均响应时间: {avg_duration:.0f}ms")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
return failed == 0
|
||||
|
||||
async def main():
|
||||
"""主测试入口"""
|
||||
print("\n" + "=" * 60)
|
||||
print(" 考培练系统 AI 功能完整测试")
|
||||
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查后端健康状态
|
||||
await test_backend_health()
|
||||
|
||||
# 运行所有测试
|
||||
await test_1_ai_basic_connectivity()
|
||||
await test_2_exam_generation()
|
||||
await test_3_answer_judge()
|
||||
await test_4_knowledge_analysis()
|
||||
await test_5_course_chat()
|
||||
await test_6_practice_scene()
|
||||
await test_7_ability_analysis()
|
||||
|
||||
# 打印汇总
|
||||
success = print_summary()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
165
tests/test_course_chat.py
Normal file
165
tests/test_course_chat.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试与课程对话功能 - Dify 集成
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import json
|
||||
|
||||
# 测试配置
|
||||
API_BASE_URL = "http://localhost:8000"
|
||||
LOGIN_ENDPOINT = f"{API_BASE_URL}/api/v1/auth/login"
|
||||
CHAT_ENDPOINT = f"{API_BASE_URL}/api/v1/course/chat"
|
||||
|
||||
# 测试账号
|
||||
TEST_USER = {
|
||||
"username": "test_user",
|
||||
"password": "123456"
|
||||
}
|
||||
|
||||
|
||||
async def login() -> str:
|
||||
"""登录获取 access_token"""
|
||||
print("🔑 正在登录...")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
LOGIN_ENDPOINT,
|
||||
data={
|
||||
"username": TEST_USER["username"],
|
||||
"password": TEST_USER["password"]
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
token = data.get("access_token")
|
||||
print(f"✅ 登录成功,token: {token[:20]}...")
|
||||
return token
|
||||
else:
|
||||
print(f"❌ 登录失败: {response.status_code} - {response.text}")
|
||||
raise Exception("登录失败")
|
||||
|
||||
|
||||
async def test_course_chat(token: str, course_id: int, query: str, conversation_id: str = None):
|
||||
"""测试课程对话"""
|
||||
print(f"\n💬 测试与课程 {course_id} 对话")
|
||||
print(f"问题: {query}")
|
||||
if conversation_id:
|
||||
print(f"会话ID: {conversation_id}")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"course_id": course_id,
|
||||
"query": query
|
||||
}
|
||||
|
||||
if conversation_id:
|
||||
payload["conversation_id"] = conversation_id
|
||||
|
||||
new_conversation_id = None
|
||||
answer = ""
|
||||
|
||||
async with httpx.AsyncClient(timeout=180.0) as client:
|
||||
async with client.stream("POST", CHAT_ENDPOINT, headers=headers, json=payload) as response:
|
||||
if response.status_code != 200:
|
||||
error_text = await response.aread()
|
||||
print(f"❌ API 调用失败: {response.status_code} - {error_text}")
|
||||
return None, None
|
||||
|
||||
print("\n📡 SSE 事件流:")
|
||||
print("-" * 60)
|
||||
|
||||
async for line in response.aiter_lines():
|
||||
if not line or not line.strip():
|
||||
continue
|
||||
|
||||
if line.startswith("data: "):
|
||||
data_str = line[6:]
|
||||
|
||||
try:
|
||||
event_data = json.loads(data_str)
|
||||
event_type = event_data.get("event")
|
||||
|
||||
if event_type == "conversation_started":
|
||||
new_conversation_id = event_data.get("conversation_id")
|
||||
print(f"🆕 会话已创建: {new_conversation_id}")
|
||||
|
||||
elif event_type == "message_content":
|
||||
answer = event_data.get("answer", "")
|
||||
print(f"\n💡 AI 回答:\n{answer}")
|
||||
|
||||
elif event_type == "message_end":
|
||||
print("\n✅ 消息接收完成")
|
||||
|
||||
elif event_type == "error":
|
||||
error_msg = event_data.get("message", "未知错误")
|
||||
print(f"\n❌ 错误: {error_msg}")
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"⚠️ 解析失败: {e} - {data_str[:100]}")
|
||||
|
||||
print("-" * 60)
|
||||
return new_conversation_id, answer
|
||||
|
||||
|
||||
async def main():
|
||||
"""主测试流程"""
|
||||
print("=" * 60)
|
||||
print("🧪 与课程对话功能测试 - Dify 集成")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 登录
|
||||
token = await login()
|
||||
|
||||
# 2. 首次对话
|
||||
print("\n" + "=" * 60)
|
||||
print("测试场景 1: 首次对话(创建新会话)")
|
||||
print("=" * 60)
|
||||
|
||||
conversation_id, answer1 = await test_course_chat(
|
||||
token=token,
|
||||
course_id=1,
|
||||
query="这门课程讲什么?"
|
||||
)
|
||||
|
||||
if not conversation_id:
|
||||
print("\n❌ 测试失败:未获取到 conversation_id")
|
||||
return
|
||||
|
||||
# 3. 续接对话
|
||||
print("\n" + "=" * 60)
|
||||
print("测试场景 2: 续接对话(使用已有会话)")
|
||||
print("=" * 60)
|
||||
|
||||
_, answer2 = await test_course_chat(
|
||||
token=token,
|
||||
course_id=1,
|
||||
query="能详细说说吗?",
|
||||
conversation_id=conversation_id
|
||||
)
|
||||
|
||||
# 4. 测试总结
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 测试总结")
|
||||
print("=" * 60)
|
||||
print(f"✅ 首次对话: {'成功' if answer1 else '失败'}")
|
||||
print(f"✅ 续接对话: {'成功' if answer2 else '失败'}")
|
||||
print(f"✅ 会话管理: conversation_id = {conversation_id}")
|
||||
print("\n🎉 所有测试通过!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
95
tests/test_coze_conversation.py
Normal file
95
tests/test_coze_conversation.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试cozepy 0.2.0的conversation续接功能
|
||||
"""
|
||||
from cozepy import Coze, TokenAuth, COZE_CN_BASE_URL, Message, ChatEventType
|
||||
|
||||
# 配置
|
||||
TOKEN = "pat_Sa5OiuUl0gDflnKstQTToIz0sSMshBV06diX0owOeuI1ZK1xDLH5YZH9fSeuKLIi"
|
||||
BOT_ID = "7560643598174683145"
|
||||
|
||||
def test_conversation_continuation():
|
||||
"""测试对话续接"""
|
||||
print("=" * 60)
|
||||
print("测试cozepy 0.2.0对话续接功能")
|
||||
print("=" * 60)
|
||||
|
||||
# 初始化客户端
|
||||
coze = Coze(
|
||||
auth=TokenAuth(token=TOKEN),
|
||||
base_url=COZE_CN_BASE_URL
|
||||
)
|
||||
|
||||
# 第1步:创建conversation
|
||||
print("\n1. 创建conversation...")
|
||||
conversation = coze.conversations.create()
|
||||
conversation_id = conversation.id
|
||||
print(f"✅ Conversation已创建: {conversation_id}")
|
||||
|
||||
# 第2步:发送第一条消息
|
||||
print("\n2. 发送第一条消息...")
|
||||
user_id = "test_user_001"
|
||||
message1 = "我的名字是张三,请记住我的名字"
|
||||
|
||||
stream1 = coze.chat.stream(
|
||||
bot_id=BOT_ID,
|
||||
user_id=user_id,
|
||||
additional_messages=[Message.build_user_question_text(message1)],
|
||||
conversation_id=conversation_id
|
||||
)
|
||||
|
||||
print(f"用户消息: {message1}")
|
||||
print("AI回复: ", end="", flush=True)
|
||||
|
||||
ai_reply1 = ""
|
||||
for event in stream1:
|
||||
if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
|
||||
ai_reply1 += event.message.content
|
||||
print(event.message.content, end="", flush=True)
|
||||
elif event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED:
|
||||
break
|
||||
|
||||
print(f"\n✅ 第一轮对话完成")
|
||||
|
||||
# 第3步:发送第二条消息(测试是否记住)
|
||||
print("\n3. 发送第二条消息(测试是否记住名字)...")
|
||||
message2 = "我叫什么名字?"
|
||||
|
||||
stream2 = coze.chat.stream(
|
||||
bot_id=BOT_ID,
|
||||
user_id=user_id,
|
||||
additional_messages=[Message.build_user_question_text(message2)],
|
||||
conversation_id=conversation_id # 使用同一个conversation_id
|
||||
)
|
||||
|
||||
print(f"用户消息: {message2}")
|
||||
print("AI回复: ", end="", flush=True)
|
||||
|
||||
ai_reply2 = ""
|
||||
for event in stream2:
|
||||
if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA:
|
||||
ai_reply2 += event.message.content
|
||||
print(event.message.content, end="", flush=True)
|
||||
elif event.event == ChatEventType.CONVERSATION_CHAT_COMPLETED:
|
||||
break
|
||||
|
||||
print(f"\n✅ 第二轮对话完成")
|
||||
|
||||
# 分析结果
|
||||
print("\n" + "=" * 60)
|
||||
print("测试结果分析")
|
||||
print("=" * 60)
|
||||
print(f"conversation_id: {conversation_id}")
|
||||
print(f"\n第一轮AI回复: {ai_reply1[:100]}...")
|
||||
print(f"\n第二轮AI回复: {ai_reply2}")
|
||||
|
||||
# 检查AI是否记住了名字
|
||||
if "张三" in ai_reply2:
|
||||
print("\n✅ 成功!AI记住了名字,对话续接正常工作")
|
||||
else:
|
||||
print("\n❌ 失败!AI没有记住名字,对话续接可能不工作")
|
||||
print(" 这说明cozepy 0.2.0的conversation_id参数可能不起作用")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_conversation_continuation()
|
||||
|
||||
199
tests/test_exam_api.py
Normal file
199
tests/test_exam_api.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试考试API接口
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
# API配置
|
||||
BASE_URL = "http://localhost:8000/api/v1"
|
||||
# 使用有效的token(需要先登录获取)
|
||||
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsImV4cCI6MTc0MTQxOTQ5Mn0.0ZoiGJaIpV0WvVdH-M2tq03RMfRd9OKUJvDIgJtJoXk"
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def test_generate_exam():
|
||||
"""测试生成考试试题"""
|
||||
print("\n" + "="*50)
|
||||
print("测试1: 生成考试试题")
|
||||
print("="*50)
|
||||
|
||||
url = f"{BASE_URL}/exams/generate"
|
||||
data = {
|
||||
"course_id": 1,
|
||||
"single_choice_count": 2,
|
||||
"multiple_choice_count": 1,
|
||||
"true_false_count": 1,
|
||||
"fill_blank_count": 1,
|
||||
"essay_count": 0,
|
||||
"difficulty_level": 3,
|
||||
"mistake_records": "[]"
|
||||
}
|
||||
|
||||
print(f"请求URL: {url}")
|
||||
print(f"请求数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||
print("\n正在调用Dify工作流生成试题,请稍候...")
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers, timeout=300)
|
||||
print(f"\n响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"响应数据: {json.dumps(result, indent=2, ensure_ascii=False)[:500]}...")
|
||||
|
||||
# 解析result字段中的试题
|
||||
if "data" in result and "result" in result["data"]:
|
||||
questions_str = result["data"]["result"]
|
||||
questions = json.loads(questions_str)
|
||||
print(f"\n✅ 成功生成 {len(questions)} 道试题")
|
||||
print(f"第一题预览: {json.dumps(questions[0], indent=2, ensure_ascii=False)[:300]}...")
|
||||
return questions
|
||||
else:
|
||||
print("❌ 响应格式不正确")
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ 请求超时(可能Dify工作流执行时间较长)")
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
def test_judge_answer():
|
||||
"""测试判断主观题答案"""
|
||||
print("\n" + "="*50)
|
||||
print("测试2: 判断主观题答案")
|
||||
print("="*50)
|
||||
|
||||
url = f"{BASE_URL}/exams/judge-answer"
|
||||
data = {
|
||||
"question": "肉毒素注射后____小时内不能平躺",
|
||||
"correct_answer": "4",
|
||||
"user_answer": "4"
|
||||
}
|
||||
|
||||
print(f"请求URL: {url}")
|
||||
print(f"请求数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||
print("\n正在调用Dify答案判断工作流...")
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers, timeout=60)
|
||||
print(f"\n响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"响应数据: {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||||
|
||||
if "data" in result:
|
||||
is_correct = result["data"].get("is_correct")
|
||||
print(f"\n✅ 判断结果: {'正确' if is_correct else '错误'}")
|
||||
return result
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
def test_record_mistake():
|
||||
"""测试记录错题"""
|
||||
print("\n" + "="*50)
|
||||
print("测试3: 记录错题")
|
||||
print("="*50)
|
||||
|
||||
url = f"{BASE_URL}/exams/record-mistake"
|
||||
data = {
|
||||
"exam_id": 1,
|
||||
"question_id": None,
|
||||
"knowledge_point_id": 199,
|
||||
"question_content": "以下哪一项最能准确描述艾维岚(Avilan)在医美注射材料中的独特作用机制?",
|
||||
"correct_answer": "B:它是唯一可以做减法的注射材料,实现收紧而不增容。",
|
||||
"user_answer": "A:它是一款主要用于增容塑形的支撑型材料。"
|
||||
}
|
||||
|
||||
print(f"请求URL: {url}")
|
||||
print(f"请求数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=data, headers=headers, timeout=30)
|
||||
print(f"\n响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"响应数据: {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||||
|
||||
if "data" in result:
|
||||
print(f"\n✅ 成功记录错题,ID: {result['data'].get('id')}")
|
||||
return result
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
def test_get_mistakes():
|
||||
"""测试获取错题记录"""
|
||||
print("\n" + "="*50)
|
||||
print("测试4: 获取错题记录")
|
||||
print("="*50)
|
||||
|
||||
exam_id = 1
|
||||
url = f"{BASE_URL}/exams/mistakes?exam_id={exam_id}"
|
||||
|
||||
print(f"请求URL: {url}")
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=30)
|
||||
print(f"\n响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"响应数据: {json.dumps(result, indent=2, ensure_ascii=False)[:500]}...")
|
||||
|
||||
if "data" in result and "mistakes" in result["data"]:
|
||||
mistakes = result["data"]["mistakes"]
|
||||
print(f"\n✅ 成功获取 {len(mistakes)} 条错题记录")
|
||||
return result
|
||||
else:
|
||||
print(f"❌ 请求失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"❌ 请求异常: {str(e)}")
|
||||
|
||||
return None
|
||||
|
||||
def main():
|
||||
"""主测试流程"""
|
||||
print("\n" + "="*60)
|
||||
print("开始测试考试API接口")
|
||||
print("="*60)
|
||||
|
||||
# 测试1: 生成考试试题(调用Dify工作流)
|
||||
# 注意:这个测试可能需要较长时间,因为要调用Dify工作流
|
||||
# questions = test_generate_exam()
|
||||
|
||||
# 测试2: 判断答案(调用Dify工作流)
|
||||
test_judge_answer()
|
||||
|
||||
# 测试3: 记录错题
|
||||
test_record_mistake()
|
||||
|
||||
# 测试4: 获取错题记录
|
||||
test_get_mistakes()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("测试完成")
|
||||
print("="*60)
|
||||
print("\n注意: 试题生成测试已注释,因为需要较长时间调用Dify工作流")
|
||||
print("如需测试,请取消注释 test_generate_exam() 行")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
124
tests/test_frontend_login.py
Normal file
124
tests/test_frontend_login.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
前端登录测试脚本
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
def test_login_flow():
|
||||
"""测试完整的登录流程"""
|
||||
print("🔍 开始前端登录测试...")
|
||||
|
||||
# 1. 测试前端页面访问
|
||||
try:
|
||||
response = requests.get("http://localhost:3001/login")
|
||||
if response.status_code == 200:
|
||||
print("✅ 前端登录页面可访问")
|
||||
else:
|
||||
print(f"❌ 前端登录页面访问失败: {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 前端页面访问失败: {e}")
|
||||
return False
|
||||
|
||||
# 2. 测试后端登录API
|
||||
try:
|
||||
login_data = {"username": "testuser", "password": "123456"}
|
||||
response = requests.post("http://localhost:8000/api/v1/auth/login", json=login_data)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ 后端登录API成功")
|
||||
print(f" 用户: {data['data']['user']['username']}")
|
||||
print(f" 角色: {data['data']['user']['role']}")
|
||||
|
||||
# 保存token用于后续测试
|
||||
global access_token, refresh_token
|
||||
access_token = data['data']['token']['access_token']
|
||||
refresh_token = data['data']['token']['refresh_token']
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 后端登录API失败: {response.status_code}")
|
||||
print(f" 响应: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 后端API调用失败: {e}")
|
||||
return False
|
||||
|
||||
def test_token_refresh():
|
||||
"""测试token刷新"""
|
||||
global access_token, refresh_token
|
||||
|
||||
if not access_token or not refresh_token:
|
||||
print("❌ 没有可用的token进行刷新测试")
|
||||
return False
|
||||
|
||||
print("\n🔄 测试token刷新...")
|
||||
|
||||
try:
|
||||
refresh_data = {"refresh_token": refresh_token}
|
||||
response = requests.post("http://localhost:8000/api/v1/auth/refresh", json=refresh_data)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ Token刷新成功")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Token刷新失败: {response.status_code}")
|
||||
print(f" 响应: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Token刷新请求失败: {e}")
|
||||
return False
|
||||
|
||||
def test_frontend_proxy():
|
||||
"""测试前端代理配置"""
|
||||
print("\n🌐 测试前端代理...")
|
||||
|
||||
try:
|
||||
# 通过前端代理调用后端API
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post("http://localhost:3001/api/v1/auth/login",
|
||||
json={"username": "testuser", "password": "123456"},
|
||||
headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ 前端代理API调用成功")
|
||||
print(f" 响应格式正确: {type(data)}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 前端代理API调用失败: {response.status_code}")
|
||||
print(f" 响应: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 前端代理API调用失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 前端登录完整测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试后端API
|
||||
if not test_login_flow():
|
||||
print("❌ 后端登录测试失败")
|
||||
return False
|
||||
|
||||
# 测试token刷新
|
||||
if not test_token_refresh():
|
||||
print("❌ Token刷新测试失败")
|
||||
return False
|
||||
|
||||
# 测试前端代理
|
||||
if not test_frontend_proxy():
|
||||
print("❌ 前端代理测试失败")
|
||||
return False
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!前端登录功能正常!")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
134
tests/test_integration.py
Normal file
134
tests/test_integration.py
Normal file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
前后端集成测试脚本
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
def test_backend_api():
|
||||
"""测试后端API"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
print("🧪 测试后端API...")
|
||||
|
||||
# 1. 健康检查
|
||||
try:
|
||||
response = requests.get(f"{base_url}/health")
|
||||
print(f"✅ 健康检查: {response.json()}")
|
||||
except Exception as e:
|
||||
print(f"❌ 健康检查失败: {e}")
|
||||
return False
|
||||
|
||||
# 2. 登录测试
|
||||
login_data = {"username": "testuser", "password": "123456"}
|
||||
try:
|
||||
response = requests.post(f"{base_url}/api/v1/auth/login", json=login_data)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ 登录成功: {data['message']}")
|
||||
print(f" 用户ID: {data['data']['user']['id']}")
|
||||
print(f" 用户名: {data['data']['user']['username']}")
|
||||
print(f" 角色: {data['data']['user']['role']}")
|
||||
|
||||
# 保存token用于后续测试
|
||||
global access_token, refresh_token
|
||||
access_token = data['data']['token']['access_token']
|
||||
refresh_token = data['data']['token']['refresh_token']
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 登录失败: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 登录请求失败: {e}")
|
||||
return False
|
||||
|
||||
def test_frontend_proxy():
|
||||
"""测试前端代理配置"""
|
||||
base_url = "http://localhost:3001"
|
||||
|
||||
print("\n🌐 测试前端代理...")
|
||||
|
||||
# 1. 检查前端是否可访问
|
||||
try:
|
||||
response = requests.get(base_url)
|
||||
if response.status_code == 200:
|
||||
print("✅ 前端页面可访问")
|
||||
else:
|
||||
print(f"❌ 前端页面访问失败: {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 前端页面访问失败: {e}")
|
||||
return False
|
||||
|
||||
# 2. 测试前端代理到后端的API调用
|
||||
try:
|
||||
# 通过前端代理调用后端API
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(f"{base_url}/api/v1/auth/login", json={"username": "testuser", "password": "123456"}, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ 前端代理API调用成功")
|
||||
print(f" 响应数据: {data}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 前端代理API调用失败: {response.status_code}")
|
||||
print(f" 响应: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 前端代理API调用失败: {e}")
|
||||
return False
|
||||
|
||||
def test_token_refresh():
|
||||
"""测试token刷新"""
|
||||
global access_token, refresh_token
|
||||
|
||||
if not access_token or not refresh_token:
|
||||
print("❌ 没有可用的token进行刷新测试")
|
||||
return False
|
||||
|
||||
print("\n🔄 测试token刷新...")
|
||||
|
||||
try:
|
||||
refresh_data = {"refresh_token": refresh_token}
|
||||
response = requests.post("http://localhost:8000/api/v1/auth/refresh", json=refresh_data)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ Token刷新成功")
|
||||
print(f" 新token: {data['data']['access_token'][:50]}...")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Token刷新失败: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Token刷新请求失败: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 开始前后端集成测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试后端API
|
||||
if not test_backend_api():
|
||||
print("❌ 后端API测试失败,终止测试")
|
||||
return False
|
||||
|
||||
# 测试前端代理
|
||||
if not test_frontend_proxy():
|
||||
print("❌ 前端代理测试失败")
|
||||
return False
|
||||
|
||||
# 测试token刷新
|
||||
if not test_token_refresh():
|
||||
print("❌ Token刷新测试失败")
|
||||
return False
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!前后端集成成功!")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
177
tests/test_login.html
Normal file
177
tests/test_login.html
Normal file
@@ -0,0 +1,177 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #666;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.result {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
pre {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>登录测试</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" value="testuser" placeholder="请输入用户名">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" value="123456" placeholder="请输入密码">
|
||||
</div>
|
||||
|
||||
<button id="loginBtn" onclick="login()">登录</button>
|
||||
|
||||
<div id="result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const resultDiv = document.getElementById('result');
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
|
||||
// 显示加载状态
|
||||
loginBtn.disabled = true;
|
||||
loginBtn.textContent = '登录中...';
|
||||
resultDiv.style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.code === 200) {
|
||||
// 登录成功
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>✅ 登录成功!</h3>
|
||||
<p><strong>用户:</strong> ${data.data.user.username}</p>
|
||||
<p><strong>角色:</strong> ${data.data.user.role}</p>
|
||||
<p><strong>Token类型:</strong> ${data.data.token.token_type}</p>
|
||||
<p><strong>Access Token:</strong> ${data.data.token.access_token.substring(0, 50)}...</p>
|
||||
<p><strong>Refresh Token:</strong> ${data.data.token.refresh_token.substring(0, 50)}...</p>
|
||||
`;
|
||||
} else {
|
||||
// 登录失败
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 登录失败</h3>
|
||||
<p><strong>状态码:</strong> ${response.status}</p>
|
||||
<p><strong>错误信息:</strong> ${data.message || data.detail || '未知错误'}</p>
|
||||
<p><strong>完整响应:</strong></p>
|
||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络错误
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `
|
||||
<h3>❌ 网络错误</h3>
|
||||
<p><strong>错误信息:</strong> ${error.message}</p>
|
||||
<p>请检查:</p>
|
||||
<ul>
|
||||
<li>前端服务是否运行 (http://localhost:3001)</li>
|
||||
<li>后端服务是否运行 (http://localhost:8000)</li>
|
||||
<li>代理配置是否正确</li>
|
||||
</ul>
|
||||
`;
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
loginBtn.disabled = false;
|
||||
loginBtn.textContent = '登录';
|
||||
resultDiv.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时自动测试
|
||||
window.addEventListener('load', function() {
|
||||
console.log('页面加载完成');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
113
tests/test_login_manual.py
Normal file
113
tests/test_login_manual.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
手动测试登录功能的脚本
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
def test_login_page():
|
||||
"""测试登录页面是否可访问"""
|
||||
print("=" * 50)
|
||||
print("登录功能测试报告")
|
||||
print("=" * 50)
|
||||
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print()
|
||||
|
||||
# 测试前端页面访问
|
||||
frontend_url = "http://localhost:3001/login?redirect=/trainee/exam"
|
||||
print(f"1. 测试前端页面访问: {frontend_url}")
|
||||
|
||||
try:
|
||||
response = requests.get(frontend_url, timeout=5)
|
||||
print(f" 状态码: {response.status_code}")
|
||||
print(f" 响应头: {dict(response.headers)}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print(" ✅ 前端页面可以正常访问")
|
||||
content_length = len(response.text)
|
||||
print(f" 页面内容长度: {content_length} 字符")
|
||||
|
||||
# 检查是否包含登录相关元素
|
||||
content = response.text.lower()
|
||||
login_indicators = ['login', '登录', 'username', 'password', 'form']
|
||||
found_indicators = [indicator for indicator in login_indicators if indicator in content]
|
||||
|
||||
if found_indicators:
|
||||
print(f" 找到登录相关元素: {found_indicators}")
|
||||
else:
|
||||
print(" ⚠️ 未找到明显的登录相关元素")
|
||||
else:
|
||||
print(f" ❌ 前端页面访问失败,状态码: {response.status_code}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(" ❌ 无法连接到前端服务 (localhost:3001)")
|
||||
except requests.exceptions.Timeout:
|
||||
print(" ❌ 前端服务响应超时")
|
||||
except Exception as e:
|
||||
print(f" ❌ 访问前端页面时出错: {e}")
|
||||
|
||||
print()
|
||||
|
||||
# 测试后端API
|
||||
backend_base = "http://localhost:8000"
|
||||
print(f"2. 测试后端API服务: {backend_base}")
|
||||
|
||||
try:
|
||||
# 测试后端健康检查
|
||||
health_url = f"{backend_base}/health"
|
||||
response = requests.get(health_url, timeout=5)
|
||||
print(f" 健康检查状态码: {response.status_code}")
|
||||
|
||||
# 测试登录API端点
|
||||
login_api_url = f"{backend_base}/api/v1/auth/login"
|
||||
response = requests.options(login_api_url, timeout=5)
|
||||
print(f" 登录API OPTIONS请求状态码: {response.status_code}")
|
||||
|
||||
if response.status_code in [200, 405]: # 405表示方法不允许但端点存在
|
||||
print(" ✅ 后端API服务正常运行")
|
||||
else:
|
||||
print(f" ⚠️ 后端API可能有问题,状态码: {response.status_code}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(" ❌ 无法连接到后端服务 (localhost:8000)")
|
||||
except requests.exceptions.Timeout:
|
||||
print(" ❌ 后端服务响应超时")
|
||||
except Exception as e:
|
||||
print(f" ❌ 访问后端API时出错: {e}")
|
||||
|
||||
print()
|
||||
|
||||
# 测试数据库连接(通过后端API)
|
||||
print("3. 测试数据库连接状态")
|
||||
try:
|
||||
db_status_url = f"{backend_base}/api/v1/system/status"
|
||||
response = requests.get(db_status_url, timeout=5)
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
print(" ✅ 系统状态API响应正常")
|
||||
print(f" 响应数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||
except json.JSONDecodeError:
|
||||
print(" ⚠️ 系统状态API响应不是有效JSON")
|
||||
else:
|
||||
print(f" ⚠️ 系统状态API状态码: {response.status_code}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(" ❌ 无法获取系统状态")
|
||||
except Exception as e:
|
||||
print(f" ❌ 获取系统状态时出错: {e}")
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
print("测试建议:")
|
||||
print("1. 确保前后端服务都在运行")
|
||||
print("2. 检查端口配置是否正确")
|
||||
print("3. 验证数据库连接是否正常")
|
||||
print("4. 使用浏览器手动访问登录页面进行测试")
|
||||
print("=" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_login_page()
|
||||
350
tests/test_practice_api.py
Normal file
350
tests/test_practice_api.py
Normal file
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试陪练功能API
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
USERNAME = "admin"
|
||||
PASSWORD = "admin123"
|
||||
|
||||
def login():
|
||||
"""登录获取token"""
|
||||
print("=" * 60)
|
||||
print("1. 登录获取token")
|
||||
print("=" * 60)
|
||||
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/v1/auth/login",
|
||||
json={"username": USERNAME, "password": PASSWORD}
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ 登录失败: {response.status_code}")
|
||||
sys.exit(1)
|
||||
|
||||
data = response.json()
|
||||
if data['code'] != 200:
|
||||
print(f"❌ 登录失败: {data['message']}")
|
||||
sys.exit(1)
|
||||
|
||||
token = data['data']['token']['access_token']
|
||||
print(f"✅ 登录成功,token: {token[:50]}...")
|
||||
return token
|
||||
|
||||
|
||||
def test_get_scenes(token):
|
||||
"""测试获取场景列表"""
|
||||
print("\n" + "=" * 60)
|
||||
print("2. 测试获取场景列表")
|
||||
print("=" * 60)
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{BASE_URL}/api/v1/practice/scenes", headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ 请求失败: {response.status_code}")
|
||||
return False
|
||||
|
||||
data = response.json()
|
||||
if data['code'] != 200:
|
||||
print(f"❌ 获取失败: {data['message']}")
|
||||
return False
|
||||
|
||||
scenes = data['data']['items']
|
||||
total = data['data']['total']
|
||||
|
||||
print(f"✅ 成功获取 {total} 个场景")
|
||||
print("\n场景列表:")
|
||||
print("-" * 80)
|
||||
for scene in scenes:
|
||||
print(f" {scene['id']}. {scene['name']} - {scene['type']} - {scene['difficulty']}")
|
||||
print("-" * 80)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_get_scene_detail(token, scene_id=1):
|
||||
"""测试获取场景详情"""
|
||||
print("\n" + "=" * 60)
|
||||
print(f"3. 测试获取场景详情 (ID={scene_id})")
|
||||
print("=" * 60)
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{BASE_URL}/api/v1/practice/scenes/{scene_id}", headers=headers)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ 请求失败: {response.status_code}")
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
if data['code'] != 200:
|
||||
print(f"❌ 获取失败: {data['message']}")
|
||||
return None
|
||||
|
||||
scene = data['data']
|
||||
print(f"✅ 成功获取场景详情")
|
||||
print(f"\n场景名称: {scene['name']}")
|
||||
print(f"类型: {scene['type']}")
|
||||
print(f"难度: {scene['difficulty']}")
|
||||
print(f"时长: {scene['duration']}分钟")
|
||||
print(f"\n场景背景:\n{scene['background']}")
|
||||
print(f"\nAI角色:\n{scene['ai_role']}")
|
||||
print(f"\n练习目标:")
|
||||
for i, obj in enumerate(scene['objectives'], 1):
|
||||
print(f" {i}. {obj}")
|
||||
|
||||
return scene
|
||||
|
||||
|
||||
def test_sse_chat(token, scene):
|
||||
"""测试SSE对话"""
|
||||
print("\n" + "=" * 60)
|
||||
print("4. 测试SSE流式对话")
|
||||
print("=" * 60)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 首次消息
|
||||
payload = {
|
||||
"scene_id": scene['id'],
|
||||
"scene_name": scene['name'],
|
||||
"scene_description": scene['description'],
|
||||
"scene_background": scene['background'],
|
||||
"scene_ai_role": scene['ai_role'],
|
||||
"scene_objectives": scene['objectives'],
|
||||
"scene_keywords": scene['keywords'],
|
||||
"user_message": "您好,我是某轻医美品牌的销售顾问,想占用您几分钟时间介绍一下我们的产品",
|
||||
"is_first": True
|
||||
}
|
||||
|
||||
print("\n发送首次消息(包含场景信息):")
|
||||
print(f" 用户消息: {payload['user_message']}")
|
||||
print(f" is_first: {payload['is_first']}")
|
||||
print(f"\n等待AI回复(流式)...")
|
||||
print("-" * 80)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/v1/practice/start",
|
||||
headers=headers,
|
||||
json=payload,
|
||||
stream=True,
|
||||
timeout=180
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ SSE请求失败: {response.status_code}")
|
||||
print(f" 响应: {response.text}")
|
||||
return None
|
||||
|
||||
conversation_id = None
|
||||
chat_id = None
|
||||
ai_message = ""
|
||||
|
||||
# 处理SSE流
|
||||
for line in response.iter_lines():
|
||||
if not line:
|
||||
continue
|
||||
|
||||
line = line.decode('utf-8')
|
||||
|
||||
# 解析SSE事件
|
||||
if line.startswith('event: '):
|
||||
event_type = line[7:].strip()
|
||||
elif line.startswith('data: '):
|
||||
data_str = line[6:].strip()
|
||||
|
||||
if data_str == '[DONE]':
|
||||
print("\n\n✅ 对话流结束")
|
||||
break
|
||||
|
||||
try:
|
||||
event_data = json.loads(data_str)
|
||||
|
||||
if event_type == 'conversation.chat.created':
|
||||
conversation_id = event_data.get('conversation_id')
|
||||
chat_id = event_data.get('chat_id')
|
||||
print(f"\n📝 对话已创建:")
|
||||
print(f" conversation_id: {conversation_id}")
|
||||
print(f" chat_id: {chat_id}")
|
||||
print(f"\n🤖 AI回复: ", end="", flush=True)
|
||||
|
||||
elif event_type == 'message.delta':
|
||||
content = event_data.get('content', '')
|
||||
ai_message += content
|
||||
print(content, end="", flush=True)
|
||||
|
||||
elif event_type == 'message.completed':
|
||||
print()
|
||||
|
||||
elif event_type == 'conversation.completed':
|
||||
token_count = event_data.get('token_count', 0)
|
||||
print(f"\n\n📊 对话完成,Token用量: {token_count}")
|
||||
|
||||
elif event_type == 'error':
|
||||
error = event_data.get('error', '未知错误')
|
||||
print(f"\n\n❌ 对话错误: {error}")
|
||||
return None
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"\n⚠️ JSON解析错误: {e}")
|
||||
|
||||
print("-" * 80)
|
||||
print(f"\n✅ SSE对话测试成功")
|
||||
print(f" conversation_id: {conversation_id}")
|
||||
print(f" AI消息长度: {len(ai_message)} 字符")
|
||||
|
||||
return conversation_id
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ SSE测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def test_follow_up_message(token, conversation_id):
|
||||
"""测试后续消息(不包含场景信息)"""
|
||||
print("\n" + "=" * 60)
|
||||
print("5. 测试后续消息")
|
||||
print("=" * 60)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 后续消息
|
||||
payload = {
|
||||
"user_message": "我们专注于轻医美行业,提供从设备到培训的一站式解决方案",
|
||||
"conversation_id": conversation_id,
|
||||
"is_first": False
|
||||
}
|
||||
|
||||
print(f"\n发送后续消息(不包含场景信息):")
|
||||
print(f" 用户消息: {payload['user_message']}")
|
||||
print(f" is_first: {payload['is_first']}")
|
||||
print(f" conversation_id: {conversation_id}")
|
||||
print(f"\n等待AI回复...")
|
||||
print("-" * 80)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/v1/practice/start",
|
||||
headers=headers,
|
||||
json=payload,
|
||||
stream=True,
|
||||
timeout=180
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ SSE请求失败: {response.status_code}")
|
||||
return False
|
||||
|
||||
ai_message = ""
|
||||
|
||||
# 处理SSE流
|
||||
for line in response.iter_lines():
|
||||
if not line:
|
||||
continue
|
||||
|
||||
line = line.decode('utf-8')
|
||||
|
||||
if line.startswith('event: '):
|
||||
event_type = line[7:].strip()
|
||||
elif line.startswith('data: '):
|
||||
data_str = line[6:].strip()
|
||||
|
||||
if data_str == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
event_data = json.loads(data_str)
|
||||
|
||||
if event_type == 'message.delta':
|
||||
content = event_data.get('content', '')
|
||||
ai_message += content
|
||||
print(content, end="", flush=True)
|
||||
|
||||
elif event_type == 'conversation.completed':
|
||||
token_count = event_data.get('token_count', 0)
|
||||
print(f"\n\n📊 对话完成,Token用量: {token_count}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
print("\n" + "-" * 80)
|
||||
print(f"\n✅ 后续消息测试成功")
|
||||
print(f" AI消息长度: {len(ai_message)} 字符")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 后续消息测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""主测试流程"""
|
||||
print("\n" + "=" * 60)
|
||||
print("陪练功能API测试")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 1. 登录
|
||||
token = login()
|
||||
|
||||
# 2. 测试场景列表
|
||||
if not test_get_scenes(token):
|
||||
print("\n❌ 场景列表测试失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 3. 测试场景详情
|
||||
scene = test_get_scene_detail(token, scene_id=1)
|
||||
if not scene:
|
||||
print("\n❌ 场景详情测试失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 4. 测试SSE对话
|
||||
conversation_id = test_sse_chat(token, scene)
|
||||
if not conversation_id:
|
||||
print("\n❌ SSE对话测试失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 5. 测试后续消息
|
||||
if not test_follow_up_message(token, conversation_id):
|
||||
print("\n❌ 后续消息测试失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 全部测试通过
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 所有测试通过!")
|
||||
print("=" * 60)
|
||||
print("\n测试总结:")
|
||||
print(" ✅ 登录认证")
|
||||
print(" ✅ 场景列表查询")
|
||||
print(" ✅ 场景详情查询")
|
||||
print(" ✅ SSE流式对话(首次消息)")
|
||||
print(" ✅ 后续消息(保持上下文)")
|
||||
print("\n陪练中心入口功能开发完成!")
|
||||
print("=" * 60)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n⚠️ 用户中断测试")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试过程出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
338
tests/test_practice_records_frontend.html
Normal file
338
tests/test_practice_records_frontend.html
Normal file
@@ -0,0 +1,338 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>陪练记录API测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
border-bottom: 3px solid #409eff;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.section {
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.form-group {
|
||||
margin: 15px 0;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
padding: 8px;
|
||||
width: 300px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background: #66b1ff;
|
||||
}
|
||||
.success { color: #67c23a; }
|
||||
.error { color: #f56c6c; }
|
||||
.warning { color: #e6a23c; }
|
||||
.info { color: #909399; }
|
||||
pre {
|
||||
background: #2d2d2d;
|
||||
color: #f8f8f2;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
.status-item {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
th, td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>陪练记录列表 - API测试工具</h1>
|
||||
|
||||
<div class="section">
|
||||
<h3>1. 登录</h3>
|
||||
<div class="form-group">
|
||||
<label>用户名:</label>
|
||||
<input type="text" id="username" value="admin" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码:</label>
|
||||
<input type="password" id="password" value="admin123" />
|
||||
</div>
|
||||
<button onclick="login()">登录</button>
|
||||
<button onclick="checkLoginStatus()">检查登录状态</button>
|
||||
<button onclick="logout()">退出登录</button>
|
||||
<div id="loginStatus" class="status-item" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>2. 获取陪练记录</h3>
|
||||
<button onclick="getPracticeRecords()">获取陪练记录列表</button>
|
||||
<button onclick="getPracticeStats()">获取统计数据</button>
|
||||
<div id="recordsStatus" class="status-item" style="display:none;"></div>
|
||||
<div id="recordsTable"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>3. 调试信息</h3>
|
||||
<button onclick="showDebugInfo()">显示调试信息</button>
|
||||
<pre id="debugInfo" style="display:none;"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = window.location.origin;
|
||||
|
||||
// 显示状态信息
|
||||
function showStatus(elementId, message, type = 'info') {
|
||||
const el = document.getElementById(elementId);
|
||||
el.style.display = 'block';
|
||||
el.className = `status-item ${type}`;
|
||||
el.innerHTML = message;
|
||||
}
|
||||
|
||||
// 登录
|
||||
async function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
try {
|
||||
showStatus('loginStatus', '正在登录...', 'info');
|
||||
|
||||
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200) {
|
||||
const token = result.data.token.access_token;
|
||||
const refreshToken = result.data.token.refresh_token;
|
||||
localStorage.setItem('access_token', token);
|
||||
localStorage.setItem('refresh_token', refreshToken);
|
||||
localStorage.setItem('user_info', JSON.stringify(result.data.user));
|
||||
|
||||
showStatus('loginStatus',
|
||||
`✓ 登录成功!<br>
|
||||
用户: ${result.data.user.username} (${result.data.user.full_name})<br>
|
||||
Token已保存到localStorage`,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
showStatus('loginStatus', `✗ 登录失败: ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('loginStatus', `✗ 登录错误: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
function checkLoginStatus() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const userInfo = localStorage.getItem('user_info');
|
||||
|
||||
if (token && userInfo) {
|
||||
const user = JSON.parse(userInfo);
|
||||
showStatus('loginStatus',
|
||||
`✓ 已登录<br>
|
||||
用户: ${user.username} (${user.full_name})<br>
|
||||
Token: ${token.substring(0, 30)}...`,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
showStatus('loginStatus', '✗ 未登录', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function logout() {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user_info');
|
||||
showStatus('loginStatus', '✓ 已退出登录', 'info');
|
||||
}
|
||||
|
||||
// 获取陪练记录
|
||||
async function getPracticeRecords() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
if (!token) {
|
||||
showStatus('recordsStatus', '✗ 请先登录', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showStatus('recordsStatus', '正在获取陪练记录...', 'info');
|
||||
|
||||
const response = await fetch(`${API_BASE}/api/v1/practice/sessions/list?page=1&size=20`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200) {
|
||||
const items = result.data.items || [];
|
||||
const total = result.data.total || 0;
|
||||
|
||||
showStatus('recordsStatus',
|
||||
`✓ 成功获取陪练记录!<br>
|
||||
总记录数: ${total}<br>
|
||||
当前页记录数: ${items.length}`,
|
||||
'success'
|
||||
);
|
||||
|
||||
// 显示表格
|
||||
displayRecordsTable(items);
|
||||
} else {
|
||||
showStatus('recordsStatus', `✗ 获取失败: ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('recordsStatus', `✗ 请求错误: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
async function getPracticeStats() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
if (!token) {
|
||||
showStatus('recordsStatus', '✗ 请先登录', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/v1/practice/stats`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.code === 200) {
|
||||
const data = result.data;
|
||||
showStatus('recordsStatus',
|
||||
`✓ 统计数据:<br>
|
||||
总次数: ${data.total_count}<br>
|
||||
平均分: ${data.avg_score.toFixed(1)}<br>
|
||||
总时长: ${data.total_duration_hours}小时<br>
|
||||
本月进步: ${data.month_improvement}%`,
|
||||
'success'
|
||||
);
|
||||
} else {
|
||||
showStatus('recordsStatus', `✗ 获取失败: ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showStatus('recordsStatus', `✗ 请求错误: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示记录表格
|
||||
function displayRecordsTable(items) {
|
||||
if (items.length === 0) {
|
||||
document.getElementById('recordsTable').innerHTML = '<p class="warning">⚠️ 没有陪练记录</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<table><thead><tr>';
|
||||
html += '<th>会话ID</th><th>场景名称</th><th>开始时间</th><th>时长(秒)</th><th>对话轮数</th><th>评分</th><th>结果</th>';
|
||||
html += '</tr></thead><tbody>';
|
||||
|
||||
items.forEach(item => {
|
||||
html += '<tr>';
|
||||
html += `<td>${item.session_id}</td>`;
|
||||
html += `<td>${item.scene_name}</td>`;
|
||||
html += `<td>${item.start_time}</td>`;
|
||||
html += `<td>${item.duration_seconds}</td>`;
|
||||
html += `<td>${item.turns}</td>`;
|
||||
html += `<td>${item.total_score}</td>`;
|
||||
html += `<td>${item.result}</td>`;
|
||||
html += '</tr>';
|
||||
});
|
||||
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('recordsTable').innerHTML = html;
|
||||
}
|
||||
|
||||
// 显示调试信息
|
||||
function showDebugInfo() {
|
||||
const debugEl = document.getElementById('debugInfo');
|
||||
debugEl.style.display = 'block';
|
||||
|
||||
const info = {
|
||||
'API Base URL': API_BASE,
|
||||
'localStorage.access_token': localStorage.getItem('access_token') ? localStorage.getItem('access_token').substring(0, 50) + '...' : 'null',
|
||||
'localStorage.user_info': localStorage.getItem('user_info') || 'null',
|
||||
'Current URL': window.location.href,
|
||||
'User Agent': navigator.userAgent
|
||||
};
|
||||
|
||||
debugEl.textContent = JSON.stringify(info, null, 2);
|
||||
}
|
||||
|
||||
// 页面加载时检查登录状态
|
||||
window.onload = function() {
|
||||
checkLoginStatus();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
123
tests/test_practice_scenes_api.html
Normal file
123
tests/test_practice_scenes_api.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>测试陪练场景API</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; }
|
||||
button { padding: 10px 20px; margin: 5px; cursor: pointer; }
|
||||
pre { white-space: pre-wrap; word-wrap: break-word; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>陪练场景API测试</h1>
|
||||
|
||||
<div>
|
||||
<button onclick="testLogin()">1. 测试登录</button>
|
||||
<button onclick="testGetScenes()">2. 获取场景列表</button>
|
||||
<button onclick="testWithBrowserToken()">3. 使用浏览器Token测试</button>
|
||||
</div>
|
||||
|
||||
<div class="result" id="result"></div>
|
||||
|
||||
<script>
|
||||
let token = '';
|
||||
const API_BASE_URL = 'http://localhost:8000';
|
||||
const resultDiv = document.getElementById('result');
|
||||
|
||||
function log(message, data = null) {
|
||||
console.log(message, data);
|
||||
let html = `<div><strong>${message}</strong></div>`;
|
||||
if (data) {
|
||||
html += `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||||
}
|
||||
resultDiv.innerHTML = html + resultDiv.innerHTML;
|
||||
}
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
log('正在登录...');
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
log('登录响应:', data);
|
||||
|
||||
if (data.code === 200 && data.data.token) {
|
||||
token = data.data.token.access_token;
|
||||
log('✅ 登录成功!Token已保存');
|
||||
localStorage.setItem('access_token', token);
|
||||
} else {
|
||||
log('❌ 登录失败', data);
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 登录错误', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function testGetScenes() {
|
||||
if (!token) {
|
||||
log('⚠️ 请先登录获取Token');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log('正在获取场景列表...');
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/practice/scenes?page=1&size=20`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
log('场景列表响应:', data);
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
log(`✅ 获取成功!共 ${data.data.total} 个场景,当前页 ${data.data.items.length} 个`);
|
||||
log('场景列表:', data.data.items.map(s => ({ id: s.id, name: s.name, type: s.type })));
|
||||
} else {
|
||||
log('❌ 获取失败', data);
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ 请求错误', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function testWithBrowserToken() {
|
||||
const browserToken = localStorage.getItem('access_token');
|
||||
if (!browserToken) {
|
||||
log('⚠️ 浏览器中未找到Token,请先在系统中登录');
|
||||
return;
|
||||
}
|
||||
|
||||
token = browserToken;
|
||||
log('✅ 使用浏览器Token');
|
||||
await testGetScenes();
|
||||
}
|
||||
|
||||
// 页面加载时检查Token
|
||||
window.onload = () => {
|
||||
const browserToken = localStorage.getItem('access_token');
|
||||
if (browserToken) {
|
||||
token = browserToken;
|
||||
log('✅ 检测到浏览器Token,可直接使用');
|
||||
} else {
|
||||
log('ℹ️ 未检测到Token,请先登录');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
117
tests/test_practice_sessions_api.py
Normal file
117
tests/test_practice_sessions_api.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试陪练记录列表API
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
|
||||
# API基础地址
|
||||
BASE_URL = "http://120.79.247.16"
|
||||
|
||||
def login(username: str, password: str):
|
||||
"""登录获取token"""
|
||||
url = f"https://aiedu.ireborn.com.cn/api/v1/auth/login"
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password
|
||||
}
|
||||
print(f"正在登录: {username}")
|
||||
response = requests.post(url, json=data, verify=False)
|
||||
print(f"登录响应: {response.status_code}")
|
||||
result = response.json()
|
||||
print(f"登录结果: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
|
||||
if response.status_code == 200 and result.get('code') == 200:
|
||||
token = result['data'].get('access_token')
|
||||
print(f"✓ 登录成功,Token: {token[:20]}...")
|
||||
return token
|
||||
else:
|
||||
print(f"✗ 登录失败")
|
||||
return None
|
||||
|
||||
def get_practice_sessions(token: str):
|
||||
"""获取陪练记录列表"""
|
||||
url = f"https://aiedu.ireborn.com.cn/api/v1/practice/sessions/list"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
params = {
|
||||
"page": 1,
|
||||
"size": 20
|
||||
}
|
||||
|
||||
print(f"\n正在获取陪练记录列表...")
|
||||
print(f"请求URL: {url}")
|
||||
print(f"请求参数: {params}")
|
||||
|
||||
response = requests.get(url, headers=headers, params=params, verify=False)
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
|
||||
result = response.json()
|
||||
print(f"响应结果: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
|
||||
if response.status_code == 200 and result.get('code') == 200:
|
||||
data = result['data']
|
||||
print(f"\n✓ 获取成功")
|
||||
print(f"总记录数: {data.get('total', 0)}")
|
||||
print(f"当前页记录数: {len(data.get('items', []))}")
|
||||
|
||||
# 打印前5条记录
|
||||
items = data.get('items', [])
|
||||
if items:
|
||||
print(f"\n前{min(5, len(items))}条记录:")
|
||||
for i, item in enumerate(items[:5], 1):
|
||||
print(f"{i}. {item.get('session_id')} - {item.get('scene_name')} - {item.get('start_time')}")
|
||||
else:
|
||||
print("\n⚠️ 记录列表为空!")
|
||||
else:
|
||||
print(f"\n✗ 获取失败")
|
||||
|
||||
def get_practice_stats(token: str):
|
||||
"""获取陪练统计数据"""
|
||||
url = f"https://aiedu.ireborn.com.cn/api/v1/practice/stats"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
|
||||
print(f"\n正在获取陪练统计数据...")
|
||||
response = requests.get(url, headers=headers, verify=False)
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
|
||||
result = response.json()
|
||||
print(f"响应结果: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("测试陪练记录列表API")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试多个用户
|
||||
test_users = [
|
||||
("admin", "admin123"), # admin
|
||||
("consultant_001", "admin123"), # consultant_001
|
||||
("consultant_002", "admin123"), # consultant_002
|
||||
]
|
||||
|
||||
for username, password in test_users:
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"测试用户: {username}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
# 登录
|
||||
token = login(username, password)
|
||||
if not token:
|
||||
print(f"用户 {username} 登录失败,跳过")
|
||||
continue
|
||||
|
||||
# 获取陪练记录
|
||||
get_practice_sessions(token)
|
||||
|
||||
# 获取统计数据
|
||||
get_practice_stats(token)
|
||||
|
||||
print("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
164
tests/test_score_report_api.py
Normal file
164
tests/test_score_report_api.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试成绩报告和错题本API
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# 测试账号token(需要先登录获取)
|
||||
# 这里假设已经有token,实际使用时需要先登录
|
||||
TOKEN = ""
|
||||
|
||||
def login():
|
||||
"""登录获取token"""
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/v1/auth/login",
|
||||
json={
|
||||
"username": "testuser",
|
||||
"password": "TestPass123!"
|
||||
}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get('code') == 200:
|
||||
return data['data']['access_token']
|
||||
return None
|
||||
|
||||
def test_exam_report(token):
|
||||
"""测试成绩报告API"""
|
||||
print("\n=== 测试成绩报告API ===")
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/api/v1/exams/statistics/report",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
print(f"状态码: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"响应code: {data.get('code')}")
|
||||
print(f"响应message: {data.get('message')}")
|
||||
|
||||
if data.get('code') == 200:
|
||||
report = data.get('data', {})
|
||||
print(f"\n概览数据:")
|
||||
print(f" - 平均成绩: {report.get('overview', {}).get('avg_score')}")
|
||||
print(f" - 考试总数: {report.get('overview', {}).get('total_exams')}")
|
||||
print(f" - 及格率: {report.get('overview', {}).get('pass_rate')}")
|
||||
|
||||
print(f"\n科目数量: {len(report.get('subjects', []))}")
|
||||
print(f"最近考试数量: {len(report.get('recent_exams', []))}")
|
||||
|
||||
# 显示第一条最近考试记录
|
||||
if report.get('recent_exams'):
|
||||
exam = report['recent_exams'][0]
|
||||
print(f"\n第一条考试记录:")
|
||||
print(f" - ID: {exam.get('id')}")
|
||||
print(f" - 课程: {exam.get('course_name')}")
|
||||
print(f" - 三轮得分: {exam.get('round_scores')}")
|
||||
else:
|
||||
print(f"业务错误: {data.get('message')}")
|
||||
else:
|
||||
print(f"HTTP错误: {response.text}")
|
||||
|
||||
def test_mistakes_statistics(token):
|
||||
"""测试错题统计API"""
|
||||
print("\n=== 测试错题统计API ===")
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/api/v1/exams/mistakes/statistics",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
print(f"状态码: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"响应code: {data.get('code')}")
|
||||
|
||||
if data.get('code') == 200:
|
||||
stats = data.get('data', {})
|
||||
print(f"\n错题总数: {stats.get('total')}")
|
||||
print(f"按课程统计: {len(stats.get('by_course', []))} 个课程")
|
||||
print(f"按题型统计: {len(stats.get('by_type', []))} 种题型")
|
||||
print(f"时间统计:")
|
||||
print(f" - 最近一周: {stats.get('by_time', {}).get('week')}")
|
||||
print(f" - 最近一月: {stats.get('by_time', {}).get('month')}")
|
||||
print(f" - 最近三月: {stats.get('by_time', {}).get('quarter')}")
|
||||
|
||||
# 显示按课程统计
|
||||
if stats.get('by_course'):
|
||||
print(f"\n按课程统计详情:")
|
||||
for course in stats['by_course'][:3]:
|
||||
print(f" - {course.get('course_name')}: {course.get('count')}道")
|
||||
else:
|
||||
print(f"业务错误: {data.get('message')}")
|
||||
else:
|
||||
print(f"HTTP错误: {response.text}")
|
||||
|
||||
def test_mistakes_list(token):
|
||||
"""测试错题列表API"""
|
||||
print("\n=== 测试错题列表API ===")
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 测试1:查询所有错题(第1页)
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/api/v1/exams/mistakes/list",
|
||||
headers=headers,
|
||||
params={"page": 1, "size": 5}
|
||||
)
|
||||
|
||||
print(f"状态码: {response.status_code}")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"响应code: {data.get('code')}")
|
||||
|
||||
if data.get('code') == 200:
|
||||
result = data.get('data', {})
|
||||
print(f"\n总数: {result.get('total')}")
|
||||
print(f"当前页: {result.get('page')}")
|
||||
print(f"每页数量: {result.get('size')}")
|
||||
print(f"总页数: {result.get('pages')}")
|
||||
print(f"本页错题数: {len(result.get('items', []))}")
|
||||
|
||||
# 显示前2条错题
|
||||
if result.get('items'):
|
||||
print(f"\n前2条错题:")
|
||||
for item in result['items'][:2]:
|
||||
print(f" - ID: {item.get('id')}")
|
||||
print(f" 课程: {item.get('course_name')}")
|
||||
print(f" 题型: {item.get('question_type')}")
|
||||
print(f" 题目: {item.get('question_content')[:50]}...")
|
||||
else:
|
||||
print(f"业务错误: {data.get('message')}")
|
||||
else:
|
||||
print(f"HTTP错误: {response.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("开始测试成绩报告和错题本API...")
|
||||
|
||||
# 1. 登录获取token
|
||||
print("\n1. 登录获取token...")
|
||||
token = login()
|
||||
if not token:
|
||||
print("❌ 登录失败")
|
||||
exit(1)
|
||||
print(f"✅ 登录成功,token: {token[:20]}...")
|
||||
|
||||
# 2. 测试成绩报告API
|
||||
test_exam_report(token)
|
||||
|
||||
# 3. 测试错题统计API
|
||||
test_mistakes_statistics(token)
|
||||
|
||||
# 4. 测试错题列表API
|
||||
test_mistakes_list(token)
|
||||
|
||||
print("\n\n测试完成!")
|
||||
|
||||
102
tests/test_update_profile.py
Normal file
102
tests/test_update_profile.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
测试更新用户个人信息(包括手机号、学校、专业)
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
# API配置
|
||||
API_BASE_URL = "http://localhost:8000"
|
||||
|
||||
def test_update_profile():
|
||||
"""测试更新个人信息"""
|
||||
|
||||
# 1. 先登录获取token
|
||||
print("1. 登录获取token...")
|
||||
login_data = {
|
||||
"username": "superadmin",
|
||||
"password": "Superadmin123!"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/api/v1/auth/login",
|
||||
json=login_data
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"登录失败: {response.text}")
|
||||
return
|
||||
|
||||
token = response.json()["data"]["token"]["access_token"]
|
||||
print(f"登录成功,获取到token")
|
||||
|
||||
# 2. 获取当前用户信息
|
||||
print("\n2. 获取当前用户信息...")
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{API_BASE_URL}/api/v1/users/me",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
user_data = response.json()["data"]
|
||||
print(f"当前用户信息:")
|
||||
print(f" 用户名: {user_data.get('username')}")
|
||||
print(f" 邮箱: {user_data.get('email')}")
|
||||
print(f" 手机号: {user_data.get('phone', '未设置')}")
|
||||
print(f" 学校: {user_data.get('school', '未设置')}")
|
||||
print(f" 专业: {user_data.get('major', '未设置')}")
|
||||
else:
|
||||
print(f"获取用户信息失败: {response.text}")
|
||||
return
|
||||
|
||||
# 3. 更新用户信息
|
||||
print("\n3. 更新用户信息(包括手机号、学校、专业)...")
|
||||
update_data = {
|
||||
"phone": "13800138000",
|
||||
"school": "清华大学",
|
||||
"major": "计算机科学与技术",
|
||||
"bio": "这是一个测试用的个人简介"
|
||||
}
|
||||
|
||||
response = requests.put(
|
||||
f"{API_BASE_URL}/api/v1/users/me",
|
||||
headers=headers,
|
||||
json=update_data
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("更新成功!")
|
||||
updated_data = response.json()["data"]
|
||||
print(f"更新后的信息:")
|
||||
print(f" 手机号: {updated_data.get('phone', '未设置')}")
|
||||
print(f" 学校: {updated_data.get('school', '未设置')}")
|
||||
print(f" 专业: {updated_data.get('major', '未设置')}")
|
||||
print(f" 个人简介: {updated_data.get('bio', '未设置')}")
|
||||
else:
|
||||
print(f"更新失败: {response.text}")
|
||||
|
||||
# 4. 再次获取信息验证
|
||||
print("\n4. 再次获取用户信息验证更新...")
|
||||
response = requests.get(
|
||||
f"{API_BASE_URL}/api/v1/users/me",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
user_data = response.json()["data"]
|
||||
print(f"验证结果:")
|
||||
print(f" 手机号: {user_data.get('phone', '未设置')}")
|
||||
print(f" 学校: {user_data.get('school', '未设置')}")
|
||||
print(f" 专业: {user_data.get('major', '未设置')}")
|
||||
else:
|
||||
print(f"获取用户信息失败: {response.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("测试更新用户个人信息API...")
|
||||
test_update_profile()
|
||||
print("\n测试完成!")
|
||||
Reference in New Issue
Block a user