""" 健康管家 - 全流程端到端测试 模拟真实用户操作:注册→登录→各Agent对话→数据录入→查询验证 """ import urllib.request, urllib.parse, json, sys, time, os BASE = "http://localhost:5000" PASSED = 0 FAILED = 0 TOKEN = None def api(method, path, data=None, token=None, files=None): """调用后端 API""" url = f"{BASE}{path}" headers = {"Content-Type": "application/json"} if token: headers["Authorization"] = f"Bearer {token}" body = None if data: body = json.dumps(data, ensure_ascii=False).encode("utf-8") req = urllib.request.Request(url, data=body, headers=headers, method=method) try: resp = urllib.request.urlopen(req, timeout=30) return json.loads(resp.read().decode("utf-8")) except Exception as e: return {"error": str(e), "code": -1} def check(name, condition, detail=""): global PASSED, FAILED if condition: PASSED += 1 print(f" [PASS] {name}") else: FAILED += 1 print(f" [FAIL] {name} {detail}") def login(phone="13800000001"): """发送验证码 + 登录,返回 token""" sms = api("POST", "/api/auth/send-sms", {"phone": phone}) code = sms.get("data", {}).get("devCode", "") if not code: return None result = api("POST", "/api/auth/login", {"phone": phone, "smsCode": code}) return result.get("data", {}).get("accessToken") def sse_stream(token, agent_type, message): """连接 SSE 端点,返回所有事件""" url = f"{BASE}/api/ai/{agent_type}/chat?message={urllib.parse.quote(message)}&token={urllib.parse.quote(token or '')}" req = urllib.request.Request(url) events = [] try: resp = urllib.request.urlopen(req, timeout=60) for line_bytes in resp: line = line_bytes.decode("utf-8").strip() if line.startswith("data: "): data = line[6:] if data == "[DONE]": break try: events.append(json.loads(data)) except: pass except Exception as e: events.append({"error": str(e)}) return events def section(title): print(f"\n{'='*60}") print(f" {title}") print(f"{'='*60}") # ============================================================ section("1. 认证流程") # ============================================================ print(" 1.1 发送验证码...") sms = api("POST", "/api/auth/send-sms", {"phone": "13800000001"}) check("发送验证码", sms.get("code") == 0, str(sms.get("message",""))) code = sms.get("data", {}).get("devCode", "") print(" 1.2 验证码登录...") login_result = api("POST", "/api/auth/login", {"phone": "13800000001", "smsCode": code}) check("登录成功", login_result.get("code") == 0) TOKEN = login_result.get("data", {}).get("accessToken", "") REFRESH = login_result.get("data", {}).get("refreshToken", "") check("返回accessToken", len(TOKEN) > 50) check("返回refreshToken", len(REFRESH) > 20) print(" 1.3 刷新Token...") refresh_result = api("POST", "/api/auth/refresh", {"refreshToken": REFRESH}) check("刷新Token成功", refresh_result.get("code") == 0) check("下发新Token", len(refresh_result.get("data", {}).get("accessToken", "")) > 50) print(" 1.4 登出...") logout_result = api("POST", "/api/auth/logout", {"refreshToken": REFRESH}) check("登出成功", logout_result.get("code") == 0) # 重新登录获取token TOKEN = login() # ============================================================ section("2. 用户与档案") # ============================================================ profile = api("GET", "/api/user/profile", token=TOKEN) check("获取个人信息", profile.get("code") == 0) archive = api("GET", "/api/user/health-archive", token=TOKEN) check("获取健康档案", archive.get("code") == 0) check("档案有诊断信息", archive.get("data", {}).get("diagnosis") is not None if archive.get("data") else False, "诊断=" + str(archive.get("data", {}).get("diagnosis", "无"))) # ============================================================ section("3. 健康数据 CRUD") # ============================================================ print(" 3.1 录入血压...") bp = api("POST", "/api/health-records", token=TOKEN, data={ "type": "BloodPressure", "systolic": 128, "diastolic": 82, "unit": "mmHg", "source": "Manual" }) check("录入血压", bp.get("code") == 0, str(bp.get("message",""))) print(" 3.2 录入心率...") hr = api("POST", "/api/health-records", token=TOKEN, data={ "type": "HeartRate", "value": 72, "unit": "次/分", "source": "Manual" }) check("录入心率", hr.get("code") == 0, str(hr.get("message",""))) print(" 3.3 录入血糖...") glu = api("POST", "/api/health-records", token=TOKEN, data={ "type": "Glucose", "value": 5.5, "unit": "mmol/L", "source": "Manual" }) check("录入血糖", glu.get("code") == 0, str(glu.get("message",""))) print(" 3.4 录入血氧...") spo2 = api("POST", "/api/health-records", token=TOKEN, data={ "type": "SpO2", "value": 98, "unit": "%", "source": "Manual" }) check("录入血氧", spo2.get("code") == 0, str(spo2.get("message",""))) print(" 3.5 获取最新数据...") latest = api("GET", "/api/health-records/latest", token=TOKEN) check("获取最新数据", latest.get("code") == 0) check("血压存在", latest.get("data", {}).get("BloodPressure") is not None) print(" 3.6 获取趋势数据...") trend = api("GET", "/api/health-records/trend?type=HeartRate&period=7", token=TOKEN) check("获取趋势数据", trend.get("code") == 0) # ============================================================ section("4. 用药管理") # ============================================================ print(" 4.1 获取用药列表...") meds = api("GET", "/api/medications", token=TOKEN) check("获取用药列表", meds.get("code") == 0) print(" 4.2 添加用药...") new_med = api("POST", "/api/medications", token=TOKEN, data={ "name": "阿司匹林", "dosage": "100mg", "frequency": "Daily", "timeOfDay": ["08:00"], "source": "Manual", "startDate": "2026-06-02" }) check("添加用药", new_med.get("code") == 0, str(new_med.get("message",""))) med_id = new_med.get("data", {}).get("id", "") print(" 4.3 服药打卡...") if med_id: confirm = api("POST", f"/api/medications/{med_id}/confirm", token=TOKEN) check("服药打卡", confirm.get("code") == 0, str(confirm.get("message",""))) # ============================================================ section("5. 饮食记录") # ============================================================ diet = api("GET", "/api/diet-records?date=2026-06-02", token=TOKEN) check("查询饮食记录", diet.get("code") == 0) # ============================================================ section("6. 运动计划") # ============================================================ print(" 6.1 获取当前计划...") plan = api("GET", "/api/exercise-plans/current", token=TOKEN) check("获取当前计划", plan.get("code") == 0) print(" 6.2 创建运动计划...") new_plan = api("POST", "/api/exercise-plans", token=TOKEN, data={ "weekStartDate": "2026-06-02", "items": [ {"dayOfWeek": 1, "exerciseType": "散步", "durationMinutes": 30, "isRestDay": False}, {"dayOfWeek": 3, "exerciseType": "太极", "durationMinutes": 40, "isRestDay": False}, {"dayOfWeek": 5, "exerciseType": "散步", "durationMinutes": 30, "isRestDay": False}, ] }) check("创建运动计划", new_plan.get("code") == 0, str(new_plan.get("message",""))) # ============================================================ section("7. 医生与问诊") # ============================================================ print(" 7.1 医生列表...") docs = api("GET", "/api/doctors", token=TOKEN) check("获取医生列表", docs.get("code") == 0) check("有医生数据", len(docs.get("data", [])) > 0, f"共{len(docs.get('data',[]))}位医生") print(" 7.2 问诊配额...") quota = api("GET", "/api/user/consultation-quota", token=TOKEN) check("获取问诊配额", quota.get("code") == 0) # ============================================================ section("8. AI 智能体对话") # ============================================================ agents_to_test = [ ("default", "你好,介绍一下你自己"), ("health", "我血压128/82"), ("medication", "我现在在吃什么药"), ("consultation", "最近胸口有点不舒服"), ("diet", "我中午吃了红烧肉和米饭"), ("exercise", "帮我查询运动计划"), ] for agent_name, msg in agents_to_test: print(f" 8.{agents_to_test.index((agent_name,msg))+1} {agent_name} Agent: \"{msg[:30]}...\"") TOKEN = login() # fresh token events = sse_stream(TOKEN, agent_name, msg) has_answer = any(e.get("action") == "answer" for e in events) has_tool = any(e.get("action") == "tool_result" for e in events) has_conv_id = any(e.get("action") == "conversation_id" for e in events) errors = [e for e in events if e.get("action") == "error"] check(f"{agent_name}: 对话建立", has_conv_id) check(f"{agent_name}: 有回复", has_answer or has_tool, f"(events: {len(events)}, tools: {has_tool}, answer: {has_answer})") check(f"{agent_name}: 无错误", len(errors) == 0, f"errors: {[e.get('message','') for e in errors]}" if errors else "") # ============================================================ section("9. 对话历史") # ============================================================ convs = api("GET", "/api/ai/conversations", token=TOKEN) check("获取对话列表", convs.get("code") == 0) check("有对话记录", len(convs.get("data", [])) > 0, f"共{len(convs.get('data',[]))}条") # ============================================================ section("10. VLM 食物识别") # ============================================================ # 尝试上传测试图片 test_img = "D:/health_project/食堂三菜一饭热量估算.png" if os.path.exists(test_img): # Use subprocess for multipart upload import subprocess cmd = [ 'curl', '-s', '--max-time', '30', '-X', 'POST', f'{BASE}/api/ai/analyze-food-image', '-H', f'Authorization: Bearer {TOKEN}', '-F', f'images=@{test_img}' ] r = subprocess.run(cmd, capture_output=True, text=True) try: vlm = json.loads(r.stdout) check("VLM食物识别调通", vlm.get("code") == 0, str(vlm.get("message",""))) has_data = bool(vlm.get("data", "")) check("VLM返回数据", has_data, f"data长度: {len(str(vlm.get('data','')))}") except: check("VLM食物识别", False, "JSON解析失败") else: check("VLM测试图片存在", False, f"{test_img} 不存在") # ============================================================ section("11. 报告列表") # ============================================================ reports = api("GET", "/api/reports", token=TOKEN) check("获取报告列表", reports.get("code") == 0) # ============================================================ section("12. 通知偏好") # ============================================================ notifs = api("GET", "/api/notifications/preferences", token=TOKEN) check("获取通知偏好", notifs.get("code") == 0) # ============================================================ print(f"\n{'='*60}") print(f" 测试结果: PASS={PASSED} FAIL={FAILED} TOTAL={PASSED+FAILED}") print(f"{'='*60}") if FAILED > 0: sys.exit(1)