feat(test): 多角色协作全流程测试+修复密码配置
- 05_test_multi_role.py: 12个场景88个测试用例 - 10个角色账户: admin/doctor1/jzys/jzhs/nkhs1/ssshs1/yjk1/医技员/sfy/hzzj1 - 场景覆盖: 门诊/住院/手术/检验/会诊/急诊/医保/药品/院感/权限/中医/质控 - 权限隔离测试: 验证角色只能访问其权限范围 - 测试结果: 28/88通过(31.8%), 主要问题是API路径不匹配
This commit is contained in:
686
MD/test/05_test_multi_role.py
Executable file
686
MD/test/05_test_multi_role.py
Executable file
@@ -0,0 +1,686 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user