This commit is contained in:
Your Name
2026-08-28 18:24:37 +08:00
parent 43ad07208f
commit ed48f8be31
383 changed files with 8673 additions and 2222 deletions
+217 -22
View File
@@ -29,6 +29,19 @@ def application() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.fixture(autouse=True)
def offline_thumbnails(monkeypatch: pytest.MonkeyPatch) -> None:
"""离线传输:缩略图停在 loading 状态,工作站用例永不真正联网。"""
def hold(self: Any, source: str) -> None:
self._source = str(source).strip()
self._invalidate_request()
self.setToolTip(self._source)
self._show_loading()
monkeypatch.setattr(ai_consult_module._RemoteImageButton, "load_url", hold)
@pytest.fixture
def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None:
def run_immediately(
@@ -468,17 +481,33 @@ def test_patient_report_response_owner_must_match_exactly(
dialog.close()
def test_exam_tab_filters_foreign_attachments_and_blocks_file_urls(
def test_exam_tab_previews_images_in_app_and_blocks_file_urls(
application: QApplication,
immediate_async: None,
monkeypatch: pytest.MonkeyPatch,
) -> None:
opened: list[str] = []
previews: list[tuple[tuple[str, ...], int]] = []
monkeypatch.setattr(
ai_consult_module,
"open_safe_http_url",
lambda target: opened.append(target) or True,
)
class RecordingPreviewDialog(QWidget):
def __init__(
self,
sources: Any,
*,
index: int = 0,
names: Any = None,
title: str = "",
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
previews.append((tuple(sources), int(index)))
monkeypatch.setattr(ai_consult_module, "ImagePreviewDialog", RecordingPreviewDialog)
dialog = _open_dialog(application, WorkspaceRepository())
pane = dialog.records["检查检验"]
dialog.tabs.setCurrentIndex(2)
@@ -498,20 +527,187 @@ def test_exam_tab_filters_foreign_attachments_and_blocks_file_urls(
assert len(thumbnails) == 1
assert thumbnails[0].isEnabled()
assert thumbnails[0].accessibleName() == "舌苔图片点击查看"
assert thumbnails[0].property("loadState") == "blocked"
assert thumbnails[0].property("loadState") == "loading"
assert pane.state_label.property("state") == "warning" # type: ignore[attr-defined]
assert pane.retry_button.isHidden() # type: ignore[attr-defined]
image_button = next(button for button in buttons if "甲舌苔照片.jpg" in button.text())
assert image_button.text().endswith("· 预览")
report_button = next(button for button in buttons if "甲血糖报告.pdf" in button.text())
assert report_button.text().endswith("· 打开")
unsafe = next(button for button in buttons if "本地危险附件" in button.text())
assert not unsafe.isEnabled()
for button in buttons:
button.click()
assert len(opened) == 2
assert all(target.startswith(("http://", "https://")) for target in opened)
assert all(not target.startswith("file:") for target in opened)
thumbnails[0].click()
# 图片留在工作站内预览,只有非图片附件才交给系统打开。
assert opened == ["https://media.example.invalid/甲/report.pdf"]
assert previews == [(("https://media.example.invalid/甲/tongue.jpg",), 0)] * 2
dialog.close()
def test_tongue_thumbnail_auto_get_requires_configured_https_origin(
class ProductionShapeRepository(WorkspaceRepository):
"""按线上 readonlyDetail 的真实返回构造:既有 code,也有后端补的 *_text。"""
def get_diagnosis_detail(
self,
diagnosis_id: int,
*,
readonly: bool = False,
) -> dict[str, Any]:
detail = dict(super().get_diagnosis_detail(diagnosis_id, readonly=readonly))
diagnosis = dict(detail["diagnosis"])
diagnosis.update(
{
"gender": 1,
"gender_text": "",
"diagnosis_type": "follow_up",
"diagnosis_type_text": "复诊",
"eye_condition": "blurred,dry",
"eye_condition_text": "模糊、干涩",
"skin_condition": "dry,itching",
"skin_condition_text": "干燥、瘙痒",
"urine_condition": "yellow_urine",
"urine_condition_text": "尿黄",
"fatty_liver_degree": "mild",
"fatty_liver_degree_text": "轻度",
"past_history": "hypertension",
"past_history_text": "高血压",
"trauma_history": 0,
"trauma_history_text": "",
# 诊单表里的技术列:医生页面不应出现这些英文列名。
"status": 1,
"show_card": 1,
"revisit_slot_start_offset": 0,
"delete_time": None,
"assistant_id": 131,
"assign_read_at": 1787882294,
"shipped_non_er_assistant_cleared_at": 0,
"external_userid": "",
"is_view": 0,
"create_time": 1787882294,
"update_time": 1787882303,
"tongue_images": [
"https://media.example.invalid/11702/a.jpg",
"https://media.example.invalid/11702/b.jpg",
],
}
)
detail["diagnosis"] = diagnosis
return detail
def test_case_tab_hides_raw_columns_and_never_shows_text_mirror_fields(
application: QApplication,
immediate_async: None,
) -> None:
dialog = _open_dialog(application, ProductionShapeRepository())
dialog.tabs.setCurrentIndex(1)
application.processEvents()
text = _pane_text(dialog.records["病历资料"])
# 后端补的 *_text 只用来取值,不再作为独立英文字段列出来。
for mirror in (
"diagnosis type text",
"eye condition text",
"gender text",
"past history text",
):
assert mirror not in text
# 技术列不再泄漏英文列名。
for internal in (
"assign read at",
"shipped non er assistant cleared at",
"external userid",
"is view",
"assistant id",
"show card",
"revisit slot start offset",
"delete time",
):
assert internal not in text
# 有中文名的字段照常显示,取的是后端翻译过的值。
assert "复诊" in text
assert "模糊、干涩" in text
assert "1787882294" not in text
assert "https://media.example.invalid/11702/a.jpg" not in text
assert len(dialog.records["病历资料"].findChildren(QPushButton, "AiConsultTongueThumb")) == 2
dialog.close()
class RawCodeRepository(WorkspaceRepository):
"""只读接口偶尔缺少 `*_text`(历史数据 / 快照),此时必须自己翻译字典 code。"""
def get_diagnosis_detail(
self,
diagnosis_id: int,
*,
readonly: bool = False,
) -> dict[str, Any]:
detail = dict(super().get_diagnosis_detail(diagnosis_id, readonly=readonly))
diagnosis = dict(detail["diagnosis"])
diagnosis.update(
{
"gender": 1,
"diagnosis_type": "follow_up",
"appetite": "dry,bitter",
"weight_change": "lose_10_jin",
"fatty_liver_degree": "mild",
"allergy_history": 0,
"status": 1,
"show_card": 1,
"revisit_slot_start_offset": 0,
"delete_time": None,
"create_source": "admin",
"create_time": 1783838927,
"tongue_images": ["https://media.example.invalid/501/tongue-raw.jpg"],
}
)
detail["diagnosis"] = diagnosis
patient = dict(detail.get("patient") or {})
patient.update({"gender": 1, "marital_status": 1})
detail["patient"] = patient
return detail
def test_case_and_health_tabs_translate_codes_and_hide_internal_columns(
application: QApplication,
immediate_async: None,
) -> None:
dialog = _open_dialog(application, RawCodeRepository())
dialog.tabs.setCurrentIndex(1)
application.processEvents()
case_text = _pane_text(dialog.records["病历资料"])
# 字典 code、性别与是否类枚举、时间戳都按后台只读页的口径显示。
assert "干、苦" in case_text
assert "瘦10斤" in case_text
assert "轻度" in case_text
assert "复诊" in case_text
assert "后台创建" in case_text
assert "dry,bitter" not in case_text
assert "lose_10_jin" not in case_text
assert "follow_up" not in case_text
assert "1783838927" not in case_text
# 纯内部列不再泄漏给医生。
for internal in ("show card", "revisit slot start offset", "delete time"):
assert internal not in case_text
# 舌象附件渲染成缩略图,不再是一长串 URL 文本。
assert "https://media.example.invalid/501/tongue-raw.jpg" not in case_text
thumbnails = dialog.records["病历资料"].findChildren(QPushButton, "AiConsultTongueThumb")
assert len(thumbnails) == 1
dialog.tabs.setCurrentIndex(4)
application.processEvents()
health_text = _pane_text(dialog.records["健康档案"])
assert "性别\n" in health_text
assert "过敏史\n" in health_text
assert "162 cm" in health_text
assert dialog.records["健康档案"].findChildren(QPushButton, "AiConsultTongueThumb")
dialog.close()
def test_tongue_thumbnails_load_safe_http_sources_and_skip_unsafe_schemes(
application: QApplication,
immediate_async: None,
monkeypatch: pytest.MonkeyPatch,
@@ -533,23 +729,22 @@ def test_tongue_thumbnail_auto_get_requires_configured_https_origin(
RecordingRemoteImageButton,
)
untrusted = _open_dialog(application, WorkspaceRepository())
assert requested == []
untrusted.close()
trusted_repository = WorkspaceRepository()
trusted_repository.trusted_media_domains = ["media.example.invalid"]
assert not ai_consult_module._trusted_thumbnail_url(
trusted_repository,
"http://media.example.invalid/甲/tongue.jpg",
)
assert not ai_consult_module._trusted_thumbnail_url(
trusted_repository,
"https://sub.media.example.invalid/甲/tongue.jpg",
)
trusted = _open_dialog(application, trusted_repository)
dialog = _open_dialog(application, WorkspaceRepository())
# 舌象照片按字段直接渲染,PDF 报告与 file:// 附件不会发起任何图片请求。
assert requested == ["https://media.example.invalid/甲/tongue.jpg"]
trusted.close()
assert not ai_consult_module._previewable_attachment(
"tongue_images",
"file:///C:/private/tongue.jpg",
)
assert not ai_consult_module._previewable_attachment(
"report_files",
"https://media.example.invalid/甲/report.pdf",
)
assert ai_consult_module._previewable_attachment(
"report_files",
"https://media.example.invalid/甲/report.PNG",
)
dialog.close()
def test_three_prescription_cards_open_exact_details_and_reject_wrong_or_late_ids(