更新
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""Repository contracts for privacy-safe AI patient diagnosis options."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from inspect import signature
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from doctor_workstation.services.mock_repository import DemoDoctorRepository
|
||||
from doctor_workstation.services.repository import DoctorRepository, RemoteDoctorRepository
|
||||
|
||||
|
||||
class AiPatientOptionsClient:
|
||||
"""Record the exact AI option request and return deliberately unsafe extras."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[str, dict[str, Any]]] = []
|
||||
|
||||
def get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any:
|
||||
self.calls.append((endpoint, dict(params or {})))
|
||||
return {
|
||||
"lists": [
|
||||
{
|
||||
"diagnosis_id": 9001,
|
||||
"source_patient_id": 42,
|
||||
"patient_name": "测试患者",
|
||||
"phone_masked": "13800138000",
|
||||
"phone": "13800138000",
|
||||
"id_card": "110101199001011234",
|
||||
"gender": 2,
|
||||
"age": 36,
|
||||
"diagnosis_date": "2026-08-19",
|
||||
"diagnosis_summary": "随访诊单",
|
||||
"last_visit_at": "2026-08-19 09:30:00",
|
||||
"next_appointment_at": "2026-08-26 09:30:00",
|
||||
}
|
||||
],
|
||||
"count": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_protocol_exposes_ai_patient_option_page_defaults() -> None:
|
||||
method = signature(DoctorRepository.list_ai_patient_options)
|
||||
|
||||
assert method.parameters["page_no"].default == 1
|
||||
assert method.parameters["page_size"].default == 20
|
||||
assert method.parameters["keyword"].default == ""
|
||||
|
||||
|
||||
def test_remote_ai_patient_options_use_exact_endpoint_params_and_safe_dto() -> None:
|
||||
client = AiPatientOptionsClient()
|
||||
repository = RemoteDoctorRepository(client) # type: ignore[arg-type]
|
||||
|
||||
page = repository.list_ai_patient_options(
|
||||
page_no=3,
|
||||
page_size=7,
|
||||
keyword=" 测试 ",
|
||||
)
|
||||
|
||||
assert client.calls == [
|
||||
(
|
||||
"tcm.diagnosis/aiPatientOptions",
|
||||
{"keyword": "测试", "page_no": 3, "page_size": 7},
|
||||
)
|
||||
]
|
||||
assert page.total == 1
|
||||
assert page.page_no == 3
|
||||
assert page.page_size == 7
|
||||
assert page.items == [
|
||||
{
|
||||
"diagnosis_id": 9001,
|
||||
"source_patient_id": 42,
|
||||
"patient_name": "测试患者",
|
||||
"phone_masked": "138****8000",
|
||||
"gender": 2,
|
||||
"age": 36,
|
||||
"diagnosis_date": "2026-08-19",
|
||||
"diagnosis_summary": "随访诊单",
|
||||
"last_visit_at": "2026-08-19 09:30:00",
|
||||
"next_appointment_at": "2026-08-26 09:30:00",
|
||||
}
|
||||
]
|
||||
assert page.items[0]["diagnosis_id"] != page.items[0]["source_patient_id"]
|
||||
assert "phone" not in page.items[0]
|
||||
assert "id_card" not in page.items[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("page_no", "page_size"), [(0, 20), (1, 0)])
|
||||
def test_remote_ai_patient_options_reject_invalid_pagination_before_get(
|
||||
page_no: int,
|
||||
page_size: int,
|
||||
) -> None:
|
||||
client = AiPatientOptionsClient()
|
||||
repository = RemoteDoctorRepository(client) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(ValueError, match="must be positive"):
|
||||
repository.list_ai_patient_options(page_no=page_no, page_size=page_size)
|
||||
|
||||
assert client.calls == []
|
||||
|
||||
|
||||
def test_demo_ai_patient_options_are_stable_searchable_paginated_and_private() -> None:
|
||||
repository = DemoDoctorRepository(today=date(2026, 8, 20))
|
||||
|
||||
first = repository.list_ai_patient_options(page_no=1, page_size=2)
|
||||
second = repository.list_ai_patient_options(page_no=2, page_size=2)
|
||||
repeated = repository.list_ai_patient_options(page_no=1, page_size=2)
|
||||
|
||||
assert first.total == 4
|
||||
assert first.pages == 2
|
||||
assert [row["diagnosis_id"] for row in first.items] == [504, 503]
|
||||
assert [row["diagnosis_id"] for row in second.items] == [502, 501]
|
||||
assert repeated.items == first.items
|
||||
assert all(row["diagnosis_id"] != row["source_patient_id"] for row in first.items)
|
||||
assert all("****" in row["phone_masked"] for row in first.items)
|
||||
assert all("phone" not in row and "id_card" not in row for row in first.items)
|
||||
assert "13700006618" not in repr(first.items)
|
||||
|
||||
by_diagnosis = repository.list_ai_patient_options(keyword=" 503 ")
|
||||
by_plain_phone = repository.list_ai_patient_options(keyword="15900007732")
|
||||
|
||||
assert [row["diagnosis_id"] for row in by_diagnosis.items] == [503]
|
||||
assert [row["diagnosis_id"] for row in by_plain_phone.items] == [503]
|
||||
assert by_plain_phone.items[0]["phone_masked"] == "159****7732"
|
||||
assert "15900007732" not in repr(by_plain_phone.items)
|
||||
|
||||
Reference in New Issue
Block a user