diff --git a/tests/unit/common/test_data_collection_parameters.py b/tests/unit/common/test_data_collection_parameters.py index b5ea0a1f..41e5f887 100644 --- a/tests/unit/common/test_data_collection_parameters.py +++ b/tests/unit/common/test_data_collection_parameters.py @@ -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" \ No newline at end of file + 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 \ No newline at end of file diff --git a/tests/unit/common/test_models_extra.py b/tests/unit/common/test_models_extra.py index 2c7c5032..7c9557b8 100644 --- a/tests/unit/common/test_models_extra.py +++ b/tests/unit/common/test_models_extra.py @@ -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) == "-" \ No newline at end of file diff --git a/tests/unit/daq/test_auth.py b/tests/unit/daq/test_auth.py index ac895b69..bbfc2f2b 100644 --- a/tests/unit/daq/test_auth.py +++ b/tests/unit/daq/test_auth.py @@ -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"] diff --git a/tests/unit/daq/test_server.py b/tests/unit/daq/test_server.py index e41b9c53..54862bc4 100644 --- a/tests/unit/daq/test_server.py +++ b/tests/unit/daq/test_server.py @@ -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"} diff --git a/tests/unit/daq/test_spreadsheetupdater.py b/tests/unit/daq/test_spreadsheetupdater.py index c747db4e..c96e9529 100644 --- a/tests/unit/daq/test_spreadsheetupdater.py +++ b/tests/unit/daq/test_spreadsheetupdater.py @@ -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() \ No newline at end of file diff --git a/tests/unit/gui/test_auth_mock.py b/tests/unit/gui/test_auth_mock.py index 29c400ad..91e908b2 100644 --- a/tests/unit/gui/test_auth_mock.py +++ b/tests/unit/gui/test_auth_mock.py @@ -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() \ No newline at end of file