""" 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"]); ' f'd1, _ = Department.objects.get_or_create(institution=inst, name="内科", defaults={{"category":"临床"}}); ' f'd2, _ = Department.objects.get_or_create(institution=inst, name="外科", defaults={{"category":"临床"}}); ' 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)