Download vetcopilot-backend/tests/test_validation.py from WWsCa/VetNet-LCPS: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-backend/tests/test_validation.py
- Command line
-
hf download hf://WWsCa/VetNet-LCPS/vetcopilot-backend/tests/test_validation.py
-
curl -L -o test_validation.py https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-backend/tests/test_validation.py
15.6 kB
| """ | |
| 输入验证测试(Pydantic Schema 边界条件) | |
| 测试各 API Schema 的字段校验规则: | |
| - 必填字段缺失 | |
| - 字段长度限制 | |
| - 数值范围约束 | |
| - 无效枚举值 | |
| """ | |
| import pytest | |
| from pydantic import ValidationError | |
| from app.schemas.consultation import ( | |
| ConsultationStartRequest, | |
| ConsultationUpdateRequest, | |
| ConsultationListRequest, | |
| ) | |
| from app.schemas.soap import ( | |
| SoapUpdateRequest, | |
| SoapResponse, | |
| GenerateDischargeInstructionsRequest, | |
| GenerateReferralLetterRequest, | |
| TemplateTypeEnum, | |
| ) | |
| from app.schemas.patient import PatientCreate, PatientUpdate | |
| # ============================================================ | |
| # ConsultationStartRequest | |
| # ============================================================ | |
| class TestConsultationStartValidation: | |
| """问诊开始请求校验""" | |
| def test_minimal_valid(self): | |
| """最简有效字段应通过校验""" | |
| req = ConsultationStartRequest(animal_name="大黄", species="犬") | |
| assert req.animal_name == "大黄" | |
| assert req.species == "犬" | |
| def test_empty_all_optional(self): | |
| """所有字段留空(使用默认值)应通过校验""" | |
| req = ConsultationStartRequest() | |
| assert req.animal_name == "" | |
| assert req.species == "" | |
| def test_animal_name_too_long(self): | |
| """动物名称超长应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationStartRequest(animal_name="A" * 101) | |
| def test_animal_name_max_length_ok(self): | |
| """动物名称等于最大长度应通过""" | |
| req = ConsultationStartRequest(animal_name="A" * 100) | |
| assert len(req.animal_name) == 100 | |
| def test_species_too_long(self): | |
| """物种超长应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationStartRequest(species="A" * 51) | |
| def test_breed_too_long(self): | |
| """品种超长应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationStartRequest(breed="A" * 101) | |
| def test_weight_negative(self): | |
| """体重为负数应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationStartRequest(animal_name="大黄", weight=-1.0) | |
| def test_weight_zero_ok(self): | |
| """体重为 0 应通过(Pydantic ge=0)""" | |
| req = ConsultationStartRequest(animal_name="大黄", weight=0.0) | |
| assert req.weight == 0.0 | |
| def test_weight_positive_ok(self): | |
| """正数体重应通过""" | |
| req = ConsultationStartRequest(animal_name="大黄", weight=25.5) | |
| assert req.weight == 25.5 | |
| def test_full_fields_valid(self): | |
| """完整字段应通过校验""" | |
| req = ConsultationStartRequest( | |
| animal_name="小白", | |
| species="猫", | |
| breed="英短", | |
| age="2岁", | |
| sex="雌性已绝育", | |
| weight=4.5, | |
| bcs="5", | |
| owner_name="李女士", | |
| hospital_type="综合医院", | |
| ) | |
| assert req.animal_name == "小白" | |
| assert req.weight == 4.5 | |
| # ============================================================ | |
| # ConsultationUpdateRequest | |
| # ============================================================ | |
| class TestConsultationUpdateValidation: | |
| """问诊更新请求校验""" | |
| def test_empty_update_ok(self): | |
| """空更新(所有字段 None)应通过校验""" | |
| req = ConsultationUpdateRequest() | |
| assert req.animal_name is None | |
| assert req.species is None | |
| def test_partial_update_ok(self): | |
| """部分字段更新应通过""" | |
| req = ConsultationUpdateRequest(animal_name="新名字", weight=10.0) | |
| assert req.animal_name == "新名字" | |
| assert req.weight == 10.0 | |
| assert req.species is None # 未传的应为 None | |
| def test_weight_negative_rejected(self): | |
| """体重负数应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationUpdateRequest(weight=-1.0) | |
| def test_animal_name_empty_ok(self): | |
| """空字符串动物名应通过(Optional 字段)""" | |
| req = ConsultationUpdateRequest(animal_name="") | |
| assert req.animal_name == "" | |
| def test_name_too_long(self): | |
| """名称超长应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationUpdateRequest(animal_name="A" * 101) | |
| # ============================================================ | |
| # ConsultationListRequest | |
| # ============================================================ | |
| class TestConsultationListValidation: | |
| """问诊列表查询参数校验""" | |
| def test_defaults(self): | |
| """默认值应正确""" | |
| req = ConsultationListRequest() | |
| assert req.page == 1 | |
| assert req.page_size == 20 | |
| assert req.sort_by == "created_at" | |
| assert req.sort_order == "desc" | |
| def test_page_zero_rejected(self): | |
| """page 为 0 应报错(ge=1)""" | |
| with pytest.raises(ValidationError): | |
| ConsultationListRequest(page=0) | |
| def test_page_negative_rejected(self): | |
| """page 为负数应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationListRequest(page=-1) | |
| def test_page_size_zero_rejected(self): | |
| """page_size 为 0 应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationListRequest(page_size=0) | |
| def test_page_size_over_100_rejected(self): | |
| """page_size 超过 100 应报错""" | |
| with pytest.raises(ValidationError): | |
| ConsultationListRequest(page_size=101) | |
| def test_page_size_max_ok(self): | |
| """page_size 等于 100 应通过""" | |
| req = ConsultationListRequest(page_size=100) | |
| assert req.page_size == 100 | |
| def test_sort_order_invalid(self): | |
| """任意 sort_order 字符串应通过(无枚举约束)""" | |
| req = ConsultationListRequest(sort_order="random") | |
| assert req.sort_order == "random" | |
| def test_custom_values_ok(self): | |
| """自定义合法值应通过""" | |
| req = ConsultationListRequest( | |
| page=3, | |
| page_size=50, | |
| keyword="大黄", | |
| status="completed", | |
| sort_by="updated_at", | |
| sort_order="asc", | |
| ) | |
| assert req.page == 3 | |
| assert req.keyword == "大黄" | |
| assert req.status == "completed" | |
| # ============================================================ | |
| # PatientCreate | |
| # ============================================================ | |
| class TestPatientCreateValidation: | |
| """患者创建请求校验""" | |
| def test_minimal_valid(self): | |
| """必填字段存在应通过""" | |
| req = PatientCreate(owner_name="张三", animal_name="旺财") | |
| assert req.owner_name == "张三" | |
| assert req.animal_name == "旺财" | |
| def test_missing_owner_name(self): | |
| """缺少 owner_name 应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(animal_name="旺财") | |
| def test_missing_animal_name(self): | |
| """缺少 animal_name 应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(owner_name="张三") | |
| def test_owner_name_empty_rejected(self): | |
| """owner_name 为空字符串应报错(min_length=1)""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(owner_name="", animal_name="旺财") | |
| def test_animal_name_empty_rejected(self): | |
| """animal_name 为空字符串应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(owner_name="张三", animal_name="") | |
| def test_owner_name_too_long(self): | |
| """owner_name 超长应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(owner_name="A" * 101, animal_name="旺财") | |
| def test_animal_name_too_long(self): | |
| """animal_name 超长应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientCreate(owner_name="张三", animal_name="A" * 101) | |
| def test_owner_name_exact_max_ok(self): | |
| """owner_name 等于最大长度应通过""" | |
| req = PatientCreate(owner_name="A" * 100, animal_name="旺财") | |
| assert len(req.owner_name) == 100 | |
| def test_optional_fields_default(self): | |
| """可选字段默认为空字符串""" | |
| req = PatientCreate(owner_name="张三", animal_name="旺财") | |
| assert req.species == "" | |
| assert req.breed == "" | |
| assert req.sex == "" | |
| assert req.age == "" | |
| assert req.weight == "" | |
| def test_full_fields_valid(self): | |
| """完整字段应通过""" | |
| req = PatientCreate( | |
| owner_name="李医生", | |
| animal_name="咪咪", | |
| species="猫", | |
| breed="布偶", | |
| sex="雌性已绝育", | |
| is_neutered="已绝育", | |
| age="3岁", | |
| weight="4.5", | |
| bcs="5", | |
| immune_history="疫苗齐全,每年加强", | |
| allergy_history="无", | |
| medical_history="2024年因肠胃炎就诊", | |
| notes="性格温顺,体检配合", | |
| ) | |
| assert req.owner_name == "李医生" | |
| assert req.immune_history == "疫苗齐全,每年加强" | |
| # ============================================================ | |
| # PatientUpdate | |
| # ============================================================ | |
| class TestPatientUpdateValidation: | |
| """患者更新请求校验""" | |
| def test_empty_update_ok(self): | |
| """空更新应通过(所有字段 optional)""" | |
| req = PatientUpdate() | |
| assert req.owner_name is None | |
| def test_partial_update_ok(self): | |
| """部分更新应通过""" | |
| req = PatientUpdate(owner_name="新主人", age="4岁") | |
| assert req.owner_name == "新主人" | |
| assert req.age == "4岁" | |
| assert req.animal_name is None | |
| def test_owner_name_empty_rejected(self): | |
| """owner_name 为空应报错(min_length=1 对 Optional 仍生效)""" | |
| with pytest.raises(ValidationError): | |
| PatientUpdate(owner_name="") | |
| def test_animal_name_empty_rejected(self): | |
| """animal_name 为空应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientUpdate(animal_name="") | |
| def test_name_too_long_rejected(self): | |
| """名称超长应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientUpdate(animal_name="A" * 101) | |
| def test_species_too_long_rejected(self): | |
| """物种超长应报错""" | |
| with pytest.raises(ValidationError): | |
| PatientUpdate(species="A" * 51) | |
| # ============================================================ | |
| # SoapUpdateRequest | |
| # ============================================================ | |
| class TestSoapUpdateValidation: | |
| """SOAP 更新请求校验""" | |
| def test_defaults(self): | |
| """默认值应正确""" | |
| req = SoapUpdateRequest() | |
| assert req.confirm is False | |
| assert req.subjective is None | |
| def test_partial_update_ok(self): | |
| """部分字段更新应通过""" | |
| req = SoapUpdateRequest(subjective="S段内容", confirm=True) | |
| assert req.subjective == "S段内容" | |
| assert req.confirm is True | |
| def test_all_sections_update_ok(self): | |
| """所有段落更新应通过""" | |
| req = SoapUpdateRequest( | |
| subjective="S", | |
| objective="O", | |
| assessment="A", | |
| plan="P", | |
| clinical_reasoning="CR", | |
| client_communication="CC", | |
| problem_list="PL", | |
| discharge_instructions="DI", | |
| referral_letter="RL", | |
| history_gaps="HG", | |
| differential_diagnosis="DDX", | |
| confirm=False, | |
| ) | |
| assert req.subjective == "S" | |
| assert req.differential_diagnosis == "DDX" | |
| def test_large_content_ok(self): | |
| """大段内容应通过(无长度限制)""" | |
| large_text = "X" * 10000 | |
| req = SoapUpdateRequest(subjective=large_text) | |
| assert len(req.subjective) == 10000 | |
| # ============================================================ | |
| # GenerateDischargeInstructionsRequest | |
| # ============================================================ | |
| class TestDischargeInstructionsRequestValidation: | |
| """出院说明生成请求校验""" | |
| def test_valid_request(self): | |
| """有效请求应通过""" | |
| req = GenerateDischargeInstructionsRequest(consultation_id="abc123") | |
| assert req.consultation_id == "abc123" | |
| def test_missing_consultation_id(self): | |
| """缺少 consultation_id 应报错""" | |
| with pytest.raises(ValidationError): | |
| GenerateDischargeInstructionsRequest() | |
| # ============================================================ | |
| # GenerateReferralLetterRequest | |
| # ============================================================ | |
| class TestReferralLetterRequestValidation: | |
| """转诊信生成请求校验""" | |
| def test_valid_request(self): | |
| """有效请求应通过""" | |
| req = GenerateReferralLetterRequest(consultation_id="xyz789") | |
| assert req.consultation_id == "xyz789" | |
| def test_missing_consultation_id(self): | |
| """缺少 consultation_id 应报错""" | |
| with pytest.raises(ValidationError): | |
| GenerateReferralLetterRequest() | |
| # ============================================================ | |
| # TemplateTypeEnum | |
| # ============================================================ | |
| class TestTemplateTypeEnum: | |
| """模板类型枚举测试""" | |
| def test_valid_values(self): | |
| """有效枚举值应能创建""" | |
| assert TemplateTypeEnum.soap.value == "soap" | |
| assert TemplateTypeEnum.lab_result.value == "lab_result" | |
| assert TemplateTypeEnum.discharge_instructions.value == "discharge_instructions" | |
| assert TemplateTypeEnum.referral_letter.value == "referral_letter" | |
| def test_invalid_value_rejected(self): | |
| """无效枚举值应报错""" | |
| with pytest.raises(ValueError): | |
| TemplateTypeEnum("invalid_type") | |
| def test_enum_membership(self): | |
| """成员检查应正确""" | |
| assert TemplateTypeEnum.soap in TemplateTypeEnum | |
| # ============================================================ | |
| # SoapResponse | |
| # ============================================================ | |
| class TestSoapResponseValidation: | |
| """SOAP 响应模型校验""" | |
| def test_minimal_valid(self): | |
| """最简有效响应应通过""" | |
| resp = SoapResponse( | |
| id="soap_001", | |
| consultation_id="consult_001", | |
| ) | |
| assert resp.id == "soap_001" | |
| assert resp.consultation_id == "consult_001" | |
| assert resp.status == "draft" # 默认值 | |
| def test_all_defaults(self): | |
| """所有默认值应正确""" | |
| resp = SoapResponse(id="s1", consultation_id="c1") | |
| assert resp.subjective == "" | |
| assert resp.objective == "" | |
| assert resp.assessment == "" | |
| assert resp.plan == "" | |
| assert resp.clinical_reasoning == "" | |
| assert resp.client_communication == "" | |
| assert resp.problem_list == "" | |
| assert resp.discharge_instructions == "" | |
| assert resp.referral_letter == "" | |
| assert resp.history_gaps == "" | |
| assert resp.differential_diagnosis == "" | |
| def test_missing_required_fields(self): | |
| """缺少必填 id 应报错""" | |
| with pytest.raises(ValidationError): | |
| SoapResponse(consultation_id="c1") | |
| def test_missing_consultation_id(self): | |
| """缺少 consultation_id 应报错""" | |
| with pytest.raises(ValidationError): | |
| SoapResponse(id="s1") | |