This commit is contained in:
Your Name
2026-08-11 17:39:41 +08:00
parent cfe4c82c90
commit 25467b9d91
350 changed files with 201115 additions and 132208 deletions
+180
View File
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import QApplication
from doctor_workstation import app as app_module
from doctor_workstation.app import ApplicationController
from doctor_workstation.config import AppConfig
from doctor_workstation.core.errors import AuthenticationExpiredError
from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.ui import login as login_module
@@ -16,6 +17,7 @@ from doctor_workstation.ui.login import LoginWindow
from doctor_workstation.ui.pages.consultations import _video_payload
from doctor_workstation.ui.shell import NAVIGATION
from doctor_workstation.ui.widgets import (
friendly_error,
gender_text,
invoke,
set_authentication_expired_handler,
@@ -47,6 +49,7 @@ def test_gender_text_maps_legacy_codes_and_preserves_labels() -> None:
def test_navigation_requires_each_pages_actual_list_capability() -> None:
assert {item.key: item.permissions for item in NAVIGATION} == {
"reception": ("doctor.appointment/lists",),
"appointments": ("doctor.appointment/lists",),
"prescription_library": ("tcm.prescriptionLibrary/lists",),
"prescriptions": ("tcm.prescription/lists",),
"patients": ("firstvisit.myPatient/lists",),
@@ -227,3 +230,180 @@ def test_real_demo_login_reaches_success_without_widget_adapter(
assert not window._loading
window.close()
application.processEvents()
def test_remembered_account_survives_a_new_login_window(tmp_path: Any) -> None:
application = QApplication.instance() or QApplication([])
settings_path = tmp_path / "remember-account.ini"
config = SimpleNamespace(
api_base_url="",
request_timeout=30,
verify_ssl=True,
demo_mode=False,
remembered_account="",
)
first = LoginWindow(
object(),
config=config,
settings=QSettings(str(settings_path), QSettings.Format.IniFormat),
)
first._on_login_success({}, "admin", True)
first.close()
application.processEvents()
restored = LoginWindow(
object(),
config=config,
settings=QSettings(str(settings_path), QSettings.Format.IniFormat),
)
assert restored.account_edit.text() == "admin"
assert restored.remember_check.isChecked()
restored.close()
application.processEvents()
def test_server_settings_panel_keeps_controls_separated_at_minimum_window(
tmp_path: Any,
) -> None:
application = QApplication.instance() or QApplication([])
settings = QSettings(str(tmp_path / "server-panel.ini"), QSettings.Format.IniFormat)
config = SimpleNamespace(
api_base_url="",
request_timeout=30,
demo_mode=False,
remembered_account="",
)
window = LoginWindow(object(), config=config, settings=settings)
window.resize(860, 590)
window.show()
window.server_toggle.setChecked(True)
window._toggle_server_panel(True)
application.processEvents()
assert window.server_panel.isVisible()
assert window.server_panel.height() >= window.server_panel.minimumSizeHint().height()
assert window.server_url_label.geometry().bottom() < window.server_url_edit.geometry().top()
assert window.server_url_edit.geometry().bottom() < window.timeout_label.geometry().top()
assert window.timeout_spin.geometry().right() < window.save_server_button.geometry().left()
assert window.timeout_label.geometry().bottom() < window.allow_self_signed_check.geometry().top()
assert window.allow_self_signed_check.geometry().bottom() < window.server_hint.geometry().top()
window.allow_self_signed_check.setChecked(True)
application.processEvents()
assert window.ssl_warning.isVisible()
assert window.allow_self_signed_check.geometry().bottom() < window.ssl_warning.geometry().top()
assert window.ssl_warning.geometry().bottom() < window.server_hint.geometry().top()
window.close()
application.processEvents()
def test_server_settings_can_persist_self_signed_debug_mode(tmp_path: Any) -> None:
application = QApplication.instance() or QApplication([])
settings = QSettings(str(tmp_path / "self-signed.ini"), QSettings.Format.IniFormat)
config = SimpleNamespace(
api_base_url="https://internal.example.test",
request_timeout=30,
verify_ssl=True,
demo_mode=False,
remembered_account="",
)
window = LoginWindow(object(), config=config, settings=settings)
emitted: list[dict[str, Any]] = []
window.server_settings_changed.connect(emitted.append)
window.allow_self_signed_check.setChecked(True)
window._save_server_settings()
assert emitted[-1]["verify_ssl"] is False
assert settings.value("server/verify_ssl", type=bool) is False
assert "证书校验已关闭" in window.error_banner.label.text()
window.close()
application.processEvents()
def test_login_applies_self_signed_setting_before_authentication(
monkeypatch: Any,
tmp_path: Any,
) -> None:
application = QApplication.instance() or QApplication([])
settings = QSettings(str(tmp_path / "self-signed-login.ini"), QSettings.Format.IniFormat)
config = AppConfig(
api_base_url="https://internal.example.test/adminapi",
demo_mode=False,
verify_ssl=True,
)
calls: list[str] = []
class Repository:
def __init__(self, name: str) -> None:
self.name = name
def login(self, **_payload: Any) -> object:
calls.append(self.name)
return object()
def get_current_user(self) -> object:
return object()
old_repository = Repository("old")
rebuilt_repository = Repository("rebuilt-without-verification")
window = LoginWindow(old_repository, config=config, settings=settings)
def rebuild_on_config_change(updated: AppConfig) -> None:
assert updated.verify_ssl is False
window.repository = rebuilt_repository
window.active_repository = rebuilt_repository
def run_immediately(function: Any, **callbacks: Any) -> object:
try:
callbacks["on_success"](function())
except Exception as error: # pragma: no cover - assertion output is more useful
callbacks["on_error"](error)
finally:
callbacks["on_finished"]()
return object()
monkeypatch.setattr(login_module, "run_async", run_immediately)
window.config_changed.connect(rebuild_on_config_change)
window.account_edit.setText("admin")
window.password_edit.setText("secret")
window.allow_self_signed_check.setChecked(True)
window.submit()
assert calls == ["rebuilt-without-verification"]
assert settings.value("server/verify_ssl", type=bool) is False
window.close()
application.processEvents()
def test_certificate_error_explains_self_signed_server_setting() -> None:
message = friendly_error(
RuntimeError("[SSL: CERTIFICATE_VERIFY_FAILED] self-signed certificate")
)
assert "信任自签名证书" in message
assert "服务器设置" in message
def test_certificate_error_opens_server_settings(tmp_path: Any) -> None:
application = QApplication.instance() or QApplication([])
settings = QSettings(str(tmp_path / "certificate-error.ini"), QSettings.Format.IniFormat)
config = SimpleNamespace(
api_base_url="https://internal.example.test",
request_timeout=30,
verify_ssl=True,
demo_mode=False,
remembered_account="",
)
window = LoginWindow(object(), config=config, settings=settings)
window._on_login_error(
RuntimeError("[SSL: CERTIFICATE_VERIFY_FAILED] self-signed certificate")
)
assert window.server_toggle.isChecked()
assert not window.server_panel.isHidden()
assert "信任自签名证书" in window.error_banner.label.text()
window.close()
application.processEvents()