2026-06-08 17:36:03 +08:00
|
|
|
"""
|
|
|
|
|
Swagger Try-it-out 等效脚本:仅测配置页三接口。
|
|
|
|
|
1) GET /api/user/institution_info/ 机构信息获取
|
|
|
|
|
2) GET /api/user/my_departments/ 所属机构科室列表(不分页)
|
|
|
|
|
3) POST /api/user/profile/config/ 医学生信息配置
|
|
|
|
|
运行方式:.venv\\Scripts\\python.exe test/swagger_profile_config.py
|
|
|
|
|
前提:Django dev server 已在 http://127.0.0.1:8000 运行,Redis 已启动。
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
import json
|
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
|
|
|
sys.stderr.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'
|
|
|
|
|
PASS, FAIL = 'PASS', 'FAIL'
|
|
|
|
|
results = []
|
|
|
|
|
|
|
|
|
|
TRIAL_INST_CODE = 'PKU_LAB_TRIAL'
|
|
|
|
|
STUDENT_PHONE = '13700000055'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def log(api_id, method, url, expected, actual, detail='', resp_body=None):
|
|
|
|
|
exp = expected if isinstance(expected, (list, tuple)) else [expected]
|
|
|
|
|
status = PASS if actual in exp else FAIL
|
|
|
|
|
results.append((api_id, status))
|
|
|
|
|
print(f' {status} {api_id:<14} {method:<5} {url:<38} expect={str(expected):<10} got={actual} {detail}')
|
|
|
|
|
if resp_body is not None:
|
|
|
|
|
body = json.dumps(resp_body, ensure_ascii=False, indent=2)
|
|
|
|
|
if len(body) > 1200:
|
|
|
|
|
body = body[:1200] + f'... (truncated, {len(body)} chars)'
|
|
|
|
|
print(f' <<< {body}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def django_eval(code):
|
|
|
|
|
preamble = (
|
|
|
|
|
'import django, os; '
|
|
|
|
|
'os.environ.setdefault("DJANGO_SETTINGS_MODULE","config.settings"); '
|
|
|
|
|
'django.setup(); '
|
|
|
|
|
)
|
|
|
|
|
proc = subprocess.run([PYTHON, '-c', preamble + code], capture_output=True, text=True, cwd=CWD)
|
|
|
|
|
return proc.stdout.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─── 准备:试用机构 + banner + 两个科室 + 学生用户 + token ─────────────────────
|
|
|
|
|
|
|
|
|
|
print('\n[准备] 配置试用机构 banner、科室、学生用户...')
|
|
|
|
|
setup = django_eval(
|
|
|
|
|
f'from apps.user.models import User, Institution, Department; '
|
|
|
|
|
f'from rest_framework_simplejwt.tokens import RefreshToken; '
|
|
|
|
|
f'inst, _ = Institution.objects.get_or_create(code="{TRIAL_INST_CODE}", '
|
|
|
|
|
f' defaults={{"name":"北大医学部(实验室)试用","type":"hospital"}}); '
|
|
|
|
|
f'inst.banner_url = "institutions/default_hospital.png"; inst.save(update_fields=["banner_url"]); '
|
2026-06-11 10:37:29 +08:00
|
|
|
f'd1, _ = Department.objects.get_or_create(name="内科", defaults={{"category":"临床"}}); '
|
|
|
|
|
f'd2, _ = Department.objects.get_or_create(name="外科", defaults={{"category":"临床"}}); '
|
2026-06-08 17:36:03 +08:00
|
|
|
f'User.objects.filter(phone="{STUDENT_PHONE}").delete(); '
|
|
|
|
|
f'u = User.objects.create_user(username="{STUDENT_PHONE}", password=None, phone="{STUDENT_PHONE}", '
|
|
|
|
|
f' real_name="配置页测试学生", role_type="student", institution=inst, status=1); '
|
|
|
|
|
f'print(str(RefreshToken.for_user(u).access_token) + "|" + str(d1.id) + "|" + str(d2.id) + "|" + str(inst.id))'
|
|
|
|
|
)
|
|
|
|
|
access, dept1_id, dept2_id, inst_id = setup.split('|')
|
|
|
|
|
auth = {'Authorization': f'Bearer {access}'}
|
|
|
|
|
print(f'[准备] 完成 inst_id={inst_id} dept1={dept1_id} dept2={dept2_id}\n')
|
|
|
|
|
|
|
|
|
|
print('=' * 100)
|
|
|
|
|
print(' 配置页三接口 Swagger Try-it-out')
|
|
|
|
|
print('=' * 100)
|
|
|
|
|
|
|
|
|
|
# ── 1. 机构信息获取 ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
r = s = requests.get(f'{BASE}/api/user/institution_info/', headers=auth)
|
|
|
|
|
body = r.json() if r.headers.get('content-type', '').startswith('application/json') else None
|
|
|
|
|
banner = (body or {}).get('banner_url', '')
|
|
|
|
|
detail = f'name={body.get("name","")}, banner_ok={banner.endswith("/static/institutions/default_hospital.png")}' if body else ''
|
|
|
|
|
log('institution_info', 'GET', '/api/user/institution_info/', 200, r.status_code, detail, resp_body=body)
|
|
|
|
|
|
|
|
|
|
# 1b. 未登录 → 401
|
|
|
|
|
r = requests.get(f'{BASE}/api/user/institution_info/')
|
|
|
|
|
log('inst_info-401', 'GET', '/api/user/institution_info/ (anon)', 401, r.status_code)
|
|
|
|
|
|
|
|
|
|
# ── 2. 所属机构科室列表(不分页)─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
r = requests.get(f'{BASE}/api/user/my_departments/', headers=auth)
|
|
|
|
|
body = r.json() if r.headers.get('content-type', '').startswith('application/json') else None
|
|
|
|
|
is_list = isinstance(body, list)
|
|
|
|
|
names = [d.get('name') for d in body] if is_list else []
|
|
|
|
|
detail = f'not_paginated={is_list}, count={len(names)}, names={names}'
|
|
|
|
|
log('my_departments', 'GET', '/api/user/my_departments/', 200, r.status_code, detail, resp_body=body)
|
|
|
|
|
|
|
|
|
|
# ── 3. 医学生信息配置 ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
cfg_body = {'department': int(dept1_id), 'title_name': '住院医师', 'practice_years': '1-3年'}
|
|
|
|
|
r = requests.post(f'{BASE}/api/user/profile/config/', json=cfg_body, headers=auth)
|
|
|
|
|
body = r.json() if r.headers.get('content-type', '').startswith('application/json') else None
|
|
|
|
|
u = (body or {}).get('user', {})
|
|
|
|
|
detail = (f'dept={u.get("department")}, title={u.get("title_name")}, '
|
|
|
|
|
f'years={u.get("practice_years")}') if body else ''
|
|
|
|
|
log('profile_config', 'POST', '/api/user/profile/config/', 200, r.status_code, detail, resp_body=body)
|
|
|
|
|
|
|
|
|
|
# 3b. 跨机构科室 → 400(用一个属于别的机构的科室)
|
|
|
|
|
other_dept = django_eval(
|
|
|
|
|
'from apps.user.models import Institution, Department; '
|
|
|
|
|
'o, _ = Institution.objects.get_or_create(code="SWAG_OTHER_CFG", defaults={"name":"配置页其它医院","type":"hospital"}); '
|
|
|
|
|
'd, _ = Department.objects.get_or_create(institution=o, name="放射科", defaults={"category":"医技"}); '
|
|
|
|
|
'print(d.id)'
|
|
|
|
|
)
|
|
|
|
|
r = requests.post(f'{BASE}/api/user/profile/config/',
|
|
|
|
|
json={'department': int(other_dept), 'title_name': '住院医师', 'practice_years': '1-3年'},
|
|
|
|
|
headers=auth)
|
|
|
|
|
body = r.json() if r.headers.get('content-type', '').startswith('application/json') else None
|
|
|
|
|
log('cfg-cross-inst', 'POST', '/api/user/profile/config/ (other inst dept)', 400, r.status_code,
|
|
|
|
|
f'code={(body or {}).get("code","")}', resp_body=body)
|
|
|
|
|
|
|
|
|
|
# 3c. 缺字段 → 400
|
|
|
|
|
r = requests.post(f'{BASE}/api/user/profile/config/', json={'title_name': '住院医师'}, headers=auth)
|
|
|
|
|
log('cfg-missing', 'POST', '/api/user/profile/config/ (missing fields)', 400, r.status_code)
|
|
|
|
|
|
|
|
|
|
# 验证落库
|
|
|
|
|
db = django_eval(
|
|
|
|
|
f'from apps.user.models import User; u=User.objects.get(phone="{STUDENT_PHONE}"); '
|
|
|
|
|
f'print(f"{{u.department_id}}|{{u.title_name}}|{{u.practice_years}}")'
|
|
|
|
|
)
|
|
|
|
|
log('cfg-db-check', 'CHECK', 'user.department/title/years persisted',
|
|
|
|
|
f'{dept1_id}|住院医师|1-3年', db)
|
|
|
|
|
|
|
|
|
|
# ─── 清理 ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
django_eval(f'from apps.user.models import User; User.objects.filter(phone="{STUDENT_PHONE}").delete(); print("cleaned")')
|
|
|
|
|
|
|
|
|
|
# ─── 汇总 ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
print('=' * 100)
|
|
|
|
|
total = len(results)
|
|
|
|
|
passed = sum(1 for _, st in results if st == PASS)
|
|
|
|
|
failed = total - passed
|
|
|
|
|
print(f' 总计: {total} | 通过: {passed} | 失败: {failed}')
|
|
|
|
|
if failed:
|
|
|
|
|
print(' 失败:', [aid for aid, st in results if st == FAIL])
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
print(' ALL PASSED — 配置页三接口验证通过!')
|
|
|
|
|
sys.exit(0)
|