171 lines
7.9 KiB
Python
171 lines
7.9 KiB
Python
|
|
"""CMS 超级管理员 - 用户管理接口测试(CMS-USER-1~8)。"""
|
||
|
|
|
||
|
|
import io
|
||
|
|
|
||
|
|
from openpyxl import Workbook
|
||
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||
|
|
from rest_framework.test import APIClient
|
||
|
|
|
||
|
|
from apps.user.models import User
|
||
|
|
from .conftest import CacheTestCase, create_test_user, get_auth_client, ensure_institution
|
||
|
|
|
||
|
|
CMS_USER_URL = '/api/cms/users/'
|
||
|
|
XLSX_CT = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
|
|
|
||
|
|
|
||
|
|
def u_detail(pk):
|
||
|
|
return f'/api/cms/users/{pk}/'
|
||
|
|
|
||
|
|
|
||
|
|
def make_xlsx(headers, rows):
|
||
|
|
wb = Workbook(); ws = wb.active
|
||
|
|
ws.append(headers)
|
||
|
|
for r in rows:
|
||
|
|
ws.append(r)
|
||
|
|
buf = io.BytesIO(); wb.save(buf); buf.seek(0)
|
||
|
|
return SimpleUploadedFile('import.xlsx', buf.read(), content_type=XLSX_CT)
|
||
|
|
|
||
|
|
|
||
|
|
def super_client(phone='13922200001'):
|
||
|
|
admin = create_test_user(phone=phone, password='Admin123', role_type='super_admin')
|
||
|
|
return get_auth_client(admin), admin
|
||
|
|
|
||
|
|
|
||
|
|
class CmsUserPermissionTest(CacheTestCase):
|
||
|
|
def test_requires_auth(self):
|
||
|
|
self.assertEqual(APIClient().get(CMS_USER_URL).status_code, 401)
|
||
|
|
|
||
|
|
def test_non_manager_forbidden(self):
|
||
|
|
# 用户管理仅超管 + 医院管理员;其它角色 403
|
||
|
|
for role in ('student', 'doctor', 'content_admin'):
|
||
|
|
u = create_test_user(phone={'student': '13922200010', 'doctor': '13922200011',
|
||
|
|
'content_admin': '13922200012'}[role], role_type=role)
|
||
|
|
resp = get_auth_client(u).get(CMS_USER_URL)
|
||
|
|
self.assertEqual(resp.status_code, 403, f'{role}: {resp.content}')
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_PERMISSION_DENIED')
|
||
|
|
|
||
|
|
|
||
|
|
class CmsUserCrudTest(CacheTestCase):
|
||
|
|
def setUp(self):
|
||
|
|
super().setUp()
|
||
|
|
self.client, self.admin = super_client()
|
||
|
|
self.inst = ensure_institution(name='测试医院', code='CU-H001')
|
||
|
|
|
||
|
|
def test_list(self):
|
||
|
|
create_test_user(phone='13922200021', real_name='学生甲', role_type='student')
|
||
|
|
resp = self.client.get(CMS_USER_URL)
|
||
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
||
|
|
self.assertIn('results', resp.json())
|
||
|
|
|
||
|
|
def test_create_success(self):
|
||
|
|
resp = self.client.post(CMS_USER_URL, {
|
||
|
|
'phone': '13922200030', 'real_name': '李医生',
|
||
|
|
'role_type': 'doctor', 'institution': self.inst.id,
|
||
|
|
})
|
||
|
|
self.assertEqual(resp.status_code, 201, resp.content)
|
||
|
|
self.assertEqual(resp.json()['phone'], '13922200030')
|
||
|
|
self.assertEqual(resp.json()['role_type'], 'doctor')
|
||
|
|
user = User.objects.get(phone='13922200030')
|
||
|
|
self.assertTrue(user.check_password('Pass13922200030')) # 默认密码
|
||
|
|
|
||
|
|
def test_create_role_required(self):
|
||
|
|
resp = self.client.post(CMS_USER_URL, {
|
||
|
|
'phone': '13922200031', 'real_name': '无角色', 'institution': self.inst.id})
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
|
||
|
|
def test_create_institution_required(self):
|
||
|
|
resp = self.client.post(CMS_USER_URL, {
|
||
|
|
'phone': '13922200032', 'real_name': '无机构', 'role_type': 'student'})
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_VALIDATION_ERROR')
|
||
|
|
|
||
|
|
def test_create_bad_phone(self):
|
||
|
|
resp = self.client.post(CMS_USER_URL, {
|
||
|
|
'phone': '123', 'real_name': 'x', 'role_type': 'student', 'institution': self.inst.id})
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
|
||
|
|
def test_create_duplicate_phone(self):
|
||
|
|
create_test_user(phone='13922200040', role_type='student')
|
||
|
|
resp = self.client.post(CMS_USER_URL, {
|
||
|
|
'phone': '13922200040', 'real_name': 'dup', 'role_type': 'student',
|
||
|
|
'institution': self.inst.id})
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_USER_PHONE_EXISTS')
|
||
|
|
|
||
|
|
def test_edit_partial_keeps_role_institution(self):
|
||
|
|
"""方案B:只改姓名、不带角色/机构 → 200,角色与机构保持原值。"""
|
||
|
|
u = create_test_user(phone='13922200050', real_name='原名', role_type='student',
|
||
|
|
institution=self.inst)
|
||
|
|
resp = self.client.patch(u_detail(u.id), {'real_name': '新名'})
|
||
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
||
|
|
u.refresh_from_db()
|
||
|
|
self.assertEqual(u.real_name, '新名')
|
||
|
|
self.assertEqual(u.role_type, 'student') # 未传 → 保持
|
||
|
|
self.assertEqual(u.institution_id, self.inst.id) # 未传 → 保持
|
||
|
|
|
||
|
|
def test_edit_cannot_blank_role(self):
|
||
|
|
"""方案B:传了 role_type 但为空 → 400(不可清空角色)。"""
|
||
|
|
u = create_test_user(phone='13922200051', real_name='x', role_type='student',
|
||
|
|
institution=self.inst)
|
||
|
|
resp = self.client.patch(u_detail(u.id), {'role_type': ''})
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_VALIDATION_ERROR')
|
||
|
|
|
||
|
|
def test_edit_cannot_blank_institution(self):
|
||
|
|
"""方案B:传了 institution=null → 400(不可清空机构)。"""
|
||
|
|
u = create_test_user(phone='13922200052', real_name='x', role_type='student',
|
||
|
|
institution=self.inst)
|
||
|
|
resp = self.client.patch(u_detail(u.id), {'institution': None}, format='json')
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_VALIDATION_ERROR')
|
||
|
|
|
||
|
|
def test_soft_delete(self):
|
||
|
|
u = create_test_user(phone='13922200060', role_type='student')
|
||
|
|
resp = self.client.delete(u_detail(u.id))
|
||
|
|
self.assertEqual(resp.status_code, 204, resp.content)
|
||
|
|
self.assertFalse(User.objects.filter(id=u.id).exists()) # 默认管理器过滤
|
||
|
|
obj = User.all_objects.get(id=u.id)
|
||
|
|
self.assertTrue(obj.is_deleted) # 实际未物删
|
||
|
|
|
||
|
|
def test_reset_password(self):
|
||
|
|
u = create_test_user(phone='13922200070', password='OldPass1', role_type='student')
|
||
|
|
resp = self.client.post(f'/api/cms/users/{u.id}/reset-password/', {})
|
||
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
||
|
|
self.assertEqual(resp.json()['password'], 'Pass13922200070')
|
||
|
|
u.refresh_from_db()
|
||
|
|
self.assertTrue(u.check_password('Pass13922200070'))
|
||
|
|
|
||
|
|
def test_import_template(self):
|
||
|
|
resp = self.client.get('/api/cms/users/import-template/')
|
||
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
||
|
|
self.assertEqual(resp['Content-Type'], XLSX_CT)
|
||
|
|
|
||
|
|
def test_export(self):
|
||
|
|
create_test_user(phone='13922200080', role_type='student')
|
||
|
|
resp = self.client.get('/api/cms/users/export/')
|
||
|
|
self.assertEqual(resp.status_code, 200)
|
||
|
|
self.assertEqual(resp['Content-Type'], XLSX_CT)
|
||
|
|
|
||
|
|
def test_import_users(self):
|
||
|
|
f = make_xlsx(
|
||
|
|
['手机号', '姓名', '角色', '机构编码'],
|
||
|
|
[
|
||
|
|
['13922200091', '导入甲', '医生', 'CU-H001'], # ✅
|
||
|
|
['13922200092', '导入乙', 'student', ''], # 机构编码空 → 失败
|
||
|
|
['bad', '格式错', '学生', 'CU-H001'], # 手机号格式错
|
||
|
|
['13922200093', '角色错', '不存在角色', 'CU-H001'], # 角色非法
|
||
|
|
],
|
||
|
|
)
|
||
|
|
resp = self.client.post('/api/cms/users/import/', {'file': f}, format='multipart')
|
||
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
||
|
|
body = resp.json()
|
||
|
|
self.assertEqual(body['total'], 4)
|
||
|
|
self.assertEqual(body['success'], 1)
|
||
|
|
self.assertEqual(body['failed'], 3)
|
||
|
|
self.assertTrue(User.objects.filter(phone='13922200091', role_type='doctor').exists())
|
||
|
|
|
||
|
|
def test_import_no_file(self):
|
||
|
|
resp = self.client.post('/api/cms/users/import/', {}, format='multipart')
|
||
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
||
|
|
self.assertEqual(resp.json()['code'], 'CMS_IMPORT_FILE_REQUIRED')
|