"""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] == { "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