删除内容: - 前端: health/(67文件), ai/(2文件), Copilot, MediaPicker, 相关API/Store/Hook - 后端: wechat_handler, wechat_service, wechat_user entity, analytics handler, ai_workflow_seed - 配置: WechatConfig, AppConfig.wechat, AuthState wechat 字段 - 启动: 微信凭据检查块, ensure_ai_workflows() 调用 - 迁移: 新增 m20260613_000170_drop_wechat_users.rs - 脚本: api_test_health_alert.py, api_test_mp.py, mpsync.sh/ps1 - E2E: health-data page, flows/ 目录 保留: erp-core/auth/workflow/message/config/plugin + 基座前端 + 通用组件
551 lines
20 KiB
Python
551 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
HMS 健康管理平台 - 随访管理 / 咨询管理 / 积分商城 端到端 API 测试
|
|
使用 subprocess + curl 避免Windows urllib兼容问题
|
|
"""
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
|
|
BASE = "http://localhost:3000/api/v1"
|
|
|
|
results = {
|
|
"follow_up": [],
|
|
"consultation": [],
|
|
"points": [],
|
|
}
|
|
|
|
|
|
def api(method, path, body=None, token=None):
|
|
"""通过 curl 发送 HTTP 请求"""
|
|
url = f"{BASE}{path}"
|
|
cmd = ["curl", "-s", "-X", method, url, "-H", "Content-Type: application/json"]
|
|
if token:
|
|
cmd += ["-H", f"Authorization: Bearer {token}"]
|
|
if body:
|
|
cmd += ["-d", json.dumps(body)]
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
|
text = result.stdout.strip()
|
|
if not text:
|
|
return {"success": False, "error": f"Empty response (stderr: {result.stderr[:100]})"}, 0
|
|
data = json.loads(text)
|
|
# Infer status code from response
|
|
status = 200
|
|
if data.get("success") is False:
|
|
error_msg = data.get("error", "") or data.get("message", "")
|
|
if "not found" in error_msg.lower() or "404" in str(data.get("status", "")):
|
|
status = 404
|
|
elif "unauthorized" in error_msg.lower() or "authentication" in error_msg.lower():
|
|
status = 401
|
|
elif "validation" in error_msg.lower() or "required" in error_msg.lower():
|
|
status = 400
|
|
elif "too many" in error_msg.lower() or "frequent" in error_msg.lower():
|
|
status = 429
|
|
elif "forbidden" in error_msg.lower():
|
|
status = 403
|
|
else:
|
|
status = 400
|
|
return data, status
|
|
except subprocess.TimeoutExpired:
|
|
return {"success": False, "error": "Request timeout"}, 0
|
|
except json.JSONDecodeError as e:
|
|
return {"success": False, "error": f"JSON decode error: {e}"}, 0
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e)}, 0
|
|
|
|
|
|
def api_raw(method, path, body=None, token=None):
|
|
"""通过 curl 发送请求并获取原始 HTTP 状态码,带重试"""
|
|
url = f"{BASE}{path}"
|
|
max_retries = 3
|
|
for attempt in range(max_retries):
|
|
cmd = ["curl", "-s", "-w", "\n%{http_code}", "-X", method, url, "-H", "Content-Type: application/json"]
|
|
if token:
|
|
cmd += ["-H", f"Authorization: Bearer {token}"]
|
|
if body:
|
|
cmd += ["-d", json.dumps(body)]
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, timeout=15)
|
|
output = result.stdout.decode("utf-8", errors="replace").strip()
|
|
if not output:
|
|
return {"success": False, "error": "Empty response"}, 0
|
|
|
|
# Split last line as status code
|
|
lines = output.rsplit("\n", 1)
|
|
if len(lines) == 2 and lines[-1].strip().isdigit():
|
|
body_text = lines[0]
|
|
status = int(lines[-1].strip())
|
|
else:
|
|
body_text = output
|
|
status = 200
|
|
|
|
if not body_text:
|
|
return {"success": False, "error": "Empty body"}, status
|
|
|
|
try:
|
|
parsed = json.loads(body_text)
|
|
except json.JSONDecodeError:
|
|
return {"success": False, "error": body_text[:200]}, status
|
|
|
|
# Retry on 503 (Redis fail-close transient)
|
|
if status == 503 and attempt < max_retries - 1:
|
|
time.sleep(5)
|
|
continue
|
|
|
|
return parsed, status
|
|
except subprocess.TimeoutExpired:
|
|
return {"success": False, "error": "Request timeout"}, 0
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e)}, 0
|
|
return {"success": False, "error": "Max retries exceeded (503)"}, 503
|
|
|
|
|
|
def record(section, name, response, status_code, expect_success=True):
|
|
"""记录测试结果"""
|
|
success = response.get("success") == expect_success
|
|
entry = {
|
|
"name": name,
|
|
"status_code": status_code,
|
|
"success": success,
|
|
"response_success": response.get("success"),
|
|
"error": response.get("error") or response.get("message", ""),
|
|
"detail": "",
|
|
}
|
|
|
|
data = response.get("data", {})
|
|
if isinstance(data, dict):
|
|
if "total" in data:
|
|
entry["detail"] = f"total={data['total']}"
|
|
if "id" in data:
|
|
entry["detail"] = f"id={data['id']}"
|
|
|
|
results[section].append(entry)
|
|
icon = "PASS" if success else "FAIL"
|
|
detail_str = f" | {entry['detail']}" if entry["detail"] else ""
|
|
err_str = f" | err={entry['error'][:60]}" if entry["error"] and not success else ""
|
|
print(f" [{icon}] {name}: HTTP {status_code}{detail_str}{err_str}")
|
|
return data
|
|
|
|
|
|
# ============================================================
|
|
# 0. 认证
|
|
# ============================================================
|
|
print("=" * 70)
|
|
print("0. Authentication")
|
|
print("=" * 70)
|
|
|
|
resp, code = api_raw("POST", "/auth/login", {"username": "admin", "password": "Admin@2026"})
|
|
# Retry login up to 5 times with delay
|
|
for _ in range(5):
|
|
if code == 200 and resp.get("success"):
|
|
break
|
|
print(f" Login attempt failed (HTTP {code}), retrying in 10s...")
|
|
time.sleep(10)
|
|
resp, code = api_raw("POST", "/auth/login", {"username": "admin", "password": "Admin@2026"})
|
|
|
|
if code != 200 or not resp.get("success"):
|
|
print(f" [FAIL] Login failed after retries: HTTP {code}, {json.dumps(resp)[:200]}")
|
|
sys.exit(1)
|
|
|
|
TOKEN = resp["data"]["access_token"]
|
|
print(f" [PASS] Login OK: HTTP {code}, token={TOKEN[:30]}...")
|
|
|
|
# ============================================================
|
|
# Base data
|
|
# ============================================================
|
|
print()
|
|
print("=" * 70)
|
|
print("Base Data Preparation")
|
|
print("=" * 70)
|
|
|
|
resp, code = api_raw("GET", "/health/patients", token=TOKEN)
|
|
PATIENT_ID = None
|
|
if resp.get("success") and resp.get("data", {}).get("items"):
|
|
PATIENT_ID = resp["data"]["items"][0]["id"]
|
|
print(f" Patients total: {resp['data']['total']}, selected ID: {PATIENT_ID}")
|
|
else:
|
|
# Create a test patient
|
|
print(f" No patients found, creating test patient...")
|
|
time.sleep(1)
|
|
test_patient = {
|
|
"name": "E2E Test Patient",
|
|
"gender": "male",
|
|
"birth_date": "1990-01-15",
|
|
"phone": "13800000001",
|
|
"id_card_number": "110101199001150011",
|
|
}
|
|
resp2, code2 = api_raw("POST", "/health/patients", test_patient, token=TOKEN)
|
|
if resp2.get("success") and isinstance(resp2.get("data"), dict) and "id" in resp2["data"]:
|
|
PATIENT_ID = resp2["data"]["id"]
|
|
print(f" Created test patient: {PATIENT_ID}")
|
|
else:
|
|
print(f" [WARN] Failed to create test patient: HTTP {code2}, {resp2.get('error', resp2.get('message', ''))[:80]}")
|
|
|
|
resp, code = api_raw("GET", "/health/doctors", token=TOKEN)
|
|
DOCTOR_ID = None
|
|
if resp.get("success") and resp.get("data", {}).get("items"):
|
|
DOCTOR_ID = resp["data"]["items"][0]["id"]
|
|
print(f" Doctors total: {resp['data']['total']}, selected ID: {DOCTOR_ID}")
|
|
else:
|
|
# Create a test doctor - needs user_id
|
|
print(f" No doctors found, trying to create...")
|
|
# List users to get a user_id for doctor
|
|
time.sleep(1)
|
|
resp_u, code_u = api_raw("GET", "/users", token=TOKEN)
|
|
if resp_u.get("success") and resp_u.get("data", {}).get("items"):
|
|
user_id = resp_u["data"]["items"][0]["id"]
|
|
test_doctor = {
|
|
"user_id": user_id,
|
|
"name": "E2E Test Doctor",
|
|
"department": "General",
|
|
"title": "Attending Physician",
|
|
"specialty": "Internal Medicine",
|
|
}
|
|
time.sleep(1)
|
|
resp2, code2 = api_raw("POST", "/health/doctors", test_doctor, token=TOKEN)
|
|
if resp2.get("success") and isinstance(resp2.get("data"), dict) and "id" in resp2["data"]:
|
|
DOCTOR_ID = resp2["data"]["id"]
|
|
print(f" Created test doctor: {DOCTOR_ID}")
|
|
else:
|
|
print(f" [WARN] Failed to create test doctor: HTTP {code2}, {resp2.get('error', resp2.get('message', ''))[:80]}")
|
|
else:
|
|
print(f" [WARN] No users to create doctor from")
|
|
|
|
|
|
# ============================================================
|
|
# Part 1: Follow-up Management
|
|
# ============================================================
|
|
print()
|
|
print("=" * 70)
|
|
print("Part 1: Follow-up Management")
|
|
print("=" * 70)
|
|
|
|
# 1.1 Task list
|
|
print()
|
|
print("1.1 List follow-up tasks")
|
|
resp, code = api_raw("GET", "/health/follow-up-tasks", token=TOKEN)
|
|
record("follow_up", "List follow-up tasks", resp, code)
|
|
|
|
# 1.2 Create task
|
|
print()
|
|
print("1.2 Create follow-up task")
|
|
if PATIENT_ID:
|
|
time.sleep(1)
|
|
next_week = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d")
|
|
body = {
|
|
"patient_id": PATIENT_ID,
|
|
"follow_up_type": "phone",
|
|
"planned_date": next_week,
|
|
"content_template": "E2E test follow-up task",
|
|
}
|
|
resp, code = api_raw("POST", "/health/follow-up-tasks", body, token=TOKEN)
|
|
task_data = record("follow_up", "Create follow-up task", resp, code)
|
|
|
|
if resp.get("success") and isinstance(task_data, dict) and "id" in task_data:
|
|
task_id = task_data["id"]
|
|
task_version = task_data.get("version", 1)
|
|
print()
|
|
print("1.2a Get follow-up task detail")
|
|
resp, code = api_raw("GET", f"/health/follow-up-tasks/{task_id}", token=TOKEN)
|
|
record("follow_up", "Get follow-up task detail", resp, code)
|
|
|
|
print()
|
|
print("1.2b Update follow-up task")
|
|
time.sleep(1)
|
|
update_body = {"content_template": "E2E test updated", "status": "in_progress", "version": task_version}
|
|
resp, code = api_raw("PUT", f"/health/follow-up-tasks/{task_id}", update_body, token=TOKEN)
|
|
record("follow_up", "Update follow-up task", resp, code)
|
|
|
|
print()
|
|
print("1.2c Create follow-up record")
|
|
time.sleep(1)
|
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
record_body = {
|
|
"task_id": task_id,
|
|
"executed_date": today,
|
|
"result": "contacted",
|
|
"patient_condition": "stable",
|
|
"medical_advice": "continue medication",
|
|
}
|
|
resp, code = api_raw("POST", f"/health/follow-up-tasks/{task_id}/records", record_body, token=TOKEN)
|
|
record("follow_up", "Create follow-up record", resp, code)
|
|
else:
|
|
print(" [SKIP] No patient ID")
|
|
|
|
# 1.3 Template list
|
|
print()
|
|
print("1.3 List follow-up templates")
|
|
resp, code = api_raw("GET", "/health/follow-up-templates", token=TOKEN)
|
|
record("follow_up", "List follow-up templates", resp, code)
|
|
|
|
# 1.4 Record list
|
|
print()
|
|
print("1.4 List follow-up records")
|
|
resp, code = api_raw("GET", "/health/follow-up-records", token=TOKEN)
|
|
record("follow_up", "List follow-up records", resp, code)
|
|
|
|
# 1.5 Auth check - no token
|
|
print()
|
|
print("1.5 Auth check - no token (expect 401)")
|
|
resp, code = api_raw("GET", "/health/follow-up-tasks")
|
|
if code == 401:
|
|
print(f" [PASS] No token -> 401: HTTP {code}")
|
|
results["follow_up"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": True,
|
|
"response_success": resp.get("success"), "error": "", "detail": "401 Unauthorized",
|
|
})
|
|
else:
|
|
print(f" [FAIL] Expected 401, got: HTTP {code}, body={json.dumps(resp)[:100]}")
|
|
results["follow_up"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": False,
|
|
"response_success": resp.get("success"), "error": f"Expected 401, got {code}", "detail": "",
|
|
})
|
|
|
|
# 1.6 Validation check - missing required fields
|
|
print()
|
|
print("1.6 Validation - missing required fields (expect 400/422)")
|
|
body_invalid = {"follow_up_type": "phone"}
|
|
resp, code = api_raw("POST", "/health/follow-up-tasks", body_invalid, token=TOKEN)
|
|
is_validation_error = code in (400, 422)
|
|
if is_validation_error:
|
|
print(f" [PASS] Validation error: HTTP {code}, msg={resp.get('error', resp.get('message', ''))[:80]}")
|
|
else:
|
|
print(f" [FAIL] Expected 400/422, got: HTTP {code}, body={json.dumps(resp)[:100]}")
|
|
results["follow_up"].append({
|
|
"name": "Validation (missing fields)", "status_code": code, "success": is_validation_error,
|
|
"response_success": resp.get("success"),
|
|
"error": resp.get("error", resp.get("message", ""))[:80],
|
|
"detail": f"Expected 400/422, got {code}",
|
|
})
|
|
|
|
|
|
# ============================================================
|
|
# Part 2: Consultation Management
|
|
# ============================================================
|
|
print()
|
|
print("=" * 70)
|
|
print("Part 2: Consultation Management")
|
|
print("=" * 70)
|
|
|
|
# 2.1 Session list
|
|
print()
|
|
print("2.1 List consultation sessions")
|
|
resp, code = api_raw("GET", "/health/consultation-sessions", token=TOKEN)
|
|
record("consultation", "List consultation sessions", resp, code)
|
|
|
|
# 2.2 Create session
|
|
print()
|
|
print("2.2 Create consultation session")
|
|
if PATIENT_ID and DOCTOR_ID:
|
|
time.sleep(1)
|
|
body = {
|
|
"patient_id": PATIENT_ID,
|
|
"doctor_id": DOCTOR_ID,
|
|
"subject": "E2E test consultation",
|
|
}
|
|
resp, code = api_raw("POST", "/health/consultation-sessions", body, token=TOKEN)
|
|
consult_data = record("consultation", "Create consultation session", resp, code)
|
|
|
|
if resp.get("success") and isinstance(consult_data, dict) and "id" in consult_data:
|
|
consult_id = consult_data["id"]
|
|
|
|
print()
|
|
print("2.2a Get consultation detail")
|
|
resp, code = api_raw("GET", f"/health/consultation-sessions/{consult_id}", token=TOKEN)
|
|
record("consultation", "Get consultation detail", resp, code)
|
|
|
|
print()
|
|
print("2.2b Send consultation message")
|
|
msg_body = {
|
|
"session_id": consult_id,
|
|
"content": "E2E test message",
|
|
"message_type": "text",
|
|
}
|
|
resp, code = api_raw("POST", "/health/consultation-messages", msg_body, token=TOKEN)
|
|
record("consultation", "Send consultation message", resp, code)
|
|
|
|
print()
|
|
print("2.2c List consultation messages")
|
|
resp, code = api_raw("GET", f"/health/consultation-sessions/{consult_id}/messages", token=TOKEN)
|
|
record("consultation", "List consultation messages", resp, code)
|
|
|
|
print()
|
|
print("2.2d Close consultation session")
|
|
resp, code = api_raw("PUT", f"/health/consultation-sessions/{consult_id}/close", token=TOKEN)
|
|
record("consultation", "Close consultation session", resp, code)
|
|
|
|
print()
|
|
print("2.2e Create follow-up from consultation")
|
|
fu_body = {
|
|
"follow_up_type": "phone",
|
|
"planned_date": next_week if PATIENT_ID else "2026-06-01",
|
|
"content_template": "Follow-up from E2E consultation",
|
|
}
|
|
resp, code = api_raw("POST", f"/health/consultation-sessions/{consult_id}/follow-up", fu_body, token=TOKEN)
|
|
record("consultation", "Create follow-up from consultation", resp, code)
|
|
else:
|
|
print(f" [SKIP] Missing patient or doctor ID")
|
|
|
|
# 2.3 Auth check
|
|
print()
|
|
print("2.3 Auth check - no token (expect 401)")
|
|
resp, code = api_raw("GET", "/health/consultation-sessions")
|
|
if code == 401:
|
|
print(f" [PASS] No token -> 401: HTTP {code}")
|
|
results["consultation"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": True,
|
|
"response_success": resp.get("success"), "error": "", "detail": "401 Unauthorized",
|
|
})
|
|
else:
|
|
print(f" [FAIL] Expected 401, got: HTTP {code}")
|
|
results["consultation"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": False,
|
|
"response_success": resp.get("success"), "error": f"Expected 401, got {code}", "detail": "",
|
|
})
|
|
|
|
# 2.4 Doctor dashboard
|
|
print()
|
|
print("2.4 Doctor dashboard")
|
|
resp, code = api_raw("GET", "/health/doctor/dashboard", token=TOKEN)
|
|
record("consultation", "Doctor dashboard", resp, code)
|
|
|
|
|
|
# ============================================================
|
|
# Part 3: Points / Rewards
|
|
# ============================================================
|
|
print()
|
|
print("=" * 70)
|
|
print("Part 3: Points / Rewards System")
|
|
print("=" * 70)
|
|
|
|
# 3.1 Rules
|
|
print()
|
|
print("3.1 List points rules (admin)")
|
|
resp, code = api_raw("GET", "/health/admin/points/rules", token=TOKEN)
|
|
record("points", "List points rules", resp, code)
|
|
|
|
# 3.2 Products
|
|
print()
|
|
print("3.2 List points products (admin)")
|
|
resp, code = api_raw("GET", "/health/admin/points/products", token=TOKEN)
|
|
record("points", "List points products (admin)", resp, code)
|
|
|
|
# 3.3 Orders
|
|
print()
|
|
print("3.3 List points orders (admin)")
|
|
resp, code = api_raw("GET", "/health/admin/points/orders", token=TOKEN)
|
|
record("points", "List points orders (admin)", resp, code)
|
|
|
|
# 3.4 Account
|
|
print()
|
|
print("3.4 Get points account (patient)")
|
|
resp, code = api_raw("GET", "/health/points/account", token=TOKEN)
|
|
record("points", "Get points account", resp, code)
|
|
|
|
# 3.5 Checkin
|
|
print()
|
|
print("3.5 Daily checkin")
|
|
resp, code = api_raw("POST", "/health/points/checkin", token=TOKEN)
|
|
is_checkin_ok = resp.get("success") == True or "already" in str(resp.get("error", "")).lower() or "already" in str(resp.get("message", "")).lower()
|
|
print(f" [{'PASS' if is_checkin_ok else 'INFO'}] Checkin: HTTP {code}, success={resp.get('success')}, error={resp.get('error', resp.get('message', ''))}")
|
|
results["points"].append({
|
|
"name": "Daily checkin", "status_code": code, "success": is_checkin_ok,
|
|
"response_success": resp.get("success"),
|
|
"error": resp.get("error", resp.get("message", "")),
|
|
"detail": "Success or already checked in = PASS",
|
|
})
|
|
|
|
# 3.6 Checkin status
|
|
print()
|
|
print("3.6 Checkin status")
|
|
resp, code = api_raw("GET", "/health/points/checkin/status", token=TOKEN)
|
|
record("points", "Checkin status", resp, code)
|
|
|
|
# 3.7 Transactions
|
|
print()
|
|
print("3.7 List transactions")
|
|
resp, code = api_raw("GET", "/health/points/transactions", token=TOKEN)
|
|
record("points", "List transactions", resp, code)
|
|
|
|
# 3.8 Products (patient view)
|
|
print()
|
|
print("3.8 List products (patient)")
|
|
resp, code = api_raw("GET", "/health/points/products", token=TOKEN)
|
|
record("points", "List products (patient)", resp, code)
|
|
|
|
# 3.9 Statistics (admin)
|
|
print()
|
|
print("3.9 Points statistics (admin)")
|
|
resp, code = api_raw("GET", "/health/admin/points/statistics", token=TOKEN)
|
|
record("points", "Points statistics", resp, code)
|
|
|
|
# 3.10 Offline events (patient)
|
|
print()
|
|
print("3.10 List offline events (patient)")
|
|
resp, code = api_raw("GET", "/health/offline-events", token=TOKEN)
|
|
record("points", "List offline events", resp, code)
|
|
|
|
# 3.11 Auth check
|
|
print()
|
|
print("3.11 Auth check - no token (expect 401)")
|
|
resp, code = api_raw("GET", "/health/admin/points/rules")
|
|
if code == 401:
|
|
print(f" [PASS] No token -> 401: HTTP {code}")
|
|
results["points"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": True,
|
|
"response_success": resp.get("success"), "error": "", "detail": "401 Unauthorized",
|
|
})
|
|
else:
|
|
print(f" [FAIL] Expected 401, got: HTTP {code}")
|
|
results["points"].append({
|
|
"name": "Auth check (no token)", "status_code": code, "success": False,
|
|
"response_success": resp.get("success"), "error": f"Expected 401, got {code}", "detail": "",
|
|
})
|
|
|
|
|
|
# ============================================================
|
|
# Summary Report
|
|
# ============================================================
|
|
print()
|
|
print("=" * 70)
|
|
print("SUMMARY REPORT")
|
|
print("=" * 70)
|
|
|
|
total_pass = 0
|
|
total_fail = 0
|
|
|
|
for section_name, section_key in [("Follow-up", "follow_up"), ("Consultation", "consultation"), ("Points/Rewards", "points")]:
|
|
items = results[section_key]
|
|
passed = sum(1 for i in items if i["success"])
|
|
failed = sum(1 for i in items if not i["success"])
|
|
total_pass += passed
|
|
total_fail += failed
|
|
|
|
print()
|
|
print(f"--- {section_name} ---")
|
|
print(f" PASS: {passed}, FAIL: {failed}, Total: {len(items)}")
|
|
for item in items:
|
|
icon = "PASS" if item["success"] else "FAIL"
|
|
detail = f" | {item['detail']}" if item["detail"] else ""
|
|
print(f" [{icon}] {item['name']}: HTTP {item['status_code']}{detail}")
|
|
|
|
total = total_pass + total_fail
|
|
rate = (total_pass / total * 100) if total > 0 else 0
|
|
print()
|
|
print("=" * 70)
|
|
print(f"TOTAL: {total} tests, PASS {total_pass} ({rate:.1f}%), FAIL {total_fail}")
|
|
print("=" * 70)
|
|
|
|
if total_fail > 0:
|
|
print()
|
|
print("FAILED ITEMS:")
|
|
for section_name, section_key in [("Follow-up", "follow_up"), ("Consultation", "consultation"), ("Points/Rewards", "points")]:
|
|
for item in results[section_key]:
|
|
if not item["success"]:
|
|
print(f" - [{section_name}] {item['name']}: HTTP {item['status_code']}, error={item['error']}")
|