bec/bec_lib/tests/test_config_helper.py
Mathias Guijarro 8bbfd10ca7 refactor(messages)!: messages refactoring, new serialization module
To have a better separation of concern between messages and how they are conveyed in connectors.
BECMessage can be simple dataclasses, leaving the serialization to the connector which transport those.
The serialization module itself can be isolated, and in the case of msgpack it can be extended to
understand how to encode/decode BECMessage, to simplify writing code like with BundleMessage or to
be able to automatically encode numpy or BECStatus objects.
Finally, client objects (producers and consumers) can receive BECMessage objects instead of having
to dump or load themselves.
2024-02-07 12:43:52 +01:00

204 lines
7.3 KiB
Python

import os
import shutil
from unittest import mock
import msgpack
import pytest
import yaml
import bec_lib
from bec_lib import messages
from bec_lib.bec_errors import DeviceConfigError
from bec_lib.config_helper import ConfigHelper
dir_path = os.path.dirname(bec_lib.__file__)
def test_load_demo_config():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
with mock.patch.object(config_helper, "update_session_with_file") as mock_update:
config_helper.load_demo_config()
dirpath = os.path.dirname(bec_lib.__file__)
fpath = os.path.join(dirpath, "configs/demo_config.yaml")
mock_update.assert_called_once_with(fpath)
def test_config_helper_update_session_with_file():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
with mock.patch.object(config_helper, "send_config_request") as mock_send_config_request:
with mock.patch.object(
config_helper, "_load_config_from_file"
) as mock_load_config_from_file:
mock_load_config_from_file.return_value = {"test": "test"}
config_helper.update_session_with_file("test.yaml")
mock_send_config_request.assert_called_once_with(action="set", config={"test": "test"})
@pytest.mark.parametrize("config_file", ["test.yaml", "test.yml"])
def test_config_helper_load_config_from_file(config_file, tmp_path):
orig_cfg_file = f"{dir_path}/tests/test_config.yaml"
test_cfg_file = tmp_path / config_file
shutil.copyfile(orig_cfg_file, test_cfg_file)
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
config = config_helper._load_config_from_file(test_cfg_file)
def test_config_helper_save_current_session():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
connector.producer().get.return_value = msgpack.dumps(
[
{
"id": "648c817f67d3c7cd6a354e8e",
"createdAt": "2023-06-16T15:36:31.215Z",
"createdBy": "unknown user",
"name": "pinz",
"sessionId": "648c817d67d3c7cd6a354df2",
"enabled": True,
"readOnly": False,
"deviceClass": "SimPositioner",
"deviceTags": ["user motors"],
"deviceConfig": {
"delay": 1,
"labels": "pinz",
"limits": [-50, 50],
"name": "pinz",
"speed": 100,
"tolerance": 0.01,
"update_frequency": 400,
},
"readoutPriority": "baseline",
"onFailure": "retry",
},
{
"id": "648c817f67d3c7cd6a354ec5",
"createdAt": "2023-06-16T15:36:31.764Z",
"createdBy": "unknown user",
"name": "transd",
"sessionId": "648c817d67d3c7cd6a354df2",
"enabled": True,
"readOnly": False,
"deviceClass": "SynAxisMonitor",
"deviceTags": ["beamline"],
"deviceConfig": {"labels": "transd", "name": "transd", "tolerance": 0.5},
"readoutPriority": "monitored",
"onFailure": "retry",
},
]
)
with mock.patch("builtins.open", mock.mock_open()) as mock_open:
config_helper.save_current_session("test.yaml")
out_data = {
"pinz": {
"deviceClass": "SimPositioner",
"deviceTags": ["user motors"],
"enabled": True,
"readOnly": False,
"deviceConfig": {
"delay": 1,
"labels": "pinz",
"limits": [-50, 50],
"name": "pinz",
"speed": 100,
"tolerance": 0.01,
"update_frequency": 400,
},
"readoutPriority": "baseline",
"onFailure": "retry",
},
"transd": {
"deviceClass": "SynAxisMonitor",
"deviceTags": ["beamline"],
"enabled": True,
"readOnly": False,
"deviceConfig": {"labels": "transd", "name": "transd", "tolerance": 0.5},
"readoutPriority": "monitored",
"onFailure": "retry",
},
}
mock_open().write.assert_called_once_with(yaml.dump(out_data))
@pytest.fixture
def config_helper():
connector = mock.MagicMock()
config_helper_inst = ConfigHelper(connector)
with mock.patch.object(config_helper_inst, "wait_for_config_reply"):
with mock.patch.object(config_helper_inst, "wait_for_service_response"):
yield config_helper_inst
def test_send_config_request_raises_with_empty_config(config_helper):
with pytest.raises(DeviceConfigError):
config_helper.send_config_request(action="update")
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
def test_send_config_request(config_helper):
config_helper.send_config_request(action="update", config={"test": "test"})
config_helper.wait_for_config_reply.return_value = messages.RequestResponseMessage(
accepted=True, message="test"
)
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
config_helper.wait_for_service_response.assert_called_once_with(mock.ANY)
def test_send_config_request_raises_for_rejected_update(config_helper):
config_helper.wait_for_config_reply.return_value = messages.RequestResponseMessage(
accepted=False, message="test"
)
with pytest.raises(DeviceConfigError):
config_helper.send_config_request(action="update", config={"test": "test"})
config_helper.wait_for_config_reply.assert_called_once_with(mock.ANY)
def test_wait_for_config_reply():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
connector.producer().get.return_value = messages.RequestResponseMessage(
accepted=True, message="test"
)
res = config_helper.wait_for_config_reply("test")
assert res == messages.RequestResponseMessage(accepted=True, message="test")
def test_wait_for_config_raises_timeout():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
connector.producer().get.return_value = None
with pytest.raises(DeviceConfigError):
config_helper.wait_for_config_reply("test", timeout=0.3)
def test_wait_for_service_response():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
connector.producer().lrange.side_effect = [
[],
[
messages.ServiceResponseMessage(
response={"service": "DeviceServer"}, metadata={"RID": "test"}
),
messages.ServiceResponseMessage(
response={"service": "ScanServer"}, metadata={"RID": "test"}
),
],
]
config_helper.wait_for_service_response("test", timeout=0.3)
def test_wait_for_service_response_raises_timeout():
connector = mock.MagicMock()
config_helper = ConfigHelper(connector)
connector.producer().lrange.return_value = []
with pytest.raises(DeviceConfigError):
config_helper.wait_for_service_response("test", timeout=0.3)