Diff coverage vs main was 77%, under the 80% CI gate; the new camera help/error/badge code and the theme+layout main_window paths were the uncovered bulk. Locally: 82%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
487 lines
15 KiB
Python
487 lines
15 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from PySide6.QtCore import QSettings
|
|
from PySide6.QtWidgets import QDockWidget
|
|
|
|
from aare.gui.main_window import MainWindow
|
|
from aare.gui.styles import THEME_BLUEBIRD, THEME_SUNRISE, THEME_SUNSET
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_ui_state():
|
|
with patch("aare.gui.main_window.UIStateManager") as mock:
|
|
yield mock
|
|
|
|
|
|
def test_main_window_init(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get") as mock_get,
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok"}
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url="http://localhost:5210",
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr="localhost",
|
|
gonio_cam_addr="localhost",
|
|
gonio_cam_id=1,
|
|
)
|
|
win.show()
|
|
qtbot.addWidget(win)
|
|
|
|
assert win.windowTitle() == "AareGUI"
|
|
assert win.isVisible()
|
|
|
|
|
|
def test_main_window_mount_view(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win.mount_view()
|
|
|
|
|
|
def test_mark_user_interaction_reports_backend(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win._last_interaction_report_ts = -999.0
|
|
win._mark_user_interaction()
|
|
|
|
win.daq.report_gui_interaction.assert_called_once_with(15)
|
|
|
|
|
|
def test_automation_activity_refreshes_idle_timestamp(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
|
|
from aarecommon.math.diffraction_geometry import DiffractionGeometry
|
|
from aarecommon.math.sample_geometry import SampleGeometryModel
|
|
from aarecommon.models.models import (
|
|
BeamlineStateEnum,
|
|
BeamlineStatus,
|
|
CrystalSize,
|
|
DAQStatusModel,
|
|
SampleCameraSettings,
|
|
SessionsStateEnum,
|
|
SessionStatus,
|
|
)
|
|
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win.job_list_panel.is_running = MagicMock(return_value=True)
|
|
win._last_user_interaction_ts = 100.0
|
|
|
|
status = DAQStatusModel(
|
|
geom=SampleGeometryModel(
|
|
beam_location_pxl=Coordinate(x=1000, y=1000),
|
|
pixel_in_mm=0.001,
|
|
aerotech=Coordinate(),
|
|
aerotech_meas=Coordinate(),
|
|
smargon=SmargonCoordinate(sh_mm=Coordinate(), phi_deg=0, chi_deg=0),
|
|
omega_deg=0,
|
|
beam_size_mm=Coordinate(x=0.01, y=0.01),
|
|
),
|
|
diffraction=DiffractionGeometry(
|
|
energy_keV=12.4,
|
|
dtz_mm=100.0,
|
|
detector_size_pxl=(1553, 1630),
|
|
pixel_size_mm=0.150,
|
|
beam_center_pxl=(750.0, 750.0),
|
|
detector_description="PILATUS 4",
|
|
detector_serial_number="1",
|
|
poni_rot1_rad=0.0,
|
|
poni_rot2_rad=0.0,
|
|
),
|
|
bl=BeamlineStatus(
|
|
name="SIMULATED",
|
|
ring_current_mA=400.0,
|
|
front_light=50.0,
|
|
back_light=50.0,
|
|
cryojet_K=100.0,
|
|
shutter_open=False,
|
|
exp_shutter_open=False,
|
|
flux_ph_s=1e12,
|
|
sample_camera=SampleCameraSettings(gain=1.0, exposure=0.02),
|
|
transmission=1.0,
|
|
zoom=1.0,
|
|
commissioning_mode=False,
|
|
dtz_min=120.0,
|
|
dtz_max=1600.0,
|
|
),
|
|
state=BeamlineStateEnum.SampleAlignment,
|
|
busy=False,
|
|
session=SessionStatus(
|
|
session=SessionsStateEnum.Vacant, current_pgroup="p123", staff=True
|
|
),
|
|
crystal_size=CrystalSize(x=0, y=0, z=0),
|
|
)
|
|
|
|
with patch("aare.gui.main_window.time.time", return_value=250.0):
|
|
win.update_daq_status(status)
|
|
|
|
assert win._last_user_interaction_ts == 250.0
|
|
|
|
|
|
def test_idle_timeout_closes_when_inactive_and_not_running(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win._idle_close_timeout_s = 10
|
|
win._last_user_interaction_ts = 100.0
|
|
win.close = MagicMock()
|
|
|
|
with patch("aare.gui.main_window.time.time", return_value=111.0):
|
|
win._check_idle_timeout()
|
|
|
|
win.close.assert_called_once()
|
|
|
|
|
|
def test_idle_timeout_does_not_close_while_automation_active(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win._idle_close_timeout_s = 10
|
|
win._last_user_interaction_ts = 100.0
|
|
win.job_list_panel.is_running = MagicMock(return_value=True)
|
|
win.close = MagicMock()
|
|
|
|
with patch("aare.gui.main_window.time.time", return_value=111.0):
|
|
win._check_idle_timeout()
|
|
|
|
win.close.assert_not_called()
|
|
|
|
|
|
def test_cleanup_returns_from_portrait_mode(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win.enter_portrait_mode()
|
|
assert win.content_stack.currentWidget() is win.portrait_mode_page
|
|
|
|
win.cleanup()
|
|
|
|
assert win.content_stack.currentWidget() is win._standard_main_page
|
|
|
|
|
|
def test_cleanup_returns_from_compact_automation_view(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
fake_token = "header.payload.signature"
|
|
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token=fake_token,
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
|
|
win.enter_compact_automation_view()
|
|
assert win.content_stack.currentWidget() is win.compact_automation_page
|
|
|
|
win.cleanup()
|
|
|
|
assert win.content_stack.currentWidget() is win._standard_main_page
|
|
|
|
|
|
def _make_window(qtbot):
|
|
win = MainWindow(
|
|
base_url=None,
|
|
token="header.payload.signature",
|
|
default_image=None,
|
|
zmq_addr=None,
|
|
pred_zmq_addr=None,
|
|
beamline_cam_addr=None,
|
|
gonio_cam_addr=None,
|
|
gonio_cam_id=None,
|
|
)
|
|
qtbot.addWidget(win)
|
|
return win
|
|
|
|
|
|
def test_theme_settings_migrate_and_slots_switch(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
win = _make_window(qtbot)
|
|
|
|
settings = QSettings("PSI", "AareGUI")
|
|
saved = settings.value("appearance/theme")
|
|
try:
|
|
# Pre-rename tokens saved by older builds must map to the new ones.
|
|
settings.setValue("appearance/theme", "portrait")
|
|
win._restore_theme_settings()
|
|
assert win._theme_mode == THEME_SUNSET
|
|
|
|
settings.setValue("appearance/theme", "original")
|
|
win._restore_theme_settings()
|
|
assert win._theme_mode == THEME_SUNRISE
|
|
|
|
settings.setValue("appearance/theme", THEME_BLUEBIRD)
|
|
win._restore_theme_settings()
|
|
assert win._theme_mode == THEME_BLUEBIRD
|
|
finally:
|
|
if saved is None:
|
|
settings.remove("appearance/theme")
|
|
else:
|
|
settings.setValue("appearance/theme", saved)
|
|
|
|
win.use_bluebird_theme()
|
|
assert win._theme_mode == THEME_BLUEBIRD
|
|
win.use_portrait_theme() # exercises the sunset palette flip
|
|
assert win._theme_mode == THEME_SUNSET
|
|
win.use_legacy_theme()
|
|
assert win._theme_mode == THEME_SUNRISE
|
|
|
|
|
|
def test_restore_window_state_heals_all_hidden_docks(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
win = _make_window(qtbot)
|
|
|
|
for dock in win.findChildren(QDockWidget):
|
|
dock.hide()
|
|
assert all(d.isHidden() for d in win.findChildren(QDockWidget))
|
|
|
|
# state_manager is mocked, so restore_window is a no-op and the
|
|
# all-hidden layout survives to the heal check.
|
|
win._restore_window_state()
|
|
|
|
assert not win.tell_samples_dock.isHidden()
|
|
|
|
|
|
def test_close_restores_pre_watch_layout(qtbot, mock_ui_state):
|
|
with (
|
|
patch("requests.get"),
|
|
patch("aare.gui.main_window.DAQWorker"),
|
|
patch("aare.gui.main_window.PredictionSubscriber"),
|
|
patch("aare.gui.main_window.VideoThread"),
|
|
patch("aare.gui.main_window.JFJochDBusClient"),
|
|
patch("aare.gui.main_window.jwt.decode") as mock_jwt,
|
|
):
|
|
mock_jwt.return_value = {
|
|
"sub": "testuser",
|
|
"staff": True,
|
|
"pgroups": ["p123"],
|
|
"session": 15,
|
|
}
|
|
win = _make_window(qtbot)
|
|
|
|
pre_watch = win.saveState()
|
|
for dock in win.findChildren(QDockWidget):
|
|
dock.hide()
|
|
win._session_operations_enabled = False
|
|
win._pre_watch_dock_state = pre_watch
|
|
|
|
win.close()
|
|
|
|
# closeEvent put the pre-watch layout back before saving state, so
|
|
# the all-hidden fold was not persisted.
|
|
assert not win.tell_samples_dock.isHidden()
|