feat: update login api

This commit is contained in:
2026-06-05 15:36:31 +08:00
parent fd0b3e1982
commit ba9fb33062
15 changed files with 714 additions and 163 deletions
+70 -28
View File
@@ -1,9 +1,11 @@
# D8 测试文档
> 测试日期:2026-05-29(单元测试);Swagger 2026-06-0325 场景,含 `case_exam_item` 校验
> 测试日期:2026-05-29(单元测试);Swagger 2026-06-03;登录逻辑 v1.1 复测 2026-06-0528 场景
> 测试人员:Claude AI + 人工审核
> 测试环境:Windows / Python 3.14 / Django 5.0 / MySQL 8 / Redis
> **v1.1 变更(2026-06-05**:登录拆分为「移动端 U4 / CMS 端 U3」。U3 改为账号(用户名或手机号)+密码+角色;U4 仅试用机构「北大医学部(实验室)试用」可自动注册,其它机构须 CMS 先录入学生且机构需匹配;新增机构列表接口 `GET /api/user/institution_list/`**U2 代注册收紧为仅超级管理员/医院管理员**(超管建所有角色、可任意机构;医院管理员建内容管理员/医生/学生、**仅限本机构**),代注册响应**不再返回 tokens**。新增测试文件 `test_login_mobile_cms.py`11 条);负向测试新增代注册权限/机构范围 6 条(N-REG1~6)。
---
## 1. 测试环境
@@ -24,10 +26,11 @@
| 类别 | 测试文件 | 用例数 | 通过 | 失败 |
|---|---|---|---|---|
| 用户域 happy-path | `test_user_happy.py` | 11 | 11 | 0 |
| 登录 v1.1(移动端/CMS | `test_login_mobile_cms.py` | 11 | 11 | 0 |
| 病例域 happy-path | `test_case_happy.py` | 2 | 2 | 0 |
| 用户域 negative | `test_user_negative.py` | 17 | 17 | 0 |
| 用户域 negative | `test_user_negative.py` | 23 | 23 | 0 |
| 病例域 negative | `test_case_negative.py` | 12 | 12 | 0 |
| **合计** | | **42** | **42** | **0** |
| **合计** | | **59** | **59** | **0** |
---
@@ -37,8 +40,8 @@
| ID | 测试方法 | 测试什么 | 结果 |
|---|---|---|---|
| HP-1 | `test_flow_register_login_me` | **管理员代注册 → 密码登录**:管理员调用注册接口(手机号+姓名+机构,无需验证码,密码自动为 Pass+手机号)→ 用默认密码登录 → 查看个人信息(确认手机号、姓名、机构正确) | PASS |
| HP-2 | `test_flow_code_login` | **验证码登录(已有用户**:预创建用户 → 发送登录验证码 → 用手机号+验证码+机构信息登录 → 确认 `is_new_user=false` → 查看个人信息确认身份正确 | PASS |
| HP-1 | `test_flow_register_login_me` | **管理员代注册 → CMS 密码登录**:管理员注册一个 CMS 角色(doctor)账号(手机号+姓名+机构,无需验证码,密码自动为 Pass+手机号)→ 用「账号+密码+角色」登录 → 查看个人信息(确认手机号、姓名、机构正确) | PASS |
| HP-2 | `test_flow_code_login` | **验证码登录(已录入学生**:预创建学生并关联机构 → 发送登录验证码 → 用手机号+验证码+所选机构编码登录 → 确认 `is_new_user=false` → 查看个人信息确认身份正确 | PASS |
| HP-3 | `test_flow_reset_password` | **忘记密码重置**:发送重置验证码 → 用验证码设置新密码 → 用新密码能登录成功 → 用旧密码登录失败(旧密码已失效) | PASS |
| HP-4 | `test_flow_change_password` | **登录后修改密码**:先用旧密码登录拿到 token → 调用修改密码接口 → 等 1 秒后旧 token 被系统自动作废(返回 401)→ 用新密码重新登录,新 token 正常可用 | PASS |
| HP-5 | `test_admin_list_all_users` | **管理员看用户列表**:创建管理员+2 个学生,管理员调用用户列表接口,确认能看到系统里所有用户(包括自己和两个学生) | PASS |
@@ -49,6 +52,22 @@
| HP-10 | `test_teacher_retrieve_own_student` | **教师查看名下学生详情**:教师和学生建立师生关系后,教师可以查看该学生的详细信息 | PASS |
| HP-11 | `test_admin_list_filter_and_search` | **列表筛选和搜索**:创建管理员+张同学(学生)+李同学(学生)+张老师(教师),管理员用 `role_type=student&search=张` 筛选,确认只返回张同学(李同学不姓张被排除,张老师不是学生被排除) | PASS |
### 3.1.1 登录 v1.1(移动端 U4 / CMS 端 U3)— `test_login_mobile_cms.py`11 条)
| ID | 测试方法 | 测试什么 | 期望 | 结果 |
|---|---|---|---|---|
| L-1 | `InstitutionListTest.test_list_unpaginated_with_trial_flag` | **机构列表不分页**:创建普通机构 + 试用机构,GET `institution_list` 返回数组,试用机构 `is_trial=true`、普通机构 `is_trial=false` | 200 | PASS |
| L-2 | `MobileLoginCodeTest.test_trial_first_register_then_login` | **试用机构首次注册→再次登录**:新手机号选试用机构,首次 `is_new_user=true`(自动建 student),二次 `is_new_user=false` | 201/200 | PASS |
| L-3 | `MobileLoginCodeTest.test_non_trial_unregistered_403` | **非试用未录入拒绝**:未录入手机号选普通机构登录,拒绝 `AUTH_NOT_REGISTERED` | 403 | PASS |
| L-4 | `MobileLoginCodeTest.test_non_trial_institution_mismatch_403` | **机构不匹配拒绝**:学生录入在机构 A,却选机构 B 登录,拒绝 `AUTH_INSTITUTION_MISMATCH` | 403 | PASS |
| L-5 | `MobileLoginCodeTest.test_non_trial_registered_match_ok` | **非试用已录入且机构匹配**:学生录入在机构 A 选机构 A 登录成功,`is_new_user=false` | 200 | PASS |
| L-6 | `MobileLoginCodeTest.test_unknown_institution_code` | **机构编码不存在**:传不存在的机构编码,`USER_INSTITUTION_NOT_FOUND` | 400 | PASS |
| L-7 | `CmsPasswordLoginTest.test_login_by_phone_ok` | **CMS 手机号登录**:doctor 用手机号+密码+角色登录成功 | 200 | PASS |
| L-8 | `CmsPasswordLoginTest.test_login_by_username_ok` | **CMS 用户名登录**super_admin(无手机号)用用户名+密码+角色登录成功 | 200 | PASS |
| L-9 | `CmsPasswordLoginTest.test_missing_role_400` | **缺少角色**:只传账号+密码不传角色,`AUTH_BAD_CREDENTIALS` | 400 | PASS |
| L-10 | `CmsPasswordLoginTest.test_invalid_role` | **非法角色**role=student(非 CMS 角色),`AUTH_INVALID_ROLE` | 400 | PASS |
| L-11 | `CmsPasswordLoginTest.test_role_mismatch` | **角色不符**doctor 账号传 role=content_admin,通用 `AUTH_BAD_CREDENTIALS`(不暴露真实角色) | 400 | PASS |
### 3.2 病例域(2 条流程)
| ID | 测试方法 | 测试什么 | 结果 |
@@ -60,18 +79,18 @@
## 4. Negative 测试结果
### 4.1 用户域(17 条)
### 4.1 用户域(23 条)
| ID | 测试方法 | 测试什么 | 期望 | 结果 |
|---|---|---|---|---|
| N1 | `test_rate_limit_sms_429` | **短信发送频率超限**:模拟 1 分钟内已发过验证码,再次请求发送时系统拒绝,返回"请求太频繁" | 429 | PASS |
| N2 | `test_unauth_change_password_401` | **没登录就想改密码**:不带任何 token 直接调用修改密码接口,系统拒绝并要求先登录 | 401 | PASS |
| N3 | `test_unauth_me_401` | **没登录就想看个人信息**:不带 token 调用 /me 接口,系统拒绝 | 401 | PASS |
| N4 | `test_register_invalid_phone_400` | **手机号格式错误**:用 "123"(不是 11 位手机号)去注册,系统拒绝并提示手机号不合法 | 400 | PASS |
| N5 | `test_register_missing_institution_400` | **注册缺少机构**管理员注册时不传机构名称,系统拒绝并提示机构名称不能为空 | 400 | PASS |
| N6 | `test_register_duplicate_phone_400` | **手机号已被注册**:先创建一个用户,再用同一手机号注册第二次,系统拒绝并提示该手机号已注册 | 400 | PASS |
| N7 | `test_login_wrong_password` | **密码错误**用正确的手机号但错误密码登录,系统拒绝并提示账号密码错误 | 400 | PASS |
| N8 | `test_login_account_lock_423` | **连续输错密码被锁定**:连续 5 次输入错误密码,第 6 次登录时系统锁定账号,返回"账号已锁定"(防暴力破解) | 423 | PASS |
| N4 | `test_register_invalid_phone_400` | **手机号格式错误**超管代注册时用 "123"(不是 11 位手机号),系统拒绝并提示手机号不合法 | 400 | PASS |
| N5 | `test_register_missing_institution_400` | **注册缺少机构**超管代注册时不传机构编码,系统拒绝并提示机构编码不能为空 | 400 | PASS |
| N6 | `test_register_duplicate_phone_400` | **手机号已被注册**:先创建一个用户,超管再用同一手机号注册,系统拒绝并提示该手机号已注册 | 400 | PASS |
| N7 | `test_login_wrong_password` | **密码错误**CMS 账号用正确账号+角色但错误密码登录,系统拒绝并提示账号密码或角色错误 | 400 | PASS |
| N8 | `test_login_account_lock_423` | **连续输错密码被锁定**CMS 账号连续 5 次输入错误密码(账号+角色正确),第 6 次登录时系统锁定账号,返回"账号已锁定"(防暴力破解) | 423 | PASS |
| N9 | `test_reset_wrong_code` | **重置密码时验证码错误**:真实验证码是 123456,但提交 999999,系统拒绝并提示验证码不匹配 | 400/401 | PASS |
| N10 | `test_refresh_revoked_token_401` | **退出登录后 token 失效**:先退出登录(logout 会吊销 refresh token),再用那个已吊销的 refresh token 去刷新,系统拒绝 | 401 | PASS |
| N11 | `test_student_list_403` | **学生不能看用户列表**:学生角色调用用户列表接口,系统拒绝(只有管理员和教师才能看) | 403 | PASS |
@@ -81,6 +100,12 @@
| N15 | `test_student_view_other_student_403` | **学生不能看别人的详情**:学生 A 试图查看学生 B 的个人信息,系统拒绝(只能看自己的) | 403 | PASS |
| N16 | `test_teacher_view_unrelated_student_403` | **教师不能看非名下学生**:教师试图查看一个和自己没有师生关系的学生的信息,系统拒绝 | 403 | PASS |
| N17 | `test_teacher_view_ended_relation_student_403` | **教师不能看已毕业学生**:教师和学生的师生关系已结束(status=0,如学生已毕业),教师再查看该学生详情,系统拒绝 | 403 | PASS |
| N-REG1 | `test_register_unauth_401` | **未登录代注册**:不带 token 调用代注册接口,系统要求先登录 | 401 | PASS |
| N-REG2 | `test_register_non_admin_403` | **非管理员代注册**:医生(doctor)调用代注册,系统拒绝 `USER_NO_REGISTER_PERMISSION`(仅超管/医院管理员可代注册) | 403 | PASS |
| N-REG3 | `test_register_hospital_admin_cannot_create_super_admin_403` | **医院管理员越权建超管**:医院管理员尝试创建超级管理员,系统拒绝 `USER_NO_REGISTER_ROLE_PERMISSION`(只能建内容管理员/医生/学生) | 403 | PASS |
| N-REG4 | `test_register_hospital_admin_creates_student_ok` | **医院管理员在本机构建学生**:医院管理员创建学生账号成功,且新用户机构=管理员所属机构 | 201 | PASS |
| N-REG5 | `test_register_hospital_admin_cross_institution_403` | **医院管理员跨机构建账号**:医院管理员(属机构A)指定机构B建账号,系统拒绝 `USER_INSTITUTION_SCOPE_FORBIDDEN`(只能在本机构内) | 403 | PASS |
| N-REG6 | `test_register_hospital_admin_no_institution_403` | **无机构的医院管理员代注册**:医院管理员未归属任何机构时代注册,系统拒绝 `USER_NO_REGISTER_INSTITUTION` | 403 | PASS |
### 4.2 病例域(12 条)
@@ -231,9 +256,10 @@ Errors: 0
| 接口 | URL | happy-path | negative |
|---|---|---|---|
| U1 发送验证码 | POST /api/user/auth/send-code/ | HP-2,3 | N1(限流) |
| U2 管理员代注册 | POST /api/user/auth/register/ | HP-1 | N4,N5,N6 |
| U3 密码登录 | POST /api/user/auth/login/ | HP-1,3,4 | N7,N8 |
| U4 验证码登录(自动注册) | POST /api/user/auth/login-code/ | HP-2 | — |
| U2 管理员代注册 | POST /api/user/auth/register/ | HP-1 | N4,N5,N6 / N-REG1~6 |
| U3 密码登录(CMS) | POST /api/user/auth/login/ | HP-1,3,4 / L-7,8 | N7,N8 / L-9,10,11 |
| U4 验证码登录(移动端) | POST /api/user/auth/login-code/ | HP-2 / L-2,5 | L-3,4,6 |
| 机构列表(移动端) | GET /api/user/institution_list/ | L-1 | — |
| U5 重置密码 | POST /api/user/auth/reset-password/ | HP-3 | N9 |
| U6 修改密码 | POST /api/user/users/change-password/ | HP-4 | N2 |
| U7 退出登录 | POST /api/user/auth/logout/ | — | N10(辅助) |
@@ -275,33 +301,49 @@ Errors: 0
| 常量 | 值 | 用途 |
|---|---|---|
| `PHONE` | `13700000099` | 主流程用户 |
| `PHONE_ALT` | `13700000098` | U4-new 自动注册(测完删除 |
| `INST_CODE` / `INST_NAME` | `SWAG_TEST_HOSP` / `Swagger测试医院` | U2/U4 必填机构字段 |
| `SUPER_PHONE` | `13700000090` | 超级管理员,执行 U2 代注册(`django_eval` 预置) |
| `PHONE` | `13700000099` | 主流程 CMS 用户(角色 `doctor`,走 U3 |
| `STUDENT_PHONE_U4` | `13700000097` | 已录入学生,走 U4(机构匹配,测完删除) |
| `PHONE_ALT` | `13700000098` | U4-new 试用机构自动注册(测完删除) |
| `INST_CODE` / `INST_NAME` | `SWAG_TEST_HOSP` / `Swagger测试医院` | U2 建机构、U4 所选机构 |
| `TRIAL_INST_CODE` / `TRIAL_INST_NAME` | `PKU_LAB_TRIAL` / `北大医学部(实验室)试用` | 试用机构(脚本预置) |
| `CMS_ROLE` | `doctor` | PHONE 用户的 CMS 登录角色 |
| `PASSWORD` | `Pass13700000099` | U2 默认密码、U3 登录 |
| `DEPT_NAME` | `Swagger儿科` | C3 科室名(避免库内多个「儿科」→ `CASE_DEPARTMENT_AMBIGUOUS` |
| `ADMIN/TEACHER/STUDENT_PHONE` | `13700000088/77/66` | U9/U10 角色夹具(`django_eval` 创建后删除) |
| `ADMIN/TEACHER/STUDENT_PHONE` | `13700000088/77/66` | U9/U10 角色夹具(`django_eval` 创建后删除teacher 直接签发 token |
**执行顺序(用户端)**
```
U1(login发码) → U2(代注册) → U3(密码登录) → U4(验证码登录+机构)
→ U4-pre/U4-new(新号自动注册 201) → U5(重置) → [sleep 1.2s] 重登
INST-LIST(机构列表) → U1(login发码)
→ U2(超管代注册 doctor, 不返回 tokens) → U2-tok(校验无 tokens) → U2-neg1(未登录 401)
→ U3(CMS 账号+密码+角色登录) → U3-neg(缺角色 400) → U2-neg2(doctor 越权代注册 403)
→ U2-ha(医院管理员本机构 201) → U2-ha-neg(医院管理员跨机构 403)
→ U4(已录入学生+机构匹配 200) → U4-neg(非试用未录入 403)
→ U4-pre/U4-new(试用机构自动注册 201) → U5(重置) → [sleep 1.2s] 重登
→ U6(改密) → [sleep 1.2s] 重登 → U8(refresh) → /me
→ U9/U9-b/U9-c/U10/U10-b/U10-c → U7(logout)
→ [sleep 1.2s] 病例段用 FINAL_PASSWORD 重登 → C1→C2→C3→C4→C5
```
### 8.1 用户端(17 个接口/场景)
### 8.1 用户端(25 个接口/场景)
| ID | Method | URL | 测试什么 | 期望 | 结果 |
|---|---|---|---|---|---|
| INST-LIST | GET | /api/user/institution_list/ | 不分页机构列表,含 `is_trial` 标识 | 200 | PASS |
| U1 | POST | /api/user/auth/send-code/ | `scene=login` 发码(未注册用户也可) | 200 | PASS |
| U2 | POST | /api/user/auth/register/ | 管理员代注册:`phone`+`real_name`+`role_type`+`institution_code`+`institution_name`**无验证码**;默认密码 `Pass{phone}` | 201 | PASS |
| U3 | POST | /api/user/auth/login/ | 手机号 + `Pass13700000099` 密码登录 | 200 | PASS |
| U4 | POST | /api/user/auth/login-code/ | 已注册用户:`code` + `institution_code` + `institution_name``is_new_user=false` | 200 | PASS |
| U2 | POST | /api/user/auth/register/ | **超管**代注册 doctor`phone`+`real_name`+`role_type`+机构字段**无验证码**;默认密码 `Pass{phone}`**不返回 tokens** | 201 | PASS |
| U2-tok | CHECK | register 响应 | 响应体不含 `tokens` 字段 | 不含 | PASS |
| U2-neg1 | POST | /api/user/auth/register/ | 未登录代注册 → `AUTH_UNAUTHORIZED` | 401 | PASS |
| U3 | POST | /api/user/auth/login/ | CMS 登录:`account`+`password`+`role=doctor` | 200 | PASS |
| U3-neg | POST | /api/user/auth/login/ | 缺少 `role``AUTH_BAD_CREDENTIALS` | 400 | PASS |
| U2-neg2 | POST | /api/user/auth/register/ | doctor(非管理员)代注册 → `USER_NO_REGISTER_PERMISSION` | 403 | PASS |
| U2-ha | POST | /api/user/auth/register/ | 医院管理员在**本机构**建学生 → 201 | 201 | PASS |
| U2-ha-neg | POST | /api/user/auth/register/ | 医院管理员**跨机构**建账号 → `USER_INSTITUTION_SCOPE_FORBIDDEN` | 403 | PASS |
| U4 | POST | /api/user/auth/login-code/ | 已录入学生 + 机构匹配:`code` + `institution_code``is_new_user=false` | 200 | PASS |
| U4-neg | POST | /api/user/auth/login-code/ | 非试用机构 + 未录入手机号 → `AUTH_NOT_REGISTERED` | 403 | PASS |
| U4-pre | POST | /api/user/auth/send-code/ | 备用号 `13700000098` 发 login 码 | 200 | PASS |
| U4-new | POST | /api/user/auth/login-code/ | 未注册备用号验证码登录 → 自动注册 | 200 或 201 | PASS |
| U4-new | POST | /api/user/auth/login-code/ | 试用机构 + 未注册备用号 → 自动注册 `is_new_user=true` | 201 | PASS |
| U5 | POST | /api/user/auth/reset-password/ | `scene=reset` 验证码 + 新密码 `SwagNew1`(8–32 位含字母数字) | 200 | PASS |
| U6 | POST | /api/user/users/change-password/ | 已登录改密:`SwagNew1``SwagFin1` | 200 | PASS |
| U8 | POST | /api/user/auth/refresh/ | refresh 换 access | 200 | PASS |
@@ -314,7 +356,7 @@ U1(login发码) → U2(代注册) → U3(密码登录) → U4(验证码登录+
| U10-c | GET | /api/user/users/{id}/ | 学生查看自己 | 200 | PASS |
| U7 | POST | /api/user/auth/logout/ | 吊销 refresh(放用户段最后,病例段前会重登) | 200 | PASS |
### 8.2 病例端(8 个接口/场景)
### 8.2 病例端(5个接口+3个场景)
| ID | Method | URL | 测试什么 | 期望 | 结果 |
|---|---|---|---|---|---|
@@ -329,8 +371,8 @@ U1(login发码) → U2(代注册) → U3(密码登录) → U4(验证码登录+
### 8.3 汇总与注意事项
- **总计 25 个接口/场景**`medical_platform` 实跑;含 C3-exam / C3-db 库表校验)
- 用户端覆盖当前认证 API代注册(机构编码)、验证码登录(机构字段)、自动注册、重置/改密、Token 刷新与吊销时序
- **总计 33个接口/场景**`medical_platform` 实跑;含 C3-exam / C3-db 库表校验),全部 PASS
- 用户端覆盖 v1.1 认证 API机构列表、CMS 登录(账号+密码+角色、缺角色拒绝)、代注册权限与机构范围(超管 201 且无 tokens / 未登录 401 / doctor 越权 403 / 医院管理员本机构 201、跨机构 403)、移动端验证码登录(已录入学生+机构匹配、非试用未录入拒绝、试用机构自动注册、重置/改密、Token 刷新与吊销时序
- 病例端 **C1→C2→C3**AI 解析检查项 → 随病例写入 `case_exam_item`C3 将「儿科」覆盖为 `Swagger儿科`,避免 `CASE_DEPARTMENT_AMBIGUOUS`
- 脚本行为:`cache.clear()`、删除残留测试用户、`django_eval` 建 U9/U10 角色与病例机构科室、`time.sleep(1.2)` 等待 `invalidate_user_tokens`
- **前提**dev server @ 8000、Redis`.env``REDIS_URL`)、`.env` 指向已 `migrate` 的业务库;`SMS_PROVIDER=mock` 时验证码固定为 `123456`