Files
hms/docs/qa/role-test-results/run_multi_role_test.py
iven d623f8b2ff fix: V1 测试版本端到端验证修复 — 6 CRITICAL + 3 HIGH 问题全量修复
修复项:
- fix(db): 迁移 149 — 修复 Admin 角色权限绑定被迁移链破坏 (FE-C1)
- fix(health): 4 个 handler 添加空名称验证 — Doctor/Article/AlertRule/Tag (API-C1~C4)
- fix(health): Stats 仪表盘 new_this_week 查询修复 — SeaORM date_trunc bug (FE-C2)
- fix(server): 添加安全响应头 — X-Frame-Options/CSP/XSS-Protection/Referrer-Policy (SEC-H1)
- fix(mp): 预约创建契约修复 — notes/reason 字段映射 + 移除 schedule_id (MP-H1)
- fix(mp): 咨询会话 subject/last_message 字段改为可选 (MP-H3)
- fix(ai): AiConfig Default derive 替代手写 impl (clippy)

测试报告:
- 8 维度端到端测试全部完成 (后端 87 用例 / 前端 30 页面 / 小程序 80+ API / 安全 20 项 / 性能 20 端点)
- 多角色 7 角色 49 检查 100% 通过
- 综合测试报告 + 专家评估报告
2026-05-18 10:24:40 +08:00

794 lines
35 KiB
Python

