2026-06-11 10:37:29 +08:00
|
|
|
|
"""CMS 师生关系管理测试:CMS-REL-1~4(医院管理员本院 / 超管全平台)。"""
|
|
|
|
|
|
import io
|
|
|
|
|
|
|
|
|
|
|
|
from openpyxl import Workbook
|
|
|
|
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
|
|
|
|
from rest_framework.test import APIClient
|
|
|
|
|
|
|
|
|
|
|
|
from apps.user.models import TeacherStudentRelation
|
|
|
|
|
|
from .conftest import CacheTestCase, create_test_user, get_auth_client, ensure_institution
|
|
|
|
|
|
|
|
|
|
|
|
REL_URL = '/api/cms/teacher-student-relations/'
|
|
|
|
|
|
XLSX_CT = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rel_detail(pk):
|
|
|
|
|
|
return f'/api/cms/teacher-student-relations/{pk}/'
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-12 11:11:48 +08:00
|
|
|
|
def rel_disable(pk):
|
|
|
|
|
|
return f'/api/cms/teacher-student-relations/{pk}/disable/' # 停用:POST(原 DELETE /{id}/)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-11 10:37:29 +08:00
|
|
|
|
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('rel.xlsx', buf.read(), content_type=XLSX_CT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CmsRelationTest(CacheTestCase):
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
self.inst = ensure_institution(name='本院', code='REL-A')
|
|
|
|
|
|
self.other = ensure_institution(name='他院', code='REL-B')
|
|
|
|
|
|
self.admin = create_test_user(phone='13932000001', role_type='hospital_admin', institution=self.inst)
|
|
|
|
|
|
self.client = get_auth_client(self.admin)
|
|
|
|
|
|
self.doc = create_test_user(phone='13932000002', role_type='doctor', institution=self.inst)
|
|
|
|
|
|
self.stu = create_test_user(phone='13932000003', role_type='student', institution=self.inst)
|
|
|
|
|
|
self.other_stu = create_test_user(phone='13932000004', role_type='student', institution=self.other)
|
|
|
|
|
|
|
|
|
|
|
|
def test_permission(self):
|
|
|
|
|
|
self.assertEqual(APIClient().get(REL_URL).status_code, 401)
|
|
|
|
|
|
stu_client = get_auth_client(self.stu)
|
|
|
|
|
|
self.assertEqual(stu_client.get(REL_URL).status_code, 403)
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_and_list(self):
|
|
|
|
|
|
resp = self.client.post(REL_URL, {'teacher': self.doc.id, 'student': self.stu.id})
|
|
|
|
|
|
self.assertEqual(resp.status_code, 201, resp.content)
|
|
|
|
|
|
self.assertEqual(resp.json()['teacher_phone'], '13932000002')
|
|
|
|
|
|
self.assertEqual(resp.json()['student_phone'], '13932000003')
|
|
|
|
|
|
# 列表
|
|
|
|
|
|
resp = self.client.get(REL_URL)
|
|
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
|
|
self.assertEqual(len(resp.json()['results']), 1)
|
|
|
|
|
|
|
|
|
|
|
|
def test_create_duplicate(self):
|
|
|
|
|
|
self.client.post(REL_URL, {'teacher': self.doc.id, 'student': self.stu.id})
|
|
|
|
|
|
resp = self.client.post(REL_URL, {'teacher': self.doc.id, 'student': self.stu.id})
|
|
|
|
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
|
|
|
|
|
self.assertEqual(resp.json()['code'], 'CMS_REL_EXISTS')
|
|
|
|
|
|
|
|
|
|
|
|
def test_teacher_must_be_doctor(self):
|
|
|
|
|
|
# 用学生当 teacher → 无效 pk
|
|
|
|
|
|
resp = self.client.post(REL_URL, {'teacher': self.stu.id, 'student': self.stu.id})
|
|
|
|
|
|
self.assertEqual(resp.status_code, 400, resp.content)
|
|
|
|
|
|
|
|
|
|
|
|
def test_scope_other_institution_student_rejected(self):
|
|
|
|
|
|
resp = self.client.post(REL_URL, {'teacher': self.doc.id, 'student': self.other_stu.id})
|
|
|
|
|
|
self.assertEqual(resp.status_code, 403, resp.content)
|
|
|
|
|
|
self.assertEqual(resp.json()['code'], 'CMS_REL_SCOPE_FORBIDDEN')
|
|
|
|
|
|
|
|
|
|
|
|
def test_soft_delete(self):
|
|
|
|
|
|
r = TeacherStudentRelation.objects.create(teacher=self.doc, student=self.stu, status=1)
|
2026-06-12 11:11:48 +08:00
|
|
|
|
resp = self.client.post(rel_disable(r.id))
|
|
|
|
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
2026-06-11 10:37:29 +08:00
|
|
|
|
self.assertFalse(TeacherStudentRelation.objects.filter(id=r.id).exists())
|
|
|
|
|
|
self.assertTrue(TeacherStudentRelation.all_objects.get(id=r.id).is_deleted)
|
|
|
|
|
|
|
|
|
|
|
|
def test_import_and_export(self):
|
|
|
|
|
|
f = make_xlsx(['带教医生手机号', '学生手机号'], [
|
|
|
|
|
|
['13932000002', '13932000003'], # ✅
|
|
|
|
|
|
['13932000002', '13932000004'], # 学生他院 → 失败
|
|
|
|
|
|
['00000000000', '13932000003'], # 医生不存在 → 失败
|
|
|
|
|
|
])
|
|
|
|
|
|
resp = self.client.post('/api/cms/teacher-student-relations/import/', {'file': f}, format='multipart')
|
|
|
|
|
|
self.assertEqual(resp.status_code, 200, resp.content)
|
|
|
|
|
|
body = resp.json()
|
|
|
|
|
|
self.assertEqual(body['success'], 1)
|
|
|
|
|
|
self.assertEqual(body['failed'], 2)
|
|
|
|
|
|
# 导出
|
|
|
|
|
|
resp = self.client.get('/api/cms/teacher-student-relations/export/')
|
|
|
|
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
|
|
self.assertEqual(resp['Content-Type'], XLSX_CT)
|
|
|
|
|
|
|
|
|
|
|
|
def test_list_scoped_to_own(self):
|
|
|
|
|
|
# 他院的师生关系不可见
|
|
|
|
|
|
other_doc = create_test_user(phone='13932000005', role_type='doctor', institution=self.other)
|
|
|
|
|
|
TeacherStudentRelation.objects.create(teacher=other_doc, student=self.other_stu, status=1)
|
|
|
|
|
|
TeacherStudentRelation.objects.create(teacher=self.doc, student=self.stu, status=1)
|
|
|
|
|
|
resp = self.client.get(REL_URL)
|
|
|
|
|
|
self.assertEqual(len(resp.json()['results']), 1) # 只看到本院那条
|