Files
zyt/app/tests/test_friendly_error.py
2026-08-22 08:51:35 +08:00

94 lines
3.7 KiB
Python

"""Tests for friendly_error's handling of AI upstream error messages.
These cover the DifyChatService error_code → user-facing copy mapping that
the doctor workstation must apply when the server-side AI assistant returns
``ok=false`` with a Chinese ``error`` string. The legacy behaviour simply
echoed the raw text, which made incidents like ``UPSTREAM_REJECTED`` opaque
to clinicians.
"""
from __future__ import annotations
from types import SimpleNamespace
import pytest
from doctor_workstation.core.errors import (
ApiBusinessError,
ApiHttpError,
ApiProtocolError,
ApiTimeoutError,
ApiTransportError,
AuthenticationExpiredError,
)
from doctor_workstation.ui.widgets import _ai_upstream_hint, friendly_error
@pytest.mark.parametrize(
("raw", "expected_fragment"),
[
# 当上游 Dify 服务 HTTP 4xx 时返回的"UPSTREAM_REJECTED"文案。
("模型未能处理本次请求", "稍后重试"),
("AI 服务凭据无效或无权限", "联系管理员"),
("AI 服务配置无效", "联系管理员"),
("AI 报告功能未启用", "联系管理员"),
("不支持的 AI 模型", "联系管理员"),
("该模型服务尚未完整配置", "联系管理员"),
("病例数据编码失败", "联系管理员"),
("无法初始化 AI 请求", "联系管理员"),
("暂时无法连接 AI 服务,请稍后重试", "网络"),
("模型服务繁忙,请稍后重试", "稍后重试"),
("模型响应超时,请稍后重试", "稍后重试"),
("AI 助手未返回内容,请重试", "稍后重试"),
],
)
def test_friendly_error_translates_ai_upstream_strings(raw: str, expected_fragment: str) -> None:
"""Upstream Dify messages should be replaced with actionable copy."""
rendered = friendly_error(raw)
assert expected_fragment in rendered
# 上游原文不应该再原样透传。
assert rendered != raw
def test_friendly_error_passes_through_unrelated_chinese_text() -> None:
"""中文业务文案不属于上游错误时,必须原样透传,避免误伤。"""
text = "AI 返回的处方草稿格式不符合要求,请重试"
assert friendly_error(text) == text
def test_friendly_error_handles_api_business_error_with_upstream_payload() -> None:
"""服务端通过 ApiBusinessError(code=0) 透传时仍要触发映射。"""
err = ApiBusinessError("模型未能处理本次请求", code=0)
rendered = friendly_error(err)
assert "稍后重试" in rendered
assert "联系管理员" in rendered
def test_friendly_error_keeps_existing_transport_mappings() -> None:
"""对网络/超时/未授权等既有规则的回归保护。"""
assert "证书" in friendly_error(Exception("certificate_verify_failed"))
assert "网络" in friendly_error(ApiTransportError("connection refused"))
assert "重新登录" in friendly_error(AuthenticationExpiredError("expired"))
timeout_render = friendly_error(ApiTimeoutError("timed out"))
assert "超时" in timeout_render
http_render = friendly_error(ApiHttpError("boom", status_code=503))
assert "503" in http_render
protocol_render = friendly_error(ApiProtocolError("api response envelope invalid"))
assert "数据格式" in protocol_render
def test_friendly_error_returns_default_for_empty_string() -> None:
"""Fallback 应当落到"操作未完成"而不是崩溃。"""
assert friendly_error(SimpleNamespace(__str__=lambda self: " ")) == "操作未完成,请稍后重试。"
def test_ai_upstream_hint_returns_none_for_unrelated_text() -> None:
assert _ai_upstream_hint("AI 返回的处方草稿格式不符合要求,请重试") is None
assert _ai_upstream_hint("connection refused") is None
assert _ai_upstream_hint("") is None