Files
medical_training/test/swagger_cms.py
T

246 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
CMS 端全接口 Swagger Try-it-out 脚本(真实 HTTP)。
- 覆盖:超级管理员(用户/医院/科室)、医院管理员(人员/师生关系)、带教医生(我的学生)。
- 详细请求/响应写入 logs/test-swagger-cms-YYYY-MM-DD.log。
- 导入/导出的 .xlsx 真实文件保存到 docx/CMS-excel样例/。
- 真实请求/响应样例写入 logs/cms-swagger-examples.json(供回填 CSV)。
前提:Django dev server 运行在 http://127.0.0.1:8000Redis 已启动。
运行:.venv\\Scripts\\python.exe test/swagger_cms.py
"""
import io
import os
import sys
import json
import subprocess
from datetime import datetime
import requests
from openpyxl import Workbook
sys.stdout.reconfigure(encoding='utf-8')
BASE = 'http://127.0.0.1:8000'
PYTHON = r'D:\01Agent\medical_training\.venv\Scripts\python.exe'
CWD = r'D:\01Agent\medical_training'
LOG_DIR = os.path.join(CWD, 'logs')
EXCEL_DIR = os.path.join(CWD, 'docx', 'CMS-excel样例')
os.makedirs(EXCEL_DIR, exist_ok=True)
LOG_FILE = os.path.join(LOG_DIR, f'test-swagger-cms-{datetime.now():%Y-%m-%d}.log')
EXAMPLES_FILE = os.path.join(LOG_DIR, 'cms-swagger-examples.json')
_fh = open(LOG_FILE, 'w', encoding='utf-8')
examples = {}
results = []
def w(text=''):
_fh.write(text + '\n'); _fh.flush(); print(text)
def django_eval(code):
pre = ('import django,os;os.environ.setdefault("DJANGO_SETTINGS_MODULE","config.settings");django.setup();')
p = subprocess.run([PYTHON, '-c', pre + code], capture_output=True, text=True, cwd=CWD, encoding='utf-8')
return (p.stdout or '').strip()
def make_xlsx_bytes(headers, rows):
wb = Workbook(); ws = wb.active
ws.append(headers)
for r in rows:
ws.append(r)
buf = io.BytesIO(); wb.save(buf); return buf.getvalue()
def call(code, name, method, path, token=None, json_body=None, params=None,
file_bytes=None, file_name=None, save_as=None, expect=200):
"""发起请求 + 记录日志 + 收集样例。"""
headers = {'Authorization': f'Bearer {token}'} if token else {}
files = {'file': (file_name, file_bytes, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')} if file_bytes else None
r = requests.request(method, BASE + path, headers=headers, json=json_body, params=params, files=files)
ct = r.headers.get('content-type', '')
is_json = ct.startswith('application/json')
resp = r.json() if is_json else f'<xlsx 文件 {len(r.content)} 字节>'
if save_as:
full = os.path.join(EXCEL_DIR, save_as)
with open(full, 'wb') as f:
f.write(r.content)
# 请求样例(给前端看:实际传了什么)
if json_body is not None:
req_example = json.dumps(json_body, ensure_ascii=False)
elif file_bytes is not None:
req_example = f'multipart/form-data: file={file_name}'
elif params:
req_example = '?' + '&'.join(f'{k}={v}' for k, v in params.items())
else:
req_example = '(无 body,仅 Header Authorization)' if '{' not in path else '(路径参数 + Header Authorization)'
ok = 'PASS' if r.status_code in (expect if isinstance(expect, (list, tuple)) else [expect]) else 'FAIL'
results.append((code, ok, r.status_code))
examples[code] = {'name': name, 'method': method, 'path': path, 'status': r.status_code,
'req': req_example, 'resp': resp, 'saved': save_as}
w(f'\n[{ok}] {code} {name} -> {method} {path} (status={r.status_code})')
w(f' 请求: {req_example}')
body_str = json.dumps(resp, ensure_ascii=False, indent=2) if is_json else resp
if len(body_str) > 1500:
body_str = body_str[:1500] + f' ...(截断, 共{len(body_str)}字符)'
w(f' 响应: {body_str}')
return r
# ─────────────────────────────────────────────────────────────────────────────
w('=' * 90)
w(f' CMS 全接口 Swagger 测试 {datetime.now():%Y-%m-%d %H:%M:%S}')
w('=' * 90)
# 1) 准备数据:两机构、两科室、超管/医院管理员/医生/学生/他院学生,并签发 token
w('\n[准备] 建测试机构/科室/用户、签发 token ...')
setup = django_eval(r'''
from apps.user.models import User, Institution, Department, TeacherStudentRelation
from rest_framework_simplejwt.tokens import RefreshToken
# 物理清理可能的残留(含软删),保证脚本可重复运行
ph = ["13800009000","13800009001","13800009002","13800009003","13800009004","13800009010","13800009011","13800009012","13800009020","13800009021","13800009022"]
for u in User.all_objects.filter(phone__in=ph): u.hard_delete()
Institution.all_objects.filter(code__startswith="SWG-CMS").hard_delete()
for d in Department.all_objects.filter(name__startswith="Swagger"): d.hard_delete()
instA,_ = Institution.objects.get_or_create(code="SWG-CMS-A", defaults={"name":"Swagger甲医院","type":"hospital","level":"三甲","province":"北京","city":"北京"})
instB,_ = Institution.objects.get_or_create(code="SWG-CMS-B", defaults={"name":"Swagger乙医院","type":"hospital"})
dN,_ = Department.objects.get_or_create(name="内科", defaults={"category":"临床"})
dW,_ = Department.objects.get_or_create(name="外科", defaults={"category":"临床"})
sup = User.objects.create_user(username="13800009000", password="x", phone="13800009000", real_name="Swagger超管", role_type="super_admin", status=1)
hos = User.objects.create_user(username="13800009001", password="x", phone="13800009001", real_name="Swagger院管", role_type="hospital_admin", institution=instA, status=1)
doc = User.objects.create_user(username="13800009002", password="x", phone="13800009002", real_name="Swagger医生", role_type="doctor", institution=instA, status=1)
stu = User.objects.create_user(username="13800009003", password="x", phone="13800009003", real_name="Swagger学生", role_type="student", institution=instA, department=dN, status=1)
stuB = User.objects.create_user(username="13800009004", password="x", phone="13800009004", real_name="他院学生", role_type="student", institution=instB, status=1)
TeacherStudentRelation.objects.create(teacher=doc, student=stu, relation_type="指导", status=1)
def tok(u): return str(RefreshToken.for_user(u).access_token)
print("|".join([tok(sup), tok(hos), tok(doc), str(instA.id), str(instB.id), str(dN.id), str(stu.id), str(doc.id)]))
''')
T_SUP, T_HOS, T_DOC, INST_A, INST_B, DEPT_N, STU_ID, DOC_ID = setup.split('|')
w(f'[准备] 完成 instA={INST_A} instB={INST_B} deptN={DEPT_N} student={STU_ID} doctor={DOC_ID}')
# ═══ 超级管理员 - 用户管理 ═══
w('\n' + '#' * 70 + '\n# 超级管理员 - 用户管理\n' + '#' * 70)
call('CMS-USER-1', '用户列表', 'GET', '/api/cms/users/', T_SUP, params={'role_type': 'doctor', 'page': 1})
r = call('CMS-USER-2', '新增用户', 'POST', '/api/cms/users/', T_SUP,
json_body={'phone': '13800009010', 'real_name': 'Swagger新医生', 'role_type': 'doctor', 'institution': int(INST_A)}, expect=201)
NEW_UID = r.json().get('id')
call('CMS-USER-3', '编辑用户', 'PATCH', f'/api/cms/users/{NEW_UID}/', T_SUP, json_body={'real_name': 'Swagger改名', 'title_name': '主治医师'})
call('CMS-USER-5', '重置密码', 'POST', f'/api/cms/users/{NEW_UID}/reset-password/', T_SUP, json_body={})
ufile = make_xlsx_bytes(['手机号', '姓名', '角色', '机构编码'],
[['13800009011', '导入医生', '医生', 'SWG-CMS-A'], ['13800009012', '导入学生', '学生', 'SWG-CMS-A']])
with open(os.path.join(EXCEL_DIR, '用户导入_样例.xlsx'), 'wb') as f:
f.write(ufile)
call('CMS-USER-6', '导入用户', 'POST', '/api/cms/users/import/', T_SUP, file_bytes=ufile, file_name='用户导入_样例.xlsx')
call('CMS-USER-7', '导入模板', 'GET', '/api/cms/users/import-template/', T_SUP, save_as='用户导入_模板.xlsx')
call('CMS-USER-8', '导出用户', 'GET', '/api/cms/users/export/', T_SUP, params={'role_type': 'doctor'}, save_as='用户导出_结果.xlsx')
call('CMS-USER-4', '停用用户', 'DELETE', f'/api/cms/users/{NEW_UID}/', T_SUP, expect=204)
# ═══ 超级管理员 - 医院管理 ═══
w('\n' + '#' * 70 + '\n# 超级管理员 - 医院管理\n' + '#' * 70)
call('CMS-INST-1', '机构列表', 'GET', '/api/cms/institutions/', T_SUP, params={'search': 'Swagger'})
r = call('CMS-INST-2', '新增机构', 'POST', '/api/cms/institutions/', T_SUP,
json_body={'code': 'SWG-CMS-NEW', 'name': 'Swagger新医院', 'type': 'hospital', 'level': '二甲', 'province': '上海', 'city': '上海'}, expect=201)
NEW_INST = r.json().get('id')
call('CMS-INST-3', '编辑机构', 'PATCH', f'/api/cms/institutions/{NEW_INST}/', T_SUP, json_body={'level': '三甲'})
ifile = make_xlsx_bytes(['机构编码', '名称', '类型', '等级', '', ''],
[['SWG-CMS-IMP1', '导入医院A', 'hospital', '三甲', '广东', '广州']])
with open(os.path.join(EXCEL_DIR, '机构导入_样例.xlsx'), 'wb') as f:
f.write(ifile)
call('CMS-INST-5', '导入机构', 'POST', '/api/cms/institutions/import/', T_SUP, file_bytes=ifile, file_name='机构导入_样例.xlsx')
call('CMS-INST-6', '导入模板', 'GET', '/api/cms/institutions/import-template/', T_SUP, save_as='机构导入_模板.xlsx')
call('CMS-INST-7', '导出机构', 'GET', '/api/cms/institutions/export/', T_SUP, save_as='机构导出_结果.xlsx')
call('CMS-INST-4', '停用机构', 'DELETE', f'/api/cms/institutions/{NEW_INST}/', T_SUP, expect=204)
# ═══ 超级管理员 - 科室管理 ═══
w('\n' + '#' * 70 + '\n# 超级管理员 - 科室管理\n' + '#' * 70)
call('CMS-DEPT-1', '科室列表', 'GET', '/api/cms/departments/', T_SUP)
r = call('CMS-DEPT-2', '新增科室', 'POST', '/api/cms/departments/', T_SUP, json_body={'name': 'Swagger儿科', 'category': '临床'}, expect=201)
NEW_DEPT = r.json().get('id')
call('CMS-DEPT-3', '编辑科室', 'PATCH', f'/api/cms/departments/{NEW_DEPT}/', T_SUP, json_body={'category': '儿童医学'})
dfile = make_xlsx_bytes(['科室名称', '分类'], [['Swagger放射科', '医技']])
with open(os.path.join(EXCEL_DIR, '科室导入_样例.xlsx'), 'wb') as f:
f.write(dfile)
call('CMS-DEPT-5', '导入科室', 'POST', '/api/cms/departments/import/', T_SUP, file_bytes=dfile, file_name='科室导入_样例.xlsx')
call('CMS-DEPT-7', '导入模板', 'GET', '/api/cms/departments/import-template/', T_SUP, save_as='科室导入_模板.xlsx')
call('CMS-DEPT-6', '导出科室', 'GET', '/api/cms/departments/export/', T_SUP, save_as='科室导出_结果.xlsx')
call('CMS-DEPT-4', '停用科室', 'DELETE', f'/api/cms/departments/{NEW_DEPT}/', T_SUP, expect=204)
# ═══ 医院管理员 - 人员管理(本院 doctor/student/content_admin)═══
w('\n' + '#' * 70 + '\n# 医院管理员 - 人员管理\n' + '#' * 70)
call('CMS-HUSER-1', '用户列表(本院)', 'GET', '/api/cms/users/', T_HOS, params={'role_type': 'student'})
r = call('CMS-HUSER-2', '新增用户(本院)', 'POST', '/api/cms/users/', T_HOS,
json_body={'phone': '13800009020', 'real_name': '院管建的内容员', 'role_type': 'content_admin'}, expect=201)
HU_UID = r.json().get('id')
call('CMS-HUSER-3', '编辑用户(本院)', 'PATCH', f'/api/cms/users/{HU_UID}/', T_HOS, json_body={'title_name': '内容主管'})
call('CMS-HUSER-5', '重置密码(本院)', 'POST', f'/api/cms/users/{HU_UID}/reset-password/', T_HOS, json_body={})
hufile = make_xlsx_bytes(['手机号', '姓名', '角色', '机构编码'], [['13800009021', '院管导入生', '学生', '忽略']])
with open(os.path.join(EXCEL_DIR, '本院用户导入_样例.xlsx'), 'wb') as f:
f.write(hufile)
call('CMS-HUSER-6', '导入用户(本院)', 'POST', '/api/cms/users/import/', T_HOS, file_bytes=hufile, file_name='本院用户导入_样例.xlsx')
call('CMS-HUSER-7', '导入模板', 'GET', '/api/cms/users/import-template/', T_HOS, save_as='本院用户导入_模板.xlsx')
call('CMS-HUSER-8', '导出用户(本院)', 'GET', '/api/cms/users/export/', T_HOS, save_as='本院用户导出_结果.xlsx')
call('CMS-HUSER-4', '停用用户(本院)', 'DELETE', f'/api/cms/users/{HU_UID}/', T_HOS, expect=204)
# ═══ 医院管理员 - 师生关系 ═══
w('\n' + '#' * 70 + '\n# 医院管理员 - 师生关系\n' + '#' * 70)
call('CMS-REL-1', '师生关系列表', 'GET', '/api/cms/teacher-student-relations/', T_HOS)
# 先把已存在的 doc×stu 关系拿来演示编辑/停用;新增用一对新的(再建一个本院学生)
new_stu = django_eval(r'''
from apps.user.models import User, Institution
instA = Institution.objects.get(code="SWG-CMS-A")
u,_ = User.objects.get_or_create(phone="13800009022", defaults=dict(username="13800009022", real_name="师生关系新学生", role_type="student", institution=instA, status=1))
print(u.id)
''')
r = call('CMS-REL-2', '新增师生关系', 'POST', '/api/cms/teacher-student-relations/', T_HOS,
json_body={'teacher': int(DOC_ID), 'student': int(new_stu), 'relation_type': '指导'}, expect=201)
REL_ID = r.json().get('id')
call('CMS-REL-3', '编辑师生关系', 'PATCH', f'/api/cms/teacher-student-relations/{REL_ID}/', T_HOS, json_body={'status': 0})
rfile = make_xlsx_bytes(['带教医生手机号', '学生手机号'], [['13800009002', '13800009003']])
with open(os.path.join(EXCEL_DIR, '师生关系导入_样例.xlsx'), 'wb') as f:
f.write(rfile)
call('CMS-REL-5', '导入师生关系', 'POST', '/api/cms/teacher-student-relations/import/', T_HOS, file_bytes=rfile, file_name='师生关系导入_样例.xlsx')
call('CMS-REL-6', '导入模板', 'GET', '/api/cms/teacher-student-relations/import-template/', T_HOS, save_as='师生关系导入_模板.xlsx')
call('CMS-REL-7', '导出师生关系', 'GET', '/api/cms/teacher-student-relations/export/', T_HOS, save_as='师生关系导出_结果.xlsx')
call('CMS-REL-4', '停用师生关系', 'DELETE', f'/api/cms/teacher-student-relations/{REL_ID}/', T_HOS, expect=204)
# ═══ 带教医生 - 我的学生 ═══
w('\n' + '#' * 70 + '\n# 带教医生 - 我的学生\n' + '#' * 70)
call('CMS-TEA-1', '我的学生列表', 'GET', '/api/cms/students/', T_DOC)
call('CMS-TEA-2', '学生基础信息', 'GET', f'/api/cms/students/{STU_ID}/', T_DOC)
# ─── 清理 ───
w('\n[清理] 删除测试数据 ...')
django_eval(r'''
from apps.user.models import User, Institution, Department, TeacherStudentRelation
ph = ["13800009000","13800009001","13800009002","13800009003","13800009004","13800009010","13800009011","13800009012","13800009020","13800009021","13800009022"]
TeacherStudentRelation.all_objects.filter(teacher__phone__in=ph).hard_delete()
for u in User.all_objects.filter(phone__in=ph): u.hard_delete()
Institution.all_objects.filter(code__startswith="SWG-CMS").hard_delete()
Department.all_objects.filter(name__startswith="Swagger").hard_delete()
print("cleaned")
''')
# ─── 落盘样例 JSON + 汇总 ───
with open(EXAMPLES_FILE, 'w', encoding='utf-8') as f:
json.dump(examples, f, ensure_ascii=False, indent=2)
w('\n' + '=' * 90)
total = len(results); passed = sum(1 for _, ok, _ in results if ok == 'PASS')
w(f' 总计 {total} 个接口 | 通过 {passed} | 失败 {total - passed}')
fails = [(c, s) for c, ok, s in results if ok == 'FAIL']
if fails:
w(' 失败: ' + ', '.join(f'{c}({s})' for c, s in fails))
w(f' 日志: {LOG_FILE}')
w(f' 样例 JSON: {EXAMPLES_FILE}')
w(f' Excel 文件目录: {EXCEL_DIR}')
_fh.close()
sys.exit(0 if not fails else 1)