From 96ffe7029722c029717b15a9d004fae2a0379f2c Mon Sep 17 00:00:00 2001 From: Zoheb Shaikh Date: Thu, 5 Sep 2024 21:45:02 +0100 Subject: [PATCH] added system test and unit tests --- src/blueapi/client/client.py | 5 +- tests/system_tests/test_blueapi_system.py | 70 +++++++++++++++++++---- tests/unit_tests/client/test_client.py | 17 +++++- tests/unit_tests/service/test_rest_api.py | 33 ++++++++++- 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/src/blueapi/client/client.py b/src/blueapi/client/client.py index c736d27ac..40a2bae2f 100644 --- a/src/blueapi/client/client.py +++ b/src/blueapi/client/client.py @@ -143,9 +143,8 @@ def get_task(self, task_id: str) -> TrackableTask[Task]: Returns: TrackableTask[Task]: Task details """ - response = self._rest.get_task(task_id) - assert isinstance(response, TrackableTask) - return response + assert task_id != "", "Task ID cannot be empty" + return self._rest.get_task(task_id) def get_all_task(self) -> TasksListResponse: """ diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index 5b18db733..68bfbbaad 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -1,3 +1,4 @@ +import time from pathlib import Path import pytest @@ -9,7 +10,8 @@ BlueapiClient, BlueskyRemoteControlError, ) -from blueapi.config import ApplicationConfig +from blueapi.client.event_bus import AnyEvent +from blueapi.config import ApplicationConfig, StompConfig from blueapi.service.model import ( DeviceResponse, EnvironmentResponse, @@ -18,7 +20,7 @@ TasksListResponse, WorkerTask, ) -from blueapi.worker.event import TaskStatusEnum, WorkerState +from blueapi.worker.event import TaskStatus, TaskStatusEnum, WorkerEvent, WorkerState from blueapi.worker.task import Task from blueapi.worker.task_worker import TrackableTask @@ -33,6 +35,11 @@ def client() -> BlueapiClient: return BlueapiClient.from_config(config=ApplicationConfig()) +@pytest.fixture +def client_with_stomp() -> BlueapiClient: + return BlueapiClient.from_config(config=ApplicationConfig(stomp=StompConfig())) + + @pytest.fixture def expected_plans() -> PlanResponse: return TypeAdapter(PlanResponse).validate_json( @@ -59,7 +66,7 @@ def test_get_plans_by_name(client: BlueapiClient, expected_plans: PlanResponse): def test_get_non_existent_plan(client: BlueapiClient): with pytest.raises(KeyError) as exception: client.get_plan("Not exists") - assert exception.value.args[0] == ("{'detail': 'Item not found'}") + assert str(exception) == ("{'detail': 'Item not found'}") def test_get_devices(client: BlueapiClient, expected_devices: DeviceResponse): @@ -74,7 +81,7 @@ def test_get_device_by_name(client: BlueapiClient, expected_devices: DeviceRespo def test_get_non_existent_device(client: BlueapiClient): with pytest.raises(KeyError) as exception: assert client.get_device("Not exists") - assert exception.value.args[0] == ("{'detail': 'Item not found'}") + assert str(exception) == ("{'detail': 'Item not found'}") def test_create_task_and_delete_task_by_id(client: BlueapiClient): @@ -85,7 +92,7 @@ def test_create_task_and_delete_task_by_id(client: BlueapiClient): def test_create_task_validation_error(client: BlueapiClient): with pytest.raises(KeyError) as exception: client.create_task(Task(name="Not-exists", params={"Not-exists": 0.0})) - assert exception.value.args[0] == ("{'detail': 'Item not found'}") + assert str(exception) == ("{'detail': 'Item not found'}") def test_get_all_tasks(client: BlueapiClient): @@ -121,13 +128,13 @@ def test_get_task_by_id(client: BlueapiClient): def test_get_non_existent_task(client: BlueapiClient): with pytest.raises(KeyError) as exception: client.get_task("Not-exists") - assert exception.value.args[0] == "{'detail': 'Item not found'}" + assert str(exception) == "{'detail': 'Item not found'}" def test_delete_non_existent_task(client: BlueapiClient): with pytest.raises(KeyError) as exception: client.clear_task("Not-exists") - assert exception.value.args[0] == "{'detail': 'Item not found'}" + assert str(exception) == "{'detail': 'Item not found'}" def test_put_worker_task(client: BlueapiClient): @@ -148,7 +155,7 @@ def test_put_worker_task_fails_if_not_idle(client: BlueapiClient): with pytest.raises(BlueskyRemoteControlError) as exception: client.start_task(WorkerTask(task_id=small_task.task_id)) - assert exception.value.args[0] == "" + assert str(exception) == "" client.abort() client.clear_task(small_task.task_id) client.clear_task(long_task.task_id) @@ -161,11 +168,11 @@ def test_get_worker_state(client: BlueapiClient): def test_set_state_transition_error(client: BlueapiClient): with pytest.raises(BlueskyRemoteControlError) as exception: client.resume() - assert exception.value.args[0] == "" + assert str(exception) == "" with pytest.raises(BlueskyRemoteControlError) as exception: client.pause() - assert exception.value.args[0] == "" + assert str(exception) == "" def test_get_task_by_status(client: BlueapiClient): @@ -186,10 +193,10 @@ def test_get_task_by_status(client: BlueapiClient): client.start_task(WorkerTask(task_id=task_1.task_id)) while not client.get_task(task_1.task_id).is_complete: - ... + time.sleep(0.1) client.start_task(WorkerTask(task_id=task_2.task_id)) while not client.get_task(task_2.task_id).is_complete: - ... + time.sleep(0.1) task_by_completed_request = requests.get( client._rest._url("/tasks"), params={"task_status": TaskStatusEnum.COMPLETE} ) @@ -205,6 +212,45 @@ def test_get_task_by_status(client: BlueapiClient): client.clear_task(task_id=task_2.task_id) +def test_progress_wit_stomp(client_with_stomp: BlueapiClient): + all_events: list[AnyEvent] = [] + + def on_event(event: AnyEvent): + all_events.append(event) + + client_with_stomp.run_task(_SIMPLE_TASK, on_event=on_event) + assert isinstance(all_events[0], WorkerEvent) and all_events[0].task_status + task_id = all_events[0].task_status.task_id + running_event = WorkerEvent( + state=WorkerState.RUNNING, + task_status=TaskStatus( + task_id=task_id, + task_complete=False, + task_failed=False, + ), + ) + pending_event = WorkerEvent( + state=WorkerState.IDLE, + task_status=TaskStatus( + task_id=task_id, + task_complete=False, + task_failed=False, + ), + ) + complete_event = WorkerEvent( + state=WorkerState.IDLE, + task_status=TaskStatus( + task_id=task_id, + task_complete=True, + task_failed=False, + ), + ) + assert running_event in all_events + assert pending_event in all_events + assert complete_event in all_events + assert len(all_events) == 3 + + def test_get_current_state_of_environment(client: BlueapiClient): assert client.get_environment() == EnvironmentResponse(initialized=True) diff --git a/tests/unit_tests/client/test_client.py b/tests/unit_tests/client/test_client.py index c2f23a626..eec14ac02 100644 --- a/tests/unit_tests/client/test_client.py +++ b/tests/unit_tests/client/test_client.py @@ -15,6 +15,7 @@ PlanModel, PlanResponse, TaskResponse, + TasksListResponse, WorkerTask, ) from blueapi.worker import ProgressEvent, Task, TrackableTask, WorkerEvent, WorkerState @@ -35,6 +36,7 @@ ) DEVICE = DeviceModel(name="foo", protocols=[]) TASK = TrackableTask(task_id="foo", task=Task(name="bar", params={})) +TASKS = TasksListResponse(tasks=[TASK]) ACTIVE_TASK = WorkerTask(task_id="bar") ENV = EnvironmentResponse(initialized=True) COMPLETE_EVENT = WorkerEvent( @@ -65,6 +67,7 @@ def mock_rest() -> BlueapiRestClient: mock.get_device.return_value = DEVICE mock.get_state.return_value = WorkerState.IDLE mock.get_task.return_value = TASK + mock.get_all_task.return_value = TASKS mock.get_active_task.return_value = ACTIVE_TASK mock.get_environment.return_value = ENV mock.delete_environment.return_value = EnvironmentResponse(initialized=False) @@ -133,7 +136,7 @@ def test_get_task(client: BlueapiClient): assert client.get_task("foo") == TASK -def test_get_nonexistant_task( +def test_get_nonexistent_task( client: BlueapiClient, mock_rest: Mock, ): @@ -142,6 +145,18 @@ def test_get_nonexistant_task( client.get_task("baz") +def test_get_task_with_empty_id(client: BlueapiClient): + with pytest.raises(AssertionError) as exc: + client.get_task("") + assert str(exc) == "Task ID cannot be empty" + + +def test_get_all_tasks( + client: BlueapiClient, +): + assert client.get_all_task() == TASKS + + def test_create_task( client: BlueapiClient, mock_rest: Mock, diff --git a/tests/unit_tests/service/test_rest_api.py b/tests/unit_tests/service/test_rest_api.py index 86b22b9c7..130dd8049 100644 --- a/tests/unit_tests/service/test_rest_api.py +++ b/tests/unit_tests/service/test_rest_api.py @@ -12,7 +12,12 @@ from blueapi.core.bluesky_types import Plan from blueapi.service import main -from blueapi.service.model import DeviceModel, PlanModel, StateChangeRequest, WorkerTask +from blueapi.service.model import ( + DeviceModel, + PlanModel, + StateChangeRequest, + WorkerTask, +) from blueapi.worker.event import WorkerState from blueapi.worker.task import Task from blueapi.worker.task_worker import TrackableTask @@ -379,6 +384,32 @@ def test_get_task(get_task_by_id: MagicMock, client: TestClient): } +@patch("blueapi.service.interface.get_tasks") +def test_get_all_task(get_all_tasks: MagicMock, client: TestClient): + task_id = str(uuid.uuid4()) + tasks = [ + TrackableTask( + task_id=task_id, + task=Task(name="third_task"), + ) + ] + + get_all_tasks.return_value = tasks + response = client.get("/tasks") + assert response.status_code == status.HTTP_200_OK + assert response.json() == { + "tasks": [ + { + "task_id": task_id, + "task": {"name": "third_task", "params": {}}, + "is_complete": False, + "is_pending": True, + "errors": [], + } + ] + } + + @patch("blueapi.service.interface.get_task_by_id") def test_get_task_error(get_task_by_id_mock: MagicMock, client: TestClient): task_id = 567