更新
This commit is contained in:
@@ -1491,6 +1491,55 @@ def test_pending_and_silently_closed_streams_never_show_a_blank_bubble(
|
||||
dialog.close()
|
||||
|
||||
|
||||
def test_stalled_stream_exits_pending_state_and_ignores_late_events(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
dialog = _silent_dialog(monkeypatch)
|
||||
dialog.show()
|
||||
application.processEvents()
|
||||
|
||||
dialog._ask("请总结当前病情")
|
||||
generation, stream_generation = dialog._generation, dialog._stream_generation
|
||||
bubble = dialog._stream_bubble
|
||||
worker = dialog._stream_worker
|
||||
assert bubble is not None and worker is not None
|
||||
assert dialog._stream_timeout_timer.isActive()
|
||||
|
||||
dialog._stream_timed_out()
|
||||
assert worker.is_cancelled()
|
||||
assert not dialog._stream_timeout_timer.isActive()
|
||||
assert not dialog._asking
|
||||
assert dialog.send_button.isEnabled()
|
||||
assert bubble._raw_payload == ai_consult_module.AI_STREAM_TIMEOUT_TEXT
|
||||
|
||||
calls: list[int] = []
|
||||
monkeypatch.setattr(
|
||||
dialog.repository,
|
||||
"stream_diagnosis_ai",
|
||||
lambda *args, **kwargs: calls.append(1),
|
||||
raising=False,
|
||||
)
|
||||
worker.run()
|
||||
assert calls == []
|
||||
dialog._stream_event(generation, stream_generation, {"event": "delta", "text": "迟到回复"})
|
||||
dialog._stream_finished(generation, stream_generation, worker)
|
||||
assert bubble._raw_payload == ai_consult_module.AI_STREAM_TIMEOUT_TEXT
|
||||
|
||||
dialog._ask("请补充说明")
|
||||
second_bubble = dialog._stream_bubble
|
||||
assert second_bubble is not None
|
||||
dialog._stream_event(
|
||||
dialog._generation,
|
||||
dialog._stream_generation,
|
||||
{"event": "delta", "text": "已收到部分分析"},
|
||||
)
|
||||
dialog._stream_timed_out()
|
||||
assert second_bubble._raw_payload.startswith("已收到部分分析\n\n>")
|
||||
assert ai_consult_module.AI_STREAM_TIMEOUT_TEXT in second_bubble._raw_payload
|
||||
dialog.close()
|
||||
|
||||
|
||||
def test_answered_stream_replaces_the_pending_placeholder(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -1501,15 +1550,29 @@ def test_answered_stream_replaces_the_pending_placeholder(
|
||||
|
||||
dialog._ask("请总结当前病情")
|
||||
generation, stream_generation = dialog._generation, dialog._stream_generation
|
||||
assert dialog._stream_bubble is not None
|
||||
assert dialog._stream_bubble._raw_payload == ai_consult_module.AI_STREAM_PENDING_TEXT
|
||||
dialog._stream_event(
|
||||
generation,
|
||||
stream_generation,
|
||||
{"event": "start", "source_summary": {"patient_name": "敏感姓名"}},
|
||||
)
|
||||
assert dialog._stream_bubble._raw_payload == ai_consult_module.AI_STREAM_WAITING_TEXT
|
||||
assert "敏感姓名" not in dialog._stream_bubble._raw_payload
|
||||
dialog._stream_event(generation, stream_generation, {"event": "delta", "text": "证候:"})
|
||||
dialog._flush_timer.stop()
|
||||
dialog._flush_stream_chunks()
|
||||
assert dialog._stream_bubble._raw_payload == "证候:"
|
||||
dialog._stream_event(generation, stream_generation, {"event": "delta", "text": "脾肾两虚"})
|
||||
dialog._stream_event(generation, stream_generation, {"event": "done", "model_label": "千问"})
|
||||
dialog._stream_finished(generation, stream_generation, dialog._stream_worker)
|
||||
application.processEvents()
|
||||
|
||||
assert dialog._stream_text == "证候:脾肾两虚"
|
||||
assert not dialog._stream_timeout_timer.isActive()
|
||||
texts = _bubble_texts(dialog)
|
||||
assert not any(ai_consult_module.AI_STREAM_PENDING_TEXT in text for text in texts)
|
||||
assert not any(ai_consult_module.AI_STREAM_WAITING_TEXT in text for text in texts)
|
||||
assert not any(ai_consult_module.AI_STREAM_SILENT_TEXT in text for text in texts)
|
||||
dialog.close()
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from doctor_workstation.core.errors import ApiBusinessError, ApiHttpError, ApiProtocolError
|
||||
from doctor_workstation.core.errors import (
|
||||
ApiBusinessError,
|
||||
ApiHttpError,
|
||||
ApiProtocolError,
|
||||
ApiTimeoutError,
|
||||
ApiTransportError,
|
||||
)
|
||||
from doctor_workstation.core.models import Appointment, Consultation, PageResult, Prescription
|
||||
from doctor_workstation.services.mock_repository import DemoDoctorRepository
|
||||
from doctor_workstation.services.repository import (
|
||||
@@ -774,10 +780,11 @@ def test_remote_diagnosis_ai_stream_normalises_chunks_in_order() -> None:
|
||||
assert client.post_calls == []
|
||||
|
||||
|
||||
def test_remote_diagnosis_ai_stream_falls_back_once_but_not_for_error_event() -> None:
|
||||
@pytest.mark.parametrize("status_code", [404, 405])
|
||||
def test_remote_diagnosis_ai_stream_falls_back_for_missing_route(status_code: int) -> None:
|
||||
class MissingStreamClient(RecordingClient):
|
||||
def post_event_stream(self, *args: Any, **kwargs: Any):
|
||||
raise ApiHttpError("missing", status_code=404)
|
||||
raise ApiHttpError("missing", status_code=status_code)
|
||||
|
||||
missing_client = MissingStreamClient()
|
||||
events = list(
|
||||
@@ -789,6 +796,57 @@ def test_remote_diagnosis_ai_stream_falls_back_once_but_not_for_error_event() ->
|
||||
"tcm.diagnosis/aiAssistant"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"stream_error",
|
||||
[
|
||||
ApiHttpError("server error", status_code=500),
|
||||
ApiTimeoutError("timed out"),
|
||||
ApiTransportError("disconnected"),
|
||||
ApiProtocolError("invalid event stream"),
|
||||
],
|
||||
)
|
||||
def test_remote_diagnosis_ai_stream_failures_do_not_issue_full_request(
|
||||
stream_error: Exception,
|
||||
) -> None:
|
||||
class FailedStreamClient(RecordingClient):
|
||||
def post_event_stream(self, *args: Any, **kwargs: Any):
|
||||
raise stream_error
|
||||
|
||||
client = FailedStreamClient()
|
||||
with pytest.raises(type(stream_error)):
|
||||
list(RemoteDoctorRepository(client).stream_diagnosis_ai(501, "请分析"))
|
||||
assert client.post_calls == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("after_start", [False, True])
|
||||
def test_remote_diagnosis_ai_stream_incomplete_response_does_not_fall_back(
|
||||
after_start: bool,
|
||||
) -> None:
|
||||
class IncompleteStreamClient(RecordingClient):
|
||||
def post_event_stream(self, *args: Any, **kwargs: Any):
|
||||
if after_start:
|
||||
yield {"event": "start", "data": {"model_key": "qwen"}}
|
||||
|
||||
client = IncompleteStreamClient()
|
||||
with pytest.raises(ApiProtocolError, match="ended before done"):
|
||||
list(RemoteDoctorRepository(client).stream_diagnosis_ai(501, "请分析"))
|
||||
assert client.post_calls == []
|
||||
|
||||
|
||||
def test_remote_diagnosis_ai_stream_does_not_fall_back_after_start() -> None:
|
||||
class FailedAfterStartClient(RecordingClient):
|
||||
def post_event_stream(self, *args: Any, **kwargs: Any):
|
||||
yield {"event": "start", "data": {"model_key": "qwen"}}
|
||||
raise ApiHttpError("missing", status_code=404)
|
||||
|
||||
client = FailedAfterStartClient()
|
||||
with pytest.raises(ApiHttpError):
|
||||
list(RemoteDoctorRepository(client).stream_diagnosis_ai(501, "请分析"))
|
||||
assert client.post_calls == []
|
||||
|
||||
|
||||
def test_remote_diagnosis_ai_stream_error_event_does_not_fall_back() -> None:
|
||||
class ErrorStreamClient(RecordingClient):
|
||||
def post_event_stream(self, *args: Any, **kwargs: Any):
|
||||
yield {"event": "error", "data": {"message": "模型繁忙"}}
|
||||
|
||||
Reference in New Issue
Block a user