107 lines
4.9 KiB
Python
107 lines
4.9 KiB
Python
"""The explicit local launcher must validate the effective endpoint after preferences."""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
spec = importlib.util.spec_from_file_location(
|
|
"local_launcher", Path(__file__).parents[1] / "scripts/run_local_workstation.py"
|
|
)
|
|
launcher = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(launcher)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def loopback_dns(monkeypatch):
|
|
monkeypatch.setattr(launcher.socket, "getaddrinfo", lambda *_: [(None, None, None, None, ("127.0.0.1", 443))])
|
|
|
|
|
|
@pytest.mark.parametrize("url", [
|
|
"http://local.test", "https://user:password@local.test", "https://local.test?token=private",
|
|
"https://local.test#fragment", "https://local.test:invalid", "https://local.test:70000",
|
|
])
|
|
def test_local_url_rejects_unsecured_or_credential_url(url):
|
|
with pytest.raises(ValueError):
|
|
launcher.validate_local_url(url)
|
|
|
|
|
|
def test_local_url_rejects_external_dns(monkeypatch):
|
|
monkeypatch.setattr(launcher.socket, "getaddrinfo", lambda *_: [(None, None, None, None, ("192.0.2.1", 443))])
|
|
with pytest.raises(ValueError):
|
|
launcher.validate_local_url("https://local.test")
|
|
|
|
|
|
@pytest.mark.parametrize("effective", [
|
|
"http://local.test/adminapi", "https://local.test:8443/adminapi", "https://local.test/other/adminapi",
|
|
])
|
|
def test_preferences_cannot_change_validated_scheme_port_or_path(effective):
|
|
with pytest.raises(ValueError):
|
|
launcher.validate_effective_config(SimpleNamespace(api_base_url=effective, verify_ssl=True), "https://local.test")
|
|
|
|
|
|
def test_local_config_keeps_certificate_verification():
|
|
launcher.validate_effective_config(SimpleNamespace(api_base_url="https://local.test/adminapi", verify_ssl=True), "https://local.test")
|
|
with pytest.raises(ValueError):
|
|
launcher.validate_effective_config(SimpleNamespace(api_base_url="https://local.test/adminapi", verify_ssl=False), "https://local.test")
|
|
|
|
|
|
@pytest.mark.parametrize("debug_mode", [False, True])
|
|
def test_local_entry_repairs_stale_preferences_and_locks_later_updates(tmp_path, monkeypatch, debug_mode):
|
|
from doctor_workstation import config as config_module
|
|
|
|
monkeypatch.setattr(config_module, "DEBUG_MODE", debug_mode)
|
|
monkeypatch.setenv("DOCTOR_CONFIG_DIR", str(tmp_path))
|
|
monkeypatch.setenv("DOCTOR_API_BASE_URL", "https://production.example.test")
|
|
(tmp_path / "preferences.json").write_text(json.dumps({
|
|
"api_base_url": "https://production.example.test/adminapi",
|
|
"demo_mode": True, "verify_ssl": False, "request_timeout": 55,
|
|
}), encoding="utf-8")
|
|
local_type = launcher.local_config_type("https://local.test")
|
|
config = local_type.load()
|
|
launcher.validate_effective_config(config, "https://local.test")
|
|
assert config.api_base_url == "https://local.test/adminapi"
|
|
assert not config.debug_mode and not config.demo_mode and config.verify_ssl
|
|
assert config.request_timeout == 55
|
|
updated = config.with_updates(api_base_url="https://production.example.test", verify_ssl=False, demo_mode=True)
|
|
launcher.validate_effective_config(updated, "https://local.test")
|
|
assert isinstance(updated, local_type)
|
|
assert not updated.demo_mode
|
|
updated.save_preferences()
|
|
persisted = json.loads((tmp_path / "preferences.json").read_text(encoding="utf-8"))
|
|
assert persisted["api_base_url"] == config.api_base_url and persisted["verify_ssl"]
|
|
|
|
|
|
def test_local_login_cannot_restore_or_save_a_production_server(tmp_path, monkeypatch):
|
|
from PySide6.QtCore import QSettings
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from doctor_workstation.ui.login import LoginWindow
|
|
|
|
monkeypatch.setenv("DOCTOR_CONFIG_DIR", str(tmp_path))
|
|
config = launcher.local_config_type("https://local.test").load()
|
|
application = QApplication.instance() or QApplication([])
|
|
settings = QSettings(str(tmp_path / "stale-login.ini"), QSettings.Format.IniFormat)
|
|
settings.setValue("server/base_url", "https://production.example.test")
|
|
settings.setValue("server/verify_ssl", False)
|
|
repository = object()
|
|
window = LoginWindow(repository, config=config, settings=settings)
|
|
try:
|
|
assert window.server_url_edit.text() == config.api_base_url
|
|
assert window._credential_scope() == config.api_base_url
|
|
assert not window.allow_self_signed_check.isChecked()
|
|
assert not window.demo_check.isChecked()
|
|
window.server_url_edit.setText("https://production.example.test")
|
|
window.allow_self_signed_check.setChecked(True)
|
|
assert window._apply_server_settings(announce=False)
|
|
assert window.server_url_edit.text() == config.api_base_url
|
|
assert window.active_repository is repository
|
|
launcher.validate_effective_config(window.config, "https://local.test")
|
|
finally:
|
|
window.close()
|
|
window.deleteLater()
|
|
application.processEvents()
|