"""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}/' def rel_disable(pk): return f'/api/cms/teacher-student-relations/{pk}/disable/' # 停用:POST(原 DELETE /{id}/) 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', real_name='张医生', role_type='doctor', institution=self.inst) self.stu = create_test_user(phone='13932000003', real_name='李同学', role_type='student', institution=self.inst) self.other_stu = create_test_user(phone='13932000004', real_name='王同学', role_type='student', institution=self.other) # 完整新增载荷(姓名+手机号均必填) def _payload(self, **over): p = {'teacher_name': '张医生', 'teacher_phone': '13932000002', 'student_name': '李同学', 'student_phone': '13932000003'} p.update(over) return p 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, self._payload()) self.assertEqual(resp.status_code, 201, resp.content) self.assertEqual(resp.json()['teacher_phone'], '13932000002') self.assertEqual(resp.json()['student_phone'], '13932000003') self.assertEqual(resp.json()['teacher_name'], '张医生') self.assertEqual(resp.json()['student_name'], '李同学') # 列表 resp = self.client.get(REL_URL) self.assertEqual(resp.status_code, 200) self.assertEqual(len(resp.json()['results']), 1) def test_create_missing_name_400(self): resp = self.client.post(REL_URL, self._payload(student_name='')) self.assertEqual(resp.status_code, 400, resp.content) self.assertEqual(resp.json()['code'], 'CMS_VALIDATION_ERROR') def test_create_name_phone_mismatch_400(self): # 姓名与手机号不符 → 解析不到 resp = self.client.post(REL_URL, self._payload(teacher_name='不存在')) self.assertEqual(resp.status_code, 400, resp.content) self.assertEqual(resp.json()['code'], 'CMS_REL_TEACHER_NOT_FOUND') def test_create_duplicate(self): self.client.post(REL_URL, self._payload()) resp = self.client.post(REL_URL, self._payload()) self.assertEqual(resp.status_code, 400, resp.content) self.assertEqual(resp.json()['code'], 'CMS_REL_EXISTS') def test_teacher_must_be_doctor(self): # 用学生姓名+手机号当带教老师 → 解析不到 doctor resp = self.client.post(REL_URL, self._payload(teacher_name='李同学', teacher_phone='13932000003')) self.assertEqual(resp.status_code, 400, resp.content) self.assertEqual(resp.json()['code'], 'CMS_REL_TEACHER_NOT_FOUND') def test_scope_other_institution_student_rejected(self): resp = self.client.post(REL_URL, self._payload(student_name='王同学', student_phone='13932000004')) self.assertEqual(resp.status_code, 403, resp.content) self.assertEqual(resp.json()['code'], 'CMS_REL_SCOPE_FORBIDDEN') def test_dropdown_lists(self): # 2 个下拉数据源:本院 doctor / student,姓名+手机号一并返回 docs = self.client.get(REL_URL + 'doctors/').json() self.assertEqual(docs, [{'id': self.doc.id, 'real_name': '张医生', 'phone': '13932000002'}]) studs = self.client.get(REL_URL + 'students/').json() # 他院学生不在内(本院仅李同学) self.assertEqual(studs, [{'id': self.stu.id, 'real_name': '李同学', 'phone': '13932000003'}]) # 学生无权访问 self.assertEqual(get_auth_client(self.stu).get(REL_URL + 'doctors/').status_code, 403) def test_soft_delete(self): r = TeacherStudentRelation.objects.create(teacher=self.doc, student=self.stu, status=1) resp = self.client.post(rel_disable(r.id)) self.assertEqual(resp.status_code, 200, resp.content) 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) # 只看到本院那条