tests: updated tests for new authentication protocols and for new sample short info

This commit is contained in:
2026-06-10 14:27:37 +02:00
parent c3bc745210
commit 076c796b4d
6 changed files with 134 additions and 117 deletions
@@ -46,4 +46,30 @@ def test_aperture_accepts_float_string():
def test_processingpipeline_accepts_unknown_value_after_refactor():
params = DataCollectionParameters(processingpipeline="xia2")
assert params.processingpipeline == "xia2"
assert params.processingpipeline == "xia2"
def test_datacollectionparameters_accepts_legacy_aliases():
params = DataCollectionParameters(
totalrange=180,
cellparameters="10 20 30 90 90 120",
userresolution=1.4,
)
assert params.totalangle == 180
assert params.unitcell == "10 20 30 90 90 120"
assert params.processingresolution == 1.4
def test_datacollectionparameters_accepts_new_fields():
params = DataCollectionParameters(
totalangle=90,
unitcell="11,22,33,90,90,120",
processingresolution=1.2,
pdbmodel="model.pdb",
cloud=False,
)
assert params.totalangle == 90
assert params.unitcell == "11,22,33,90,90,120"
assert params.processingresolution == 1.2
assert params.pdbmodel == "model.pdb"
assert params.cloud is False
+40 -28
View File
@@ -1,5 +1,14 @@
import pytest
from aare.common.models import SampleShortInfo, DewarAddress, BeamMarkCoeffModel, MLOutputModel, MLBoxType, BeamlineStateEnum
from aare.common.models import (
SampleShortInfo,
DewarAddress,
BeamMarkCoeffModel,
MLOutputModel,
MLBoxType,
BeamlineStateEnum,
DataCollectionParameters,
)
def test_sample_short_info_methods():
info = SampleShortInfo(
@@ -8,34 +17,42 @@ def test_sample_short_info_methods():
dewar_name="dew1",
sample_name="sample1",
run_number=1,
aaredb_params=DataCollectionParameters(
totalangle=180,
processingresolution=1.5,
cloud=True,
),
user="group1",
pin=3,
location=DewarAddress(segment="A", pos=2),
)
# Test tell_address (line 380)
addr = info.tell_address()
assert addr.puck.segment == "A"
assert addr.puck.pos == 2
assert addr.pin == 3
# Test loc_str (lines 383-386)
assert info.loc_str() == "A2-3"
# Test loc_str_sort (lines 389-392)
assert info.loc_str_sort() == "A2-03"
assert info.aaredb_params is not None
assert info.aaredb_params.totalangle == 180
assert info.aaredb_params.processingresolution == 1.5
info_no_loc = info.model_copy(update={"location": None})
assert info_no_loc.loc_str() == "-"
assert info_no_loc.loc_str_sort() == ""
# Test from_dict (line 396)
data = {
"db_id": 2,
"puck_name": "puck2",
"dewar_name": "dew2",
"sample_name": "sample2",
"run_number": 2,
"aaredb_params": {
"totalrange": 120,
"userresolution": 1.8,
"cloud": "",
},
"user": "group2",
"pin": 4,
"location": {"segment": "B", "pos": 5}
@@ -43,47 +60,43 @@ def test_sample_short_info_methods():
info2 = SampleShortInfo.from_dict(data)
assert info2.db_id == 2
assert info2.location.segment == "B"
assert info2.aaredb_params is not None
assert info2.aaredb_params.totalangle == 120
assert info2.aaredb_params.processingresolution == 1.8
assert info2.aaredb_params.cloud is True
def test_beam_mark_coeff_model_apply():
# Test apply (line 414)
model = BeamMarkCoeffModel(
coeff_x=(1.0, 2.0, 5.0),
coeff_y=(3.0, 4.0, 6.0)
)
# zoom = 10
# x = 1.0 * 100 + 2.0 * 10 + 5.0 = 125.0
# y = 3.0 * 100 + 4.0 * 10 + 6.0 = 346.0
res = model.apply(10.0)
assert res.x == 125.0
assert res.y == 346.0
def test_ml_output_model_extra_methods():
model = MLOutputModel()
key = model.add_box(MLBoxType.CRYSTAL, (1, 2, 3, 4), 0.8)
# Test get_box_model (line 499)
box_model = model.get_box_model(key)
assert box_model.conf == 0.8
# Test get_box_tuple (lines 502-505)
assert model.get_box_tuple(key) == (1, 2, 3, 4)
assert model.get_box_tuple("NonExistent") is None
# Test get_box_tuple_with_conf (lines 508-512)
assert model.get_box_tuple_with_conf(key) == (1, 2, 3, 4, 0.8)
assert model.get_box_tuple_with_conf("NonExistent") is None
# Test get_tuples_for_class (lines 523-527)
tuples = model.get_tuples_for_class(MLBoxType.CRYSTAL)
assert len(tuples) == 1
assert tuples[0] == (1, 2, 3, 4)
# Test get_tuples_with_conf_for_class (lines 530-534)
tuples_conf = model.get_tuples_with_conf_for_class(MLBoxType.CRYSTAL)
assert len(tuples_conf) == 1
assert tuples_conf[0] == (1, 2, 3, 4, 0.8)
# Test get_class_str (lines 470, 475-481)
assert MLOutputModel.get_class_str(MLBoxType.LOOP_ALL) == "Loop_all"
assert MLOutputModel.get_class_str(MLBoxType.PIN) == "Pin"
assert MLOutputModel.get_class_str(MLBoxType.CRYSTAL) == "Crystal"
@@ -92,9 +105,8 @@ def test_ml_output_model_extra_methods():
assert MLOutputModel.get_class_str(MLBoxType.NEEDLE) == "Needle"
assert MLOutputModel.get_class_str(100) == "Unknown"
def test_beamline_state_enum_display_name():
# Test display_name (line 556)
assert BeamlineStateEnum.SampleExchange.display_name() == "Sample exchange"
assert BeamlineStateEnum.Moving.display_name() == "Moving"
# Using value for potentially unknown
assert BeamlineStateEnum.display_name(None) == "-"
assert BeamlineStateEnum.display_name(None) == "-"
+10 -11
View File
@@ -50,25 +50,24 @@ def test_authenticate_user(mock_cfg):
patch('os.getgrouplist') as mock_groups, \
patch('grp.getgrgid') as mock_grp, \
patch('aare.daq.auth.SECRET_KEY', 'test_secret'):
mock_pwd.return_value.pw_name = "testuser"
mock_pwd.return_value.pw_gid = 1000
mock_groups.return_value = [1000, 1001]
def get_group(gid):
m = MagicMock()
if gid == 1000: m.gr_name = "p12345"
else: m.gr_name = "unx-MXgroup"
if gid == 1000:
m.gr_name = "p12345"
else:
m.gr_name = "unx-MXgroup"
return m
mock_grp.side_effect = get_group
form_data = MagicMock()
form_data.username = "testuser"
token = authenticate_user(mock_cfg, form_data)
token = authenticate_user(mock_cfg, "testuser")
assert isinstance(token, str)
payload = jwt.decode(token, 'test_secret', algorithms=["HS256"])
assert payload["sub"] == "testuser"
assert "p12345" in payload["pgroups"]
+7 -3
View File
@@ -49,9 +49,13 @@ def test_omega_put(client):
def test_login_success(client):
with patch("aare.daq.auth.authenticate_user") as mock_auth:
mock_auth.return_value = "fake-access-token"
response = client.post("/token", data={"username": "user", "password": "pwd"})
with patch("aare.daq.auth.authenticate_from_proxy_header", return_value="user"), \
patch("aare.daq.auth.authenticate_user", return_value="fake-access-token"):
response = client.post(
"/token",
data={"username": "user", "password": "pwd"},
headers={"X-Remote-User": "user"},
)
assert response.status_code == 200
assert response.json() == {"access_token": "fake-access-token", "token_type": "bearer"}
+22 -18
View File
@@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch
from aare.daq.spreadsheetupdater import on_message, get_ws_headers, set_spreadsheet_in_redis
from aare.common.models import SampleShortInfoList
@pytest.fixture
def mock_config():
with patch('aare.daq.spreadsheetupdater.config') as mock:
@@ -14,30 +15,28 @@ def mock_config():
mock._BeamlineConfig__bl = mock._BeamlineConfig__bl
yield mock
def test_get_ws_headers_success():
with patch('os.getenv', return_value="secret"):
headers = get_ws_headers()
assert headers == ["X-Shared-Password: secret"]
def test_get_ws_headers_fail():
with patch('os.getenv', return_value=None):
with pytest.raises(ValueError):
get_ws_headers()
def test_set_spreadsheet_in_redis(mock_config):
data = {"test": "data"}
with patch('aare.daq.spreadsheetupdater.config') as mock_cfg_internal:
# Mocking BOTH possible name-mangled names for the client
mock_client = MagicMock()
mock_cfg_internal._BeamlineConfig__client = mock_client
mock_cfg_internal.client = mock_client # In case it's not mangled or mangled differently
# Also need to mock where it's actually used: config.__client.set
# If I can't guess the mangling, I'll just check what attributes mock_cfg_internal has
mock_cfg_internal.client = mock_client
set_spreadsheet_in_redis(data)
# Check all mock attributes for a 'set' call
found = False
for attr in dir(mock_cfg_internal):
val = getattr(mock_cfg_internal, attr)
@@ -46,6 +45,7 @@ def test_set_spreadsheet_in_redis(mock_config):
break
assert found or mock_client.set.called
def test_on_message_success(mock_config):
message = json.dumps({
"samples": [
@@ -67,6 +67,9 @@ def test_on_message_success(mock_config):
"position": 1,
"priority": 1,
"mount_count": 0,
"rotation_count": 0,
"raster_count": 0,
"screening_count": 0,
"data_collection_parameters": {}
}
]
@@ -89,24 +92,26 @@ def test_on_message_success(mock_config):
"position": 1,
"priority": 1,
"mount_count": 0,
"rotation_count": 0,
"raster_count": 0,
"screening_count": 0,
"data_collection_parameters": {}
}
]
}
]
})
on_message(None, message)
# Check if normal spreadsheet was written
normal_key = "X10SA:sample_spreadsheet"
calls = mock_config._BeamlineConfig__client.set.call_args_list
assert any(call.args[0] == normal_key for call in calls)
# Check if reference tools were written
ref_key = "X10SA:reference-tools"
assert any(call.args[0] == ref_key for call in calls)
def test_on_message_empty_ref(mock_config):
message = json.dumps({
"samples": [
@@ -123,14 +128,13 @@ def test_on_message_empty_ref(mock_config):
}
]
})
on_message(None, message)
# Check if reference tools were deleted
ref_key = "X10SA:reference-tools"
mock_config._BeamlineConfig__client.delete.assert_called_with(ref_key)
def test_on_message_invalid_json(mock_config):
# Should not raise exception, just print error
on_message(None, "invalid json")
mock_config._BeamlineConfig__client.set.assert_not_called()
mock_config._BeamlineConfig__client.set.assert_not_called()
+28 -56
View File
@@ -1,72 +1,44 @@
import json
import subprocess
import pytest
import requests
from aare.gui.auth import auth
# pytest-mock provides the 'mocker' fixture, which is a wrapper around the
# standard unittest.mock. It simplifies mocking by automatically handling
# cleanup (unpatching) after each test, and providing a more "pytest-native"
# feel compared to using @patch decorators or context managers.
def test_auth_success(mocker):
"""
Test successful authentication using pytest-mock's mocker fixture.
In standard pytest/unittest, you would typically use:
with mock.patch('requests.post') as mock_post:
...
Or a decorator:
@patch('requests.post')
def test_auth(mock_post):
...
pytest-mock allows you to use the 'mocker' fixture directly in the function arguments.
This avoids deeply nested context managers and makes it easier to mock multiple things.
"""
# We mock 'requests.post' to simulate a successful server response.
# mocker.patch returns a MagicMock object.
mock_post = mocker.patch("requests.post")
# Configure the mock response
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"access_token": "fake_token_abc.123.xyz"}
mock_post.return_value = mock_response
# Call the function under test
mock_run = mocker.patch("aare.gui.auth.subprocess.run")
mock_run.return_value = mocker.Mock(
returncode=0,
stdout=json.dumps({"access_token": "fake_token_abc.123.xyz"}),
stderr="",
)
token = auth("http://test-server")
# Verify the results
assert token == "fake_token_abc.123.xyz"
mock_post.assert_called_once()
# Check that it was called with the expected URL
args, kwargs = mock_post.call_args
assert args[0] == "http://test-server/token"
mock_run.assert_called_once()
args, kwargs = mock_run.call_args
assert "curl" in args[0]
assert "http://test-server/token" in args[0]
def test_auth_network_failure(mocker):
"""
Test authentication failure due to network error using mocker.
"""
# Mock requests.post to raise an exception
mock_post = mocker.patch("requests.post")
mock_post.side_effect = requests.RequestException("Connection refused")
mock_run = mocker.patch("aare.gui.auth.subprocess.run")
mock_run.side_effect = OSError("Connection refused")
with pytest.raises(RuntimeError) as excinfo:
auth("http://test-server")
assert "Cannot reach AareDAQ server" in str(excinfo.value)
def test_auth_no_url_returns_dummy_jwt(mocker):
"""
Test that when base_url is None, it returns a dummy JWT without network calls.
We can use mocker to verify that requests.post was NEVER called.
"""
mock_post = mocker.patch("requests.post")
mocker.patch("os.getlogin", return_value="testuser")
mock_run = mocker.patch("aare.gui.auth.subprocess.run")
mocker.patch("aare.gui.auth.get_user", return_value="testuser")
token = auth(None)
assert isinstance(token, str)
assert token.count('.') == 2 # Basic JWT structure check
mock_post.assert_not_called()
assert token.count('.') == 2
mock_run.assert_not_called()