Files
his/MD/test/04_test_business_logic_v2.py
华佗 e31337b58a feat(test): 业务逻辑级测试脚本+测试报告
- 04_test_business_logic.py: 业务逻辑测试v1(111用例)
- 04_test_business_logic_v2.py: 修正API路径后v2(107用例,通过率31.8%)
- 测试报告: 揭示大量API路径不匹配和参数问题
- 测试数据: SQL脚本覆盖31个业务模块
- 测试流程: 30个业务流程图+API映射

测试发现的问题:
1. 多个Controller缺少/page端点
2. 部分接口需要必填参数(patientId, startTime等)
3. 部分接口响应格式非标准(rows嵌套为dict)
4. DB列名不匹配(create_by不存在等)
2026-06-07 21:54:20 +08:00

632 lines
25 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HealthLink-HIS 三甲医院全流程业务逻辑测试 v2
使用实际Controller中的正确API路径
"""
import requests, json, sys, os, time
from datetime import datetime
from typing import Dict, Any, List
BASE_URL = "http://localhost:18082/healthlink-his"
TOKEN = ""
class TestStats:
def __init__(self):
self.total = self.passed = self.failed = 0
self.results = []
def record(self, module, case_id, name, passed, detail=""):
self.total += 1
if passed: self.passed += 1
else: self.failed += 1
status = "✅ PASS" if passed else "❌ FAIL"
self.results.append({"module":module,"case_id":case_id,"name":name,"status":status,"detail":detail})
print(f" {status} [{module}] {case_id}: {name}")
if detail and not passed: 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 = TestStats()
def login():
r = requests.post(f"{BASE_URL}/login", json={"username":"admin","password":"admin123","tenantId":"1"})
return r.json().get("token","")
def H(): return {"Authorization":f"Bearer {TOKEN}","Content-Type":"application/json"}
def GET(p, params=None): return requests.get(f"{BASE_URL}{p}", headers=H(), params=params).json()
def POST(p, d=None): return requests.post(f"{BASE_URL}{p}", headers=H(), json=d).json()
def PUT(p, d=None): return requests.put(f"{BASE_URL}{p}", headers=H(), json=d).json()
def chk_resp(resp, mod, cid, name, code=200, fields=None, not_empty=None):
ok = True; detail = ""
if resp.get("code") != code:
ok = False; detail = f"code={resp.get('code')}, msg={resp.get('msg','')[:120]}"
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(mod, cid, name, ok, detail)
return resp
def chk_page(resp, mod, cid, name, min_rows=0):
ok = True; detail = ""
if resp.get("code") != 200:
ok = False; detail = f"code={resp.get('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(mod, cid, name, ok, detail)
return resp
# ============================
# 模块1: 系统登录认证
# ============================
def test_auth():
print(f"\n{'='*50}\n模块1: 系统登录认证\n{'='*50}")
r = POST("/login", {"username":"admin","password":"admin123","tenantId":"1"})
chk_resp(r, "认证","1.1","登录成功-返回token", 200, ["token"], ["token"])
r = POST("/login", {"username":"admin","password":"wrong","tenantId":"1"})
chk_resp(r, "认证","1.2","错误密码-应失败", 500)
r = GET("/getInfo")
chk_resp(r, "认证","1.3","获取用户信息", 200, ["user","roles"], ["user"])
r = GET("/getRouters")
chk_resp(r, "认证","1.4","获取路由菜单", 200, ["data"], ["data"])
# ============================
# 模块2: 门诊挂号 (实际路径: /charge-manage/register)
# ============================
def test_registration():
print(f"\n{'='*50}\n模块2: 门诊挂号流程\n{'='*50}")
# 2.1 挂号初始化 - 应返回选项数据
r = GET("/charge-manage/register/init")
chk_resp(r, "挂号","2.1","挂号初始化", 200)
# 2.2 当日挂号列表 (current-day-encounter)
r = GET("/charge-manage/register/current-day-encounter")
chk_resp(r, "挂号","2.2","当日挂号列表", 200)
# 2.3 患者元数据查询
r = GET("/charge-manage/register/patient-metadata", {"searchKey":"测试"})
chk_resp(r, "挂号","2.3","患者元数据查询", 200)
# 2.4 医生列表
r = GET("/charge-manage/register/all-doctors")
chk_resp(r, "挂号","2.4","全部医生列表", 200)
# ============================
# 模块3: 门诊医生站 (实际路径)
# ============================
def test_doctor_station():
print(f"\n{'='*50}\n模块3: 门诊医生站\n{'='*50}")
# 3.1 医生站初始化
r = GET("/doctor-station/main/init")
chk_resp(r, "医生站","3.1","医生站初始化", 200)
# 3.2 患者信息查询
r = GET("/doctor-station/main/patient-info")
chk_resp(r, "医生站","3.2","患者信息查询", 200)
# 3.3 接诊统计
r = GET("/doctor-station/main/reception-statistics")
chk_resp(r, "医生站","3.3","接诊统计", 200)
# 3.4 医嘱基础信息
r = GET("/doctor-station/advice/advice-base-info")
chk_resp(r, "医生站","3.4","医嘱基础信息", 200)
# 3.5 诊断初始化
r = GET("/doctor-station/diagnosis/init")
chk_resp(r, "医生站","3.5","诊断初始化", 200)
# 3.6 诊断定义分类
r = GET("/doctor-station/diagnosis/get-condition-definition-class")
chk_resp(r, "医生站","3.6","诊断定义分类", 200)
# 3.7 检查申请
r = GET("/doctor-station/inspection/init")
chk_resp(r, "医生站","3.7","检查申请初始化", 200)
# ============================
# 模块4: 收费管理 (实际路径)
# ============================
def test_charge():
print(f"\n{'='*50}\n模块4: 收费管理\n{'='*50}")
# 4.1 门诊收费初始化
r = GET("/charge-manage/charge/init")
chk_resp(r, "收费","4.1","门诊收费初始化", 200)
# 4.2 门诊收费-患者列表
r = GET("/charge-manage/charge/encounter-patient-page")
chk_resp(r, "收费","4.2","收费患者列表", 200)
# 4.3 退费初始化
r = GET("/charge-manage/refund/init")
chk_resp(r, "收费","4.3","退费初始化", 200)
# 4.4 退费患者列表
r = GET("/charge-manage/refund/encounter-patient-page")
chk_resp(r, "收费","4.4","退费患者列表", 200)
# 4.5 住院收费初始化
r = GET("/charge-manage/inpatient-charge/init")
chk_resp(r, "收费","4.5","住院收费初始化", 200)
# 4.6 住院收费-患者列表
r = GET("/charge-manage/inpatient-charge/encounter-patient-page")
chk_resp(r, "收费","4.6","住院收费患者列表", 200)
# 4.7 定价-患者信息
r = GET("/charge-manage/pricing/patient-info")
chk_resp(r, "收费","4.7","定价患者信息", 200)
# ============================
# 模块5: 住院管理 (实际路径)
# ============================
def test_inpatient():
print(f"\n{'='*50}\n模块5: 住院管理\n{'='*50}")
# 5.1 患者主页初始化
r = GET("/patient-home-manage/init")
chk_resp(r, "住院","5.1","患者主页初始化", 200)
# 5.2 空床查询
r = GET("/patient-home-manage/empty-bed")
chk_resp(r, "住院","5.2","空床查询", 200)
# 5.3 科室统计
r = GET("/patient-home-manage/caty")
chk_resp(r, "住院","5.3","科室统计", 200)
# 5.4 押金初始化
r = GET("/deposit-manage/init")
chk_resp(r, "住院","5.4","押金初始化", 200)
# 5.5 入院登记-按医生
r = GET("/inhospitalmanage/register/ward-list")
chk_resp(r, "住院","5.5","入院登记-病区列表", 200)
# 5.6 入院登记-床位数
r = GET("/inhospitalmanage/register/beds-num")
chk_resp(r, "住院","5.6","入院登记-床位数", 200)
# 5.7 入院登记-患者信息
r = GET("/inhospitalmanage/register/patient-info")
chk_resp(r, "住院","5.7","入院登记-患者信息", 200)
# ============================
# 模块6: 护理管理
# ============================
def test_nursing():
print(f"\n{'='*50}\n模块6: 护理管理\n{'='*50}")
# 6.1 护理评估列表
r = GET("/nursing-assessment-enhanced/page", {"pageNum":1,"pageSize":10})
chk_page(r, "护理","6.1","护理评估列表")
# 6.2 护理评估统计
r = GET("/nursing-assessment-enhanced/stats")
chk_resp(r, "护理","6.2","护理评估统计", 200)
# 6.3 Braden评估 - 验证分数计算
braden = {"patientName":"测试患者甲","encounterId":"6006",
"itemScores":json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}),
"detail":"压疮高危"}
r = POST("/nursing-assessment-enhanced/braden/assess", braden)
chk_resp(r, "护理","6.3","Braden评估-成功", 200)
# 6.4 Morse跌倒评估
morse = {"patientName":"测试患者乙","encounterId":"6007",
"itemScores":json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}),
"detail":"跌倒高危"}
r = POST("/nursing-assessment-enhanced/morse/assess", morse)
chk_resp(r, "护理","6.4","Morse评估-成功", 200)
# 6.5 体征记录查询
r = GET("/vital-signs/record-search")
chk_resp(r, "护理","6.5","体征记录查询", 200)
# 6.6 体征图表
r = GET("/vital-signs-chart/page", {"pageNum":1,"pageSize":10})
chk_page(r, "护理","6.6","体征图表")
# 6.7 护理执行列表
r = GET("/nurse-station/advice-process/page", {"pageNum":1,"pageSize":10})
chk_page(r, "护理","6.7","护理执行列表")
# 6.8 护理质量
r = GET("/nursing-quality/page", {"pageNum":1,"pageSize":10})
chk_page(r, "护理","6.8","护理质量指标")
# ============================
# 模块7: 检验检查
# ============================
def test_inspection():
print(f"\n{'='*50}\n模块7: 检验检查\n{'='*50}")
endpoints = [
("7.1","标本采集","/inspection/collection/page"),
("7.2","检验观察","/inspection/observation/page"),
("7.3","标本定义","/inspection/specimen/page"),
("7.4","LIS配置","/inspection/lisConfig/page"),
("7.5","仪器管理","/inspection/instrument/page"),
("7.6","检验结果","/inspection/laboratory/page"),
("7.7","参考范围","/lab-ref-range/page"),
("7.8","检查申请","/exam/apply/page"),
]
for cid,name,path in endpoints:
r = GET(path, {"pageNum":1,"pageSize":10})
chk_page(r, "检验", cid, name)
# ============================
# 模块8: 影像检查
# ============================
def test_radiology():
print(f"\n{'='*50}\n模块8: 影像检查\n{'='*50}")
r = GET("/radiology-image/page", {"pageNum":1,"pageSize":10})
chk_page(r, "影像","8.1","影像列表")
r = GET("/radiology-enhanced/page", {"pageNum":1,"pageSize":10})
chk_page(r, "影像","8.2","影像增强")
r = GET("/radiology-comparison/page", {"pageNum":1,"pageSize":10})
chk_page(r, "影像","8.3","影像对比")
r = GET("/reconstruction/page", {"pageNum":1,"pageSize":10})
chk_page(r, "影像","8.4","3D重建")
# ============================
# 模块9: 手术麻醉
# ============================
def test_surgery():
print(f"\n{'='*50}\n模块9: 手术麻醉\n{'='*50}")
r = GET("/clinical-manage/surgery/page", {"pageNum":1,"pageSize":10})
chk_page(r, "手术","9.1","手术列表")
r = GET("/clinical-manage/surgery-schedule/page", {"pageNum":1,"pageSize":10})
chk_page(r, "手术","9.2","手术排程")
r = GET("/preop-discussion/page", {"pageNum":1,"pageSize":10})
chk_page(r, "手术","9.3","术前讨论")
r = GET("/surgery-safety-check/page", {"pageNum":1,"pageSize":10})
chk_page(r, "手术","9.4","安全核查")
r = GET("/api/v1/anesthesia/page", {"pageNum":1,"pageSize":10})
chk_page(r, "麻醉","9.5","麻醉记录")
r = GET("/anesthesia-enhanced/page", {"pageNum":1,"pageSize":10})
chk_page(r, "麻醉","9.6","麻醉增强")
# ============================
# 模块10: 院感管理
# ============================
def test_infection():
print(f"\n{'='*50}\n模块10: 院感管理\n{'='*50}")
r = GET("/infection-enhanced/surveillance/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.1","院感监测")
r = GET("/infection-enhanced/warning/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.2","院感预警")
r = GET("/infection-enhanced/resistance/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.3","耐药监测")
r = GET("/infection-enhanced/exposure/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.4","职业暴露")
r = GET("/infection-enhanced/hand-hygiene/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.5","手卫生")
r = GET("/infection-enhanced/environment/page", {"pageNum":1,"pageSize":10})
chk_page(r, "院感","10.6","环境监测")
# ============================
# 模块11: 质量管理
# ============================
def test_quality():
print(f"\n{'='*50}\n模块11: 质量管理\n{'='*50}")
r = GET("/quality-enhanced/runtime/page", {"pageNum":1,"pageSize":10})
chk_page(r, "质控","11.1","运行质控")
r = GET("/api/v1/emr-quality/page", {"pageNum":1,"pageSize":10})
chk_page(r, "质控","11.2","终末质控")
r = GET("/quality-enhanced/statistics/page", {"pageNum":1,"pageSize":10})
chk_page(r, "质控","11.3","质量统计")
# ============================
# 模块12: 中医管理
# ============================
def test_tcm():
print(f"\n{'='*50}\n模块12: 中医管理\n{'='*50}")
r = GET("/api/v1/tcm/constitution/page", {"pageNum":1,"pageSize":10})
chk_page(r, "中医","12.1","中医体质", min_rows=1)
r = GET("/api/v1/tcm/prescriptions", {"pageNum":1,"pageSize":10})
rows = r.get("rows", r.get("data", []))
ok = isinstance(rows, list) and len(rows) >= 3
stats.record("中医","12.2","方剂列表>=3个", ok, "" if ok else f"实际={len(rows)}")
r = GET("/api/v1/tcm/statistics")
chk_resp(r, "中医","12.3","中医统计", 200)
# ============================
# 模块13: 会诊管理
# ============================
def test_consultation():
print(f"\n{'='*50}\n模块13: 会诊管理\n{'='*50}")
r = GET("/consultation/page", {"pageNum":1,"pageSize":10})
chk_page(r, "会诊","13.1","会诊记录")
r = GET("/cross-module/consult-feedback/page", {"pageNum":1,"pageSize":10})
chk_page(r, "会诊","13.2","会诊反馈")
r = GET("/cross-module/consulttimeout/page", {"pageNum":1,"pageSize":10})
chk_page(r, "会诊","13.3","会诊超时")
# ============================
# 模块14: 临床路径
# ============================
def test_pathway():
print(f"\n{'='*50}\n模块14: 临床路径\n{'='*50}")
r = GET("/clinical-pathway/page", {"pageNum":1,"pageSize":10})
rows = r.get("rows", r.get("data", []))
ok = isinstance(rows, list) and len(rows) >= 2
stats.record("路径","14.1","临床路径>=2条", ok, "" if ok else f"实际={len(rows)}")
# ============================
# 模块15: 危急值
# ============================
def test_critical():
print(f"\n{'='*50}\n模块15: 危急值管理\n{'='*50}")
r = GET("/api/v1/critical-value/page", {"pageNum":1,"pageSize":10})
chk_page(r, "危急值","15.1","危急值列表")
# ============================
# 模块16: 处方点评
# ============================
def test_review():
print(f"\n{'='*50}\n模块16: 处方点评\n{'='*50}")
r = GET("/api/v1/review/plans", {"pageNum":1,"pageSize":10})
chk_page(r, "点评","16.1","点评计划")
r = GET("/api/v1/review/records", {"pageNum":1,"pageSize":10})
chk_page(r, "点评","16.2","点评记录")
r = GET("/api/v1/review/statistics")
chk_resp(r, "点评","16.3","点评统计", 200)
# ============================
# 模块17: 合理用药
# ============================
def test_rational_drug():
print(f"\n{'='*50}\n模块17: 合理用药\n{'='*50}")
r = GET("/api/v1/rational-drug/page", {"pageNum":1,"pageSize":10})
chk_page(r, "用药","17.1","合理用药")
r = GET("/api/v1/rational-drug/interaction/page", {"pageNum":1,"pageSize":10})
chk_page(r, "用药","17.2","相互作用")
r = GET("/api/v1/rational-drug/statistics")
chk_resp(r, "用药","17.3","用药统计", 200)
r = GET("/api/v1/rational-drug/audit-log", {"pageNum":1,"pageSize":10})
chk_page(r, "用药","17.4","审计日志")
# ============================
# 模块18: 药品追溯
# ============================
def test_drug_trace():
print(f"\n{'='*50}\n模块18: 药品追溯\n{'='*50}")
r = GET("/drugtrace/page", {"pageNum":1,"pageSize":10})
chk_page(r, "追溯","18.1","药品追溯")
# ============================
# 模块19: EMPI
# ============================
def test_empi():
print(f"\n{'='*50}\n模块19: EMPI主索引\n{'='*50}")
r = GET("/api/v1/empi/page", {"pageNum":1,"pageSize":10})
chk_page(r, "EMPI","19.1","EMPI索引")
# ============================
# 模块20: ESB
# ============================
def test_esb():
print(f"\n{'='*50}\n模块20: ESB数据集成\n{'='*50}")
r = GET("/esb/message/page", {"pageNum":1,"pageSize":10})
chk_page(r, "ESB","20.1","ESB消息")
r = GET("/esb/registry/page", {"pageNum":1,"pageSize":10})
chk_page(r, "ESB","20.2","ESB服务注册")
# ============================
# 模块21: CA签名
# ============================
def test_ca():
print(f"\n{'='*50}\n模块21: 电子签名\n{'='*50}")
r = GET("/api/v1/ca-signature/page", {"pageNum":1,"pageSize":10})
chk_page(r, "CA","21.1","CA签名")
r = GET("/api/v1/ca-signature/statistics")
chk_resp(r, "CA","21.2","CA签名统计", 200)
# ============================
# 模块22: 病案管理
# ============================
def test_mr():
print(f"\n{'='*50}\n模块22: 病案管理\n{'='*50}")
r = GET("/api/v1/mr-homepage/page", {"pageNum":1,"pageSize":10})
chk_page(r, "病案","22.1","病案首页")
# ============================
# 模块23: 随访
# ============================
def test_followup():
print(f"\n{'='*50}\n模块23: 随访管理\n{'='*50}")
r = GET("/followup/page", {"pageNum":1,"pageSize":10})
chk_page(r, "随访","23.1","随访计划")
# ============================
# 模块24: 知情同意
# ============================
def test_consent():
print(f"\n{'='*50}\n模块24: 知情同意\n{'='*50}")
r = GET("/informed-consent/page", {"pageNum":1,"pageSize":10})
chk_page(r, "知情","24.1","知情同意")
# ============================
# 模块25: 消毒供应
# ============================
def test_cssd():
print(f"\n{'='*50}\n模块25: 消毒供应\n{'='*50}")
r = GET("/cssd/page", {"pageNum":1,"pageSize":10})
chk_page(r, "CSSD","25.1","消毒追溯")
# ============================
# 模块26: 急诊
# ============================
def test_emergency():
print(f"\n{'='*50}\n模块26: 急诊管理\n{'='*50}")
r = GET("/emergency/page", {"pageNum":1,"pageSize":10})
chk_page(r, "急诊","26.1","急诊记录")
r = GET("/triage/queue/page", {"pageNum":1,"pageSize":10})
chk_page(r, "急诊","26.2","分诊排队")
# ============================
# 模块27: 医保
# ============================
def test_insurance():
print(f"\n{'='*50}\n模块27: 医保管理\n{'='*50}")
r = GET("/yb-request/page", {"pageNum":1,"pageSize":10})
chk_page(r, "医保","27.1","医保请求")
# ============================
# 模块28: 抗菌药物
# ============================
def test_antibiotic():
print(f"\n{'='*50}\n模块28: 抗菌药物\n{'='*50}")
r = GET("/api/v1/antibiotic/page", {"pageNum":1,"pageSize":10})
chk_page(r, "抗菌","28.1","抗菌药物")
# ============================
# 模块29: DRG分析
# ============================
def test_drg():
print(f"\n{'='*50}\n模块29: DRG分析\n{'='*50}")
r = GET("/drg-analysis/page", {"pageNum":1,"pageSize":10})
chk_page(r, "DRG","29.1","DRG分析")
r = GET("/mr-drg/page", {"pageNum":1,"pageSize":10})
chk_page(r, "DRG","29.2","DRG分组")
# ============================
# 模块30: 经营分析
# ============================
def test_analytics():
print(f"\n{'='*50}\n模块30: 经营分析\n{'='*50}")
r = GET("/business-analytics/page", {"pageNum":1,"pageSize":10})
chk_page(r, "经营","30.1","经营分析")
# ============================
# 模块31: 系统管理
# ============================
def test_system():
print(f"\n{'='*50}\n模块31: 系统管理\n{'='*50}")
r = GET("/dict-dictionary/definition/page", {"pageNum":1,"pageSize":10})
chk_page(r, "系统","31.1","字典定义")
r = GET("/system/user/list")
data = r.get("data",[])
ok = isinstance(data, list) and len(data) > 0
stats.record("系统","31.2","用户列表非空", ok, "" if ok else "用户列表为空")
r = GET("/system/role/list")
data = r.get("data",[])
ok = isinstance(data, list) and len(data) > 0
stats.record("系统","31.3","角色列表非空", ok, "" if ok else "角色列表为空")
r = GET("/system/menu/list")
data = r.get("data",[])
ok = isinstance(data, list) and len(data) > 50
stats.record("系统","31.4","菜单>50条", ok, "" if ok else f"实际={len(data) if isinstance(data,list) else 'N/A'}")
r = GET("/system/dept/list")
data = r.get("data",[])
ok = isinstance(data, list) and len(data) > 5
stats.record("系统","31.5","部门>5个", ok, "" if ok else f"实际={len(data) if isinstance(data,list) else 'N/A'}")
# ============================
# 模块32: 药房管理
# ============================
def test_pharmacy():
print(f"\n{'='*50}\n模块32: 药房管理\n{'='*50}")
r = GET("/pharmacy-stock-alert/page", {"pageNum":1,"pageSize":10})
chk_page(r, "药房","32.1","库存预警")
r = GET("/pharmacy-manage/western-medicine-dispense/page", {"pageNum":1,"pageSize":10})
chk_page(r, "药房","32.2","西药发药")
r = GET("/pharmacy-manage/return-medicine/page", {"pageNum":1,"pageSize":10})
chk_page(r, "药房","32.3","退药管理")
# ============================
# 模块33: 报表管理
# ============================
def test_reports():
print(f"\n{'='*50}\n模块33: 报表管理\n{'='*50}")
r = GET("/report-manage/register/page", {"pageNum":1,"pageSize":10})
chk_page(r, "报表","33.1","挂号报表")
r = GET("/report-manage/charge/page", {"pageNum":1,"pageSize":10})
chk_page(r, "报表","33.2","收费报表")
r = GET("/report-manage/report-statistics/page", {"pageNum":1,"pageSize":10})
chk_page(r, "报表","33.3","经营统计")
# ============================
# 主入口
# ============================
if __name__ == "__main__":
print(f"{'='*70}")
print("HealthLink-HIS 三甲医院全流程业务逻辑测试 v2")
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"测试环境: {BASE_URL}")
print(f"{'='*70}")
print("\n>>> 登录系统...")
TOKEN = login()
if not TOKEN:
print("❌ 登录失败!"); sys.exit(1)
print("✅ 登录成功")
for fn in [test_auth, test_registration, test_doctor_station, test_charge,
test_inpatient, test_nursing, test_inspection, test_radiology,
test_surgery, test_infection, test_quality, test_tcm,
test_consultation, test_pathway, test_critical, test_review,
test_rational_drug, test_drug_trace, test_empi, test_esb,
test_ca, test_mr, test_followup, test_consent, test_cssd,
test_emergency, test_insurance, test_antibiotic, test_drg,
test_analytics, test_system, test_pharmacy, test_reports]:
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/biz_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- 总数: {stats.total}\n- 通过: {stats.passed}\n- 失败: {stats.failed}\n- 通过率: {stats.passed*100/stats.total:.1f}%\n\n" if stats.total else "")
f.write("## 详细\n\n| 模块 | 编号 | 测试项 | 状态 | 说明 |\n|------|------|--------|------|------|\n")
for r in stats.results:
f.write(f"| {r['module']} | {r['case_id']} | {r['name']} | {r['status']} | {r['detail']} |\n")
print(f"\n📄 报告: {rp}")
sys.exit(0 if ok else 1)