188 lines
6.8 KiB
Python
188 lines
6.8 KiB
Python
"""Exact endpoint and fail-closed tests for diagnosis-detail mutations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from datetime import date
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from doctor_workstation.services.mock_repository import DemoDoctorRepository
|
|
from doctor_workstation.services.repository import RemoteDoctorRepository
|
|
|
|
|
|
class RecordingClient:
|
|
"""Minimal no-network client that preserves exact endpoint DTOs."""
|
|
|
|
def __init__(self) -> None:
|
|
self.get_calls: list[tuple[str, dict[str, Any]]] = []
|
|
self.post_calls: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any:
|
|
body = dict(params or {})
|
|
self.get_calls.append((endpoint, body))
|
|
if endpoint == "tcm.diagnosis/getImChatMessages":
|
|
return {"lists": [], "patient_im_id": "patient_301"}
|
|
return {"lists": [], "count": 0}
|
|
|
|
def post(self, endpoint: str, payload: dict[str, Any] | None = None) -> Any:
|
|
body = dict(payload or {})
|
|
self.post_calls.append((endpoint, body))
|
|
if endpoint == "tcm.diagnosis/createManualCallRecord":
|
|
return {"id": 88}
|
|
if endpoint == "tcm.diagnosis/generateMiniProgramQrcode":
|
|
return {"qrcode_url": "https://example.invalid/mini.png"}
|
|
if endpoint == "tcm.diagnosis/generateOrderQrcode":
|
|
return {"qrcode_url": "https://example.invalid/order.png"}
|
|
if endpoint == "order.order/create":
|
|
return {"id": 99, "order_no": "ORDER99"}
|
|
return {"ok": True}
|
|
|
|
|
|
def test_remote_detail_actions_use_exact_confirmed_endpoints() -> None:
|
|
client = RecordingClient()
|
|
repository = RemoteDoctorRepository(client) # type: ignore[arg-type]
|
|
today = date.today().isoformat()
|
|
|
|
repository.set_revisit_slot_start_offset(501, 4)
|
|
repository.add_blood_record(
|
|
diagnosis_id=501,
|
|
patient_id=301,
|
|
record_date=today,
|
|
record_time="09:30",
|
|
fasting_blood_sugar=6.2,
|
|
)
|
|
repository.add_diet_record(
|
|
diagnosis_id=501,
|
|
patient_id=301,
|
|
record_date=today,
|
|
breakfast_foods="燕麦",
|
|
breakfast_images=[],
|
|
lunch_images=[],
|
|
dinner_images=[],
|
|
)
|
|
repository.add_exercise_record(
|
|
diagnosis_id=501,
|
|
patient_id=301,
|
|
record_date=today,
|
|
exercise_type="步行",
|
|
duration=30,
|
|
intensity=2,
|
|
images=[],
|
|
)
|
|
repository.add_tracking_note(501, "继续观察")
|
|
repository.add_diagnosis_todo(501, "复测餐后血糖", int(time.time()) + 120)
|
|
repository.cancel_diagnosis_todo(77)
|
|
repository.list_call_records(501)
|
|
repository.create_manual_call_record(501)
|
|
repository.attach_local_call_recording(
|
|
501, "https://media.example.invalid/replay.mp4", call_record_id=88
|
|
)
|
|
assert repository.list_im_chat_messages(501)["lists"] == []
|
|
repository.sync_im_chat_messages(501)
|
|
repository.list_appointment_logs(501)
|
|
repository.generate_video_qrcode(1001, 301, 1001)
|
|
repository.generate_diagnosis_qrcode(501, 1001, 301, 1001)
|
|
repository.create_diagnosis_order(301, 2, 88.6, remark="检查费")
|
|
repository.generate_order_qrcode("ORDER99")
|
|
repository.cancel_diagnosis_appointment(101)
|
|
|
|
assert ("tcm.diagnosis/getCallRecords", {"diagnosis_id": 501}) in client.get_calls
|
|
assert (
|
|
"tcm.diagnosis/getImChatMessages",
|
|
{"diagnosis_id": 501, "only_archived": 1},
|
|
) in client.get_calls
|
|
assert ("tcm.diagnosis/guahaoLogList", {"id": 501}) in client.get_calls
|
|
assert (
|
|
"doctor.appointment/cancel",
|
|
{"id": 101},
|
|
) in client.post_calls
|
|
assert (
|
|
"tcm.diagnosis/setRevisitSlotStartOffset",
|
|
{"id": 501, "revisit_slot_start_offset": 4},
|
|
) in client.post_calls
|
|
assert (
|
|
"order.order/create",
|
|
{"patient_id": 301, "order_type": 2, "amount": 88.6, "remark": "检查费"},
|
|
) in client.post_calls
|
|
qr_payloads = [
|
|
body
|
|
for endpoint, body in client.post_calls
|
|
if endpoint == "tcm.diagnosis/generateMiniProgramQrcode"
|
|
]
|
|
assert qr_payloads[0] == {
|
|
"diagnosis_id": 1001,
|
|
"doctor_id": 1001,
|
|
"patient_id": 301,
|
|
"share_user_id": 1001,
|
|
"mini_program_path": "pages/login/login",
|
|
}
|
|
assert qr_payloads[1] == {
|
|
"diagnosis_id": 501,
|
|
"doctor_id": 1001,
|
|
"patient_id": 301,
|
|
"share_user_id": 1001,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("call", "message"),
|
|
[
|
|
(lambda repo: repo.set_revisit_slot_start_offset(501, 21), "between 0 and 20"),
|
|
(lambda repo: repo.add_tracking_note(501, ""), "1 to 1000"),
|
|
(
|
|
lambda repo: repo.add_diagnosis_todo(501, "稍后", int(time.time()) + 5),
|
|
"30 seconds",
|
|
),
|
|
(lambda repo: repo.cancel_diagnosis_todo(0), "positive"),
|
|
(lambda repo: repo.create_diagnosis_order(301, 9, 1), "between 1 and 8"),
|
|
(lambda repo: repo.cancel_diagnosis_appointment(0), "positive"),
|
|
],
|
|
)
|
|
def test_remote_detail_validation_fails_before_transport(call: Any, message: str) -> None:
|
|
client = RecordingClient()
|
|
repository = RemoteDoctorRepository(client) # type: ignore[arg-type]
|
|
|
|
with pytest.raises(ValueError, match=message):
|
|
call(repository)
|
|
assert client.get_calls == []
|
|
assert client.post_calls == []
|
|
|
|
|
|
def test_demo_detail_mutations_round_trip(tmp_path: Path) -> None:
|
|
repository = DemoDoctorRepository(today=date(2026, 8, 10))
|
|
blood = repository.add_blood_record(
|
|
diagnosis_id=501,
|
|
patient_id=301,
|
|
record_date="2026-08-10",
|
|
fasting_blood_sugar=5.8,
|
|
)
|
|
assert blood["id"] > 0
|
|
assert (
|
|
repository.get_tracking_window(501, start_date="2026-08-10", end_date="2026-08-10")[
|
|
"blood_records"
|
|
][-1]["fasting_blood_sugar"]
|
|
== 5.8
|
|
)
|
|
|
|
todo = repository.add_diagnosis_todo(501, "今晚回访", int(time.time()) + 120)
|
|
cancelled = repository.cancel_diagnosis_todo(todo["id"])
|
|
assert cancelled["status_text"] == "已取消"
|
|
|
|
replay = tmp_path / "replay.mp4"
|
|
replay.write_bytes(b"demo-video")
|
|
uploaded = repository.upload_call_recording(replay, 501)
|
|
assert uploaded["file_url"].startswith("/demo/uploads/video/")
|
|
assert uploaded["file_url"] in repository.list_call_records(501)[0]["recording_urls_list"]
|
|
|
|
archive = repository.list_im_chat_messages(501, only_archived=True)
|
|
assert archive["only_archived"] is True
|
|
assert {row["msg_type"] for row in archive["lists"]} >= {"text", "image", "file"}
|
|
assert repository.sync_im_chat_messages(501)["queued"] is True
|
|
|
|
repository.set_revisit_slot_start_offset(501, 7)
|
|
assert repository.get_diagnosis_detail(501)["diagnosis"]["revisit_slot_start_offset"] == 7
|
|
assert repository.cancel_diagnosis_appointment(101)["status"] == 2
|