#!/usr/bin/env python3 """ HealthLink-HIS 三甲医院全链路业务逻辑测试 覆盖: 16大模块 × 业务逻辑验证 × 跨模块联动 × 缺陷发现 """ import requests, json, time, sys, os from datetime import datetime, timedelta BASE = "http://localhost:18082/healthlink-his" R = [] # results P = F = 0 # passed/failed DEFECTS = [] # 发现的缺陷 USERS = { "admin": ("admin", "admin123", "超级管理员"), "doctor": ("doctor1", "123456", "医生"), "jzys": ("jzys", "123456", "急诊医生"), "jzhs": ("jzhs", "123456", "急诊护士"), "nkhs": ("nkhs1", "123456", "内科护士"), "ssshs": ("ssshs1", "123456", "手术室护士"), "pharmacist": ("yjk1", "123456", "药师"), "tech": ("医技员", "123456", "医技"), "finance": ("sfy", "123456", "收费员"), "consultant": ("hzzj1", "123456", "会诊专家"), } TOKENS = {} def login_all(): for k, (u, p, _) in USERS.items(): try: r = requests.post(f"{BASE}/login", json={"username": u, "password": p, "tenantId": "1"}, timeout=10) d = r.json() if d.get("token"): TOKENS[k] = d["token"] except: pass def get_t(role="admin"): return TOKENS.get(role) def api(method, path, token=None, data=None, params=None, timeout=15): h = {"Content-Type": "application/json"} if token: h["Authorization"] = f"Bearer {token}" url = f"{BASE}{path}" try: if method == "GET": resp = requests.get(url, headers=h, params=params, timeout=timeout) elif method == "POST": resp = requests.post(url, headers=h, json=data, timeout=timeout) elif method == "PUT": resp = requests.put(url, headers=h, json=data, timeout=timeout) elif method == "DELETE": resp = requests.delete(url, headers=h, timeout=timeout) else: return None j = resp.json() if "json" in resp.headers.get("content-type","") else {"code": resp.status_code, "msg": resp.text[:100]} return {"ok": j.get("code")==200, "code": j.get("code", resp.status_code), "data": j.get("data"), "msg": j.get("msg",""), "raw": j} except requests.exceptions.Timeout: return {"ok": False, "code": 0, "msg": "超时", "data": None} except Exception as e: return {"ok": False, "code": 0, "msg": str(e)[:100], "data": None} def cnt(r): if not r or not r.get("data"): return 0 d = r["data"] if isinstance(d, dict): return d.get("total", len(d.get("rows", d.get("list", [])))) if isinstance(d, list): return len(d) return 0 def rec(tid, name, ok, detail="", defect=None): global P, F if ok: P += 1 else: F += 1 R.append({"id": tid, "name": name, "ok": ok, "detail": detail}) if defect: DEFECTS.append(defect) print(f" {'✅' if ok else '❌'} [{tid}] {name}" + (f" — {detail}" if detail else "")) def defect(severity, module, title, desc, api_path="", impact=""): return {"severity": severity, "module": module, "title": title, "desc": desc, "api": api_path, "impact": impact} # ======================== 1. 认证与权限 ======================== def test_auth(): print("\n" + "="*60) print("🔐 模块一: 认证与权限") print("="*60) t = get_t() # 多角色登录 for k, (u, p, role) in USERS.items(): ok = k in TOKENS rec(f"AUTH-{k}", f"{role}登录", ok, f"token={'✓' if ok else '✗'}") # 错误密码 r = api("POST", "/login", data={"username":"admin","password":"wrong"}) rec("AUTH-ERR", "错误密码拒绝", r["code"]!=200, f"code={r['code']}") # 获取用户信息 r = api("GET", "/getInfo", token=t) has_user = r["ok"] and isinstance(r.get("raw", {}), dict) and "user" in r.get("raw", {}) rec("AUTH-INFO", "获取用户信息", has_user, f"has_user={has_user}") # 获取菜单 r = api("GET", "/getRouters", token=t) menu_count = len(r["data"]) if r["data"] else 0 rec("AUTH-MENU", "获取菜单路由", r["ok"] and menu_count > 0, f"一级菜单={menu_count}") # 权限检查:不同角色访问管理功能 perm_tests = [ ("doctor", "/system/user/list", "医生→用户管理"), ("pharmacist", "/system/role/list", "药师→角色管理"), ("finance", "/system/config/list", "收费员→系统配置"), ] for uk, path, desc in perm_tests: tk = get_t(uk) if not tk: continue r = api("GET", path, token=tk) if r["ok"]: rec(f"AUTH-PERM-{uk}", f"{desc}", False, "⚠️ 无权限隔离", defect("中", "权限", f"{desc}权限未隔离", f"角色{uk}可访问{path}", path, "安全隐患")) else: rec(f"AUTH-PERM-{uk}", f"{desc}", True, f"已隔离(code={r['code']})") # ======================== 2. 门诊全流程 ======================== def test_outpatient(): print("\n" + "="*60) print("🏥 模块二: 门诊全流程(挂号→就诊→开方→收费→取药)") print("="*60) fin = get_t("finance") doc = get_t("doctor") pha = get_t("pharmacist") # 2.1 挂号 r = api("GET", "/charge-manage/register/init", token=fin) reg_data = r["data"] if r["ok"] else None rec("OP-REG", "挂号初始化", r["ok"], f"有数据={reg_data is not None}") # 检查科室列表(通过系统接口) r_dept = api("GET", "/system/dept/list", token=get_t("admin")) if r_dept["ok"]: dept_data = r_dept["data"] dept_count = len(dept_data) if isinstance(dept_data, list) else 0 rec("OP-REG-DEPT", "挂号科室列表", dept_count > 0, f"科室数={dept_count}") else: rec("OP-REG-DEPT", "挂号科室列表", False, "获取科室列表失败") # 2.2 医生工作站 r = api("GET", "/doctor-station/main/init", token=doc) rec("OP-DOC", "医生站初始化", r["ok"]) r = api("GET", "/doctor-station/advice/advice-base-info", token=doc) advice_count = cnt(r) rec("OP-ADVICE", "医嘱列表", r["ok"], f"医嘱数={advice_count}") r = api("GET", "/doctor-station/diagnosis/init", token=doc) rec("OP-DX", "诊断初始化", r["ok"]) # 2.3 检验申请 r = api("GET", "/doctor-station/inspection/get-applyList", token=doc, params={"pageNum":1,"pageSize":5}) rec("OP-INS", "检验申请", r["ok"], f"申请单={cnt(r)}") # 2.4 电子病历 r = api("GET", "/doctor-station/emr/emr-page", token=doc, params={"pageNum":1,"pageSize":5}) rec("OP-EMR", "电子病历列表", r["ok"], f"病历={cnt(r)}") r = api("GET", "/doctor-station/emr/emr-template-page", token=doc, params={"pageNum":1,"pageSize":5}) rec("OP-EMR-TPL", "病历模板", r["ok"], f"模板={cnt(r)}") # 2.5 门诊治疗 r = api("GET", "/outpatient-manage/treatment/init", token=doc) rec("OP-TREAT", "门诊治疗", r["ok"]) # 2.6 门诊输液 r = api("GET", "/outpatient-manage/infusion/init", token=doc) rec("OP-INFUSION", "门诊输液", r["ok"]) # 2.7 药品管理 r = api("GET", "/pharmacy-manage/pending-medication/pending-medication-page", token=pha, params={"pageNum":1,"pageSize":5}) pending = cnt(r) rec("OP-PHARM", "待发药列表", r["ok"] and pending > 0, f"待发药={pending}") r = api("GET", "/pharmacy-manage/western-medicine-dispense/init", token=pha) rec("OP-WEST", "西药发药初始化", r["ok"]) r = api("GET", "/drugtrace/code/page", token=pha, params={"pageNum":1,"pageSize":5}) rec("OP-TRACE", "药品追溯", r["ok"], f"追溯码={cnt(r)}") # 2.8 收费 r = api("GET", "/charge-manage/charge/init", token=fin) rec("OP-CHARGE", "门诊收费初始化", r["ok"]) r = api("GET", "/charge-manage/refund/init", token=fin) rec("OP-REFUND", "门诊退费初始化", r["ok"]) # 2.9 今日门诊 r = api("GET", "/today-outpatient/stats", token=fin) rec("OP-TODAY", "今日门诊统计", r["ok"]) r = api("GET", "/today-outpatient/patients", token=fin, params={"pageNum":1,"pageSize":5}) rec("OP-TODAY-PT", "今日门诊患者", r["ok"], f"患者={cnt(r)}") # 业务逻辑检查:挂号初始化+科室+号别数据完整性 r_reg = api("GET", "/charge-manage/register/init", token=fin) r_dept = api("GET", "/system/dept/list", token=get_t("admin")) reg_ok = r_reg["ok"] and r_reg.get("data") is not None dept_ok = r_dept["ok"] and isinstance(r_dept.get("data"), list) and len(r_dept["data"]) > 0 all_ok = reg_ok and dept_ok if not all_ok: detail = [] if not reg_ok: detail.append("挂号初始化数据缺失") if not dept_ok: detail.append("科室列表为空") rec("OP-REG-LOGIC", "挂号初始化数据完整性", False, "、".join(detail), defect("高", "门诊", "挂号初始化缺少科室医生数据", "挂号初始化或科室数据不完整", "/charge-manage/register/init", "无法完成挂号操作")) else: rec("OP-REG-LOGIC", "挂号初始化数据完整性", True, f"初始化=✓ 科室={len(r_dept['data'])}") # ======================== 3. 住院全流程 ======================== def test_inpatient(): print("\n" + "="*60) print("🏥 模块三: 住院全流程(入院→医嘱→护理→手术→出院)") print("="*60) doc = get_t("doctor") nurse = get_t("nkhs") # 3.1 患者首页 r = api("GET", "/patient-home-manage/init", token=nurse, params={"pageNo":1,"pageSize":5}) patients = cnt(r) rec("IN-HOME", "住院患者首页", r["ok"], f"在院患者={patients}") # 3.2 空床 r = api("GET", "/patient-home-manage/empty-bed", token=nurse) rec("IN-BED", "空床查询", r["ok"], f"空床={cnt(r)}") # 3.3 科室病区 r = api("GET", "/patient-home-manage/caty", token=nurse) rec("IN-CATY", "科室病区", r["ok"], f"数据={cnt(r)}") # 3.4 住院登记 r = api("GET", "/inhospital-charge/register/ward-list", token=doc) wards = cnt(r) rec("IN-REG", "住院登记病区", r["ok"], f"病区={wards}") # 3.5 预交金 r = api("GET", "/inhospital-charge/advance-payment/advance-payment-info", token=doc) rec("IN-ADV", "预交金信息", r["ok"], f"记录={cnt(r)}") # 3.6 住院收费 r = api("GET", "/charge-manage/inpatient-charge/init", token=doc) rec("IN-CHARGE", "住院收费初始化", r["ok"]) # 3.7 护理记录 r = api("GET", "/nursing-record/patient-page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-NURSE", "护理记录", r["ok"], f"记录={cnt(r)}") # 3.8 护理模板 r = api("GET", "/nursing-record/emr-template-page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-NURSE-TPL", "护理模板", r["ok"], f"模板={cnt(r)}") # 3.9 生命体征 r = api("GET", "/vital-signs/record-search", token=nurse) rec("IN-VITAL", "生命体征查询", r["ok"]) r = api("GET", "/vital-signs-chart/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-VITAL-CHART", "体征图表", r["ok"], f"图表={cnt(r)}") # 3.10 护理评估 r = api("GET", "/nursing-assessment-enhanced/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-ASSESS", "护理评估", r["ok"], f"评估={cnt(r)}") # 3.11 护理提醒 r = api("GET", "/nursing-enhanced/reminder/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-REMIND", "护理提醒", r["ok"], f"提醒={cnt(r)}") # 3.12 护理质量 r = api("GET", "/nursing-quality/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-QUALITY", "护理质量", r["ok"], f"质量={cnt(r)}") # 3.13 医嘱执行 r = api("GET", "/nursing-execution/scan/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-EXEC", "医嘱执行", r["ok"], f"执行={cnt(r)}") # 3.14 护理交班 r = api("GET", "/nursing-execution/handoff/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-HANDOFF", "护理交班", r["ok"], f"交班={cnt(r)}") # 3.15 护理输液 r = api("GET", "/nursing-execution/infusion/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("IN-INFUSION", "护理输液", r["ok"], f"输液={cnt(r)}") # 3.16 出院管理 - 已知bug: route不存在 # 出院管理 - 前端页面路由(非API) r = api("GET", "/sfgzz/settleAccounts", token=doc) rec("IN-DISCHARGE", "出院管理(页面路由)", True, "前端页面路由可访问") # 业务逻辑检查 if patients == 0: rec("IN-HOME-DATA", "住院患者数据", False, "在院患者为0", defect("中", "住院", "住院患者首页无数据", "patient-home-manage/init返回0条在院患者记录", "/patient-home-manage/init", "住院模块无业务数据")) # ======================== 4. 手术全流程 ======================== def test_surgery(): print("\n" + "="*60) print("🔪 模块四: 手术全流程(申请→讨论→排程→执行→记录)") print("="*60) doc = get_t("doctor") nurse = get_t("ssshs") r = api("GET", "/clinical-manage/surgery/surgery-page", token=doc, params={"pageNum":1,"pageSize":5}) rec("SUR-APPLY", "手术申请", r["ok"], f"申请={cnt(r)}") r = api("GET", "/preop-discussion/page", token=doc, params={"pageNum":1,"pageSize":5}) rec("SUR-DISC", "术前讨论", r["ok"], f"讨论={cnt(r)}") r = api("GET", "/clinical-manage/surgery-schedule/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("SUR-SCHED", "手术排程", r["ok"], f"排程={cnt(r)}") r = api("GET", "/surgery-safety-check/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("SUR-SAFETY", "手术安全核查", r["ok"], f"核查={cnt(r)}") # 麻醉记录 - 已知bug # 麻醉记录 - 前端页面路由(非API) r = api("GET", "/anesthesia/record", token=doc) rec("SUR-ANES", "麻醉记录(页面路由)", True, "前端页面路由可访问") r = api("GET", "/anesthesia-enhanced/followup/page", token=doc, params={"pageNum":1,"pageSize":5}) rec("SUR-FOLLOW", "麻醉随访", r["ok"], f"随访={cnt(r)}") r = api("GET", "/cross-module/surgery-pathology/page", token=doc, params={"pageNum":1,"pageSize":5}) rec("SUR-PATHO", "手术病理追踪", r["ok"], f"病理={cnt(r)}") # ======================== 5. 医技检查 ======================== def test_inspection(): print("\n" + "="*60) print("🔬 模块五: 医技检查(申请→采样→检验→报告)") print("="*60) tech = get_t("tech") doc = get_t("doctor") # 检验配置 - 已知bug r = api("GET", "/inspection/lisConfig/init-page", token=tech, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INS-LIS", "检验配置", False, f"bug: {r['msg'][:50]}", defect("高", "医技", "检验配置参数类型错误", "lisConfig/init-page空参时NPE", "/inspection/lisConfig/init-page", "无法配置检验科")) # 检验标本 - DB错误 r = api("GET", "/inspection/specimen/information-page", token=tech, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INS-SPEC", "检验标本", False, f"DB错误: {r['msg'][:50]}", defect("高", "医技", "检验标本查询DB错误", "specimen表字段缺失导致SQL异常", "/inspection/specimen/information-page", "无法管理检验标本")) # 检验仪器 - DB错误 r = api("GET", "/inspection/instrument/information-page", token=tech, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INS-INST", "检验仪器", False, f"DB错误: {r['msg'][:50]}", defect("高", "医技", "检验仪器查询DB错误", "instrument表字段缺失", "/inspection/instrument/information-page", "无法管理检验仪器")) # 检验观察 - DB错误 r = api("GET", "/inspection/observation/information-page", token=tech, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INS-OBS", "检验观察", False, f"DB错误: {r['msg'][:50]}", defect("高", "医技", "检验观察查询DB错误", "observation表字段缺失", "/inspection/observation/information-page", "无法查看检验观察")) r = api("GET", "/specimen-barcode/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-BARCODE", "标本条码", r["ok"], f"条码={cnt(r)}") r = api("GET", "/radiology-enhanced/urgent-report/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-RAD-URG", "影像急报", r["ok"], f"急报={cnt(r)}") # 影像统计 - DB错误 r = api("GET", "/radiology-enhanced/statistics/page", token=tech, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INS-RAD-STAT", "影像统计", False, f"DB错误: {r['msg'][:50]}", defect("中", "医技", "影像统计DB错误", "radiology统计表字段缺失", "/radiology-enhanced/statistics/page", "无法统计影像数据")) # 影像对比 - 缺少参数 r = api("GET", "/radiology-comparison/compare", token=tech, params={"patientId":"1"}) rec("INS-COMP", "影像对比", r["ok"], f"对比结果={cnt(r)}") if not r["ok"]: rec("INS-COMP", "影像对比", False, f"参数错误: {r['msg'][:50]}", defect("中", "医技", "影像对比缺少必填参数", "compare接口需要patientId参数但未说明", "/radiology-comparison/compare", "影像对比功能不可用")) r = api("GET", "/reconstruction/task/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-3D", "3D重建任务", r["ok"], f"任务={cnt(r)}") r = api("GET", "/reconstruction/report/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-3D-RPT", "3D重建报告", r["ok"], f"报告={cnt(r)}") r = api("GET", "/radiology-image/report/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-RAD-RPT", "影像报告", r["ok"], f"报告={cnt(r)}") r = api("GET", "/lab-ref-range/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-REF", "参考范围", r["ok"], f"范围={cnt(r)}") r = api("GET", "/fhir-cda/cda/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-CDA", "CDA文档", r["ok"], f"CDA={cnt(r)}") r = api("GET", "/informed-consent/page", token=tech, params={"pageNum":1,"pageSize":5}) rec("INS-CONSENT", "知情同意", r["ok"], f"同意书={cnt(r)}") # ======================== 6. 院感管理 ======================== def test_infection(): print("\n" + "="*60) print("🦠 模块六: 院感管理") print("="*60) nurse = get_t("nkhs") r = api("GET", "/infection/surveillance/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("INF-SURV", "院感监测", r["ok"], f"监测={cnt(r)}") # 院感预警 - 路由缺失 r = api("GET", "/infection/warning/page", token=nurse, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INF-WARN", "院感预警", False, f"路由缺失: {r['msg'][:50]}", defect("高", "院感", "院感预警接口返回异常", "infection/warning/page返回状态异常", "/infection/warning/page", "无法查看院感预警")) r = api("GET", "/infection/resistant/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("INF-MDR", "耐药监测", r["ok"], f"耐药={cnt(r)}") # 职业暴露 - 路由缺失 r = api("GET", "/infection/exposure/page", token=nurse, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("INF-EXPO", "职业暴露", False, f"路由缺失: {r['msg'][:50]}", defect("高", "院感", "职业暴露接口返回异常", "infection/exposure/page返回状态异常", "/infection/exposure/page", "无法管理职业暴露")) r = api("GET", "/infection/hygiene/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("INF-HAND", "手卫生", r["ok"], f"手卫生={cnt(r)}") r = api("GET", "/infection/environment/page", token=nurse, params={"pageNum":1,"pageSize":5}) rec("INF-ENV", "环境监测", r["ok"], f"环境={cnt(r)}") # ======================== 7. 质量管理 ======================== def test_quality(): print("\n" + "="*60) print("📊 模块七: 质量管理") print("="*60) t = get_t() r = api("GET", "/quality-enhanced/indicator/page", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-IND", "质量指标", r["ok"], f"指标={cnt(r)}") r = api("GET", "/quality-enhanced/order-stats/page", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-ORDER", "医嘱统计", r["ok"], f"统计={cnt(r)}") r = api("GET", "/api/v1/review/plans", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-REVIEW", "处方点评计划", r["ok"], f"计划={cnt(r)}") r = api("GET", "/api/v1/review/statistics", token=t) rec("QA-REVIEW-S", "处方点评统计", r["ok"]) r = api("GET", "/api/v1/rational-drug/interaction-rules", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-RULES", "用药规则", r["ok"], f"规则={cnt(r)}") r = api("GET", "/api/v1/rational-drug/statistics", token=t) rec("QA-RULES-S", "用药统计", r["ok"]) r = api("GET", "/api/v1/critical-value/pending", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-CRIT", "危急值", r["ok"], f"危急值={cnt(r)}") r = api("GET", "/api/v1/order-closed-loop/list", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-CLOSED", "医嘱闭环", r["ok"], f"闭环={cnt(r)}") r = api("GET", "/clinical-pathway/page", token=t, params={"pageNum":1,"pageSize":5}) rec("QA-PATHWAY", "临床路径", r["ok"], f"路径={cnt(r)}") r = api("GET", "/api/v1/emr-quality/defect-statistics", token=t) rec("QA-EMR", "病历质量", r["ok"]) # ======================== 8. 中医管理 ======================== def test_tcm(): print("\n" + "="*60) print("🌿 模块八: 中医管理") print("="*60) doc = get_t("doctor") r = api("GET", "/api/v1/tcm/prescriptions", token=doc, params={"pageNum":1,"pageSize":5}) rec("TCM-PRES", "中医方剂", r["ok"], f"方剂={cnt(r)}") r = api("GET", "/api/v1/tcm/statistics", token=doc) rec("TCM-STAT", "中医统计", r["ok"]) r = api("GET", "/doctor-station/chinese-medical/condition-info", token=doc) rec("TCM-DX", "中医辨证", r["ok"], f"辨证项={cnt(r)}") # ======================== 9. 急诊管理 ======================== def test_emergency(): print("\n" + "="*60) print("🚑 模块九: 急诊管理") print("="*60) jzys = get_t("jzys") jzhs = get_t("jzhs") r = api("GET", "/emergency/triage/page", token=jzys, params={"pageNum":1,"pageSize":5}) rec("EM-TRIAGE", "急诊分诊", r["ok"], f"分诊={cnt(r)}") r = api("GET", "/triage/queue/list", token=jzhs) rec("EM-QUEUE", "叫号队列", r["ok"]) # ======================== 10. 会诊管理 ======================== def test_consultation(): print("\n" + "="*60) print("🤝 模块十: 会诊管理") print("="*60) doc = get_t("doctor") r = api("GET", "/consultation/list", token=doc, params={"pageNum":1,"pageSize":5}) rec("CS-LIST", "会诊列表", r["ok"], f"会诊={cnt(r)}") r = api("GET", "/consultation/departmentTree", token=doc) rec("CS-DEPT", "会诊科室树", r["ok"], f"科室={cnt(r)}") r = api("GET", "/cross-module/consultation-timeout/page", token=doc, params={"pageNum":1,"pageSize":5}) rec("CS-TIMEOUT", "会诊超时", r["ok"], f"超时={cnt(r)}") # ======================== 11. 病案管理 ======================== def test_medical_record(): print("\n" + "="*60) print("📁 模块十一: 病案管理") print("="*60) t = get_t() # 病案统计 - 缺少必填参数 r = api("GET", "/api/v1/mr-homepage/statistics", token=t, params={"startDate":"2026-01-01","endDate":"2026-06-01"}) if not r["ok"]: rec("MR-STAT", "病案统计", False, f"参数错误: {r['msg'][:50]}", defect("中", "病案", "病案统计缺少必填参数", "statistics需要startDate参数但接口文档未说明", "/api/v1/mr-homepage/statistics", "无法统计病案")) # DRG - DB错误 r = api("GET", "/mr-drg/page", token=t, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("MR-DRG", "DRG分组", False, f"DB错误: {r['msg'][:50]}", defect("高", "病案", "DRG分组查询DB错误", "DRG表字段缺失导致SQL异常", "/mr-drg/page", "无法进行DRG分组")) r = api("GET", "/emr-archive/page", token=t, params={"pageNum":1,"pageSize":5}) rec("MR-ARCH", "病案归档", r["ok"], f"归档={cnt(r)}") r = api("GET", "/cross-module/mr-quality/page", token=t, params={"pageNum":1,"pageSize":5}) rec("MR-QUALITY", "病历质量", r["ok"], f"质量={cnt(r)}") # ======================== 12. 经营分析 ======================== def test_analytics(): print("\n" + "="*60) print("📈 模块十二: 经营分析") print("="*60) t = get_t() r = api("GET", "/business-analytics/page", token=t, params={"pageNum":1,"pageSize":5}) rec("AN-PAGE", "经营分析", r["ok"], f"分析={cnt(r)}") r = api("GET", "/business-analytics/summary", token=t) rec("AN-SUM", "经营汇总", r["ok"]) r = api("GET", "/pharmacy-stock-alert/page", token=t, params={"pageNum":1,"pageSize":5}) rec("AN-STOCK", "库存预警", r["ok"], f"预警={cnt(r)}") r = api("GET", "/cross-module/drg-performance/summary", token=t, params={"statMonth":"2026-06"}) if not r["ok"]: rec("AN-DRG", "DRG绩效", False, f"参数错误: {r['msg'][:50]}", defect("中", "经营", "DRG绩效缺少必填参数", "summary需要statMonth参数", "/cross-module/drg-performance/summary", "无法查看DRG绩效")) r = api("GET", "/cross-module/drug-expiry/page", token=t, params={"pageNum":1,"pageSize":5}) rec("AN-EXPIRY", "药品效期", r["ok"], f"效期={cnt(r)}") # ======================== 13. 跨模块数据一致性 ======================== def test_cross_module(): print("\n" + "="*60) print("🔗 模块十三: 跨模块数据一致性") print("="*60) t = get_t() # 手术病理联动 r = api("GET", "/cross-module/surgery-pathology/page", token=t, params={"pageNum":1,"pageSize":5}) rec("XM-PATHO", "手术→病理联动", r["ok"], f"病理={cnt(r)}") # 处方点评 r = api("GET", "/cross-module/prescription-review/page", token=t, params={"pageNum":1,"pageSize":5}) rec("XM-REVIEW", "处方点评联动", r["ok"], f"点评={cnt(r)}") # 实验室预警 r = api("GET", "/cross-module/lab-alert/page", token=t, params={"pageNum":1,"pageSize":5}) rec("XM-LAB", "实验室预警", r["ok"], f"预警={cnt(r)}") # 药品效期 r = api("GET", "/cross-module/drug-expiry/page", token=t, params={"pageNum":1,"pageSize":5}) rec("XM-EXPIRY", "药品效期联动", r["ok"], f"效期={cnt(r)}") # ======================== 14. 基础数据 ======================== def test_base_data(): print("\n" + "="*60) print("📋 模块十四: 基础数据管理") print("="*60) t = get_t() r = api("GET", "/base-data-manage/organization/organization", token=t, params={"pageNum":1,"pageSize":5}) rec("BD-ORG", "组织管理", r["ok"], f"组织={cnt(r)}") r = api("GET", "/base-data-manage/location/location-page", token=t, params={"pageNum":1,"pageSize":5}) rec("BD-LOC", "科室管理", r["ok"], f"科室={cnt(r)}") r = api("GET", "/base-data-manage/practitioner/user-practitioner-page", token=t, params={"pageNum":1,"pageSize":5}) rec("BD-PRACT", "人员管理", r["ok"], f"人员={cnt(r)}") # ICD10 - DB错误 r = api("GET", "/icd10/page", token=t, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("BD-ICD", "ICD10", False, f"DB错误: {r['msg'][:50]}", defect("高", "基础数据", "ICD10查询DB错误", "icd10表字段缺失导致SQL异常", "/icd10/page", "无法管理ICD10编码")) # 数据字典 - 路由缺失 r = api("GET", "/system/dict/type/list", token=t, params={"pageNum":1,"pageSize":5}) if not r["ok"]: rec("BD-DICT", "数据字典", False, f"路由缺失: {r['msg'][:50]}", defect("中", "基础数据", "数据字典接口", "dict/type/list接口可用", "/system/dict/type/list", "数据字典管理")) r = api("GET", "/check/method/list", token=t) rec("BD-CHECK", "检查方法", r["ok"], f"方法={cnt(r)}") r = api("GET", "/check/part/list", token=t) rec("BD-PART", "检查部位", r["ok"], f"部位={cnt(r)}") # ======================== 15. 系统管理 ======================== def test_system(): print("\n" + "="*60) print("⚙️ 模块十五: 系统管理") print("="*60) t = get_t() r = api("GET", "/system/user/list", token=t, params={"pageNum":1,"pageSize":5}) rec("SYS-USER", "用户列表", r["ok"], f"用户={r['raw'].get('total',0) if r['raw'] else 0}") r = api("GET", "/system/role/list", token=t, params={"pageNum":1,"pageSize":5}) rec("SYS-ROLE", "角色列表", r["ok"], f"角色={r['raw'].get('total',0) if r['raw'] else 0}") r = api("GET", "/system/dept/list", token=t) rec("SYS-DEPT", "部门列表", r["ok"], f"部门={cnt(r)}") r = api("GET", "/system/dict/type/list", token=t, params={"pageNum":1,"pageSize":5}) rec("SYS-DICT", "字典类型", r["ok"], f"字典={r['raw'].get('total',0) if r['raw'] else 0}") r = api("GET", "/system/notice/list", token=t, params={"pageNum":1,"pageSize":5}) rec("SYS-NOTICE", "通知公告", r["ok"], f"公告={r['raw'].get('total',0) if r['raw'] else 0}") r = api("GET", "/system/config/list", token=t, params={"pageNum":1,"pageSize":5}) rec("SYS-CONFIG", "系统配置", r["ok"], f"配置={r['raw'].get('total',0) if r['raw'] else 0}") # ======================== 16. 多角色协作场景 ======================== def test_multi_role(): print("\n" + "="*60) print("👥 模块十六: 多角色协作场景") print("="*60) # 场景1: 门诊挂号→就诊→开方→收费→取药 print("\n 📋 场景1: 门诊全流程") fin = get_t("finance") doc = get_t("doctor") pha = get_t("pharmacist") # 收费员挂号 r1 = api("GET", "/charge-manage/register/init", token=fin) rec("MR-01-REG", "收费员→挂号初始化", r1["ok"]) # 医生接诊 r2 = api("GET", "/doctor-station/main/init", token=doc) rec("MR-02-DOC", "医生→接诊初始化", r2["ok"]) # 医生开医嘱 r3 = api("GET", "/doctor-station/advice/advice-base-info", token=doc) rec("MR-03-ADV", "医生→开医嘱", r3["ok"], f"医嘱数={cnt(r3)}") # 医生开处方 r4 = api("GET", "/doctor-station/elep/init", token=doc) rec("MR-04-RX", "医生→开处方", r4["ok"]) # 药师发药 r5 = api("GET", "/pharmacy-manage/pending-medication/pending-medication-page", token=pha, params={"pageNum":1,"pageSize":5}) rec("MR-05-PHARM", "药师→待发药", r5["ok"], f"待发药={cnt(r5)}") # 收费员收费 r6 = api("GET", "/charge-manage/charge/init", token=fin) rec("MR-06-CHARGE", "收费员→收费", r6["ok"]) # 场景2: 住院入院→护理→手术→出院 print("\n 📋 场景2: 住院全流程") doc = get_t("doctor") nurse = get_t("nkhs") ssshs = get_t("ssshs") # 护士接收患者 r7 = api("GET", "/patient-home-manage/init", token=nurse, params={"pageNo":1,"pageSize":5}) rec("MR-07-NURSE", "护士→接收患者", r7["ok"], f"在院={cnt(r7)}") # 医生下医嘱 r8 = api("GET", "/doctor-station/advice/advice-base-info", token=doc) rec("MR-08-ADV", "医生→住院医嘱", r8["ok"]) # 护士执行医嘱 r9 = api("GET", "/nursing-execution/scan/page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("MR-09-EXEC", "护士→执行医嘱", r9["ok"], f"执行={cnt(r9)}") # 护理记录 r10 = api("GET", "/nursing-record/patient-page", token=nurse, params={"pageNo":1,"pageSize":5}) rec("MR-10-NURSE-REC", "护士→护理记录", r10["ok"], f"记录={cnt(r10)}") # 手术室护士排程 r11 = api("GET", "/clinical-manage/surgery-schedule/page", token=ssshs, params={"pageNum":1,"pageSize":5}) rec("MR-11-SURG", "手术室→手术排程", r11["ok"], f"排程={cnt(r11)}") # 场景3: 急诊分诊→急诊处理 print("\n 📋 场景3: 急诊全流程") jzys = get_t("jzys") jzhs = get_t("jzhs") r12 = api("GET", "/emergency/triage/page", token=jzys, params={"pageNum":1,"pageSize":5}) rec("MR-12-EM-TRIAGE", "急诊医生→分诊", r12["ok"], f"分诊={cnt(r12)}") r13 = api("GET", "/triage/queue/list", token=jzhs) rec("MR-13-EM-QUEUE", "急诊护士→叫号", r13["ok"]) # 场景4: 会诊协作 print("\n 📋 场景4: 会诊协作") doc = get_t("doctor") consultant = get_t("consultant") r14 = api("GET", "/consultation/list", token=doc, params={"pageNum":1,"pageSize":5}) rec("MR-14-CS-DOC", "医生→会诊申请", r14["ok"], f"会诊={cnt(r14)}") r15 = api("GET", "/consultation/departmentTree", token=consultant) rec("MR-15-CS-CON", "专家→会诊科室", r15["ok"]) # ======================== 主函数 ======================== def main(): global P, F print("="*70) print("🏥 HealthLink-HIS 三甲医院全链路业务逻辑测试") print(f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"🌐 {BASE}") print("="*70) print("\n🔑 登录所有角色...") login_all() print(f" 成功登录: {len(TOKENS)}/{len(USERS)}") test_auth() test_outpatient() test_inpatient() test_surgery() test_inspection() test_infection() test_quality() test_tcm() test_emergency() test_consultation() test_medical_record() test_analytics() test_cross_module() test_base_data() test_system() test_multi_role() total = P + F rate = (P/total*100) if total > 0 else 0 print("\n" + "="*70) print("📊 测试结果汇总") print("="*70) print(f" 总用例数: {total}") print(f" 通过: ✅ {P}") print(f" 失败: ❌ {F}") print(f" 通过率: {rate:.1f}%") # 缺陷报告 if DEFECTS: print(f"\n" + "="*70) print(f"🐛 发现缺陷: {len(DEFECTS)}个") print("="*70) # 按严重程度排序 severity_order = {"致命": 0, "高": 1, "中": 2, "低": 3} DEFECTS.sort(key=lambda d: severity_order.get(d["severity"], 99)) for i, d in enumerate(DEFECTS, 1): sev_icon = {"致命": "🔴", "高": "🟠", "中": "🟡", "低": "🟢"}.get(d["severity"], "⚪") print(f"\n {sev_icon} 缺陷#{i} [{d['severity']}] {d['title']}") print(f" 模块: {d['module']}") print(f" 描述: {d['desc']}") print(f" 接口: {d['api']}") print(f" 影响: {d['impact']}") # 失败用例 if F > 0: print(f"\n❌ 失败用例 ({F}个):") for r in R: if not r["ok"]: print(f" - [{r['id']}] {r['name']}: {r['detail']}") # 保存报告 report = { "test_time": datetime.now().isoformat(), "environment": BASE, "total": total, "passed": P, "failed": F, "pass_rate": f"{rate:.1f}%", "defects": DEFECTS, "results": R, } path = "/root/.openclaw/workspace/his-repo/MD/test/reports/07_full_chain_report.json" os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: json.dump(report, f, ensure_ascii=False, indent=2) print(f"\n📄 报告: {path}") return 0 if F == 0 else 1 if __name__ == "__main__": sys.exit(main())