Skip to content

Commit 32a6aa5

Browse files
authored
[Bridge] Gracefully handle unknown message IDs (#8)
1 parent 22233b2 commit 32a6aa5

File tree

3 files changed

+79
-8
lines changed

3 files changed

+79
-8
lines changed

src/arduino/app_utils/bridge.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -516,10 +516,10 @@ def _handle_msg(self, msg: list):
516516
try:
517517
if msg_type == 0: # Request: [0, msgid, method, params]
518518
if len(msg) != 4:
519-
raise ValueError("Invalid RPC request: expected length 4")
519+
raise ValueError(f"Invalid RPC request: expected length 4, got {len(msg)}")
520520
_, msgid, method, params = msg
521521
if not isinstance(params, (list, tuple)):
522-
raise ValueError("Invalid RPC request params: expected array/tuple")
522+
raise ValueError("Invalid RPC request params: expected array or tuple")
523523

524524
method_name = self._decode_method(method)
525525

@@ -538,7 +538,7 @@ def _handle_msg(self, msg: list):
538538

539539
elif msg_type == 1: # Response: [1, msgid, error, result]
540540
if len(msg) != 4:
541-
raise ValueError("Invalid RPC response: expected length 4")
541+
raise ValueError(f"Invalid RPC response: expected length 4, got {len(msg)}")
542542
_, msgid, error, result = msg
543543
if error and (not isinstance(error, list) or len(error) < 2):
544544
raise ValueError("Invalid error format in RPC response")
@@ -559,11 +559,11 @@ def _handle_msg(self, msg: list):
559559
else:
560560
on_result([GENERIC_ERR, "Unknown error occurred."])
561561
else:
562-
on_error([GENERIC_ERR, f"Response for unknown msgid {msgid} received."])
562+
logger.warning(f"Response for unknown msgid {msgid} received.")
563563

564564
elif msg_type == 2: # Notification: [2, method, params]
565565
if len(msg) != 3:
566-
raise ValueError("Invalid RPC notification: expected length 3")
566+
raise ValueError(f"Invalid RPC notification: expected length 3, got {len(msg)}")
567567
_, method, params = msg
568568
if not isinstance(params, (list, tuple)):
569569
raise ValueError("Invalid RPC notification params: expected array or tuple")

tests/arduino/app_utils/bridge/test_unit_common.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ def setUp(self):
1515

1616
# Patch dependencies
1717
# Mock the logger used by ClientServer
18-
patch("arduino.app_utils.bridge.logger", MagicMock()).start()
18+
self.mock_logger = MagicMock()
19+
self.logger_patcher = patch("arduino.app_utils.bridge.logger", self.mock_logger)
20+
self.logger_patcher.start()
1921

2022
# Mock the socket instance that will be created
2123
self.mock_socket_instance = MagicMock()
@@ -32,5 +34,6 @@ def setUp(self):
3234

3335
def tearDown(self):
3436
"""This method is called after each test and cleans up the patched dependencies."""
37+
self.logger_patcher.stop()
3538
self.socket_patcher.stop()
3639
self.threading_patcher.stop()

tests/arduino/app_utils/bridge/test_unit_handle_msg.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,58 @@
99

1010

1111
class TestHandleMsg(UnitTest):
12+
def test_empty_msg(self):
13+
"""Test handling of an empty message."""
14+
client = ClientServer()
15+
client._handle_msg([])
16+
self.mock_logger.warning.assert_called_once_with("Invalid RPC message received (must be a non-empty list).")
17+
self.mock_logger.error.assert_not_called()
18+
19+
def test_unknown_msg_type(self):
20+
"""Test handling of an unknown message type."""
21+
client = ClientServer()
22+
client._handle_msg([99, 1, None, "result"]) # Msg type 99 does not exist
23+
self.mock_logger.warning.assert_called_once_with("Invalid RPC message type received: 99")
24+
self.mock_logger.error.assert_not_called()
25+
26+
def test_unknown_msg_id(self):
27+
"""Test handling of an unknown message id."""
28+
client = ClientServer()
29+
client._handle_msg([1, 9999, None, "result"]) # Msg id 9999 does not exist
30+
self.mock_logger.warning.assert_called_once_with("Response for unknown msgid 9999 received.")
31+
self.mock_logger.error.assert_not_called()
32+
33+
def test_malformed_messages(self):
34+
"""Test handling of malformed messages."""
35+
client = ClientServer()
36+
37+
client._handle_msg([0, 1, "method", [0, 1], "extra field"]) # Malformed payload
38+
self.mock_logger.warning.assert_not_called()
39+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid RPC request: expected length 4, got 5")
40+
self.mock_logger.reset_mock()
41+
client._handle_msg([0, 1, "method", 1]) # Malformed params
42+
self.mock_logger.warning.assert_not_called()
43+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid RPC request params: expected array or tuple")
44+
self.mock_logger.reset_mock()
45+
46+
client._handle_msg([1, 1, None, "result", "extra field"]) # Malformed payload
47+
self.mock_logger.warning.assert_not_called()
48+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid RPC response: expected length 4, got 5")
49+
self.mock_logger.reset_mock()
50+
client._handle_msg([1, 1, 42, "result"]) # Malformed error
51+
self.mock_logger.warning.assert_not_called()
52+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid error format in RPC response")
53+
self.mock_logger.reset_mock()
54+
55+
client._handle_msg([2, 1, [0, 1], "extra field"]) # Malformed payload
56+
self.mock_logger.warning.assert_not_called()
57+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid RPC notification: expected length 3, got 4")
58+
self.mock_logger.reset_mock()
59+
client._handle_msg([2, 1, 42]) # Malformed params
60+
self.mock_logger.warning.assert_not_called()
61+
self.mock_logger.error.assert_called_once_with("Message validation error: Invalid RPC notification params: expected array or tuple")
62+
self.mock_logger.reset_mock()
63+
1264
def test_handle_msg_request(self):
1365
"""Test handling of an incoming request message."""
1466
client = ClientServer()
@@ -27,6 +79,22 @@ def test_handle_msg_request(self):
2779
handler_mock.assert_called_once_with(*params)
2880
client._send_response.assert_called_once_with(msgid, None, "handled")
2981

82+
def test_handle_msg_request_handler_fail(self):
83+
"""Test handling of a request for a method that fails running its handler."""
84+
client = ClientServer()
85+
client._send_response = MagicMock()
86+
87+
request_msg = [0, 111, "failing_method", []]
88+
client.handlers["failing_method"] = MagicMock(side_effect=ValueError("Handler failed"))
89+
90+
client._handle_msg(request_msg)
91+
92+
client._send_response.assert_called_once()
93+
args, _ = client._send_response.call_args
94+
self.assertEqual(args[0], 111) # msgid
95+
self.assertIsInstance(args[1], ValueError) # error
96+
self.assertIsNone(args[2]) # result
97+
3098
def test_handle_msg_request_method_not_found(self):
3199
"""Test handling of a request for a method that is not found."""
32100
client = ClientServer()
@@ -79,7 +147,7 @@ def test_handle_msg_response(self):
79147
on_error_mock.assert_not_called()
80148
self.assertNotIn(msgid, client.callbacks) # Callback should be removed
81149

82-
def test_handle_msg_response_function_not_found(self):
150+
def test_handle_msg_generic_error_response(self):
83151
"""Test handling of an incoming error response message."""
84152
client = ClientServer()
85153

@@ -100,7 +168,7 @@ def test_handle_msg_response_function_not_found(self):
100168
on_error_mock.assert_called_once_with(result_error)
101169
self.assertNotIn(msgid, client.callbacks) # Callback should be removed
102170

103-
def test_handle_msg_response_already_provided_error(self):
171+
def test_handle_msg_method_exists_error_response(self):
104172
"""Test handling of an incoming error response message that signals a method is already provided."""
105173
client = ClientServer()
106174

0 commit comments

Comments
 (0)