feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user