#!/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}")