Files
medical_training/test/test_case_happy.py
T

172 lines
7.3 KiB
Python
Raw Normal View History

2026-05-29 15:58:00 +08:00
"""病例域 2 条 happy-path 流程测试。"""
from unittest.mock import patch, MagicMock
from django.core.cache import cache
from apps.case.models import CaseBase, TraditionalCase, ScoringRule, CaseExamItem
2026-05-29 15:58:00 +08:00
from apps.user.throttling import PdfParseUserThrottle, ScoringRuleGenerateUserThrottle
from .conftest import (
CacheTestCase,
CASE_PARSE_URL, CASE_GENERATE_RULES_URL, CASE_FULL_CREATE_URL,
case_full_url, create_test_user, get_auth_client, ensure_department,
build_traditional_payload, make_fake_pdf,
MOCK_C1_PARSE_RESULT, MOCK_C2_SCORING_RULES,
)
class CaseFormHappyPathTest(CacheTestCase):
"""HP-5: 表单录入 → full-create → GET → PATCH → GET 验证"""
def setUp(self):
super().setUp()
self.user = create_test_user(phone='13900100001', password='CaseTest1')
self.client = get_auth_client(self.user)
ensure_department('儿科')
def test_flow_form_create_read_update(self):
"""HP-5: C3 full-create → C4 GET full → C5 PATCH → C4 GET verify"""
# C3: full-create2 条评分规则)
payload = build_traditional_payload(
department_name='儿科', scoring_rules_count=2, with_exam_items=True,
)
2026-05-29 15:58:00 +08:00
resp = self.client.post(CASE_FULL_CREATE_URL, payload, format='json')
self.assertEqual(resp.status_code, 201, resp.content)
created = resp.json()
case_id = created['case']['id']
self.assertEqual(created['case']['case_type'], 'traditional')
self.assertEqual(created['case']['publish_status'], 0) # 草稿
self.assertIn('traditional', created)
self.assertIsNotNone(created['traditional'])
self.assertEqual(len(created['scoring_rules']), 2)
self.assertEqual(len(created['exam_items']), 1)
self.assertEqual(created['exam_items'][0]['item_code'], 'blood_routine')
2026-05-29 15:58:00 +08:00
# C4: GET full
resp = self.client.get(case_full_url(case_id))
self.assertEqual(resp.status_code, 200, resp.content)
full = resp.json()
self.assertEqual(full['case']['title'], payload['title'])
self.assertEqual(len(full['scoring_rules']), 2)
self.assertEqual(len(full['exam_items']), 1)
2026-05-29 15:58:00 +08:00
# C5: PATCH(改标题 + 改子表 + 替换为 1 条评分规则)
patch_data = {
'title': '更新后的标题',
'traditional': {
'standard_diagnosis': '更新后的诊断',
},
'scoring_rules': [
{
'dimension': '更新后的维度',
'score_weight': 1.0,
'ai_auto_score': False,
'scoring_standard': '更新后的标准',
},
],
}
resp = self.client.patch(case_full_url(case_id), patch_data, format='json')
self.assertEqual(resp.status_code, 200, resp.content)
# C4: GET 验证更新
resp = self.client.get(case_full_url(case_id))
self.assertEqual(resp.status_code, 200, resp.content)
full = resp.json()
self.assertEqual(full['case']['title'], '更新后的标题')
self.assertEqual(full['traditional']['standard_diagnosis'], '更新后的诊断')
self.assertEqual(len(full['scoring_rules']), 1)
self.assertEqual(full['scoring_rules'][0]['dimension'], '更新后的维度')
# 验证 DB
case = CaseBase.objects.get(id=case_id)
self.assertEqual(case.title, '更新后的标题')
self.assertEqual(ScoringRule.objects.filter(case_id=case_id).count(), 1)
self.assertEqual(CaseExamItem.objects.filter(case_id=case_id).count(), 1)
2026-05-29 15:58:00 +08:00
tc = TraditionalCase.objects.get(case_id=case_id)
self.assertEqual(tc.standard_diagnosis, '更新后的诊断')
class CasePdfMockHappyPathTest(CacheTestCase):
"""HP-6: PDF 解析(mock AI)→ 生成评分规则 → full-create → GET → PATCH"""
def setUp(self):
super().setUp()
self.user = create_test_user(phone='13900100002', password='CaseTest2')
self.client = get_auth_client(self.user)
ensure_department('儿科')
@patch('apps.case.services.case_importer.extract_text_from_pdfs',
return_value='患儿,男,4岁,因发热3天就诊。体温38.5°C...')
def test_flow_pdf_mock_full_pipeline(self, mock_pdf):
"""HP-6: C1 parse-pdf → C2 generate-scoring-rules → C3 full-create → C4 GET → C5 PATCH"""
# mock call_deepseek: 第 1 次返回 C1 解析结果,第 2 次返回 C2 评分规则
call_count = {'n': 0}
def mock_deepseek(system_prompt, user_content):
call_count['n'] += 1
if call_count['n'] == 1:
return MOCK_C1_PARSE_RESULT
return MOCK_C2_SCORING_RULES
with (
patch('apps.case.services.deepseek_client.call_deepseek', side_effect=mock_deepseek),
patch.object(PdfParseUserThrottle, 'allow_request', return_value=True),
patch.object(ScoringRuleGenerateUserThrottle, 'allow_request', return_value=True),
):
# C1: parse-pdf
fake_pdf = make_fake_pdf()
resp = self.client.post(
CASE_PARSE_URL,
{'files': fake_pdf, 'case_type': 'traditional'},
format='multipart',
)
self.assertEqual(resp.status_code, 200, resp.content)
parse_result = resp.json()
self.assertIn('parse_id', parse_result)
self.assertEqual(parse_result['case_type'], 'traditional')
data = parse_result['data']
self.assertEqual(data['case_type'], 'traditional')
self.assertIn('traditional', data)
self.assertNotIn('scoring_rules', data)
# C2: generate-scoring-rules
resp = self.client.post(
CASE_GENERATE_RULES_URL,
data,
format='json',
)
self.assertEqual(resp.status_code, 200, resp.content)
gen_result = resp.json()
self.assertGreaterEqual(gen_result['generated'], 1)
scoring_rules = gen_result['scoring_rules']
# C3: full-create(组装 C1 data + C2 scoring_rules
create_payload = {**data}
create_payload['scoring_rules'] = scoring_rules
create_payload['parse_id'] = parse_result['parse_id']
resp = self.client.post(CASE_FULL_CREATE_URL, create_payload, format='json')
self.assertEqual(resp.status_code, 201, resp.content)
created = resp.json()
case_id = created['case']['id']
self.assertEqual(len(created['scoring_rules']), len(scoring_rules))
self.assertEqual(len(created['exam_items']), 1)
2026-05-29 15:58:00 +08:00
# C4: GET full
resp = self.client.get(case_full_url(case_id))
self.assertEqual(resp.status_code, 200, resp.content)
full = resp.json()
self.assertEqual(full['case']['id'], case_id)
self.assertEqual(full['case']['case_type'], 'traditional')
# C5: PATCH
resp = self.client.patch(case_full_url(case_id), {
'title': 'AI-更新标题',
}, format='json')
self.assertEqual(resp.status_code, 200, resp.content)
# 验证 PATCH 生效
resp = self.client.get(case_full_url(case_id))
self.assertEqual(resp.status_code, 200, resp.content)
self.assertEqual(resp.json()['case']['title'], 'AI-更新标题')