Files
his/MD/test/05_test_multi_role_v2.py
华佗 3e98aaae1b feat(test): 多角色协作测试v2 - 通过率69.8%
12个场景126个用例, 通过88个(69.8%)

通过的模块:
- 门诊: 挂号/医生站/收费 30/30
- 住院: 患者主页/护理评估/体征 13/15
- 手术: 手术排程/麻醉/知情同意 8/10
- 院感: 监测/暴发/手卫生/耐药/环境 9/9
- 中医+质控: 方剂/统计/体质/指标 6/6
- 药品: 库存/发药/追溯/合理用药 7/8

失败项(需修复):
- P0: 检验模块缺少/page端点(observation/specimen/lisConfig/instrument)
- P0: 报表模块缺少/page端点(charge/register/monthly等)
- P1: 术前讨论preop_discussion表缺delete_flag列
- P1: 合理用药dosage-rules表缺create_by列
- P2: 权限隔离问题(所有角色都能互相访问)
2026-06-07 22:09:29 +08:00

322 lines
23 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HealthLink-HIS 三甲医院多角色协作全流程测试 v2
基于实际Controller端点修正
"""
import requests, json, sys, os
from datetime import datetime
BASE_URL = "http://localhost:18082/healthlink-his"
ACCOUNTS = {
"admin": {"user":"admin","pwd":"admin123","role":"超级管理员"},
"doctor1": {"user":"doctor1","pwd":"123456","role":"医生"},
"doctor_jz": {"user":"jzys","pwd":"123456","role":"急诊医生"},
"nurse_jz": {"user":"jzhs","pwd":"123456","role":"急诊护士"},
"nurse_nk": {"user":"nkhs1","pwd":"123456","role":"内科护士"},
"nurse_ss": {"user":"ssshs1","pwd":"123456","role":"手术室护士"},
"pharmacist":{"user":"yjk1","pwd":"123456","role":"药剂科"},
"tech": {"user":"医技员","pwd":"123456","role":"医技"},
"finance": {"user":"sfy","pwd":"123456","role":"收费员"},
"consult": {"user":"hzzj1","pwd":"123456","role":"会诊专家"},
}
class S:
def __init__(self):
self.t=self.p=self.f=0; self.r=[]
def ok(self,sc,step,role,name,detail=""):
self.t+=1;self.p+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"","d":detail})
print(f" ✅ [{sc}] {step} ({role}): {name}")
def fail(self,sc,step,role,name,detail=""):
self.t+=1;self.f+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"","d":detail})
print(f" ❌ [{sc}] {step} ({role}): {name}")
if detail: print(f"{detail}")
def chk(self,sc,step,role,name,resp,code=200,fields=None):
if resp.get("code")!=code:
self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp
if fields:
for f in fields:
if f not in resp:
self.fail(sc,step,role,name,f"缺少字段: {f}"); return resp
self.ok(sc,step,role,name); return resp
def chk_list(self,sc,step,role,name,resp):
if resp.get("code")!=200:
self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp
rows=resp.get("rows",resp.get("data",[]))
if isinstance(rows,list):
self.ok(sc,step,role,name,f"返回{len(rows)}"); return resp
elif isinstance(rows,dict):
# 嵌套格式,也算通过但标记格式问题
self.ok(sc,step,role,name,f"返回dict格式(非标准rows)"); return resp
else:
self.fail(sc,step,role,name,f"rows类型异常: {type(rows)}"); return resp
s=S(); tokens={}
def login(k):
a=ACCOUNTS[k]
try:
r=requests.post(f"{BASE_URL}/login",json={"username":a["user"],"password":a["pwd"],"tenantId":"1"}).json()
if r.get("token"): tokens[k]=r["token"]; return True
except: pass
return False
def H(k): return {"Authorization":f"Bearer {tokens.get(k,'')}","Content-Type":"application/json"}
def G(k,p,params=None): return requests.get(f"{BASE_URL}{p}",headers=H(k),params=params).json()
def P(k,p,d=None): return requests.post(f"{BASE_URL}{p}",headers=H(k),json=d).json()
def U(k,p,d=None): return requests.put(f"{BASE_URL}{p}",headers=H(k),json=d).json()
# ============================
# 场景1: 门诊就诊全流程
# ============================
def s1_outpatient():
print(f"\n{'='*60}\n场景1: 门诊就诊全流程\n{'='*60}")
s.chk("门诊","1.1","收费员","挂号初始化",G("finance","/charge-manage/register/init"),200)
s.chk("门诊","1.2","收费员","查询患者",G("finance","/charge-manage/register/patient-metadata",{"searchKey":"测试"}),200)
s.chk("门诊","1.3","收费员","医生列表",G("finance","/charge-manage/register/all-doctors"),200)
s.chk("门诊","1.4","医生","医生站初始化",G("doctor1","/doctor-station/main/init"),200)
s.chk("门诊","1.5","医生","患者信息",G("doctor1","/doctor-station/main/patient-info"),200)
s.chk("门诊","1.6","医生","医嘱基础",G("doctor1","/doctor-station/advice/advice-base-info"),200)
s.chk("门诊","1.7","医生","诊断初始化",G("doctor1","/doctor-station/diagnosis/init"),200)
s.chk("门诊","1.8","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.9","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.10","医技","LIS配置",G("tech","/inspection/lisConfig/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.11","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.12","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.13","医技","影像列表",G("tech","/radiology-image/list"),200)
s.chk("门诊","1.14","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.15","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.16","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.17","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200)
s.chk("门诊","1.18","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200)
s.chk("门诊","1.19","药师","药品追溯",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.20","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.21","药师","追溯扫码",G("pharmacist","/drugtrace/scan/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.22","药师","追溯预警",G("pharmacist","/drugtrace/alert/page",{"pageNum":1,"pageSize":10}),200)
s.chk("门诊","1.23","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200)
s.chk("门诊","1.24","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200)
s.chk("门诊","1.25","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200)
s.chk("门诊","1.26","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200)
s.chk("门诊","1.27","收费员","收费患者",G("finance","/charge-manage/charge/encounter-patient-page"),200)
s.chk("门诊","1.28","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200)
s.chk("门诊","1.29","收费员","退费患者",G("finance","/charge-manage/refund/encounter-patient-page"),200)
s.chk("门诊","1.30","收费员","定价患者",G("finance","/charge-manage/pricing/patient-info"),200)
# ============================
# 场景2: 住院入院全流程
# ============================
def s2_inpatient():
print(f"\n{'='*60}\n场景2: 住院入院全流程\n{'='*60}")
s.chk("住院","2.1","收费员","住院收费初始化",G("finance","/charge-manage/inpatient-charge/init"),200)
s.chk("住院","2.2","收费员","住院患者",G("finance","/charge-manage/inpatient-charge/encounter-patient-page"),200)
s.chk("住院","2.3","医生","患者主页",G("doctor1","/patient-home-manage/init"),200)
s.chk("住院","2.4","医生","空床查询",G("doctor1","/patient-home-manage/empty-bed"),200)
s.chk("住院","2.5","医生","科室统计",G("doctor1","/patient-home-manage/caty"),200)
s.chk("住院","2.6","护士","护理评估统计",G("nurse_nk","/nursing-assessment-enhanced/stats"),200)
r=P("nurse_nk","/nursing-assessment-enhanced/braden/assess",{"patientName":"测试患者甲","encounterId":"6006","itemScores":json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}),"detail":"压疮高危"})
s.chk("住院","2.7","护士","Braden评估",r,200)
r=P("nurse_nk","/nursing-assessment-enhanced/morse/assess",{"patientName":"测试患者乙","encounterId":"6007","itemScores":json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}),"detail":"跌倒高危"})
s.chk("住院","2.8","护士","Morse评估",r,200)
s.chk("住院","2.9","护士","体征查询",G("nurse_nk","/vital-signs/record-search"),200)
s.chk("住院","2.10","护士","体征图表",G("nurse_nk","/vital-signs-chart/page",{"pageNum":1,"pageSize":10}),200)
s.chk("住院","2.11","护士","交接班",G("nurse_nk","/nursing-handoff/page",{"pageNum":1,"pageSize":10}),200)
s.chk("住院","2.12","药师","待发药",G("pharmacist","/pharmacy-manage/pending-medication/pending-medication-page"),200)
s.chk("住院","2.13","药师","药品详情初始化",G("pharmacist","/pharmacy-manage/medication-details/init"),200)
s.chk("住院","2.14","药师","药品汇总发药",G("pharmacist","/pharmacy-manage/summary-dispense-medicine/init"),200)
s.chk("住院","2.15","药师","住院退药",G("pharmacist","/pharmacy-manage/inHospital-return-medicine/init"),200)
# ============================
# 场景3: 手术全流程
# ============================
def s3_surgery():
print(f"\n{'='*60}\n场景3: 手术全流程\n{'='*60}")
s.chk("手术","3.1","医生","手术列表",G("doctor1","/clinical-manage/surgery/surgery-page"),200)
s.chk("手术","3.2","医生","手术排程",G("doctor1","/clinical-manage/surgery-schedule/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.3","医生","手术统计",G("doctor1","/clinical-manage/surgery/statistics"),200)
s.chk("手术","3.4","专家","术前讨论",G("consult","/preop-discussion/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.5","手术室护士","安全核查",G("nurse_ss","/surgery-safety-check/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.6","医生","麻醉标本",G("doctor1","/anesthesia-enhanced/specimen/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.7","医生","麻醉随访",G("doctor1","/anesthesia-enhanced/followup/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.8","医生","麻醉质控",G("doctor1","/anesthesia-enhanced/qc/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.9","医生","知情同意",G("doctor1","/informed-consent/page",{"pageNum":1,"pageSize":10}),200)
s.chk("手术","3.10","医生","CA签名统计",G("doctor1","/api/v1/ca-signature/statistics"),200)
# ============================
# 场景4: 检验全流程
# ============================
def s4_inspection():
print(f"\n{'='*60}\n场景4: 检验全流程\n{'='*60}")
s.chk("检验","4.1","医生","检查申请",G("doctor1","/exam/apply/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.2","护士","标本采集",G("nurse_nk","/inspection/collection/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.3","医技","检验结果",G("tech","/inspection/laboratory/init-page"),200)
s.chk("检验","4.4","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.5","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.6","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.7","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.8","医技","影像列表",G("tech","/radiology-image/list"),200)
s.chk("检验","4.9","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.10","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200)
s.chk("检验","4.11","医技","3D统计",G("tech","/reconstruction/stats"),200)
s.chk("检验","4.12","医技","标本条码",G("tech","/specimen-barcode/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 场景5: 会诊全流程
# ============================
def s5_consultation():
print(f"\n{'='*60}\n场景5: 会诊全流程\n{'='*60}")
s.chk("会诊","5.1","医生","会诊记录",G("doctor1","/consultation/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.2","专家","会诊反馈",G("consult","/cross-module/consult-feedback/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.3","医生","会诊超时",G("doctor1","/cross-module/consulttimeout/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.4","医生","临床路径",G("doctor1","/clinical-pathway/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.5","医生","危急值",G("doctor1","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.6","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200)
s.chk("会诊","5.7","医生","电子病历",G("doctor1","/api/v1/emr/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 场景6: 急诊全流程
# ============================
def s6_emergency():
print(f"\n{'='*60}\n场景6: 急诊全流程\n{'='*60}")
s.chk("急诊","6.1","急诊医生","急诊记录",G("doctor_jz","/emergency/page",{"pageNum":1,"pageSize":10}),200)
s.chk("急诊","6.2","急诊护士","分诊排队",G("nurse_jz","/triage/queue/page",{"pageNum":1,"pageSize":10}),200)
s.chk("急诊","6.3","急诊护士","护理评估统计",G("nurse_jz","/nursing-assessment-enhanced/stats"),200)
r=P("nurse_jz","/nursing-assessment-enhanced/braden/assess",{"patientName":"急诊患者庚","encounterId":"6011","itemScores":json.dumps({"sensation":1,"moisture":1,"activity":1,"mobility":1,"nutrition":1,"friction":1}),"detail":"急诊压疮评估"})
s.chk("急诊","6.4","急诊护士","Braden评估",r,200)
s.chk("急诊","6.5","急诊护士","体征查询",G("nurse_jz","/vital-signs/record-search"),200)
s.chk("急诊","6.6","急诊护士","危急值",G("nurse_jz","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 场景7: 医保结算全流程
# ============================
def s7_insurance():
print(f"\n{'='*60}\n场景7: 医保结算全流程\n{'='*60}")
s.chk("医保","7.1","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200)
s.chk("医保","7.2","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200)
s.chk("医保","7.3","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200)
s.chk("医保","7.4","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200)
s.chk("医保","7.5","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 场景8: 药品全流程
# ============================
def s8_pharmacy():
print(f"\n{'='*60}\n场景8: 药品全流程\n{'='*60}")
s.chk("药品","8.1","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200)
s.chk("药品","8.2","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200)
s.chk("药品","8.3","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200)
s.chk("药品","8.4","药师","药品追溯码",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200)
s.chk("药品","8.5","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200)
s.chk("药品","8.6","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200)
s.chk("药品","8.7","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200)
s.chk("药品","8.8","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200)
# ============================
# 场景9: 院感全流程
# ============================
def s9_infection():
print(f"\n{'='*60}\n场景9: 院感全流程\n{'='*60}")
s.chk("院感","9.1","护士","院感监测",G("nurse_nk","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.2","护士","院感暴发",G("nurse_nk","/infection-enhanced/outbreak/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.3","护士","手卫生",G("nurse_nk","/infection-enhanced/hand-hygiene/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.4","护士","手卫生统计",G("nurse_nk","/infection-enhanced/hand-hygiene/stats"),200)
s.chk("院感","9.5","护士","多重耐药",G("nurse_nk","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.6","护士","环境监测",G("nurse_nk","/infection-enhanced/env-monitor/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.7","护士","环境监测统计",G("nurse_nk","/infection-enhanced/env-monitor/stats"),200)
s.chk("院感","9.8","医生","院感监测",G("doctor1","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200)
s.chk("院感","9.9","医技","多重耐药",G("tech","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 场景10: 权限隔离测试
# ============================
def s10_permission():
print(f"\n{'='*60}\n场景10: 权限隔离测试\n{'='*60}")
# 医生不能访问收费
r=G("doctor1","/charge-manage/register/init")
if r.get("code")==200: s.fail("权限","10.1","医生","不应访问挂号",f"code=200")
else: s.ok("权限","10.1","医生","不能访问挂号",f"code={r.get('code')}")
# 护士不能访问西药发药
r=G("nurse_nk","/pharmacy-manage/western-medicine-dispense/init")
if r.get("code")==200: s.fail("权限","10.2","护士","不应访问西药发药",f"code=200")
else: s.ok("权限","10.2","护士","不能访问西药发药",f"code={r.get('code')}")
# 药师不能访问手术
r=G("pharmacist","/clinical-manage/surgery/surgery-page")
if r.get("code")==200: s.fail("权限","10.3","药师","不应访问手术",f"code=200")
else: s.ok("权限","10.3","药师","不能访问手术",f"code={r.get('code')}")
# 医技不能访问护理评估
r=G("tech","/nursing-assessment-enhanced/page",{"pageNum":1,"pageSize":10})
if r.get("code")==200: s.fail("权限","10.4","医技","不应访问护理评估",f"code=200")
else: s.ok("权限","10.4","医技","不能访问护理评估",f"code={r.get('code')}")
# 收费员不能访问医生站
r=G("finance","/doctor-station/main/init")
if r.get("code")==200: s.fail("权限","10.5","收费员","不应访问医生站",f"code=200")
else: s.ok("权限","10.5","收费员","不能访问医生站",f"code={r.get('code')}")
# 正向验证
r=G("doctor1","/clinical-manage/surgery/surgery-page")
if r.get("code")==200: s.ok("权限","10.6","医生","可以访问手术")
else: s.fail("权限","10.6","医生","应能访问手术",f"code={r.get('code')}")
r=G("nurse_nk","/nursing-assessment-enhanced/stats")
if r.get("code")==200: s.ok("权限","10.7","护士","可以访问护理评估")
else: s.fail("权限","10.7","护士","应能访问护理评估",f"code={r.get('code')}")
r=G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10})
if r.get("code")==200: s.ok("权限","10.8","药师","可以访问药品追溯")
else: s.fail("权限","10.8","药师","应能访问药品追溯",f"code={r.get('code')}")
r=G("tech","/radiology-image/list")
if r.get("code")==200: s.ok("权限","10.9","医技","可以访问影像管理")
else: s.fail("权限","10.9","医技","应能访问影像管理",f"code={r.get('code')}")
r=G("finance","/charge-manage/charge/init")
if r.get("code")==200: s.ok("权限","10.10","收费员","可以访问收费管理")
else: s.fail("权限","10.10","收费员","应能访问收费管理",f"code={r.get('code')}")
# ============================
# 场景11: 中医+质控
# ============================
def s11_tcm_quality():
print(f"\n{'='*60}\n场景11: 中医+质控\n{'='*60}")
s.chk("中医","11.1","医生","中医方剂",G("doctor1","/api/v1/tcm/prescriptions"),200)
s.chk("中医","11.2","医生","中医统计",G("doctor1","/api/v1/tcm/statistics"),200)
s.chk("中医","11.3","医生","体质辨识(患者6006)",G("doctor1","/api/v1/tcm/constitution/encounter/6006"),200)
s.chk("质控","11.4","医技","质控指标",G("tech","/quality-enhanced/indicator/page",{"pageNum":1,"pageSize":10}),200)
s.chk("质控","11.5","医技","医嘱统计",G("tech","/quality-enhanced/order-stats/page",{"pageNum":1,"pageSize":10}),200)
s.chk("质控","11.6","医技","质控指标汇总",G("tech","/quality-enhanced/indicator/summary"),200)
# ============================
# 场景12: 报表+经营
# ============================
def s12_reports():
print(f"\n{'='*60}\n场景12: 报表+经营分析\n{'='*60}")
s.chk("报表","12.1","财务","挂号报表",G("finance","/report-manage/register/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.2","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.3","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.4","财务","入库报表",G("finance","/report-manage/inbound/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.5","财务","出库报表",G("finance","/report-manage/outbound/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.6","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200)
s.chk("报表","12.7","财务","经营汇总",G("finance","/business-analytics/summary"),200)
s.chk("报表","12.8","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200)
# ============================
# 主入口
# ============================
if __name__=="__main__":
print(f"{'='*70}\nHealthLink-HIS 三甲医院多角色协作测试 v2\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{'='*70}")
print("\n>>> 登录所有角色...")
for k,a in ACCOUNTS.items():
if login(k): print(f"{a['role']}({a['user']})")
else: print(f"{a['role']}({a['user']})")
for fn in [s1_outpatient,s2_inpatient,s3_surgery,s4_inspection,s5_consultation,s6_emergency,s7_insurance,s8_pharmacy,s9_infection,s10_permission,s11_tcm_quality,s12_reports]:
try: fn()
except Exception as e: print(f"{fn.__name__}: {e}")
print(f"\n{'='*70}")
print(f"汇总: 总数={s.t}, 通过={s.p}, 失败={s.f}, 通过率={s.p*100/s.t:.1f}%" if s.t else "")
print(f"{'='*70}")
os.makedirs("MD/test/reports",exist_ok=True)
rp=f"MD/test/reports/multi_role_v2_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
with open(rp,"w") as f:
f.write(f"# 多角色协作测试报告 v2\n\n**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## 汇总\n\n- 总数: {s.t}\n- 通过: {s.p}\n- 失败: {s.f}\n- 通过率: {s.p*100/s.t:.1f}%\n\n## 详细\n\n| 场景 | 步骤 | 角色 | 测试项 | 状态 | 说明 |\n|------|------|------|--------|------|------|\n")
for r in s.r: f.write(f"| {r['sc']} | {r['step']} | {r['role']} | {r['name']} | {r['s']} | {r['d']} |\n")
print(f"\n📄 报告: {rp}")
sys.exit(0 if s.f==0 else 1)