diff --git a/tests/client/test_client_base.py b/tests/client/test_client_base.py new file mode 100644 index 0000000..b6faf2c --- /dev/null +++ b/tests/client/test_client_base.py @@ -0,0 +1,140 @@ +import unittest +from unittest import mock + +from aprsd.client.base import APRSClient +from aprsd.packets import core + + +class MockAPRSClient(APRSClient): + """Concrete implementation of APRSClient for testing.""" + + def stats(self): + return {"packets_received": 0, "packets_sent": 0} + + def setup_connection(self): + mock_connection = mock.MagicMock() + # Configure the mock with required methods + mock_connection.close = mock.MagicMock() + mock_connection.stop = mock.MagicMock() + mock_connection.set_filter = mock.MagicMock() + mock_connection.send = mock.MagicMock() + self._client = mock_connection + return mock_connection + + def decode_packet(self, *args, **kwargs): + return mock.MagicMock() + + def consumer(self, callback, blocking=False, immortal=False, raw=False): + pass + + def is_alive(self): + return True + + def close(self): + pass + + @staticmethod + def is_enabled(): + return True + + @staticmethod + def transport(): + return "mock" + + def reset(self): + """Mock implementation of reset.""" + if self._client: + self._client.close() + self._client = self.setup_connection() + if self.filter: + self._client.set_filter(self.filter) + + +class TestAPRSClient(unittest.TestCase): + def setUp(self): + # Reset the singleton instance before each test + APRSClient._instance = None + APRSClient._client = None + self.client = MockAPRSClient() + + def test_singleton_pattern(self): + """Test that multiple instantiations return the same instance.""" + client1 = MockAPRSClient() + client2 = MockAPRSClient() + self.assertIs(client1, client2) + + def test_set_filter(self): + """Test setting APRS filter.""" + # Get the existing mock client that was created in __init__ + mock_client = self.client._client + + test_filter = "m/50" + self.client.set_filter(test_filter) + self.assertEqual(self.client.filter, test_filter) + # The filter is set once during set_filter() and once during reset() + mock_client.set_filter.assert_called_with(test_filter) + + @mock.patch("aprsd.client.base.LOG") + def test_reset(self, mock_log): + """Test client reset functionality.""" + # Create a new mock client with the necessary methods + old_client = mock.MagicMock() + self.client._client = old_client + + self.client.reset() + + # Verify the old client was closed + old_client.close.assert_called_once() + + # Verify a new client was created + self.assertIsNotNone(self.client._client) + self.assertNotEqual(old_client, self.client._client) + + def test_send_packet(self): + """Test sending an APRS packet.""" + mock_packet = mock.Mock(spec=core.Packet) + self.client.send(mock_packet) + self.client._client.send.assert_called_once_with(mock_packet) + + def test_stop(self): + """Test stopping the client.""" + # Ensure client is created first + self.client._create_client() + + self.client.stop() + self.client._client.stop.assert_called_once() + + @mock.patch("aprsd.client.base.LOG") + def test_create_client_failure(self, mock_log): + """Test handling of client creation failure.""" + # Make setup_connection raise an exception + with mock.patch.object( + self.client, "setup_connection", + side_effect=Exception("Connection failed"), + ): + with self.assertRaises(Exception): + self.client._create_client() + + self.assertIsNone(self.client._client) + mock_log.error.assert_called_once() + + def test_client_property(self): + """Test the client property creates client if none exists.""" + self.client._client = None + client = self.client.client + self.assertIsNotNone(client) + + def test_filter_applied_on_creation(self): + """Test that filter is applied when creating new client.""" + test_filter = "m/50" + self.client.set_filter(test_filter) + + # Force client recreation + self.client.reset() + + # Verify filter was applied to new client + self.client._client.set_filter.assert_called_with(test_filter) + + +if __name__ == "__main__": + unittest.main()