From 128e9b3c0076e25d5e045f52262f230ee02d1ef1 Mon Sep 17 00:00:00 2001 From: JobvAlewijk Date: Tue, 6 Jan 2026 14:52:53 +0100 Subject: [PATCH] chore: fixed sending stuff to ui --- src/control_backend/api/v1/endpoints/robot.py | 5 +- .../api/v1/endpoints/test_robot_endpoint.py | 209 ++++++++---------- 2 files changed, 96 insertions(+), 118 deletions(-) diff --git a/src/control_backend/api/v1/endpoints/robot.py b/src/control_backend/api/v1/endpoints/robot.py index c0316ec..6ad7f74 100644 --- a/src/control_backend/api/v1/endpoints/robot.py +++ b/src/control_backend/api/v1/endpoints/robot.py @@ -78,7 +78,8 @@ async def get_available_gesture_tags(request: Request, count=0): amount = count or None timeout = 5 # seconds - await req_socket.send(f"{amount}".encode() if amount else b"None") + await req_socket.send_json({"type": "tags", "count": amount}) + try: body = await asyncio.wait_for(req_socket.recv(), timeout=timeout) except TimeoutError: @@ -94,7 +95,7 @@ async def get_available_gesture_tags(request: Request, count=0): logger.error(f"Failed to parse gesture tags JSON: {e}, body: {body}") # Return empty list on JSON error available_tags = [] - return {"available_gesture_tags": available_tags} + return {"available_gestures": available_tags} @router.get("/commands/gesture/single") diff --git a/test/unit/api/v1/endpoints/test_robot_endpoint.py b/test/unit/api/v1/endpoints/test_robot_endpoint.py index e9e637d..05aab79 100644 --- a/test/unit/api/v1/endpoints/test_robot_endpoint.py +++ b/test/unit/api/v1/endpoints/test_robot_endpoint.py @@ -229,120 +229,60 @@ async def test_ping_stream_yields_json_values(monkeypatch): mock_sub_socket.recv_multipart.assert_awaited() -# ---------------------------- -# Updated get_available_gesture_tags tests (REQ socket on tcp://localhost:7788) -# ---------------------------- @pytest.mark.asyncio -async def test_get_available_gesture_tags_success(client, monkeypatch): +async def test_get_available_single_gestures_success(client, monkeypatch): """ - Test successful retrieval of available gesture tags using a REQ socket. + Test successful retrieval of single gestures. """ - # Arrange mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() - response_data = {"tags": ["wave", "nod", "point", "dance"]} + mock_req_socket.send_json = AsyncMock() + response_data = {"single_gestures": ["wave", "point"]} mock_req_socket.recv = AsyncMock(return_value=json.dumps(response_data).encode()) mock_context = MagicMock() mock_context.socket.return_value = mock_req_socket monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) - # Replace logger methods to avoid noisy logs in tests monkeypatch.setattr(robot.logger, "debug", MagicMock()) monkeypatch.setattr(robot.logger, "error", MagicMock()) - # Act - response = client.get("/commands/gesture/tags") + response = client.get("/commands/gesture/single") - # Assert assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": ["wave", "nod", "point", "dance"]} + assert response.json() == {"available_gestures": ["wave", "point"]} - # Verify ZeroMQ REQ interactions mock_req_socket.connect.assert_called_once_with("tcp://localhost:7788") - mock_req_socket.send.assert_awaited_once_with(b"None") + mock_req_socket.send_json.assert_awaited_once_with({"type": "single", "count": None}) mock_req_socket.recv.assert_awaited_once() @pytest.mark.asyncio -async def test_get_available_gesture_tags_with_amount(client, monkeypatch): - """ - The endpoint currently ignores the 'amount' TODO, so behavior is the same as 'success'. - This test asserts that the endpoint still sends b"None" and returns the tags. - """ - # Arrange +async def test_get_available_single_gestures_timeout(client, monkeypatch): mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() - response_data = {"tags": ["wave", "nod"]} - mock_req_socket.recv = AsyncMock(return_value=json.dumps(response_data).encode()) - - mock_context = MagicMock() - mock_context.socket.return_value = mock_req_socket - monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) - - monkeypatch.setattr(robot.logger, "debug", MagicMock()) - monkeypatch.setattr(robot.logger, "error", MagicMock()) - - # Act - response = client.get("/commands/gesture/tags") - - # Assert - assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": ["wave", "nod"]} - - mock_req_socket.connect.assert_called_once_with("tcp://localhost:7788") - mock_req_socket.send.assert_awaited_once_with(b"None") - - -@pytest.mark.asyncio -async def test_get_available_gesture_tags_timeout(client, monkeypatch): - """ - Test timeout scenario when fetching gesture tags. Endpoint should handle TimeoutError - and return an empty list while logging the timeout. - """ - # Arrange - mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) - mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() + mock_req_socket.send_json = AsyncMock() mock_req_socket.recv = AsyncMock(side_effect=TimeoutError) mock_context = MagicMock() mock_context.socket.return_value = mock_req_socket monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) - # Patch logger.debug so we can assert it was called with the expected message - mock_debug = MagicMock() - monkeypatch.setattr(robot.logger, "debug", mock_debug) - monkeypatch.setattr(robot.logger, "error", MagicMock()) + response = client.get("/commands/gesture/single") - # Act - response = client.get("/commands/gesture/tags") - - # Assert assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": []} - - # Verify the timeout was logged using the exact string from the endpoint code - mock_debug.assert_called_once_with("Got timeout error fetching gestures.") - - mock_req_socket.connect.assert_called_once_with("tcp://localhost:7788") - mock_req_socket.send.assert_awaited_once_with(b"None") - mock_req_socket.recv.assert_awaited_once() + assert response.json() == {"available_gestures": []} @pytest.mark.asyncio -async def test_get_available_gesture_tags_empty_response(client, monkeypatch): +async def test_get_available_single_gestures_missing_key(client, monkeypatch): """ - Test scenario when response contains an empty 'tags' list. + Test response missing 'single_gestures' key. """ - # Arrange mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() - response_data = {"tags": []} - mock_req_socket.recv = AsyncMock(return_value=json.dumps(response_data).encode()) + mock_req_socket.send_json = AsyncMock() + mock_req_socket.recv = AsyncMock(return_value=json.dumps({"unexpected": "value"}).encode()) mock_context = MagicMock() mock_context.socket.return_value = mock_req_socket @@ -351,52 +291,21 @@ async def test_get_available_gesture_tags_empty_response(client, monkeypatch): monkeypatch.setattr(robot.logger, "debug", MagicMock()) monkeypatch.setattr(robot.logger, "error", MagicMock()) - # Act - response = client.get("/commands/gesture/tags") + response = client.get("/commands/gesture/single") - # Assert assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": []} + assert response.json() == {"available_gestures": []} @pytest.mark.asyncio -async def test_get_available_gesture_tags_missing_tags_key(client, monkeypatch): +async def test_get_available_single_gestures_invalid_json(client, monkeypatch): """ - Test scenario when response JSON doesn't contain 'tags' key. + Test invalid JSON response for single gestures. """ - # Arrange mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() - response_data = {"some_other_key": "value"} - mock_req_socket.recv = AsyncMock(return_value=json.dumps(response_data).encode()) - - mock_context = MagicMock() - mock_context.socket.return_value = mock_req_socket - monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) - - monkeypatch.setattr(robot.logger, "debug", MagicMock()) - monkeypatch.setattr(robot.logger, "error", MagicMock()) - - # Act - response = client.get("/commands/gesture/tags") - - # Assert - assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": []} - - -@pytest.mark.asyncio -async def test_get_available_gesture_tags_invalid_json(client, monkeypatch): - """ - Test scenario when response contains invalid JSON. Endpoint should log the error - and return an empty list. - """ - # Arrange - mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) - mock_req_socket.connect = MagicMock() - mock_req_socket.send = AsyncMock() - mock_req_socket.recv = AsyncMock(return_value=b"invalid json") + mock_req_socket.send_json = AsyncMock() + mock_req_socket.recv = AsyncMock(return_value=b"not-json") mock_context = MagicMock() mock_context.socket.return_value = mock_req_socket @@ -406,10 +315,78 @@ async def test_get_available_gesture_tags_invalid_json(client, monkeypatch): monkeypatch.setattr(robot.logger, "error", mock_error) monkeypatch.setattr(robot.logger, "debug", MagicMock()) - # Act - response = client.get("/commands/gesture/tags") + response = client.get("/commands/gesture/single") - # Assert - invalid JSON should lead to empty list and error log invocation assert response.status_code == 200 - assert response.json() == {"available_gesture_tags": []} + assert response.json() == {"available_gestures": []} + assert mock_error.call_count == 1 + + +@pytest.mark.asyncio +async def test_get_available_basic_gestures_success(client, monkeypatch): + """ + Test successful retrieval of basic gestures. + """ + mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) + mock_req_socket.connect = MagicMock() + mock_req_socket.send_json = AsyncMock() + response_data = {"basic_gestures": ["nod", "shake"]} + mock_req_socket.recv = AsyncMock(return_value=json.dumps(response_data).encode()) + + mock_context = MagicMock() + mock_context.socket.return_value = mock_req_socket + monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) + + monkeypatch.setattr(robot.logger, "debug", MagicMock()) + monkeypatch.setattr(robot.logger, "error", MagicMock()) + + response = client.get("/commands/gesture/basic") + + assert response.status_code == 200 + assert response.json() == {"available_gestures": ["nod", "shake"]} + + mock_req_socket.connect.assert_called_once_with("tcp://localhost:7788") + mock_req_socket.send_json.assert_awaited_once_with({"type": "basic", "count": None}) + mock_req_socket.recv.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_get_available_basic_gestures_timeout(client, monkeypatch): + mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) + mock_req_socket.connect = MagicMock() + mock_req_socket.send_json = AsyncMock() + mock_req_socket.recv = AsyncMock(side_effect=TimeoutError) + + mock_context = MagicMock() + mock_context.socket.return_value = mock_req_socket + monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) + + response = client.get("/commands/gesture/basic") + + assert response.status_code == 200 + assert response.json() == {"available_gestures": []} + + +@pytest.mark.asyncio +async def test_get_available_basic_gestures_invalid_json(client, monkeypatch): + """ + Test invalid JSON response for basic gestures. + """ + mock_req_socket = AsyncMock(spec=zmq.asyncio.Socket) + mock_req_socket.connect = MagicMock() + mock_req_socket.send_json = AsyncMock() + mock_req_socket.recv = AsyncMock(return_value=b"{invalid json") + + mock_context = MagicMock() + mock_context.socket.return_value = mock_req_socket + monkeypatch.setattr(robot.Context, "instance", lambda: mock_context) + + mock_error = MagicMock() + monkeypatch.setattr(robot.logger, "error", mock_error) + monkeypatch.setattr(robot.logger, "debug", MagicMock()) + + response = client.get("/commands/gesture/basic") + + assert response.status_code == 200 + assert response.json() == {"available_gestures": []} assert mock_error.call_count == 1