fix: 第一轮修复 - 记忆管理/IoT操控/历史消息持久化/动作消息/链路优化/安全配置
- 修复记忆管理数据库连接不可用 (ai-core重编译+Unicode修复) - 修复IoT子会话工具调用链路日志缺失 - 新增最终审查子会话(review_provider) 支持消息格式解析拆分 - 实现历史消息持久化(后端存储+前端分页加载) - 前端新增动作消息(ActionMessage)类型和渲染 - 优化对话链路速度(非阻塞子会话+快速问候通道) - JWT密钥环境变量化(无默认值启动panic) - Token自动刷新机制(401拦截器+refresh接口) - WebSocket指数退避重连(jitter+最大10次) - localStorage清理一致性(cyrene_前缀+版本检查) - IoT环境变量统一为IOT_SERVICE_URL
This commit is contained in:
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Round 12: 聊天 E2E + 子服务连通性测试"""
|
||||
import json, urllib.request, urllib.error, sys
|
||||
|
||||
BASE = "http://localhost:8080"
|
||||
TOKEN_FILE = "/tmp/cyrene_test_token.txt"
|
||||
SID_FILE = "/tmp/cyrene_test_sid.txt"
|
||||
|
||||
def req(method, path, body=None, token=None, timeout=10):
|
||||
url = f"{BASE}{path}"
|
||||
data = json.dumps(body).encode() if body else None
|
||||
r = urllib.request.Request(url, data=data, method=method)
|
||||
r.add_header("Content-Type", "application/json")
|
||||
if token:
|
||||
r.add_header("Authorization", f"Bearer {token}")
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=timeout)
|
||||
return resp.status, resp.read().decode()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode()
|
||||
except Exception as e:
|
||||
return 0, str(e)
|
||||
|
||||
# ====== Step 1: Admin Login ======
|
||||
print("=== Step 1: Admin Login ===")
|
||||
status, body = req("POST", "/api/v1/auth/login",
|
||||
{"username":"yeij0942","password":"Jiang1143218570"})
|
||||
print(f" status={status}")
|
||||
d = json.loads(body)
|
||||
token = d.get("token", "")
|
||||
print(f" user_id={d.get('user_id')} has_token={'token' in d} token_len={len(token)}")
|
||||
|
||||
# ====== Step 2: 所有服务健康检查 ======
|
||||
print("\n=== Step 2: 子服务健康检查 ===")
|
||||
services = [
|
||||
("ai-core (8081)", "http://localhost:8081/api/v1/health"),
|
||||
("memory-service (8091)", "http://localhost:8091/api/v1/health"),
|
||||
("tool-engine (8092)", "http://localhost:8092/api/v1/health"),
|
||||
("voice-service (8093)", "http://localhost:8093/api/v1/health"),
|
||||
("gateway (8080)", "http://localhost:8080/api/v1/health"),
|
||||
]
|
||||
for name, url in services:
|
||||
r = urllib.request.Request(url)
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=5)
|
||||
body = resp.read().decode()
|
||||
print(f" [{resp.status}] {name}: {body[:120]}")
|
||||
except Exception as e:
|
||||
print(f" [FAIL] {name}: {e}")
|
||||
|
||||
# ====== Step 3: 获取/创建 Session ======
|
||||
print("\n=== Step 3: Session 管理 ===")
|
||||
status, body = req("GET", "/api/v1/sessions", token=token)
|
||||
print(f" GET /sessions status={status}")
|
||||
try:
|
||||
sessions = json.loads(body)
|
||||
if isinstance(sessions, list) and len(sessions) > 0:
|
||||
sid = sessions[0].get("id", "")
|
||||
print(f" existing session: {sid}")
|
||||
else:
|
||||
# 创建
|
||||
status, body = req("POST", "/api/v1/sessions", {"user_id": "admin"}, token=token)
|
||||
print(f" POST /sessions status={status} body={body[:200]}")
|
||||
sid = json.loads(body).get("id", "")
|
||||
print(f" new session: {sid}")
|
||||
except Exception as e:
|
||||
print(f" parse error: {e}")
|
||||
sid = ""
|
||||
|
||||
# ====== Step 4: 聊天消息发送 (测试 ai-core 转发) ======
|
||||
print(f"\n=== Step 4: 聊天消息发送 (session={sid}) ===")
|
||||
if sid and token:
|
||||
msg_body = {
|
||||
"session_id": sid,
|
||||
"message": "你好,请简单介绍一下你自己",
|
||||
"mode": "text"
|
||||
}
|
||||
status, body = req("POST", "/api/v1/chat", msg_body, token=token)
|
||||
print(f" POST /api/v1/chat status={status}")
|
||||
if status == 404:
|
||||
print(f" WARNING: /api/v1/chat 端点返回 404 - 端点可能未注册!")
|
||||
# 检查 gateway 日志
|
||||
import subprocess
|
||||
result = subprocess.run(["grep", "-i", "chat", "/tmp/gateway.log"], capture_output=True, text=True)
|
||||
print(f" gateway log (chat): {result.stdout[:500]}")
|
||||
else:
|
||||
print(f" response: {body[:500]}")
|
||||
else:
|
||||
print(" SKIPPED: no session or token")
|
||||
|
||||
# ====== Step 5: 直接调用 ai-core chat ======
|
||||
print("\n=== Step 5: 直接调用 ai-core /api/v1/chat ===")
|
||||
ai_body = {
|
||||
"user_id": "admin",
|
||||
"session_id": sid or "test_session_001",
|
||||
"message": "Hello",
|
||||
"mode": "text"
|
||||
}
|
||||
r = urllib.request.Request("http://localhost:8081/api/v1/chat",
|
||||
data=json.dumps(ai_body).encode(),
|
||||
method="POST")
|
||||
r.add_header("Content-Type", "application/json")
|
||||
r.add_header("Accept", "text/event-stream")
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=30)
|
||||
print(f" ai-core status={resp.status}")
|
||||
# 读取前5行 SSE
|
||||
lines = []
|
||||
for i in range(5):
|
||||
line = resp.readline().decode().strip()
|
||||
lines.append(line)
|
||||
print(f" first 5 SSE lines: {lines}")
|
||||
except Exception as e:
|
||||
print(f" ai-core FAIL: {e}")
|
||||
|
||||
# ====== Step 6: memory-service 直调测试 ======
|
||||
print("\n=== Step 6: memory-service 直调测试 ===")
|
||||
r = urllib.request.Request("http://localhost:8091/api/v1/memory/search",
|
||||
data=json.dumps({"query": "test", "user_id": "admin", "limit": 5}).encode(),
|
||||
method="POST")
|
||||
r.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=5)
|
||||
print(f" memory/search status={resp.status} body={resp.read().decode()[:200]}")
|
||||
except Exception as e:
|
||||
print(f" memory/search FAIL: {e}")
|
||||
|
||||
# ====== Step 7: tool-engine 直调测试 ======
|
||||
print("\n=== Step 7: tool-engine 直调测试 ===")
|
||||
r = urllib.request.Request("http://localhost:8092/api/v1/tools",
|
||||
data=json.dumps({"action": "list"}).encode(),
|
||||
method="POST")
|
||||
r.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=5)
|
||||
print(f" tools/list status={resp.status} body={resp.read().decode()[:200]}")
|
||||
except Exception as e:
|
||||
print(f" tools/list FAIL: {e}")
|
||||
|
||||
# ====== Step 8: Gateway 路由检查 ======
|
||||
print("\n=== Step 8: Gateway 路由检查 (哪些chat端点注册了) ===")
|
||||
for path in ["/api/v1/chat", "/api/v1/chat/stream", "/api/v1/chat/send"]:
|
||||
status, _ = req("POST", path, {"message": "test"}, token=token)
|
||||
print(f" POST {path}: {status}")
|
||||
|
||||
# ====== Step 9: WebSocket 端点检查 ======
|
||||
print("\n=== Step 9: WebSocket 端点基本测试 ===")
|
||||
import socket
|
||||
# 先检查 /ws/chat 是否可访问 (HTTP层面)
|
||||
r = urllib.request.Request("http://localhost:8080/ws/chat")
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=3)
|
||||
print(f" GET /ws/chat (no upgrade): {resp.status}")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f" GET /ws/chat HTTP status: {e.code} body={e.read().decode()[:100]}")
|
||||
except Exception as e:
|
||||
print(f" GET /ws/chat: {e}")
|
||||
|
||||
# 保存 token 和 sid
|
||||
with open(TOKEN_FILE, "w") as f:
|
||||
f.write(token)
|
||||
with open(SID_FILE, "w") as f:
|
||||
f.write(sid or "")
|
||||
print(f"\n[DONE] Token saved to {TOKEN_FILE}, sid={sid}")
|
||||
Reference in New Issue
Block a user