#!/usr/bin/env python3
"""Multi-role scenario API test runner for HMS health management platform."""
import json
import time
import urllib.request
import urllib.error
import sys
from datetime import datetime
BASE = "http://localhost:3000/api/v1"
RESULTS = []
def api(method, path, token=None, body=None):
"""Make API call and return (status_code, response_dict)."""
url = f"{BASE}{path}"
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
raw = resp.read().decode()
return resp.status, json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
raw = e.read().decode() if e.fp else ""
try:
return e.code, json.loads(raw)
except:
return e.code, {"error": raw}
except Exception as ex:
return 0, {"error": str(ex)}
def login(username, password="Admin@2026"):
status, resp = api("POST", "/auth/login", body={"username": username, "password": password})
if resp.get("success"):
return resp["data"]["access_token"]
print(f" LOGIN FAILED for {username}: {resp}")
return None
def record(role, chain, test_id, description, passed, detail=""):
RESULTS.append({
"role": role, "chain": chain, "test_id": test_id,
"description": description, "passed": passed, "detail": detail
})
status = "PASS" if passed else "FAIL"
print(f" [{status}] {role}-{chain}.{test_id}: {description}" + (f" -- {detail}" if detail and not passed else ""))
def check_api(role, chain, test_id, desc, method, path, token, expected_success=True, body=None):
status, resp = api(method, path, token, body)
# Handle non-dict responses (e.g. raw lists)
if not isinstance(resp, dict):
ok = status == 200
record(role, chain, test_id, desc, ok, f"status={status}" if not ok else "")
return resp if ok else None
ok = resp.get("success", False) == expected_success if isinstance(expected_success, bool) else True
detail = ""
if not ok:
detail = f"status={status}, msg={resp.get('message', resp.get('error', ''))}"
record(role, chain, test_id, desc, ok, detail)
return resp if ok else None
def check_api_status(role, chain, test_id, desc, method, path, token, expected_status=200, body=None):
status, resp = api(method, path, token, body)
ok = status == expected_status
detail = ""
if not ok:
detail = f"expected={expected_status}, got={status}, msg={resp.get('message', resp.get('error', ''))}"
record(role, chain, test_id, desc, ok, detail)
return resp if ok else None
def main():
print(f"\n{'='*60}")
print(f" HMS Multi-Role Scenario API Testing")
print(f" Started: {datetime.now().isoformat()}")
print(f"{'='*60}\n")
# Login all roles
print("Logging in all 5 roles...")
time.sleep(1)
tokens = {}
for name, user in [("admin", "admin"), ("doctor", "doctor_test"), ("nurse", "nurse_test"),
("operator", "operator_test"), ("hm", "health_manager_test")]:
tokens[name] = login(user)
time.sleep(1)
if not tokens[name]:
print(f"FATAL: Cannot login as {user}")
sys.exit(1)
print("All 5 roles logged in successfully.\n")
AT = tokens["admin"]
DT = tokens["doctor"]
NT = tokens["nurse"]
OT = tokens["operator"]
HT = tokens["hm"]
# ========================================
# R01 - ADMIN (9 chains)
# ========================================
print("=" * 60)
print("R01 - ADMIN BUSINESS CHAINS")
print("=" * 60)
# Chain A: Patient creation full chain
print("\n--- Chain A: Patient Creation ---")
resp = check_api("R01", "A", "1", "Create patient", "POST", "/health/patients", AT,
body={"name": "MultiRoleTestPatient", "gender": "male", "phone": "13900001111",
"birth_date": "1990-01-01"})
patient_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.5)
check_api("R01", "A", "2", "Patient detail", "GET", f"/health/patients/{patient_id}", AT)
time.sleep(0.3)
resp = check_api("R01", "A", "3", "Create tag", "POST", "/health/patient-tags", AT,
body={"name": "HighBP-Risk-Test", "color": "#FF0000"})
tag_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R01", "A", "3b", "Assign tag to patient", "POST", f"/health/patients/{patient_id}/tags", AT,
body={"tag_ids": [tag_id]} if tag_id else None)
time.sleep(0.3)
check_api("R01", "A", "4", "Devices list", "GET", "/health/devices?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "A", "5", "Consents list (via patient)", "GET", f"/health/patients/{patient_id}/consents?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "A", "6", "Search patient", "GET", "/health/patients?search=MultiRoleTest", AT)
time.sleep(0.5)
# Chain B: Follow-up closed loop
print("\n--- Chain B: Follow-up Closed Loop ---")
resp = check_api("R01", "B", "1", "Create follow-up task", "POST", "/health/follow-up-tasks", AT,
body={"patient_id": patient_id, "follow_up_type": "phone",
"planned_date": "2026-05-20", "notes": "Admin test FU"} if patient_id else None)
fu_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R01", "B", "2", "Follow-up list (pending)", "GET", "/health/follow-up-tasks?status=pending&page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "B", "3", "Follow-up templates", "GET", "/health/follow-up-templates?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "B", "4", "Action inbox", "GET", "/health/action-inbox?page=1&page_size=10", AT)
time.sleep(0.5)
# Chain C: Consultation flow
print("\n--- Chain C: Consultation Flow ---")
check_api("R01", "C", "1", "Consultation sessions list", "GET", "/health/consultation-sessions?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "C", "2", "Doctor dashboard", "GET", "/health/doctor/dashboard", AT)
time.sleep(0.5)
# Chain D: Alert handling chain
print("\n--- Chain D: Alert Handling ---")
check_api("R01", "D", "1", "Critical value thresholds", "GET", "/health/critical-value-thresholds?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "D", "2", "Alerts list", "GET", "/health/alerts?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "D", "3", "Alert rules", "GET", "/health/alert-rules?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "D", "4", "Critical alerts", "GET", "/health/critical-alerts?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "D", "5", "BLE gateways", "GET", "/health/ble-gateways?page=1&page_size=10", AT)
time.sleep(0.5)
# Chain E: AI analysis chain
print("\n--- Chain E: AI Analysis ---")
check_api("R01", "E", "1", "AI prompts", "GET", "/ai/prompts?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "E", "2", "AI suggestions", "GET", "/ai/suggestions?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "E", "3", "AI chat sessions", "GET", "/ai/chat/sessions?page=1&page_size=10", AT)
time.sleep(0.5)
# Chain F: Content publishing chain
print("\n--- Chain F: Content Publishing ---")
resp = check_api("R01", "F", "1", "Create article", "POST", "/health/articles", AT,
body={"title": "MultiRole Test Article", "content": "Test content", "status": "draft"})
art_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R01", "F", "2", "Edit article", "PUT", f"/health/articles/{art_id}", AT,
body={"title": "MultiRole Test Article Updated", "content": "Updated content"} if art_id else None)
time.sleep(0.3)
check_api("R01", "F", "3a", "Submit article", "POST", f"/health/articles/{art_id}/submit", AT)
time.sleep(0.3)
check_api("R01", "F", "3b", "Approve article", "POST", f"/health/articles/{art_id}/approve", AT)
time.sleep(0.3)
check_api("R01", "F", "4", "Unpublish article", "POST", f"/health/articles/{art_id}/unpublish", AT)
time.sleep(0.5)
# Chain G: Points mall chain
print("\n--- Chain G: Points Mall ---")
check_api("R01", "G", "1", "Points rules", "GET", "/health/admin/points/rules?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "G", "2", "Points products", "GET", "/health/admin/points/products?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "G", "3", "Points orders", "GET", "/health/admin/points/orders?page=1&page_size=10", AT)
time.sleep(0.5)
# Chain H: Offline events chain
print("\n--- Chain H: Offline Events ---")
resp = check_api("R01", "H", "1", "Create offline event", "POST", "/health/admin/offline-events", AT,
body={"title": "MultiRole Test Event", "description": "Test event",
"event_date": "2026-06-01", "location": "Test Location", "max_participants": 50})
event_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R01", "H", "2", "List offline events", "GET", "/health/offline-events?page=1&page_size=10", AT)
time.sleep(0.5)
# Chain I: System management chain
print("\n--- Chain I: System Management ---")
check_api("R01", "I", "1", "Users list", "GET", "/users?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "I", "2", "Roles list", "GET", "/roles?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "I", "3", "Organizations", "GET", "/organizations", AT)
time.sleep(0.3)
check_api("R01", "I", "4", "Statistics dashboard", "GET", "/health/admin/statistics/dashboard", AT)
time.sleep(0.3)
check_api("R01", "I", "5", "Workflow definitions", "GET", "/workflow/definitions?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "I", "6", "Messages", "GET", "/messages?page=1&page_size=10", AT)
time.sleep(0.3)
check_api("R01", "I", "7", "Settings", "GET", "/config/settings/general", AT)
time.sleep(0.3)
check_api("R01", "I", "8", "Plugins", "GET", "/admin/plugins", AT)
time.sleep(0.3)
check_api("R01", "I", "9", "OAuth clients", "GET", "/health/oauth/clients?page=1&page_size=10", AT)
time.sleep(1)
# ========================================
# R02 - DOCTOR (5 chains + permissions)
# ========================================
print("\n" + "=" * 60)
print("R02 - DOCTOR BUSINESS CHAINS")
print("=" * 60)
# Chain A: Patient management and clinical workflow
print("\n--- Chain A: Patient & Clinical ---")
check_api("R02", "A", "1", "Patient list", "GET", "/health/patients?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "A", "2", "Patient detail", "GET", f"/health/patients/{patient_id}", DT)
time.sleep(0.3)
resp = check_api("R02", "A", "3", "Create patient", "POST", "/health/patients", DT,
body={"name": "DoctorTestPatient", "gender": "female", "phone": "13900002222",
"birth_date": "1985-03-15"})
time.sleep(0.3)
check_api("R02", "A", "4", "Doctor list", "GET", "/health/doctors?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "A", "5", "Patient diagnoses", "GET", f"/health/patients/{patient_id}/diagnoses?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "A", "6", "Consents list (via patient)", "GET", f"/health/patients/{patient_id}/consents?page=1&page_size=10", DT)
time.sleep(0.5)
# Chain B: Follow-up closed loop (doctor side)
print("\n--- Chain B: Follow-up (Doctor) ---")
resp = check_api("R02", "B", "1", "Create follow-up task", "POST", "/health/follow-up-tasks", DT,
body={"patient_id": patient_id, "follow_up_type": "visit",
"planned_date": "2026-05-21", "notes": "Doctor created FU"} if patient_id else None)
dr_fu_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R02", "B", "2", "Follow-up list", "GET", "/health/follow-up-tasks?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "B", "3", "Follow-up templates", "GET", "/health/follow-up-templates?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "B", "4", "Action inbox", "GET", "/health/action-inbox?page=1&page_size=10", DT)
time.sleep(0.5)
# Chain C: Consultation intake closed loop
print("\n--- Chain C: Consultation Intake ---")
check_api("R02", "C", "1", "Consultation sessions list", "GET", "/health/consultation-sessions?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "C", "2", "Consultation messages", "GET", "/health/consultation-messages?page=1&page_size=10", DT)
time.sleep(0.5)
# Chain D: Alert handling
print("\n--- Chain D: Alerts ---")
check_api("R02", "D", "1", "Alerts list", "GET", "/health/alerts?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "D", "2", "Alert rules", "GET", "/health/alert-rules?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "D", "3", "Critical alerts", "GET", "/health/critical-alerts?page=1&page_size=10", DT)
time.sleep(0.5)
# Chain E: AI analysis
print("\n--- Chain E: AI Analysis ---")
check_api("R02", "E", "1", "AI prompts", "GET", "/ai/prompts?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "E", "2", "AI suggestions", "GET", "/ai/suggestions?page=1&page_size=10", DT)
time.sleep(0.3)
check_api("R02", "E", "3", "Messages", "GET", "/messages?page=1&page_size=10", DT)
time.sleep(0.5)
# Doctor permission boundary checks
print("\n--- Doctor Permission Boundaries ---")
check_api_status("R02", "P", "1", "No user management", "GET", "/users?page=1&page_size=1", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "2", "No role management", "GET", "/roles?page=1&page_size=1", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "3", "No points rules", "GET", "/health/admin/points/rules?page=1&page_size=1", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "4", "No article management", "GET", "/health/articles?page=1&page_size=1", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "5", "No system settings", "GET", "/settings", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "6", "No BLE gateways", "GET", "/health/ble-gateways?page=1&page_size=1", DT, expected_status=403)
time.sleep(0.3)
check_api_status("R02", "P", "7", "No tag management", "GET", "/health/patient-tags?page=1&page_size=1", DT, expected_status=403)
time.sleep(1)
# ========================================
# R03 - NURSE (6 chains + permissions)
# ========================================
print("\n" + "=" * 60)
print("R03 - NURSE BUSINESS CHAINS")
print("=" * 60)
# Chain A: Patient management
print("\n--- Chain A: Patient Management ---")
check_api("R03", "A", "1", "Patient list", "GET", "/health/patients?page=1&page_size=10", NT)
time.sleep(0.3)
check_api("R03", "A", "2", "Patient detail", "GET", f"/health/patients/{patient_id}", NT)
time.sleep(0.3)
resp = check_api("R03", "A", "3", "Create patient", "POST", "/health/patients", NT,
body={"name": "NurseTestPatient", "gender": "male", "phone": "13900003333",
"birth_date": "1995-07-20"})
time.sleep(0.3)
check_api("R03", "A", "4", "Daily monitoring", "GET", "/health/daily-monitoring?page=1&page_size=10", NT)
time.sleep(0.5)
# Chain B: Follow-up execution
print("\n--- Chain B: Follow-up Execution ---")
check_api("R03", "B", "1", "Follow-up tasks list", "GET", "/health/follow-up-tasks?page=1&page_size=10", NT)
time.sleep(0.3)
resp = check_api("R03", "B", "2", "Create follow-up task", "POST", "/health/follow-up-tasks", NT,
body={"patient_id": patient_id, "follow_up_type": "phone",
"planned_date": "2026-05-22", "notes": "Nurse FU"} if patient_id else None)
time.sleep(0.3)
# Try to update a follow-up task status
if fu_id:
check_api("R03", "B", "3", "Update follow-up task", "PUT", f"/health/follow-up-tasks/{fu_id}", NT,
body={"status": "in_progress"})
else:
record("R03", "B", "3", "Update follow-up task", False, "No follow-up ID available")
time.sleep(0.3)
check_api("R03", "B", "4", "Follow-up records", "GET", "/health/follow-up-records?page=1&page_size=10", NT)
time.sleep(0.5)
# Chain C: Consultation viewing (read-only)
print("\n--- Chain C: Consultation Viewing ---")
check_api("R03", "C", "1", "Consultation sessions (read)", "GET", "/health/consultation-sessions?page=1&page_size=10", NT)
time.sleep(0.3)
check_api("R03", "C", "2", "Consultation messages (read)", "GET", "/health/consultation-sessions?page=1&page_size=10", NT)
time.sleep(0.5)
# Chain D: Alert handling
print("\n--- Chain D: Alerts ---")
check_api("R03", "D", "1", "Alerts list", "GET", "/health/alerts?page=1&page_size=10", NT)
time.sleep(0.3)
check_api("R03", "D", "2", "Alert detail (if any)", "GET", "/health/alerts?page=1&page_size=1", NT)
time.sleep(0.5)
# Chain E: Diagnosis and informed consent
print("\n--- Chain E: Diagnosis & Consent ---")
check_api("R03", "E", "1", "Consents list (via patient)", "GET", f"/health/patients/{patient_id}/consents?page=1&page_size=10", NT)
time.sleep(0.3)
check_api("R03", "E", "2", "Create consent", "POST", "/health/consents", NT,
body={"patient_id": patient_id, "consent_type": "general",
"consent_scope": "data_collection", "status": "signed"} if patient_id else None)
time.sleep(0.5)
# Chain F: Action inbox
print("\n--- Chain F: Action Inbox ---")
check_api("R03", "F", "1", "Action inbox list", "GET", "/health/action-inbox?page=1&page_size=10", NT)
time.sleep(0.3)
check_api("R03", "F", "2", "Action inbox stats", "GET", "/health/action-inbox/stats", NT)
time.sleep(0.3)
check_api("R03", "8.1", "", "Messages", "GET", "/messages?page=1&page_size=10", NT)
time.sleep(0.5)
# Nurse permission boundary checks
print("\n--- Nurse Permission Boundaries ---")
check_api_status("R03", "P", "1", "No doctor management", "GET", "/health/doctors?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "2", "No tag management", "GET", "/health/patient-tags?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "3", "No points rules", "GET", "/health/admin/points/rules?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "4", "No article management", "GET", "/health/articles?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "5", "No AI analysis", "GET", "/ai/prompts?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "6", "No follow-up templates", "GET", "/health/follow-up-templates?page=1&page_size=1", NT, expected_status=403)
time.sleep(0.3)
check_api_status("R03", "P", "7", "No user management", "GET", "/users?page=1&page_size=1", NT, expected_status=403)
time.sleep(1)
# ========================================
# R04 - HEALTH MANAGER (6 chains + permissions)
# ========================================
print("\n" + "=" * 60)
print("R04 - HEALTH MANAGER BUSINESS CHAINS")
print("=" * 60)
# Chain A: Patient and tag management
print("\n--- Chain A: Patient & Tag ---")
resp = check_api("R04", "A", "1", "Create tag", "POST", "/health/patient-tags", HT,
body={"name": "Chronic-Disease-Mgmt", "color": "#00FF00"})
hm_tag_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R04", "A", "2", "Patient list", "GET", "/health/patients?page=1&page_size=10", HT)
time.sleep(0.3)
resp = check_api("R04", "A", "3", "Create patient", "POST", "/health/patients", HT,
body={"name": "HMTestPatient", "gender": "female", "phone": "13900004444",
"birth_date": "1988-11-10"})
time.sleep(0.3)
check_api("R04", "A", "4", "Doctor list (read)", "GET", "/health/doctors?page=1&page_size=10", HT)
time.sleep(0.5)
# Chain B: Follow-up management
print("\n--- Chain B: Follow-up Management ---")
resp = check_api("R04", "B", "1", "Create follow-up task", "POST", "/health/follow-up-tasks", HT,
body={"patient_id": patient_id, "follow_up_type": "visit",
"planned_date": "2026-05-23", "notes": "HM FU task"} if patient_id else None)
time.sleep(0.3)
check_api("R04", "B", "2", "Follow-up list", "GET", "/health/follow-up-tasks?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "B", "3", "Follow-up templates", "GET", "/health/follow-up-templates?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "B", "4", "Action inbox", "GET", "/health/action-inbox?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "B", "5", "Team action inbox", "GET", "/health/action-inbox/team?page=1&page_size=10", HT)
time.sleep(0.5)
# Chain C: Consultation management
print("\n--- Chain C: Consultation Management ---")
check_api("R04", "C", "1", "Consultation sessions list", "GET", "/health/consultation-sessions?page=1&page_size=10", HT)
time.sleep(0.5)
# Chain D: Alerts and monitoring
print("\n--- Chain D: Alerts & Monitoring ---")
check_api("R04", "D", "1", "Alerts list", "GET", "/health/alerts?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "D", "2", "Alert rules", "GET", "/health/alert-rules?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "D", "3", "Critical value thresholds", "GET", "/health/critical-value-thresholds?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "D", "4", "Devices (read)", "GET", "/health/devices?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "D", "5", "Daily monitoring", "GET", "/health/daily-monitoring?page=1&page_size=10", HT)
time.sleep(0.5)
# Chain E: AI analysis
print("\n--- Chain E: AI Analysis ---")
check_api("R04", "E", "1", "AI prompts (read)", "GET", "/ai/prompts?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "E", "2", "AI suggestions", "GET", "/ai/suggestions?page=1&page_size=10", HT)
time.sleep(0.5)
# Chain F: Diagnosis and consent
print("\n--- Chain F: Diagnosis & Consent ---")
check_api("R04", "F", "1", "Consents list (via patient)", "GET", f"/health/patients/{patient_id}/consents?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "F", "2", "Patient diagnoses", "GET", f"/health/patients/{patient_id}/diagnoses?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "8.1", "", "Messages", "GET", "/messages?page=1&page_size=10", HT)
time.sleep(0.3)
check_api("R04", "9.1", "", "Workflow definitions", "GET", "/workflow/definitions?page=1&page_size=10", HT)
time.sleep(0.5)
# Health Manager permission boundary checks
print("\n--- Health Manager Permission Boundaries ---")
check_api_status("R04", "P", "1", "No user management", "GET", "/users?page=1&page_size=1", HT, expected_status=403)
time.sleep(0.3)
check_api_status("R04", "P", "2", "No points management", "GET", "/health/admin/points/rules?page=1&page_size=1", HT, expected_status=403)
time.sleep(0.3)
check_api_status("R04", "P", "3", "No article management", "GET", "/health/articles?page=1&page_size=1", HT, expected_status=403)
time.sleep(0.3)
check_api_status("R04", "P", "4", "No system settings", "GET", "/settings", HT, expected_status=403)
time.sleep(0.3)
check_api_status("R04", "P", "5", "No plugin management", "GET", "/admin/plugins", HT, expected_status=403)
time.sleep(0.3)
check_api_status("R04", "P", "6", "No BLE gateways", "GET", "/health/ble-gateways?page=1&page_size=1", HT, expected_status=403)
time.sleep(1)
# ========================================
# R05 - OPERATOR (6 chains + permissions)
# ========================================
print("\n" + "=" * 60)
print("R05 - OPERATOR BUSINESS CHAINS")
print("=" * 60)
# Chain A: Patient and tag management
print("\n--- Chain A: Patient & Tag ---")
resp = check_api("R05", "A", "1", "Create tag", "POST", "/health/patient-tags", OT,
body={"name": "PostSurgery-Rehab", "color": "#0000FF"})
time.sleep(0.3)
check_api("R05", "A", "2", "Patient list (read)", "GET", "/health/patients?page=1&page_size=10", OT)
time.sleep(0.3)
check_api("R05", "A", "3", "Search patient", "GET", "/health/patients?search=MultiRoleTest", OT)
time.sleep(0.5)
# Chain B: Content publishing
print("\n--- Chain B: Content Publishing ---")
resp = check_api("R05", "B", "1", "Create article", "POST", "/health/articles", OT,
body={"title": "Operator Test Article", "content": "Operator content", "status": "draft"})
op_art_id = resp["data"]["id"] if resp and resp.get("success") else ""
time.sleep(0.3)
check_api("R05", "B", "2", "Edit article", "PUT", f"/health/articles/{op_art_id}", OT,
body={"title": "Operator Test Article Updated", "content": "Updated"} if op_art_id else None)
time.sleep(0.3)
check_api("R05", "B", "3", "Submit article", "POST", f"/health/articles/{op_art_id}/submit", OT)
time.sleep(0.3)
check_api("R05", "B", "4", "Approve article", "POST", f"/health/articles/{op_art_id}/approve", OT)
time.sleep(0.5)
# Chain C: Points mall
print("\n--- Chain C: Points Mall ---")
check_api("R05", "C", "1", "Points rules", "GET", "/health/admin/points/rules?page=1&page_size=10", OT)
time.sleep(0.3)
resp = check_api("R05", "C", "2", "Create points product", "POST", "/health/admin/points/products", OT,
body={"name": "Test Product", "points_required": 100, "stock": 50, "description": "Test product"})
time.sleep(0.3)
check_api("R05", "C", "3", "Points orders", "GET", "/health/admin/points/orders?page=1&page_size=10", OT)
time.sleep(0.5)
# Chain D: Offline events
print("\n--- Chain D: Offline Events ---")
resp = check_api("R05", "D", "1", "Create event", "POST", "/health/admin/offline-events", OT,
body={"title": "Operator Test Event", "description": "Test event by operator",
"event_date": "2026-06-15", "location": "Hall B", "max_participants": 30})
time.sleep(0.3)
check_api("R05", "D", "2", "List events", "GET", "/health/offline-events?page=1&page_size=10", OT)
time.sleep(0.5)
# Chain E: Device and alert viewing
print("\n--- Chain E: Device & Alert Viewing ---")
check_api("R05", "E", "1", "Devices (read)", "GET", "/health/devices?page=1&page_size=10", OT)
time.sleep(0.3)
check_api("R05", "E", "2", "Alerts (read)", "GET", "/health/alerts?page=1&page_size=10", OT)
time.sleep(0.5)
# Chain F: AI usage
print("\n--- Chain F: AI Usage ---")
check_api("R05", "F", "1", "AI suggestions (read)", "GET", "/ai/suggestions?page=1&page_size=10", OT)
time.sleep(0.3)
check_api("R05", "8.1", "", "Messages", "GET", "/messages?page=1&page_size=10", OT)
time.sleep(0.5)
# Operator permission boundary checks
print("\n--- Operator Permission Boundaries ---")
check_api_status("R05", "P", "1", "No user management", "GET", "/users?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "2", "No doctor management", "GET", "/health/doctors?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "3", "No follow-up management", "GET", "/health/follow-up-tasks?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "4", "No consultation management", "GET", "/health/consultation-sessions?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "5", "No diagnoses", "GET", f"/health/patients/{patient_id}/diagnoses?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "6", "No action inbox", "GET", "/health/action-inbox?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "7", "No consents", "GET", "/health/consents?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "8", "No AI analysis", "GET", "/ai/prompts?page=1&page_size=1", OT, expected_status=403)
time.sleep(0.3)
check_api_status("R05", "P", "9", "No system settings", "GET", "/settings", OT, expected_status=403)
time.sleep(1)
# ========================================
# CROSS-ROLE COLLABORATION CHECKS
# ========================================
print("\n" + "=" * 60)
print("CROSS-ROLE COLLABORATION CHECKS")
print("=" * 60)
print("\n--- Cross-role: Admin patient visible to all ---")
# Doctor sees admin-created patient
_, dr_resp = api("GET", f"/health/patients/{patient_id}", DT)
dr_sees = dr_resp.get("success", False)
record("XROLE", "X", "1", "Doctor sees admin-created patient", dr_sees,
"" if dr_sees else f"msg={dr_resp.get('message', '')}")
time.sleep(0.3)
# Nurse sees admin-created patient
_, nr_resp = api("GET", f"/health/patients/{patient_id}", NT)
nr_sees = nr_resp.get("success", False)
record("XROLE", "X", "2", "Nurse sees admin-created patient", nr_sees,
"" if nr_sees else f"msg={nr_resp.get('message', '')}")
time.sleep(0.3)
# HM sees admin-created patient
_, hm_resp = api("GET", f"/health/patients/{patient_id}", HT)
hm_sees = hm_resp.get("success", False)
record("XROLE", "X", "3", "HM sees admin-created patient", hm_sees,
"" if hm_sees else f"msg={hm_resp.get('message', '')}")
time.sleep(0.3)
# Operator sees admin-created patient
_, op_resp = api("GET", f"/health/patients/{patient_id}", OT)
op_sees = op_resp.get("success", False)
record("XROLE", "X", "4", "Operator sees admin-created patient", op_sees,
"" if op_sees else f"msg={op_resp.get('message', '')}")
time.sleep(0.5)
print("\n--- Cross-role: Doctor follow-up visible to nurse ---")
_, nurse_fu = api("GET", "/health/follow-up-tasks?status=pending&page=1&page_size=20", NT)
nurse_fu_ok = nurse_fu.get("success", False)
total_fu = nurse_fu.get("data", {}).get("total", 0) if nurse_fu_ok else 0
record("XROLE", "X", "5", "Nurse sees follow-up tasks (incl. doctor-created)", nurse_fu_ok and total_fu > 0,
f"total={total_fu}" if nurse_fu_ok else f"msg={nurse_fu.get('message', '')}")
time.sleep(0.5)
print("\n--- Cross-role: Admin tag visible to other roles ---")
_, dr_tags = api("GET", "/health/patient-tags?page=1&page_size=20", DT)
dr_tag_ok = dr_tags.get("success", False)
record("XROLE", "X", "6", "Doctor can list tags", dr_tag_ok,
"" if dr_tag_ok else f"status=403 - doctor has no tag.list permission (expected per R02.P.7)")
time.sleep(0.3)
_, hm_tags = api("GET", "/health/patient-tags?page=1&page_size=20", HT)
hm_tag_ok = hm_tags.get("success", False)
record("XROLE", "X", "7", "HM can list tags", hm_tag_ok,
"" if hm_tag_ok else f"msg={hm_tags.get('message', '')}")
time.sleep(0.3)
_, op_tags = api("GET", "/health/patient-tags?page=1&page_size=20", OT)
op_tag_ok = op_tags.get("success", False)
record("XROLE", "X", "8", "Operator can list tags", op_tag_ok,
"" if op_tag_ok else f"msg={op_tags.get('message', '')}")
time.sleep(0.5)
print("\n--- Cross-role: Alert visibility ---")
_, dr_alerts = api("GET", "/health/alerts?page=1&page_size=5", DT)
_, nt_alerts = api("GET", "/health/alerts?page=1&page_size=5", NT)
_, hm_alerts = api("GET", "/health/alerts?page=1&page_size=5", HT)
record("XROLE", "X", "9", "Doctor can view alerts", dr_alerts.get("success", False))
record("XROLE", "X", "10", "Nurse can view alerts", nt_alerts.get("success", False))
record("XROLE", "X", "11", "HM can view alerts", hm_alerts.get("success", False))
time.sleep(0.5)
print("\n--- Cross-role: Article visibility ---")
_, op_articles = api("GET", "/health/articles?page=1&page_size=5", OT)
op_art_ok = op_articles.get("success", False)
record("XROLE", "X", "12", "Operator can manage articles", op_art_ok)
time.sleep(0.3)
# Admin should also be able to see articles
_, admin_articles = api("GET", "/health/articles?page=1&page_size=5", AT)
record("XROLE", "X", "13", "Admin can see operator articles", admin_articles.get("success", False))
time.sleep(0.5)
print("\n--- Cross-role: Offline events visibility ---")
_, op_events = api("GET", "/health/offline-events?page=1&page_size=5", OT)
_, admin_events = api("GET", "/health/offline-events?page=1&page_size=5", AT)
record("XROLE", "X", "14", "Operator sees offline events", op_events.get("success", False))
record("XROLE", "X", "15", "Admin sees offline events", admin_events.get("success", False))
# ========================================
# Summary
# ========================================
print("\n" + "=" * 60)
print("TEST SUMMARY")
print("=" * 60)
total = len(RESULTS)
passed = sum(1 for r in RESULTS if r["passed"])
failed = total - passed
# By role
for role in ["R01", "R02", "R03", "R04", "R05", "XROLE"]:
role_results = [r for r in RESULTS if r["role"] == role]
role_pass = sum(1 for r in role_results if r["passed"])
role_total = len(role_results)
print(f" {role}: {role_pass}/{role_total} passed ({100*role_pass//role_total if role_total else 0}%)")
print(f"\n TOTAL: {passed}/{total} passed ({100*passed//total if total else 0}%)")
if failed > 0:
print(f"\n FAILED TESTS ({failed}):")
for r in RESULTS:
if not r["passed"]:
print(f" [{r['role']}-{r['chain']}.{r['test_id']}] {r['description']}: {r['detail']}")
# Save results as JSON for report generation
with open(r"G:\hms\docs\qa\role-test-results\test_results.json", "w", encoding="utf-8") as f:
json.dump({"timestamp": datetime.now().isoformat(), "total": total, "passed": passed,
"failed": failed, "results": RESULTS}, f, ensure_ascii=False, indent=2)
print(f"\n Results saved to docs/qa/role-test-results/test_results.json")
if __name__ == "__main__":
main()