Files
his/MD/test/05_test_multi_role.py
华佗 db66f158cf feat(test): 多角色协作全流程测试+修复密码配置
- 05_test_multi_role.py: 12个场景88个测试用例
- 10个角色账户: admin/doctor1/jzys/jzhs/nkhs1/ssshs1/yjk1/医技员/sfy/hzzj1
- 场景覆盖: 门诊/住院/手术/检验/会诊/急诊/医保/药品/院感/权限/中医/质控
- 权限隔离测试: 验证角色只能访问其权限范围
- 测试结果: 28/88通过(31.8%), 主要问题是API路径不匹配
2026-06-07 22:03:41 +08:00

687 lines
30 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HealthLink-HIS 三甲医院多角色协作全流程测试
版本: v2.0
日期: 2026-06-07
测试理念:
- 模拟真实三甲医院工作流:多角色按序协作
- 每个场景由不同角色依次操作,验证数据流转
- 验证角色权限隔离A角色不能操作B角色的功能
- 验证业务链路完整性(挂号→就诊→检查→发药→结算)
"""
import requests, json, sys, os, time
from datetime import datetime
from typing import Dict, Any, Optional, Tuple
BASE_URL = "http://localhost:18082/healthlink-his"
# ============================
# 角色账户配置(基于系统现有用户)
# ============================
ACCOUNTS = {
"admin": {"user": "admin", "pwd": "admin123", "role": "超级管理员", "role_id": 1},
"doctor1": {"user": "doctor1", "pwd": "123456", "role": "医生", "role_id": 200},
"doctor_jz": {"user": "jzys", "pwd": "123456", "role": "急诊医生", "role_id": 200},
"nurse_jz": {"user": "jzhs", "pwd": "123456", "role": "急诊护士", "role_id": 201},
"nurse_nk": {"user": "nkhs1", "pwd": "123456", "role": "内科护士", "role_id": 201},
"nurse_ss": {"user": "ssshs1", "pwd": "123456", "role": "手术室护士", "role_id": 201},
"pharmacist":{"user": "yjk1", "pwd": "123456", "role": "药剂科", "role_id": 203},
"tech": {"user": "医技员", "pwd": "123456", "role": "医技", "role_id": 204},
"finance": {"user": "sfy", "pwd": "123456", "role": "收费员", "role_id": 213},
"consult": {"user": "hzzj1", "pwd": "123456", "role": "会诊专家", "role_id": 200},
}
class Stats:
def __init__(self):
self.total = self.passed = self.failed = 0
self.results = []
def record(self, scenario, step, role, name, ok, detail=""):
self.total += 1
if ok: self.passed += 1
else: self.failed += 1
s = "✅ PASS" if ok else "❌ FAIL"
self.results.append({"scenario":scenario,"step":step,"role":role,"name":name,"status":s,"detail":detail})
print(f" {s} [{scenario}] {step} ({role}): {name}")
if detail and not ok: print(f"{detail}")
def summary(self):
print(f"\n{'='*70}")
print(f"测试汇总: 总数={self.total}, 通过={self.passed}, 失败={self.failed}")
if self.total > 0: print(f"通过率: {self.passed*100/self.total:.1f}%")
print(f"{'='*70}")
return self.failed == 0
stats = Stats()
# ============================
# Token管理
# ============================
tokens = {}
def login_as(account_key: str) -> str:
"""以指定角色登录"""
if account_key in tokens and tokens[account_key]:
return tokens[account_key]
acc = ACCOUNTS[account_key]
try:
r = requests.post(f"{BASE_URL}/login", json={
"username": acc["user"], "password": acc["pwd"], "tenantId": "1"
}).json()
token = r.get("token", "")
if token:
tokens[account_key] = token
return token
except Exception as e:
print(f"{account_key} 登录异常: {e}")
return ""
def H(account_key: str):
return {"Authorization": f"Bearer {tokens.get(account_key,'')}", "Content-Type": "application/json"}
def GET(account_key, path, params=None):
return requests.get(f"{BASE_URL}{path}", headers=H(account_key), params=params).json()
def POST(account_key, path, data=None):
return requests.post(f"{BASE_URL}{path}", headers=H(account_key), json=data).json()
def PUT(account_key, path, data=None):
return requests.put(f"{BASE_URL}{path}", headers=H(account_key), json=data).json()
def DELETE(account_key, path):
return requests.delete(f"{BASE_URL}{path}", headers=H(account_key)).json()
# ============================
# 断言工具
# ============================
def chk(scenario, step, role, name, resp, expect_code=200, fields=None, not_empty=None):
ok = True; detail = ""
code = resp.get("code")
if code != expect_code:
ok = False
msg = resp.get("msg", "")[:150]
detail = f"code={code}, msg={msg}"
if ok and fields:
for f in fields:
if f not in resp:
ok = False; detail = f"缺少字段: {f}"; break
if ok and not_empty:
for f in not_empty:
v = resp.get(f)
if v is None or (isinstance(v, (list, dict)) and len(v) == 0):
ok = False; detail = f"字段{f}为空"; break
stats.record(scenario, step, role, name, ok, detail)
return resp
def chk_page(scenario, step, role, name, resp, min_rows=0):
ok = True; detail = ""
code = resp.get("code")
if code != 200:
ok = False; detail = f"code={code}, msg={resp.get('msg','')[:120]}"
else:
rows = resp.get("rows", resp.get("data", []))
if not isinstance(rows, list):
ok = False; detail = f"rows类型异常: {type(rows)}"
elif len(rows) < min_rows:
ok = False; detail = f"rows={len(rows)}, 需要>={min_rows}"
stats.record(scenario, step, role, name, ok, detail)
return resp
# ================================================================
# 场景1: 门诊就诊全流程(收费员→医生→医技→药师→收费员)
# ================================================================
def scenario_outpatient():
print(f"\n{'='*60}")
print("场景1: 门诊就诊全流程")
print("角色链: 收费员(挂号) → 医生(接诊+开方) → 医技(检查) → 药师(发药) → 收费员(结算)")
print(f"{'='*60}")
# Step 1: 收费员初始化挂号
r = GET("finance", "/charge-manage/register/init")
chk("门诊", "1.1", "收费员", "挂号初始化", r, 200, ["priorityLevelOptionOptions"])
# Step 2: 收费员查询患者
r = GET("finance", "/charge-manage/register/patient-metadata", {"searchKey": "测试"})
chk("门诊", "1.2", "收费员", "查询患者信息", r, 200)
# Step 3: 收费员查询医生列表
r = GET("finance", "/charge-manage/register/all-doctors")
chk("门诊", "1.3", "收费员", "查询医生列表", r, 200)
# Step 4: 医生登录后查看待诊患者
r = GET("doctor1", "/doctor-station/main/init")
chk("门诊", "1.4", "医生", "医生站初始化", r, 200)
# Step 5: 医生查看患者信息
r = GET("doctor1", "/doctor-station/main/patient-info")
chk("门诊", "1.5", "医生", "查看患者信息", r, 200)
# Step 6: 医生查看接诊统计
r = GET("doctor1", "/doctor-station/main/reception-statistics")
chk("门诊", "1.6", "医生", "接诊统计", r, 200)
# Step 7: 医生查看医嘱基础信息
r = GET("doctor1", "/doctor-station/advice/advice-base-info")
chk("门诊", "1.7", "医生", "医嘱基础信息", r, 200)
# Step 8: 医生诊断初始化
r = GET("doctor1", "/doctor-station/diagnosis/init")
chk("门诊", "1.8", "医生", "诊断初始化", r, 200)
# Step 9: 医技查看检查申请(医技角色视角)
r = GET("tech", "/inspection/laboratory/page", {"pageNum": 1, "pageSize": 10})
chk_page("门诊", "1.9", "医技", "查看检验结果列表", r)
# Step 10: 医技查看影像
r = GET("tech", "/radiology-image/page", {"pageNum": 1, "pageSize": 10})
chk_page("门诊", "1.10", "医技", "查看影像列表", r)
# Step 11: 药师查看药房管理
r = GET("pharmacist", "/pharmacy-stock-alert/page", {"pageNum": 1, "pageSize": 10})
chk_page("门诊", "1.11", "药师", "药品库存预警", r)
# Step 12: 药师查看西药发药
r = GET("pharmacist", "/pharmacy-manage/western-medicine-dispense/page", {"pageNum": 1, "pageSize": 10})
chk_page("门诊", "1.12", "药师", "西药发药列表", r)
# Step 13: 收费员收费初始化
r = GET("finance", "/charge-manage/charge/init")
chk("门诊", "1.13", "收费员", "收费初始化", r, 200)
# Step 14: 收费员查看收费患者列表
r = GET("finance", "/charge-manage/charge/encounter-patient-page")
chk("门诊", "1.14", "收费员", "收费患者列表", r, 200)
# Step 15: 收费员退费初始化
r = GET("finance", "/charge-manage/refund/init")
chk("门诊", "1.15", "收费员", "退费初始化", r, 200)
# ================================================================
# 场景2: 住院入院全流程(收费员→医生→护士→药师→医生)
# ================================================================
def scenario_inpatient():
print(f"\n{'='*60}")
print("场景2: 住院入院全流程")
print("角色链: 收费员(入院) → 医生(医嘱) → 护士(护理) → 药师(发药) → 医生(出院)")
print(f"{'='*60}")
# Step 1: 收费员查看入院登记
r = GET("finance", "/charge-manage/inpatient-charge/init")
chk("住院", "2.1", "收费员", "住院收费初始化", r, 200)
# Step 2: 收费员查看住院患者列表
r = GET("finance", "/charge-manage/inpatient-charge/encounter-patient-page")
chk("住院", "2.2", "收费员", "住院患者列表", r, 200)
# Step 3: 医生查看患者主页
r = GET("doctor1", "/patient-home-manage/init")
chk("住院", "2.3", "医生", "患者主页初始化", r, 200)
# Step 4: 医生查看空床
r = GET("doctor1", "/patient-home-manage/empty-bed")
chk("住院", "2.4", "医生", "空床查询", r, 200)
# Step 5: 医生查看科室统计
r = GET("doctor1", "/patient-home-manage/caty")
chk("住院", "2.5", "医生", "科室统计", r, 200)
# Step 6: 护士查看护理评估
r = GET("nurse_nk", "/nursing-assessment-enhanced/page", {"pageNum": 1, "pageSize": 10})
chk_page("住院", "2.6", "护士", "护理评估列表", r)
# Step 7: 护士查看护理评估统计
r = GET("nurse_nk", "/nursing-assessment-enhanced/stats")
chk("住院", "2.7", "护士", "护理评估统计", r, 200)
# Step 8: 护士进行Braden评估
braden = {
"patientName": "测试患者甲",
"encounterId": "6006",
"itemScores": json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}),
"detail": "压疮高危患者"
}
r = POST("nurse_nk", "/nursing-assessment-enhanced/braden/assess", braden)
chk("住院", "2.8", "护士", "Braden压疮评估", r, 200)
# Step 9: 护士进行Morse跌倒评估
morse = {
"patientName": "测试患者乙",
"encounterId": "6007",
"itemScores": json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}),
"detail": "跌倒高危患者"
}
r = POST("nurse_nk", "/nursing-assessment-enhanced/morse/assess", morse)
chk("住院", "2.9", "护士", "Morse跌倒评估", r, 200)
# Step 10: 护士查看体征记录
r = GET("nurse_nk", "/vital-signs/record-search")
chk("住院", "2.10", "护士", "体征记录查询", r, 200)
# Step 11: 护士查看护理质量
r = GET("nurse_nk", "/nursing-quality/page", {"pageNum": 1, "pageSize": 10})
chk_page("住院", "2.11", "护士", "护理质量指标", r)
# Step 12: 药师查看住院发药
r = GET("pharmacist", "/pharmacy-manage/pending-medication/page", {"pageNum": 1, "pageSize": 10})
chk_page("住院", "2.12", "药师", "待发药品列表", r)
# Step 13: 药师查看药品详情
r = GET("pharmacist", "/pharmacy-manage/medication-details/page", {"pageNum": 1, "pageSize": 10})
chk_page("住院", "2.13", "药师", "药品详情列表", r)
# Step 14: 医生查看病历质量
r = GET("doctor1", "/quality-enhanced/runtime/page", {"pageNum": 1, "pageSize": 10})
chk_page("住院", "2.14", "医生", "运行质控", r)
# ================================================================
# 场景3: 手术全流程(医生→麻醉→手术室护士→医生)
# ================================================================
def scenario_surgery():
print(f"\n{'='*60}")
print("场景3: 手术全流程")
print("角色链: 医生(申请) → 专家(讨论) → 手术室护士(核查) → 医生(记录)")
print(f"{'='*60}")
# Step 1: 医生查看手术列表
r = GET("doctor1", "/clinical-manage/surgery/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.1", "医生", "手术列表", r)
# Step 2: 医生查看手术排程
r = GET("doctor1", "/clinical-manage/surgery-schedule/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.2", "医生", "手术排程", r)
# Step 3: 专家查看术前讨论
r = GET("consult", "/preop-discussion/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.3", "会诊专家", "术前讨论", r)
# Step 4: 手术室护士查看安全核查
r = GET("nurse_ss", "/surgery-safety-check/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.4", "手术室护士", "手术安全核查", r)
# Step 5: 医生查看麻醉记录
r = GET("doctor1", "/api/v1/anesthesia/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.5", "医生", "麻醉记录", r)
# Step 6: 医生查看麻醉增强
r = GET("doctor1", "/anesthesia-enhanced/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.6", "医生", "麻醉增强", r)
# Step 7: 医生查看知情同意
r = GET("doctor1", "/informed-consent/page", {"pageNum": 1, "pageSize": 10})
chk_page("手术", "3.7", "医生", "知情同意", r)
# Step 8: 医生查看电子签名
r = GET("doctor1", "/api/v1/ca-signature/statistics")
chk("手术", "3.8", "医生", "电子签名统计", r, 200)
# ================================================================
# 场景4: 检验全流程(医生→护士→医技→医生)
# ================================================================
def scenario_inspection():
print(f"\n{'='*60}")
print("场景4: 检验全流程")
print("角色链: 医生(开单) → 护士(采样) → 医技(检验) → 医生(查看)")
print(f"{'='*60}")
# Step 1: 医生查看检查申请
r = GET("doctor1", "/exam/apply/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.1", "医生", "检查申请列表", r)
# Step 2: 护士查看标本采集
r = GET("nurse_nk", "/inspection/collection/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.2", "护士", "标本采集列表", r)
# Step 3: 医技查看检验结果
r = GET("tech", "/inspection/laboratory/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.3", "医技", "检验结果列表", r)
# Step 4: 医技查看参考范围
r = GET("tech", "/lab-ref-range/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.4", "医技", "参考范围", r)
# Step 5: 医技查看标本定义
r = GET("tech", "/inspection/specimen/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.5", "医技", "标本定义", r)
# Step 6: 医技查看仪器管理
r = GET("tech", "/inspection/instrument/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.6", "医技", "仪器管理", r)
# Step 7: 医生查看影像对比
r = GET("doctor1", "/radiology-comparison/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.7", "医生", "影像对比", r)
# Step 8: 医生查看3D重建
r = GET("doctor1", "/reconstruction/page", {"pageNum": 1, "pageSize": 10})
chk_page("检验", "4.8", "医生", "3D重建", r)
# ================================================================
# 场景5: 会诊全流程(申请医生→会诊专家→申请医生)
# ================================================================
def scenario_consultation():
print(f"\n{'='*60}")
print("场景5: 会诊全流程")
print("角色链: 医生(申请) → 专家(会诊) → 医生(执行)")
print(f"{'='*60}")
# Step 1: 医生查看会诊列表
r = GET("doctor1", "/consultation/page", {"pageNum": 1, "pageSize": 10})
chk_page("会诊", "5.1", "医生", "会诊记录", r)
# Step 2: 专家查看会诊反馈
r = GET("consult", "/cross-module/consult-feedback/page", {"pageNum": 1, "pageSize": 10})
chk_page("会诊", "5.2", "会诊专家", "会诊反馈", r)
# Step 3: 医生查看会诊超时
r = GET("doctor1", "/cross-module/consulttimeout/page", {"pageNum": 1, "pageSize": 10})
chk_page("会诊", "5.3", "医生", "会诊超时", r)
# Step 4: 医生查看临床路径
r = GET("doctor1", "/clinical-pathway/page", {"pageNum": 1, "pageSize": 10})
chk_page("会诊", "5.4", "医生", "临床路径", r)
# Step 5: 医生查看危急值
r = GET("doctor1", "/api/v1/critical-value/page", {"pageNum": 1, "pageSize": 10})
chk_page("会诊", "5.5", "医生", "危急值列表", r)
# ================================================================
# 场景6: 急诊全流程(急诊医生→急诊护士→医生)
# ================================================================
def scenario_emergency():
print(f"\n{'='*60}")
print("场景6: 急诊全流程")
print("角色链: 急诊医生(接诊) → 急诊护士(护理) → 医生(会诊)")
print(f"{'='*60}")
# Step 1: 急诊医生查看急诊记录
r = GET("doctor_jz", "/emergency/page", {"pageNum": 1, "pageSize": 10})
chk_page("急诊", "6.1", "急诊医生", "急诊记录", r)
# Step 2: 急诊护士查看分诊排队
r = GET("nurse_jz", "/triage/queue/page", {"pageNum": 1, "pageSize": 10})
chk_page("急诊", "6.2", "急诊护士", "分诊排队", r)
# Step 3: 急诊护士查看护理评估
r = GET("nurse_jz", "/nursing-assessment-enhanced/page", {"pageNum": 1, "pageSize": 10})
chk_page("急诊", "6.3", "急诊护士", "护理评估", r)
# Step 4: 急诊护士查看体征记录
r = GET("nurse_jz", "/vital-signs/record-search")
chk("急诊", "6.4", "急诊护士", "体征记录", r, 200)
# Step 5: 急诊护士查看危急值
r = GET("nurse_jz", "/api/v1/critical-value/page", {"pageNum": 1, "pageSize": 10})
chk_page("急诊", "6.5", "急诊护士", "危急值", r)
# ================================================================
# 场景7: 医保结算全流程(收费员→医保→财务)
# ================================================================
def scenario_insurance():
print(f"\n{'='*60}")
print("场景7: 医保结算全流程")
print("角色链: 收费员(收费) → 医保(结算) → 财务(审核)")
print(f"{'='*60}")
# Step 1: 收费员收费
r = GET("finance", "/charge-manage/charge/init")
chk("医保", "7.1", "收费员", "收费初始化", r, 200)
# Step 2: 收费员退费
r = GET("finance", "/charge-manage/refund/init")
chk("医保", "7.2", "收费员", "退费初始化", r, 200)
# Step 3: 财务查看报表
r = GET("finance", "/report-manage/charge/page", {"pageNum": 1, "pageSize": 10})
chk_page("医保", "7.3", "财务", "收费报表", r)
# Step 4: 财务查看经营分析
r = GET("finance", "/business-analytics/page", {"pageNum": 1, "pageSize": 10})
chk_page("医保", "7.4", "财务", "经营分析", r)
# Step 5: 财务查看库房管理
r = GET("finance", "/inventory-manage/product/page", {"pageNum": 1, "pageSize": 10})
chk_page("医保", "7.5", "财务", "库存商品", r)
# ================================================================
# 场景8: 药品全流程(药师→医生→护士)
# ================================================================
def scenario_pharmacy():
print(f"\n{'='*60}")
print("场景8: 药品全流程")
print("角色链: 药师(库存管理) → 医生(合理用药) → 护士(执行)")
print(f"{'='*60}")
# Step 1: 药师查看药品库存
r = GET("pharmacist", "/pharmacy-stock-alert/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.1", "药师", "库存预警", r)
# Step 2: 药师查看药房管理
r = GET("pharmacist", "/pharmacy-manage/western-medicine-dispense/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.2", "药师", "西药发药", r)
# Step 3: 药师查看药品追溯
r = GET("pharmacist", "/drugtrace/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.3", "药师", "药品追溯", r)
# Step 4: 药师查看合理用药
r = GET("pharmacist", "/api/v1/rational-drug/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.4", "药师", "合理用药", r)
# Step 5: 医生查看合理用药
r = GET("doctor1", "/api/v1/rational-drug/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.5", "医生", "合理用药(医生视角)", r)
# Step 6: 护士查看药房库存
r = GET("nurse_nk", "/pharmacy-stock-alert/page", {"pageNum": 1, "pageSize": 10})
chk_page("药品", "8.6", "护士", "药房库存(护士视角)", r)
# ================================================================
# 场景9: 院感全流程(护士→医生→医技)
# ================================================================
def scenario_infection():
print(f"\n{'='*60}")
print("场景9: 院感全流程")
print("角色链: 护士(监测) → 医生(诊断) → 医技(检验)")
print(f"{'='*60}")
# Step 1: 护士查看院感监测
r = GET("nurse_nk", "/infection-enhanced/surveillance/page", {"pageNum": 1, "pageSize": 10})
chk_page("院感", "9.1", "护士", "院感监测", r)
# Step 2: 医生查看院感预警
r = GET("doctor1", "/infection-enhanced/warning/page", {"pageNum": 1, "pageSize": 10})
chk_page("院感", "9.2", "医生", "院感预警", r)
# Step 3: 医技查看耐药监测
r = GET("tech", "/infection-enhanced/resistance/page", {"pageNum": 1, "pageSize": 10})
chk_page("院感", "9.3", "医技", "耐药监测", r)
# Step 4: 护士查看手卫生
r = GET("nurse_nk", "/infection-enhanced/hand-hygiene/page", {"pageNum": 1, "pageSize": 10})
chk_page("院感", "9.4", "护士", "手卫生", r)
# Step 5: 医生查看职业暴露
r = GET("doctor1", "/infection-enhanced/exposure/page", {"pageNum": 1, "pageSize": 10})
chk_page("院感", "9.5", "医生", "职业暴露", r)
# ================================================================
# 场景10: 权限隔离测试
# ================================================================
def scenario_permission():
print(f"\n{'='*60}")
print("场景10: 权限隔离测试")
print("验证: 不同角色只能访问其权限范围内的功能")
print(f"{'='*60}")
# 10.1 医生不能访问收费功能
r = GET("doctor1", "/charge-manage/register/init")
ok = r.get("code") != 200
stats.record("权限", "10.1", "医生", "医生不能访问挂号初始化", ok,
"" if ok else f"意外成功: code={r.get('code')}")
# 10.2 护士不能访问药房管理
r = GET("nurse_nk", "/pharmacy-manage/western-medicine-dispense/page", {"pageNum": 1, "pageSize": 10})
ok = r.get("code") != 200
stats.record("权限", "10.2", "护士", "护士不能访问西药发药", ok,
"" if ok else f"意外成功: code={r.get('code')}")
# 10.3 药师不能访问手术管理
r = GET("pharmacist", "/clinical-manage/surgery/page", {"pageNum": 1, "pageSize": 10})
ok = r.get("code") != 200
stats.record("权限", "10.3", "药师", "药师不能访问手术管理", ok,
"" if ok else f"意外成功: code={r.get('code')}")
# 10.4 医技不能访问护理评估
r = GET("tech", "/nursing-assessment-enhanced/page", {"pageNum": 1, "pageSize": 10})
ok = r.get("code") != 200
stats.record("权限", "10.4", "医技", "医技不能访问护理评估", ok,
"" if ok else f"意外成功: code={r.get('code')}")
# 10.5 收费员不能访问医生站
r = GET("finance", "/doctor-station/main/init")
ok = r.get("code") != 200
stats.record("权限", "10.5", "收费员", "收费员不能访问医生站", ok,
"" if ok else f"意外成功: code={r.get('code')}")
# 10.6 医生可以访问手术管理
r = GET("doctor1", "/clinical-manage/surgery/page", {"pageNum": 1, "pageSize": 10})
stats.record("权限", "10.6", "医生", "医生可以访问手术管理", r.get("code") == 200,
"" if r.get("code") == 200 else f"被拒绝: code={r.get('code')}")
# 10.7 护士可以访问护理评估
r = GET("nurse_nk", "/nursing-assessment-enhanced/page", {"pageNum": 1, "pageSize": 10})
stats.record("权限", "10.7", "护士", "护士可以访问护理评估", r.get("code") == 200,
"" if r.get("code") == 200 else f"被拒绝: code={r.get('code')}")
# 10.8 药师可以访问药品追溯
r = GET("pharmacist", "/drugtrace/page", {"pageNum": 1, "pageSize": 10})
stats.record("权限", "10.8", "药师", "药师可以访问药品追溯", r.get("code") == 200,
"" if r.get("code") == 200 else f"被拒绝: code={r.get('code')}")
# 10.9 医技可以访问影像管理
r = GET("tech", "/radiology-image/page", {"pageNum": 1, "pageSize": 10})
stats.record("权限", "10.9", "医技", "医技可以访问影像管理", r.get("code") == 200,
"" if r.get("code") == 200 else f"被拒绝: code={r.get('code')}")
# 10.10 收费员可以访问收费管理
r = GET("finance", "/charge-manage/charge/init")
stats.record("权限", "10.10", "收费员", "收费员可以访问收费管理", r.get("code") == 200,
"" if r.get("code") == 200 else f"被拒绝: code={r.get('code')}")
# ================================================================
# 场景11: 中医全流程(医生→护士)
# ================================================================
def scenario_tcm():
print(f"\n{'='*60}")
print("场景11: 中医全流程")
print("角色链: 医生(辨证论治) → 护士(护理)")
print(f"{'='*60}")
# Step 1: 医生查看中医体质
r = GET("doctor1", "/api/v1/tcm/constitution/page", {"pageNum": 1, "pageSize": 10})
chk_page("中医", "11.1", "医生", "中医体质列表", r)
# Step 2: 医生查看中药方剂
r = GET("doctor1", "/api/v1/tcm/prescriptions", {"pageNum": 1, "pageSize": 10})
rows = r.get("rows", r.get("data", []))
ok = isinstance(rows, list) and len(rows) >= 2
stats.record("中医", "11.2", "医生", "中药方剂>=2个", ok, "" if ok else f"实际={len(rows)}")
# Step 3: 医生查看中医统计
r = GET("doctor1", "/api/v1/tcm/statistics")
chk("中医", "11.3", "医生", "中医统计", r, 200)
# ================================================================
# 场景12: 质控全流程(医生→医技→护士)
# ================================================================
def scenario_quality():
print(f"\n{'='*60}")
print("场景12: 质控全流程")
print("角色链: 医生(病历质控) → 医技(检查质控) → 护士(护理质控)")
print(f"{'='*60}")
# Step 1: 医生查看运行质控
r = GET("doctor1", "/quality-enhanced/runtime/page", {"pageNum": 1, "pageSize": 10})
chk_page("质控", "12.1", "医生", "运行质控", r)
# Step 2: 医技查看终末质控
r = GET("tech", "/api/v1/emr-quality/page", {"pageNum": 1, "pageSize": 10})
chk_page("质控", "12.2", "医技", "终末质控", r)
# Step 3: 护士查看护理质量
r = GET("nurse_nk", "/nursing-quality/page", {"pageNum": 1, "pageSize": 10})
chk_page("质控", "12.3", "护士", "护理质量指标", r)
# Step 4: 医生查看质量统计
r = GET("doctor1", "/quality-enhanced/statistics/page", {"pageNum": 1, "pageSize": 10})
chk_page("质控", "12.4", "医生", "质量统计", r)
# ================================================================
# 主入口
# ================================================================
if __name__ == "__main__":
print(f"{'='*70}")
print("HealthLink-HIS 三甲医院多角色协作全流程测试")
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"测试环境: {BASE_URL}")
print(f"{'='*70}")
# 登录所有角色
print("\n>>> 登录所有角色...")
all_ok = True
for key, acc in ACCOUNTS.items():
token = login_as(key)
if token:
print(f"{acc['role']}({acc['user']}) 登录成功")
else:
print(f"{acc['role']}({acc['user']}) 登录失败")
all_ok = False
if not all_ok:
print("\n⚠️ 部分角色登录失败,继续测试...")
# 执行所有场景
scenarios = [
scenario_outpatient,
scenario_inpatient,
scenario_surgery,
scenario_inspection,
scenario_consultation,
scenario_emergency,
scenario_insurance,
scenario_pharmacy,
scenario_infection,
scenario_permission,
scenario_tcm,
scenario_quality,
]
for fn in scenarios:
try: fn()
except Exception as e:
print(f" ❌ 场景异常: {fn.__name__}: {e}")
stats.record("异常", fn.__name__, "执行异常", False, str(e))
ok = stats.summary()
# 生成报告
os.makedirs("MD/test/reports", exist_ok=True)
rp = f"MD/test/reports/multi_role_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
with open(rp, "w") as f:
f.write(f"# 三甲医院多角色协作测试报告\n\n")
f.write(f"**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(f"**环境**: {BASE_URL}\n\n")
f.write(f"## 角色清单\n\n")
f.write("| 角色 | 账号 | 说明 |\n|------|------|------|\n")
for k, v in ACCOUNTS.items():
f.write(f"| {v['role']} | {v['user']} | role_id={v['role_id']} |\n")
f.write(f"\n## 汇总\n\n- 总数: {stats.total}\n- 通过: {stats.passed}\n- 失败: {stats.failed}\n")
if stats.total > 0: f.write(f"- 通过率: {stats.passed*100/stats.total:.1f}%\n")
f.write(f"\n## 详细结果\n\n| 场景 | 步骤 | 角色 | 测试项 | 状态 | 说明 |\n|------|------|------|--------|------|------|\n")
for r in stats.results:
f.write(f"| {r['scenario']} | {r['step']} | {r['role']} | {r['name']} | {r['status']} | {r['detail']} |\n")
print(f"\n📄 报告: {rp}")
sys.exit(0 if ok else 1)