-
Notifications
You must be signed in to change notification settings - Fork 1
test(oocana): add comprehensive tests for Mainframe callback management #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,159 @@ | ||||||||||||||||||||||||||||
| import unittest | ||||||||||||||||||||||||||||
| from unittest.mock import Mock, MagicMock, patch | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class TestMainframeCallbacks(unittest.TestCase): | ||||||||||||||||||||||||||||
| """Test Mainframe callback management methods.""" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def setUp(self): | ||||||||||||||||||||||||||||
| """Create a Mainframe instance with mocked MQTT client.""" | ||||||||||||||||||||||||||||
| from oocana import Mainframe | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.mainframe = Mainframe("mqtt://localhost:1883") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Manually set up mock client without calling connect() | ||||||||||||||||||||||||||||
| self.mock_mqtt_client = MagicMock() | ||||||||||||||||||||||||||||
| self.mock_mqtt_client.is_connected.return_value = True | ||||||||||||||||||||||||||||
| self.mainframe.client = self.mock_mqtt_client | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_add_report_callback_adds_callable(self): | ||||||||||||||||||||||||||||
| """Test add_report_callback adds a callable function.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| self.mainframe.add_report_callback(callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Internal check - callback should be in the set | ||||||||||||||||||||||||||||
| self.assertIn(callback, self.mainframe._Mainframe__report_callbacks) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+26
|
||||||||||||||||||||||||||||
| def test_add_report_callback_rejects_non_callable(self): | ||||||||||||||||||||||||||||
| """Test add_report_callback raises ValueError for non-callable.""" | ||||||||||||||||||||||||||||
| with self.assertRaises(ValueError): | ||||||||||||||||||||||||||||
| self.mainframe.add_report_callback("not a function") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_remove_report_callback_removes_existing(self): | ||||||||||||||||||||||||||||
| """Test remove_report_callback removes existing callback.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| self.mainframe.add_report_callback(callback) | ||||||||||||||||||||||||||||
| self.mainframe.remove_report_callback(callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.assertNotIn(callback, self.mainframe._Mainframe__report_callbacks) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_remove_report_callback_logs_warning_for_missing(self): | ||||||||||||||||||||||||||||
| """Test remove_report_callback logs warning for non-existent callback.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| # Should not raise, just log warning | ||||||||||||||||||||||||||||
| self.mainframe.remove_report_callback(callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+45
|
||||||||||||||||||||||||||||
| def test_add_session_callback_adds_callable(self): | ||||||||||||||||||||||||||||
| """Test add_session_callback adds a callable function.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| session_id = "test_session" | ||||||||||||||||||||||||||||
| self.mainframe.add_session_callback(session_id, callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.assertIn(session_id, self.mainframe._Mainframe__session_callbacks) | ||||||||||||||||||||||||||||
| self.assertIn(callback, self.mainframe._Mainframe__session_callbacks[session_id]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_add_session_callback_rejects_non_callable(self): | ||||||||||||||||||||||||||||
| """Test add_session_callback raises ValueError for non-callable.""" | ||||||||||||||||||||||||||||
| with self.assertRaises(ValueError): | ||||||||||||||||||||||||||||
| self.mainframe.add_session_callback("session", "not a function") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_add_session_callback_subscribes_once(self): | ||||||||||||||||||||||||||||
| """Test add_session_callback only subscribes once for same session.""" | ||||||||||||||||||||||||||||
| callback1 = Mock() | ||||||||||||||||||||||||||||
| callback2 = Mock() | ||||||||||||||||||||||||||||
| session_id = "test_session" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.mainframe.add_session_callback(session_id, callback1) | ||||||||||||||||||||||||||||
| subscribe_count = self.mock_mqtt_client.message_callback_add.call_count | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.mainframe.add_session_callback(session_id, callback2) | ||||||||||||||||||||||||||||
| # Should not subscribe again | ||||||||||||||||||||||||||||
| self.assertEqual(self.mock_mqtt_client.message_callback_add.call_count, subscribe_count) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_remove_session_callback_removes_existing(self): | ||||||||||||||||||||||||||||
| """Test remove_session_callback removes existing callback.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| session_id = "test_session" | ||||||||||||||||||||||||||||
| self.mainframe.add_session_callback(session_id, callback) | ||||||||||||||||||||||||||||
| self.mainframe.remove_session_callback(session_id, callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # After removing last callback, session_id should be removed from dict | ||||||||||||||||||||||||||||
| self.assertNotIn(session_id, self.mainframe._Mainframe__session_callbacks) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def test_remove_session_callback_unsubscribes_on_last(self): | ||||||||||||||||||||||||||||
| """Test remove_session_callback unsubscribes when last callback removed.""" | ||||||||||||||||||||||||||||
| callback = Mock() | ||||||||||||||||||||||||||||
| session_id = "test_session" | ||||||||||||||||||||||||||||
| self.mainframe.add_session_callback(session_id, callback) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Reset mock to track unsubscribe call | ||||||||||||||||||||||||||||
| self.mock_mqtt_client.reset_mock() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.mainframe.remove_session_callback(session_id, callback) | ||||||||||||||||||||||||||||
| self.mock_mqtt_client.unsubscribe.assert_called_once() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+92
to
+94
|
||||||||||||||||||||||||||||
| self.mainframe.remove_session_callback(session_id, callback) | |
| self.mock_mqtt_client.unsubscribe.assert_called_once() | |
| self.mainframe.remove_session_callback(session_id, callback) | |
| # Ensure unsubscribe and message_callback_remove are each called once | |
| self.mock_mqtt_client.unsubscribe.assert_called_once() | |
| self.mock_mqtt_client.message_callback_remove.assert_called_once() | |
| # Ensure both operations use the same topic argument | |
| unsubscribe_args, _ = self.mock_mqtt_client.unsubscribe.call_args | |
| remove_args, _ = self.mock_mqtt_client.message_callback_remove.call_args | |
| self.assertGreaterEqual(len(unsubscribe_args), 1) | |
| self.assertGreaterEqual(len(remove_args), 1) | |
| self.assertEqual(unsubscribe_args[0], remove_args[0]) |
Copilot
AI
Jan 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue here: assert_called_once() doesn’t verify which topic was unsubscribed. Consider asserting unsubscribe/message_callback_remove were called with f"session/{session_id}/request/{request_id}/response" so the test actually validates the request/response lifecycle behavior.
| self.mock_mqtt_client.unsubscribe.assert_called_once() | |
| self.mock_mqtt_client.unsubscribe.assert_called_once_with( | |
| f"session/{session_id}/request/{request_id}/response" | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
patchis imported but never used. If the project runs any linting/static checks, this will fail; even without linting it adds noise. Remove the unused import (or use it in the tests if intended).