Files
AareDAQ/tests/unit/daq/test_aaredb.py
T
perl_d 7b79dd8ec5
CI / lint (push) Successful in 30s
CI / test (3.11) (push) Successful in 53s
CI / test (3.13) (push) Successful in 51s
CI / test (3.12) (push) Successful in 56s
style: remove pointless name-mangled attrs
the fact that removing them didn't break anything shows that they
were completely unneccessary - there is no inheritance tree
using the same names
2026-07-31 09:45:16 +02:00

373 lines
13 KiB
Python

from unittest.mock import MagicMock, patch
import numpy as np
import pytest
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
from aarecommon.math.sample_geometry import SampleGeometryModel
from aarecommon.models.models import DewarAddress, PuckLoadedInfo, SampleShortInfo
from aarecommon.models.raster_grid import CenterOfMassModel, RasterGridRequest
from aarecommon.models.rotation_scan import RotationScanRequest
from jfjoch_client.models import ScanResult
from aare.daq.aaredb import AareWrapper
@pytest.fixture
def mock_bl():
bl = MagicMock()
bl.value = "X10SA"
bl.name = "X10SA"
return bl
@pytest.fixture
def sample_info():
return SampleShortInfo(
db_id=123,
puck_name="P1",
dewar_name="D1",
sample_name="S1",
run_number=1,
user="testuser",
pin=5,
location=DewarAddress(segment="A", pos=1),
)
@pytest.fixture
def daq_status(mock_bl):
status = MagicMock()
status.bl = mock_bl
status.diffraction.detector_description = "EIGER2 16M"
status.diffraction.detector_serial_number = "1234"
status.diffraction.dtz_mm = 200.0
status.diffraction.max_resolution_angstrom = 1.5
status.diffraction.beam_center_pxl = [1000.0, 1000.0]
status.diffraction.pixel_size_mm = 0.075
status.diffraction.energy_keV = 12.658
status.diffraction.wavelength_angstrom = 0.979
status.bl.ring_current_mA = 400.0
status.bl.transmission = 1.0
status.bl.flux_ph_s = 1e12
status.bl.cryojet_K = 100.0
status.geom.beam_size_mm = Coordinate(x=0.01, y=0.01)
status.geom.smargon = SmargonCoordinate(sh_mm=Coordinate(x=0, y=0, z=0), phi_deg=0, chi_deg=0)
return status
@pytest.fixture
def geom_model():
return SampleGeometryModel(
beam_location_pxl=Coordinate(x=1000, y=1000),
pixel_in_mm=0.001,
aerotech=Coordinate(x=0, y=0),
aerotech_meas=Coordinate(x=0, y=0),
smargon=SmargonCoordinate(sh_mm=Coordinate(x=0, y=0, z=0), phi_deg=0, chi_deg=0),
omega_deg=0,
beam_size_mm=Coordinate(x=0.01, y=0.01),
)
@patch("aareDB.ApiClient")
@patch("aareDB.TellsRunnerApi")
@patch("aareDB.SamplesRunnerApi")
@patch("aareDB.ProcessingsRunnerApi")
@patch("aareDB.GridscanRunnerApi")
def test_aare_wrapper_init(mock_grid, mock_proc, mock_sample, mock_tell, mock_api, mock_bl):
wrapper = AareWrapper(bl=mock_bl)
mock_api.assert_called_once()
assert wrapper._bl == mock_bl
@patch("aareDB.ApiClient")
@patch("aareDB.TellsRunnerApi")
@patch("aareDB.SamplesRunnerApi")
@patch("aareDB.ProcessingsRunnerApi")
@patch("aareDB.GridscanRunnerApi")
def test_set_pucks_beamline(mock_grid, mock_proc, mock_sample, mock_tell, mock_api, mock_bl):
wrapper = AareWrapper(bl=mock_bl)
pucks = [PuckLoadedInfo(puck_name="P1", location=DewarAddress(segment="A", pos=1))]
wrapper.set_pucks_beamline(pucks)
mock_tell.return_value.set_tell_positions.assert_called_once()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_manual_sample(mock_sample, mock_api, mock_bl, sample_info):
wrapper = AareWrapper(bl=mock_bl)
mock_sample.return_value.insert_sample.return_value.id = 456
wrapper.create_manual_sample(sample_info)
assert sample_info.db_id == 456
mock_sample.return_value.insert_sample.assert_called_once()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_send_sample_event(mock_sample, mock_api, mock_bl, sample_info):
from aareDB import SampleEventType
wrapper = AareWrapper(bl=mock_bl)
wrapper.send_sample_event(sample_info.db_id, SampleEventType.MOUNTED, "Test comment")
mock_sample.return_value.create_sample_event.assert_called_once()
# Test None sample
mock_sample.return_value.create_sample_event.reset_mock()
wrapper.send_sample_event(None, SampleEventType.MOUNTED)
mock_sample.return_value.create_sample_event.assert_not_called()
# Test invalid ID
sample_info.db_id = -1
wrapper.send_sample_event(sample_info.db_id, SampleEventType.MOUNTED)
mock_sample.return_value.create_sample_event.assert_not_called()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_manual_sample_error(mock_sample, mock_api, mock_bl, sample_info, caplog):
import logging
caplog.set_level(logging.ERROR)
wrapper = AareWrapper(bl=mock_bl)
mock_sample.return_value.insert_sample.side_effect = Exception("DB Error")
wrapper.create_manual_sample(sample_info)
assert "Error inserting sample: DB Error" in caplog.text
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_send_sample_event_error(mock_sample, mock_api, mock_bl, sample_info, caplog):
import logging
from aareDB import SampleEventType
caplog.set_level(logging.ERROR)
wrapper = AareWrapper(bl=mock_bl)
mock_sample.return_value.create_sample_event.side_effect = Exception("Event Error")
wrapper.send_sample_event(sample_info.db_id, SampleEventType.MOUNTED)
assert "Error sending sample event" in caplog.text
assert "MOUNTED" in caplog.text
assert "Event Error" in caplog.text
@patch("aareDB.ApiClient")
@patch("requests.post")
def test_upload_image(mock_post, mock_api, mock_bl):
wrapper = AareWrapper(bl=mock_bl)
mock_post.return_value.status_code = 200
img = np.zeros((100, 100, 3), dtype=np.uint8)
wrapper.upload_image(123, "test_img", img, "test message")
mock_post.assert_called_once()
assert "test_img.jpg" in str(mock_post.call_args)
@patch("aareDB.ApiClient")
@patch("requests.post")
def test_upload_jpg(mock_post, mock_api, mock_bl):
wrapper = AareWrapper(bl=mock_bl)
mock_post.return_value.status_code = 200
wrapper.upload_jpg(123, "test_img", b"fake jpeg bytes")
mock_post.assert_called_once()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_rotation_run(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
# Standard rotation
req = RotationScanRequest(
exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", dtz=200.0
)
wrapper.create_rotation_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
# None sample - should return early
mock_sample.return_value.create_experiment_parameters_for_sample.reset_mock()
wrapper.create_rotation_run(None, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_not_called()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_rotation_run_screening(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
req = RotationScanRequest(
exp_time_s=0.1,
incr_omega_deg=0.1,
steps=100,
file_prefix="test_prefix",
screening=True,
wedge_omega_deg=5.0,
dtz=200.0,
)
wrapper.create_rotation_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_rotation_run_error(mock_sample, mock_api, mock_bl, sample_info, daq_status, caplog):
wrapper = AareWrapper(bl=mock_bl)
req = RotationScanRequest(
exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", dtz=200.0
)
mock_sample.return_value.create_experiment_parameters_for_sample.side_effect = Exception(
"Rotation error"
)
wrapper.create_rotation_run(sample_info, req, daq_status)
assert "Rotation error" in caplog.text
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_gridscan_run(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
req = RasterGridRequest(
exp_time_s=0.1,
n_x=10,
n_y=10,
grid_size_mm=Coordinate(x=0.01, y=0.01),
smargon_top_left=None,
file_prefix="test_grid_prefix",
dtz=200.0,
)
wrapper.create_gridscan_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
# None sample
mock_sample.return_value.create_experiment_parameters_for_sample.reset_mock()
wrapper.create_gridscan_run(None, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_not_called()
@patch("aareDB.ApiClient")
@patch("aareDB.SamplesRunnerApi")
def test_create_gridscan_run_error(mock_sample, mock_api, mock_bl, sample_info, daq_status, caplog):
wrapper = AareWrapper(bl=mock_bl)
req = RasterGridRequest(
exp_time_s=0.1,
n_x=10,
n_y=10,
grid_size_mm=Coordinate(x=0.01, y=0.01),
smargon_top_left=None,
file_prefix="test_grid_prefix",
dtz=200.0,
)
mock_sample.return_value.create_experiment_parameters_for_sample.side_effect = Exception(
"Grid error"
)
wrapper.create_gridscan_run(sample_info, req, daq_status)
assert "Grid error" in caplog.text
@patch("aareDB.ApiClient")
@patch("requests.post")
def test_ingest_gridscan(mock_post, mock_api, mock_bl, sample_info, geom_model):
wrapper = AareWrapper(bl=mock_bl)
mock_post.return_value.status_code = 200
raster_result = MagicMock(spec=ScanResult)
raster_request = RasterGridRequest(
exp_time_s=0.1,
n_x=10,
n_y=10,
grid_size_mm=Coordinate(x=0.01, y=0.01),
smargon_top_left=None,
)
com = CenterOfMassModel(n_x=5.0, n_y=5.0)
wrapper.ingest_gridscan(
sample_info, raster_result, raster_request, geom_model, com, (500.0, 500.0)
)
mock_post.assert_called_once()
# None sample
mock_post.reset_mock()
wrapper.ingest_gridscan(None, raster_result, raster_request, geom_model, com, (500.0, 500.0))
mock_post.assert_not_called()
@patch("aareDB.ApiClient")
@patch("requests.post")
def test_ingest_scan(mock_post, mock_api, mock_bl, sample_info, geom_model):
wrapper = AareWrapper(bl=mock_bl)
mock_post.return_value.status_code = 200
result = MagicMock(spec=ScanResult)
wrapper.ingest_scan(sample_info, result, geom_model, (500.0, 500.0))
mock_post.assert_called_once()
# None sample
mock_post.reset_mock()
wrapper.ingest_scan(None, result, geom_model, (500.0, 500.0))
mock_post.assert_not_called()
@patch("aareDB.ApiClient")
def test_format_gridscan_payload_no_com(mock_api, mock_bl, sample_info, geom_model):
wrapper = AareWrapper(bl=mock_bl)
raster_result = MagicMock(spec=ScanResult)
raster_request = RasterGridRequest(
exp_time_s=0.1,
n_x=10,
n_y=10,
grid_size_mm=Coordinate(x=0.01, y=0.01),
smargon_top_left=None,
)
payload = wrapper.format_gridscan_payload(
sample_info, raster_result, raster_request, geom_model, None, (500.0, 500.0)
)
assert payload.center_pxl is None
assert payload.sample_id == sample_info.db_id
@patch("aareDB.ApiClient")
def test_format_gridscan_payload_with_top_left(mock_api, mock_bl, sample_info, geom_model):
wrapper = AareWrapper(bl=mock_bl)
raster_result = MagicMock(spec=ScanResult)
top_left = SmargonCoordinate(sh_mm=Coordinate(x=0.1, y=0.1, z=0.1), phi_deg=0, chi_deg=0)
raster_request = RasterGridRequest(
exp_time_s=0.1,
n_x=10,
n_y=10,
grid_size_mm=Coordinate(x=0.01, y=0.01),
smargon_top_left=top_left,
)
payload = wrapper.format_gridscan_payload(
sample_info, raster_result, raster_request, geom_model, None, (500.0, 500.0)
)
assert payload.sample_id == sample_info.db_id
@patch("aareDB.ApiClient")
def test_ingest_gridscan_payload_none(mock_api, mock_bl, sample_info, geom_model):
wrapper = AareWrapper(bl=mock_bl)
# We want format_gridscan_payload to return None
# Looking at aaredb.py, it returns None if an exception occurs and it's caught
# But wait, it raises 'e' after logging. Oh, I see.
# Lines 351-352 in aaredb.py: if payload is None: return
# This happens if format_gridscan_payload returns None.
with patch.object(AareWrapper, "format_gridscan_payload", return_value=None):
wrapper.ingest_gridscan(sample_info, None, None, geom_model, None, (0, 0))
with patch.object(AareWrapper, "format_scan_payload", return_value=None):
wrapper.ingest_scan(sample_info, None, geom_model, (0, 0))
@patch("aareDB.ApiClient")
def test_format_gridscan_payload_error(mock_api, mock_bl, sample_info, caplog):
wrapper = AareWrapper(bl=mock_bl)
# Passing None for geom_model should trigger an error in smargon_to_picture
with pytest.raises(Exception):
wrapper.format_gridscan_payload(sample_info, None, None, None, None, (0, 0))
assert "NoneType" in caplog.text
@patch("aareDB.ApiClient")
def test_format_scan_payload_error(mock_api, mock_bl, sample_info, caplog):
wrapper = AareWrapper(bl=mock_bl)
with pytest.raises(Exception):
# Passing None for geom should trigger error when accessing geom.beam_size_mm
wrapper.format_scan_payload(sample_info, None, None, (0, 0))
assert "NoneType" in caplog.text