tests: updated tests for new authentication protocols and for new sample short info
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user